diff --git a/.golangci.yml b/.golangci.yml index 3260c0f987..aea7a35b66 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -50,3 +50,6 @@ issues: - path: cmd/faucet/ linters: - deadcode + - path: core/blockchain_af.go + linters: + - deadcode diff --git a/cmd/geth/main.go b/cmd/geth/main.go index ca7afafced..020c9c0ee9 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -166,6 +166,7 @@ var ( utils.LegacyGpoPercentileFlag, utils.EWASMInterpreterFlag, utils.EVMInterpreterFlag, + utils.ECBP1100Flag, configFileFlag, } diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 4e87fcb515..53dba45e36 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -60,6 +60,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{ utils.IdentityFlag, utils.LightKDFFlag, utils.WhitelistFlag, + utils.ECBP1100Flag, }, }, { diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 90a67a01f6..27e3fcc3da 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -749,6 +749,10 @@ var ( Usage: "External EVM configuration (default = built-in interpreter)", Value: "", } + ECBP1100Flag = cli.Uint64Flag{ + Name: "ecbp1100", + Usage: "Configure ECBP-1100 (MESS) block activation number", + } ) // MakeDataDir retrieves the currently requested data directory, terminating @@ -1664,6 +1668,13 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { if gen := genesisForCtxChainConfig(ctx); gen != nil { cfg.Genesis = gen } + // Handle temporary chain configuration override cases. + if ctx.GlobalIsSet(ECBP1100Flag.Name) { + n := ctx.GlobalUint64(ECBP1100Flag.Name) + if err := cfg.Genesis.Config.SetECBP1100Transition(&n); err != nil { + Fatalf("Failed to set ECBP-1100 activation number: %v", err) + } + } // Establish NetworkID. // If dev-mode is used, then NetworkID will be overridden. diff --git a/core/blockchain.go b/core/blockchain.go index 4e03dd4580..d27d89f92a 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -210,6 +210,8 @@ type BlockChain struct { badBlocks *lru.Cache // Bad block cache shouldPreserve func(*types.Block) bool // Function used to determine whether should preserve the given block. terminateInsert func(common.Hash, uint64) bool // Testing hook used to terminate ancient receipt chain insertion. + + artificialFinalityEnabled int32 // toggles artificial finality features } // NewBlockChain returns a fully initialised block chain using information @@ -1411,15 +1413,17 @@ func (bc *BlockChain) writeBlockWithoutState(block *types.Block, td *big.Int) (e return nil } -// writeKnownBlock updates the head block flag with a known block +// writeKnownBlockAsHead updates the head block flag with a known block // and introduces chain reorg if necessary. -func (bc *BlockChain) writeKnownBlock(block *types.Block) error { +// In ethereum/go-ethereum this is called writeKnownBlock. Same logic, better name. +func (bc *BlockChain) writeKnownBlockAsHead(block *types.Block) error { bc.wg.Add(1) defer bc.wg.Done() current := bc.CurrentBlock() if block.ParentHash() != current.Hash() { - if err := bc.reorg(current, block); err != nil { + d := bc.getReorgData(current, block) + if err := bc.reorg(d); err != nil { return err } } @@ -1540,17 +1544,58 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types. reorg = !currentPreserve && (blockPreserve || mrand.Float64() < 0.5) } } + if reorg { - // Reorganise the chain if the parent is not the head block + // If code reaches AF check, and it does not error, canonical status will be allowed (not disallowed). + canonicalDisallowed := false + if block.ParentHash() != currentBlock.Hash() { - if err := bc.reorg(currentBlock, block); err != nil { - return NonStatTy, err + // Reorganise the chain if the parent is not the head block + d := bc.getReorgData(currentBlock, block) + if d.err == nil { + // Reorg data error was nil. + // Proceed with further reorg arbitration. + // If the node is mining and trying to insert their own block, we want to allow that (do not override miners). + if bc.IsArtificialFinalityEnabled() && + bc.chainConfig.IsEnabled(bc.chainConfig.GetECBP1100Transition, currentBlock.Number()) { + + if err := bc.ecbp1100(d.commonBlock.Header(), currentBlock.Header(), block.Header()); err != nil { + + canonicalDisallowed = true + log.Warn("Reorg disallowed", "error", err) + + } else if len(d.oldChain) > 2 { + + // Reorg is allowed, only log the MESS line if old chain is longer than normal. + log.Info("ECBP1100-MESS 🔓", + "status", "accepted", + "age", common.PrettyAge(time.Unix(int64(d.commonBlock.Time()), 0)), + "current.span", common.PrettyDuration(time.Duration(currentBlock.Time()-d.commonBlock.Time())*time.Second), + "proposed.span", common.PrettyDuration(time.Duration(block.Time()-d.commonBlock.Time())*time.Second), + "common.bno", d.commonBlock.Number().Uint64(), "common.hash", d.commonBlock.Hash(), + "current.bno", currentBlock.Number().Uint64(), "current.hash", currentBlock.Hash(), + "proposed.bno", block.Number().Uint64(), "proposed.hash", block.Hash(), + ) + } + } + } + // If there was a reorg(data) error, we leave it to the reorg method to handle, if it wants to wrap it or log it or whatever. + if !canonicalDisallowed { + if err := bc.reorg(d); err != nil { + return NonStatTy, err + } } } - status = CanonStatTy + // Status is canon; reorg succeeded. + if !canonicalDisallowed { + status = CanonStatTy + } else { + status = SideStatTy + } } else { status = SideStatTy } + // Set new head. if status == CanonStatTy { bc.writeHeadBlock(block) @@ -1647,7 +1692,11 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er senderCacher.recoverFromBlocks(types.MakeSigner(bc.chainConfig, chain[0].Number()), chain) var ( - stats = insertStats{startTime: mclock.Now()} + stats = insertStats{ + startTime: mclock.Now(), + artificialFinality: bc.IsArtificialFinalityEnabled() && + bc.chainConfig.IsEnabled(bc.chainConfig.GetECBP1100Transition, bc.CurrentBlock().Number()), + } lastCanon *types.Block ) // Fire a single chain head event if we've progressed the chain @@ -1685,15 +1734,59 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er externTd = bc.GetTd(block.ParentHash(), block.NumberU64()-1) // The first block can't be nil ) for block != nil && err == ErrKnownBlock { + + // canonicalDisallowed is set to true if the total difficulty is greater than + // our local head, but the segment fails to meet the criteria required by any artificial finality features, + // namely that it requires a reorg (parent != current) and does not meet an inflated difficulty ratio. + canonicalDisallowed := false + externTd = new(big.Int).Add(externTd, block.Difficulty()) if localTd.Cmp(externTd) < 0 { - break + // Have found a known block with GREATER THAN local total difficulty. + // Do not ignore this block, and as such, do not continue inserter iteration. + + // Check if known block write will cause a reorg. + if block.ParentHash() != current.Hash() { + reorgData := bc.getReorgData(current, block) + if reorgData.err == nil { + // If the reorgData is NOT nil, we know that the writeKnownBlockAsHead -> reorg + // logic will return the error. + // We let that part of the flow handle that error. + // We're only concerned with the non-error case, where the reorg + // will be permitted. + + // It will. That means we are on a different chain currently. + // Check if artificial finality forbids the reorganization, + // effectively overriding the simple (original) TD comparison check. + + if bc.IsArtificialFinalityEnabled() && + bc.chainConfig.IsEnabled(bc.chainConfig.GetECBP1100Transition, current.Number()) { + + if err := bc.ecbp1100(reorgData.commonBlock.Header(), current.Header(), block.Header()); err != nil { + + canonicalDisallowed = true + log.Trace("Reorg disallowed", "error", err) + + } + } + } + } + if !canonicalDisallowed { + break + } + // canonicalDisallowed == true + // Total difficulty was greater, but that condition has been overridden by the artificial + // finality check. Continue like nothing happened. } + + // Local vs. External total difficulty was less than or equal. + // This block is deep in our chain and is not a head contender. log.Debug("Ignoring already known block", "number", block.Number(), "hash", block.Hash()) stats.ignored++ block, err = it.next() } + // The remaining blocks are still known blocks, the only scenario here is: // During the fast sync, the pivot point is already submitted but rollback // happens. Then node resets the head full block to a lower height via `rollback` @@ -1703,8 +1796,9 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er // `insertChain` while a part of them have higher total difficulty than current // head full block(new pivot point). for block != nil && err == ErrKnownBlock { + log.Debug("Writing previously known block", "number", block.Number(), "hash", block.Hash()) - if err := bc.writeKnownBlock(block); err != nil { + if err := bc.writeKnownBlockAsHead(block); err != nil { return it.index, err } lastCanon = block @@ -1781,7 +1875,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er log.Error("Please file an issue, skip known block execution without receipt", "hash", block.Hash(), "number", block.NumberU64()) } - if err := bc.writeKnownBlock(block); err != nil { + if err := bc.writeKnownBlockAsHead(block); err != nil { return it.index, err } stats.processed++ @@ -2045,17 +2139,44 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (i return 0, nil } -// reorg takes two blocks, an old chain and a new chain and will reconstruct the -// blocks and inserts them to be part of the new canonical chain and accumulates -// potential missing transactions and post an event about them. -func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error { +// errReorgImpossible denotes impossible reorgs. +// And yet, there is an error for if, and when they occur. +// Ah, sweet mystery of life. +var errReorgImpossible = errors.New("impossible reorg") + +// errReorgNewChain denotes an attempted reorg to an invalid incoming chain. +var errReorgNewChain = errors.New("invalid new chain") + +// errReorgNewChain denotes an attempted reorg to an invalid existing chain. +var errReorgOldChain = errors.New("invalid old chain") + +// reorgData is consumed by the reorg method. +type reorgData struct { + oldBlock *types.Block + newBlock *types.Block + + newChain types.Blocks + oldChain types.Blocks + commonBlock *types.Block + + deletedTxs types.Transactions + + deletedLogs [][]*types.Log + rebirthLogs [][]*types.Log + + err error +} + +// getReorgData gets the data required by the chain reorg method. +// This data is aggregated separately to facilitate the modularization of reorg acceptance +// arbitration logic. +func (bc *BlockChain) getReorgData(oldBlock, newBlock *types.Block) *reorgData { var ( newChain types.Blocks oldChain types.Blocks commonBlock *types.Block deletedTxs types.Transactions - addedTxs types.Transactions deletedLogs [][]*types.Log rebirthLogs [][]*types.Log @@ -2089,20 +2210,6 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error { } } } - // mergeLogs returns a merged log slice with specified sort order. - mergeLogs = func(logs [][]*types.Log, reverse bool) []*types.Log { - var ret []*types.Log - if reverse { - for i := len(logs) - 1; i >= 0; i-- { - ret = append(ret, logs[i]...) - } - } else { - for i := 0; i < len(logs); i++ { - ret = append(ret, logs[i]...) - } - } - return ret - } ) // Reduce the longer chain to the same number as the shorter one if oldBlock.NumberU64() > newBlock.NumberU64() { @@ -2119,10 +2226,10 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error { } } if oldBlock == nil { - return fmt.Errorf("invalid old chain") + return &reorgData{err: errReorgOldChain} } if newBlock == nil { - return fmt.Errorf("invalid new chain") + return &reorgData{err: errReorgNewChain} } // Both sides of the reorg are at the same number, reduce both until the common // ancestor is found @@ -2142,46 +2249,120 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error { // Step back with both chains oldBlock = bc.GetBlock(oldBlock.ParentHash(), oldBlock.NumberU64()-1) if oldBlock == nil { - return fmt.Errorf("invalid old chain") + return &reorgData{err: errReorgOldChain} } newBlock = bc.GetBlock(newBlock.ParentHash(), newBlock.NumberU64()-1) if newBlock == nil { - return fmt.Errorf("invalid new chain") + return &reorgData{err: errReorgNewChain} } } - // Ensure the user sees large reorgs - if len(oldChain) > 0 && len(newChain) > 0 { - logFn := log.Info - msg := "Chain reorg detected" - if len(oldChain) > 63 { - msg = "Large chain reorg detected" - logFn = log.Warn - } - logFn(msg, "number", commonBlock.Number(), "hash", commonBlock.Hash(), - "drop", len(oldChain), "dropfrom", oldChain[0].Hash(), "add", len(newChain), "addfrom", newChain[0].Hash()) - blockReorgAddMeter.Mark(int64(len(newChain))) - blockReorgDropMeter.Mark(int64(len(oldChain))) - blockReorgMeter.Mark(1) - } else { - log.Error("Impossible reorg, please file an issue", "oldnum", oldBlock.Number(), "oldhash", oldBlock.Hash(), "newnum", newBlock.Number(), "newhash", newBlock.Hash()) - return fmt.Errorf("impossible reorg") + + if len(oldChain) == 0 || len(newChain) == 0 { + return &reorgData{err: errReorgImpossible} + } + return &reorgData{ + oldBlock: oldBlock, + newBlock: newBlock, + newChain: newChain, + oldChain: oldChain, + commonBlock: commonBlock, + deletedTxs: deletedTxs, + deletedLogs: deletedLogs, + rebirthLogs: rebirthLogs, + } +} + +// reorg takes two blocks, an old chain and a new chain and will reconstruct the +// blocks and inserts them to be part of the new canonical chain and accumulates +// potential missing transactions and post an event about them. +// If reorgData passed contains an a non-nil error, the method is expect to return it immediately. +// reorgData is NOT expected to ever return an error of its own, since reorg arbitration +// should happen externally. +// This kind-of-strange pattern is in place to allow the function to issue "special case" warning logs +// consistent with its behavior prior to refactoring. +func (bc *BlockChain) reorg(data *reorgData) error { + if data.err != nil { + if data.err == errReorgImpossible { + log.Error("Impossible reorg, please file an issue", "oldnum", data.oldBlock.Number(), "oldhash", data.oldBlock.Hash(), "newnum", data.newBlock.Number(), "newhash", data.newBlock.Hash()) + } + return data.err } + var ( + addedTxs types.Transactions + // mergeLogs returns a merged log slice with specified sort order. + mergeLogs = func(logs [][]*types.Log, reverse bool) []*types.Log { + var ret []*types.Log + if reverse { + for i := len(logs) - 1; i >= 0; i-- { + ret = append(ret, logs[i]...) + } + } else { + for i := 0; i < len(logs); i++ { + ret = append(ret, logs[i]...) + } + } + return ret + } + // collectLogs collects the logs that were generated or removed during + // the processing of the block that corresponds with the given hash. + // These logs are later announced as deleted or reborn + collectLogs = func(hash common.Hash, removed bool) { + number := bc.hc.GetBlockNumber(hash) + if number == nil { + return + } + receipts := rawdb.ReadReceipts(bc.db, hash, *number, bc.chainConfig) + + var logs []*types.Log + for _, receipt := range receipts { + for _, log := range receipt.Logs { + l := *log + if removed { + l.Removed = true + } else { + } + logs = append(logs, &l) + } + } + if len(logs) > 0 { + if removed { + data.deletedLogs = append(data.deletedLogs, logs) + } else { + data.rebirthLogs = append(data.rebirthLogs, logs) + } + } + } + ) + + // Ensure the user sees large reorgs + logFn := log.Info + msg := "Chain reorg detected" + if len(data.oldChain) > 63 { + msg = "Large chain reorg detected" + logFn = log.Warn + } + logFn(msg, "number", data.commonBlock.Number(), "hash", data.commonBlock.Hash(), + "drop", len(data.oldChain), "dropfrom", data.oldChain[0].Hash(), "add", len(data.newChain), "addfrom", data.newChain[0].Hash()) + blockReorgAddMeter.Mark(int64(len(data.newChain))) + blockReorgDropMeter.Mark(int64(len(data.oldChain))) + blockReorgMeter.Mark(1) + // Insert the new chain(except the head block(reverse order)), // taking care of the proper incremental order. - for i := len(newChain) - 1; i >= 1; i-- { + for i := len(data.newChain) - 1; i >= 1; i-- { // Insert the block in the canonical way, re-writing history - bc.writeHeadBlock(newChain[i]) + bc.writeHeadBlock(data.newChain[i]) // Collect reborn logs due to chain reorg - collectLogs(newChain[i].Hash(), false) + collectLogs(data.newChain[i].Hash(), false) // Collect the new added transactions. - addedTxs = append(addedTxs, newChain[i].Transactions()...) + addedTxs = append(addedTxs, data.newChain[i].Transactions()...) } // Delete useless indexes right now which includes the non-canonical // transaction indexes, canonical chain indexes which above the head. indexesBatch := bc.db.NewBatch() - for _, tx := range types.TxDifference(deletedTxs, addedTxs) { + for _, tx := range types.TxDifference(data.deletedTxs, addedTxs) { rawdb.DeleteTxLookupEntry(indexesBatch, tx.Hash()) } // Delete any canonical number assignments above the new head @@ -2200,15 +2381,15 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error { // this goroutine if there are no events to fire, but realistcally that only // ever happens if we're reorging empty blocks, which will only happen on idle // networks where performance is not an issue either way. - if len(deletedLogs) > 0 { - bc.rmLogsFeed.Send(RemovedLogsEvent{mergeLogs(deletedLogs, true)}) + if len(data.deletedLogs) > 0 { + bc.rmLogsFeed.Send(RemovedLogsEvent{mergeLogs(data.deletedLogs, true)}) } - if len(rebirthLogs) > 0 { - bc.logsFeed.Send(mergeLogs(rebirthLogs, false)) + if len(data.rebirthLogs) > 0 { + bc.logsFeed.Send(mergeLogs(data.rebirthLogs, false)) } - if len(oldChain) > 0 { - for i := len(oldChain) - 1; i >= 0; i-- { - bc.chainSideFeed.Send(ChainSideEvent{Block: oldChain[i]}) + if len(data.oldChain) > 0 { + for i := len(data.oldChain) - 1; i >= 0; i-- { + bc.chainSideFeed.Send(ChainSideEvent{Block: data.oldChain[i]}) } } return nil diff --git a/core/blockchain_af.go b/core/blockchain_af.go new file mode 100644 index 0000000000..47466e095a --- /dev/null +++ b/core/blockchain_af.go @@ -0,0 +1,240 @@ +package core + +import ( + "errors" + "fmt" + "math" + "math/big" + "sync/atomic" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" +) + +// errReorgFinality represents an error caused by artificial finality mechanisms. +var errReorgFinality = errors.New("finality-enforced invalid new chain") + +// EnableArtificialFinality enables and disable artificial finality features for the blockchain. +// Currently toggled features include: +// - ECBP1100-MESS: modified exponential subject scoring +// +// This level of activation works BELOW the chain configuration for any of the +// potential features. eg. If ECBP1100 is not activated at the chain config x block number, +// then calling bc.EnableArtificialFinality(true) will be a noop. +// The method is idempotent. +func (bc *BlockChain) EnableArtificialFinality(enable bool, logValues ...interface{}) { + // Store enable/disable value regardless of config activation. + var statusLog string + if enable { + statusLog = "Enabled" + atomic.StoreInt32(&bc.artificialFinalityEnabled, 1) + } else { + statusLog = "Disabled" + atomic.StoreInt32(&bc.artificialFinalityEnabled, 0) + } + if !bc.chainConfig.IsEnabled(bc.chainConfig.GetECBP1100Transition, bc.CurrentHeader().Number) { + // Don't log anything if the config hasn't enabled it yet. + return + } + logFn := log.Warn // Deactivated and enabled + if enable { + logFn = log.Info // Activated and enabled + } + logFn(fmt.Sprintf("%s artificial finality features", statusLog), logValues...) +} + +// IsArtificialFinalityEnabled returns the status of the blockchain's artificial +// finality feature setting. +// This status is agnostic of feature activation by chain configuration. +func (bc *BlockChain) IsArtificialFinalityEnabled() bool { + return atomic.LoadInt32(&bc.artificialFinalityEnabled) == 1 +} + +// getTDRatio is a helper function returning the total difficulty ratio of +// proposed over current chain segments. +func (bc *BlockChain) getTDRatio(commonAncestor, current, proposed *types.Header) float64 { + // Get the total difficulty ratio of the proposed chain segment over the existing one. + commonAncestorTD := bc.GetTd(commonAncestor.Hash(), commonAncestor.Number.Uint64()) + + proposedParentTD := bc.GetTd(proposed.ParentHash, proposed.Number.Uint64()-1) + proposedTD := new(big.Int).Add(proposed.Difficulty, proposedParentTD) + + localTD := bc.GetTd(current.Hash(), current.Number.Uint64()) + + tdRatio, _ := new(big.Float).Quo( + new(big.Float).SetInt(new(big.Int).Sub(proposedTD, commonAncestorTD)), + new(big.Float).SetInt(new(big.Int).Sub(localTD, commonAncestorTD)), + ).Float64() + return tdRatio +} + +// ecbp1100 implements the "MESS" artificial finality mechanism +// "Modified Exponential Subjective Scoring" used to prefer known chain segments +// over later-to-come counterparts, especially proposed segments stretching far into the past. +func (bc *BlockChain) ecbp1100(commonAncestor, current, proposed *types.Header) error { + + // Get the total difficulties of the proposed chain segment and the existing one. + commonAncestorTD := bc.GetTd(commonAncestor.Hash(), commonAncestor.Number.Uint64()) + proposedParentTD := bc.GetTd(proposed.ParentHash, proposed.Number.Uint64()-1) + proposedTD := new(big.Int).Add(proposed.Difficulty, proposedParentTD) + localTD := bc.GetTd(current.Hash(), current.Number.Uint64()) + + // if proposed_subchain_td * CURVE_FUNCTION_DENOMINATOR < get_curve_function_numerator(proposed.Time - commonAncestor.Time) * local_subchain_td. + proposedSubchainTD := new(big.Int).Sub(proposedTD, commonAncestorTD) + localSubchainTD := new(big.Int).Sub(localTD, commonAncestorTD) + + xBig := big.NewInt(int64(current.Time - commonAncestor.Time)) + eq := ecbp1100PolynomialV(xBig) + want := eq.Mul(eq, localSubchainTD) + + got := new(big.Int).Mul(proposedSubchainTD, ecbp1100PolynomialVCurveFunctionDenominator) + + if got.Cmp(want) < 0 { + prettyRatio, _ := new(big.Float).Quo( + new(big.Float).SetInt(got), + new(big.Float).SetInt(want), + ).Float64() + return fmt.Errorf(`%w: ECBP1100-MESS 🔒 status=rejected age=%v current.span=%v proposed.span=%v tdr/gravity=%0.6f common.bno=%d common.hash=%s current.bno=%d current.hash=%s proposed.bno=%d proposed.hash=%s`, + errReorgFinality, + common.PrettyAge(time.Unix(int64(commonAncestor.Time), 0)), + common.PrettyDuration(time.Duration(current.Time-commonAncestor.Time)*time.Second), + common.PrettyDuration(time.Duration(int32(xBig.Uint64()))*time.Second), + prettyRatio, + commonAncestor.Number.Uint64(), commonAncestor.Hash().Hex(), + current.Number.Uint64(), current.Hash().Hex(), + proposed.Number.Uint64(), proposed.Hash().Hex(), + ) + } + return nil +} + +/* +ecbp1100PolynomialV is a cubic function that looks a lot like Option 3's sin function, +but adds the benefit that the calculation can be done with integers (instead of yucky floating points). +> https://github.com/ethereumclassic/ECIPs/issues/374#issuecomment-694156719 + +CURVE_FUNCTION_DENOMINATOR = 128 + +def get_curve_function_numerator(time_delta: int) -> int: + xcap = 25132 # = floor(8000*pi) + ampl = 15 + height = CURVE_FUNCTION_DENOMINATOR * (ampl * 2) + if x > xcap: + x = xcap + # The sine approximator `y = 3*x**2 - 2*x**3` rescaled to the desired height and width + return CURVE_FUNCTION_DENOMINATOR + (3 * x**2 - 2 * x**3 // xcap) * height // xcap ** 2 + + +The if tdRatio < antiGravity check would then be + +if proposed_subchain_td * CURVE_FUNCTION_DENOMINATOR < get_curve_function_numerator(current.Time - commonAncestor.Time) * local_subchain_td. +*/ +func ecbp1100PolynomialV(x *big.Int) *big.Int { + + // Make a copy; do not mutate argument value. + + // if x > xcap: + // x = xcap + xA := new(big.Int).Set(x) + if xA.Cmp(ecbp1100PolynomialVXCap) > 0 { + xA.Set(ecbp1100PolynomialVXCap) + } + + xB := new(big.Int).Set(x) + if xB.Cmp(ecbp1100PolynomialVXCap) > 0 { + xB.Set(ecbp1100PolynomialVXCap) + } + + out := big.NewInt(0) + + // 3 * x**2 + xA.Exp(xA, big2, nil) + xA.Mul(xA, big3) + + // 3 * x**2 // xcap + xB.Exp(xB, big3, nil) + xB.Mul(xB, big2) + xB.Div(xB, ecbp1100PolynomialVXCap) + + // (3 * x**2 - 2 * x**3 // xcap) + out.Sub(xA, xB) + + // // (3 * x**2 - 2 * x**3 // xcap) * height + out.Mul(out, ecbp1100PolynomialVHeight) + + // xcap ** 2 + xcap2 := new(big.Int).Exp(ecbp1100PolynomialVXCap, big2, nil) + + // (3 * x**2 - 2 * x**3 // xcap) * height // xcap ** 2 + out.Div(out, xcap2) + + // CURVE_FUNCTION_DENOMINATOR + (3 * x**2 - 2 * x**3 // xcap) * height // xcap ** 2 + out.Add(out, ecbp1100PolynomialVCurveFunctionDenominator) + return out +} + +var big2 = big.NewInt(2) +var big3 = big.NewInt(3) + +// ecbp1100PolynomialVCurveFunctionDenominator +// CURVE_FUNCTION_DENOMINATOR = 128 +var ecbp1100PolynomialVCurveFunctionDenominator = big.NewInt(128) + +// ecbp1100PolynomialVXCap +// xcap = 25132 # = floor(8000*pi) +var ecbp1100PolynomialVXCap = big.NewInt(25132) + +// ecbp1100PolynomialVAmpl +// ampl = 15 +var ecbp1100PolynomialVAmpl = big.NewInt(15) + +// ecbp1100PolynomialVHeight +// height = CURVE_FUNCTION_DENOMINATOR * (ampl * 2) +var ecbp1100PolynomialVHeight = new(big.Int).Mul(new(big.Int).Mul(ecbp1100PolynomialVCurveFunctionDenominator, ecbp1100PolynomialVAmpl), big2) + +/* +ecbp1100AGSinusoidalA is a sinusoidal function. + +OPTION 3: Yet slower takeoff, yet steeper eventual ascent. Has a differentiable ceiling transition. +h(x)=15 sin((x+12000 π)/(8000))+15+1 + +*/ +func ecbp1100AGSinusoidalA(x float64) (antiGravity float64) { + ampl := float64(15) // amplitude + pDiv := float64(8000) // period divisor + phaseShift := math.Pi * (pDiv * 1.5) + peakX := math.Pi * pDiv // x value of first sin peak where x > 0 + if x > peakX { + // Cause the x value to limit to the x value of the first peak of the sin wave (ceiling). + x = peakX + } + return (ampl * math.Sin((x+phaseShift)/pDiv)) + ampl + 1 +} + +/* +ecbp1100AGExpB is an exponential function with x as a base (and rationalized exponent). + +OPTION 2: Slightly slower takeoff, steeper eventual ascent +g(x)=x^(x*0.00002) +*/ +func ecbp1100AGExpB(x float64) (antiGravity float64) { + return math.Pow(x, x*0.00002) +} + +/* +ecbp1100AGExpA is an exponential function with x as exponent. + +This was (one of?) Vitalik's "original" specs: +> 1.0001 ** (number of seconds between when S1 was received and when S2 was received) +- https://bitcointalk.org/index.php?topic=865169.msg16349234#msg16349234 +> gravity(B') = gravity(B) * 0.99 ^ n +- https://blog.ethereum.org/2014/11/25/proof-stake-learned-love-weak-subjectivity/ + +OPTION 1 (Original ESS) +f(x)=1.0001^(x) +*/ +func ecbp1100AGExpA(x float64) (antiGravity float64) { + return math.Pow(1.0001, x) +} diff --git a/core/blockchain_af_test.go b/core/blockchain_af_test.go new file mode 100644 index 0000000000..ced8f6fc0c --- /dev/null +++ b/core/blockchain_af_test.go @@ -0,0 +1,1097 @@ +package core + +import ( + "fmt" + "image/color" + "log" + "math" + "math/big" + "math/rand" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/params" + "gonum.org/v1/plot" + "gonum.org/v1/plot/plotter" + "gonum.org/v1/plot/vg" + "gonum.org/v1/plot/vg/draw" +) + +func runMESSTest2(t *testing.T, enableMess bool, easyL, hardL, caN int, easyT, hardT int64) (hardHead bool, err error, hard, easy []*types.Block) { + // Generate the original common chain segment and the two competing forks + engine := ethash.NewFaker() + + db := rawdb.NewMemoryDatabase() + genesis := params.DefaultMessNetGenesisBlock() + genesisB := MustCommitGenesis(db, genesis) + + chain, err := NewBlockChain(db, nil, genesis.Config, engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatal(err) + } + defer chain.Stop() + chain.EnableArtificialFinality(enableMess) + + easy, _ = GenerateChain(genesis.Config, genesisB, engine, db, easyL, func(i int, b *BlockGen) { + b.SetNonce(types.EncodeNonce(uint64(rand.Int63n(math.MaxInt64)))) + b.OffsetTime(easyT) + }) + commonAncestor := easy[caN-1] + hard, _ = GenerateChain(genesis.Config, commonAncestor, engine, db, hardL, func(i int, b *BlockGen) { + b.SetNonce(types.EncodeNonce(uint64(rand.Int63n(math.MaxInt64)))) + b.OffsetTime(hardT) + }) + + if _, err := chain.InsertChain(easy); err != nil { + t.Fatal(err) + } + _, err = chain.InsertChain(hard) + hardHead = chain.CurrentBlock().Hash() == hard[len(hard)-1].Hash() + return +} + +func TestBlockChain_AF_ECBP1100_2(t *testing.T) { + offsetGreaterDifficulty := int64(-2) // 1..8 = -9..-2 + offsetSameDifficulty := int64(0) // 9..17 = -1..8 + offsetWorseDifficulty := int64(8) // 18.. + + cases := []struct { + easyLen, hardLen, commonAncestorN int + easyOffset, hardOffset int64 + hardGetsHead, accepted bool + }{ + // NOTE: Random coin tosses involved for equivalent difficulty. + // Short trials for those are skipped. + + { + 1000, 30, 970, + 0, offsetSameDifficulty, // same difficulty + false, true, + }, + + { + 1000, 1, 999, + 0, offsetWorseDifficulty, // worse! difficulty + false, true, + }, + { + 1000, 1, 999, + 0, offsetGreaterDifficulty, // better difficulty + true, true, + }, + { + 1000, 5, 995, + 0, offsetGreaterDifficulty, + true, true, + }, + { + 1000, 25, 975, + 0, offsetGreaterDifficulty, + false, true, + }, + { + 1000, 30, 970, + 0, offsetGreaterDifficulty, + false, true, + }, + { + 1000, 50, 950, + 0, offsetGreaterDifficulty, + false, true, + }, + { + 1000, 50, 950, + 0, offsetGreaterDifficulty, + false, true, + }, + { + 1000, 1000, 900, + 0, offsetGreaterDifficulty, + true, true, + }, + { + 1000, 2000, 800, + 0, offsetGreaterDifficulty, + true, true, + }, + { + 1000, 2000, 700, + 0, offsetGreaterDifficulty, + true, true, + }, + { + 1000, 2000, 700, + 0, offsetGreaterDifficulty, + true, true, + }, + { + 1000, 999, 1, + 0, offsetGreaterDifficulty, + false, true, + }, + { + 1000, 999, 1, + 0, offsetGreaterDifficulty, + false, true, + }, + { + 1000, 500, 500, + 0, offsetGreaterDifficulty, + false, true, + }, + { + 1000, 500, 500, + 0, offsetGreaterDifficulty, + false, true, + }, + { + 1000, 300, 700, + 0, offsetGreaterDifficulty, + false, true, + }, + { + 1000, 600, 700, + 0, offsetGreaterDifficulty, + true, true, + }, + // Will pass, takes a long time. + // { + // 5000, 4000, 1000, + // 0, -2, + // true, true, + // }, + } + + for i, c := range cases { + hardHead, err, hard, easy := runMESSTest2(t, true, c.easyLen, c.hardLen, c.commonAncestorN, c.easyOffset, c.hardOffset) + + ee, hh := easy[len(easy)-1], hard[len(hard)-1] + rat, _ := new(big.Float).Quo( + new(big.Float).SetInt(hh.Difficulty()), + new(big.Float).SetInt(ee.Difficulty()), + ).Float64() + + logf := fmt.Sprintf("case=%d [easy=%d hard=%d ca=%d eo=%d ho=%d] drat=%0.6f span=%v hardHead(w|g)=%v|%v err=%v", + i, + c.easyLen, c.hardLen, c.commonAncestorN, c.easyOffset, c.hardOffset, + rat, + common.PrettyDuration(time.Second*time.Duration(10*(c.easyLen-c.commonAncestorN))), + c.hardGetsHead, hardHead, err) + + if (err != nil && c.accepted) || (err == nil && !c.accepted) || (hardHead != c.hardGetsHead) { + t.Error("FAIL", logf) + } else { + t.Log("PASS", logf) + } + } +} + +/* +TestAFKnownBlock tests that AF functionality works for chain re-insertions. + +Chain re-insertions use BlockChain.writeKnownBlockAsHead, where first-pass insertions +will hit writeBlockWithState. + +AF needs to be implemented at both sites to prevent re-proposed chains from sidestepping +the AF criteria. +*/ +func TestAFKnownBlock(t *testing.T) { + engine := ethash.NewFaker() + + db := rawdb.NewMemoryDatabase() + genesis := params.DefaultMessNetGenesisBlock() + // genesis.Timestamp = 1 + genesisB := MustCommitGenesis(db, genesis) + + chain, err := NewBlockChain(db, nil, genesis.Config, engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatal(err) + } + defer chain.Stop() + chain.EnableArtificialFinality(true) + + easy, _ := GenerateChain(genesis.Config, genesisB, engine, db, 1000, func(i int, gen *BlockGen) { + gen.OffsetTime(0) + }) + easyN, err := chain.InsertChain(easy) + if err != nil { + t.Fatal(err) + } + hard, _ := GenerateChain(genesis.Config, easy[easyN-300], engine, db, 300, func(i int, gen *BlockGen) { + gen.OffsetTime(-7) + }) + // writeBlockWithState + if _, err := chain.InsertChain(hard); err != nil { + t.Error("hard 1 not inserted (should be side)") + } + // writeKnownBlockAsHead + if _, err := chain.InsertChain(hard); err != nil { + t.Error("hard 2 inserted (will have 'ignored' known blocks, and never tried a reorg)") + } + hardHeadHash := hard[len(hard)-1].Hash() + if chain.CurrentBlock().Hash() == hardHeadHash { + t.Fatal("hard block got chain head, should be side") + } + if h := chain.GetHeaderByHash(hardHeadHash); h == nil { + t.Fatal("missing hard block (should be imported as side, but still available)") + } +} + +// TestEcbp1100PolynomialV tests the general shape and return values of the ECBP1100 polynomial curve. +// It makes sure domain values above the 'cap' do indeed get limited, as well +// as sanity check some normal domain values. +func TestEcbp1100PolynomialV(t *testing.T) { + cases := []struct { + block, ag int64 + }{ + {100, 1}, + {300, 2}, + {500, 5}, + {1000, 16}, + {2000, 31}, + {10000, 31}, + {1e9, 31}, + } + for i, c := range cases { + y := ecbp1100PolynomialV(big.NewInt(c.block * 13)) + y.Div(y, ecbp1100PolynomialVCurveFunctionDenominator) + if c.ag != y.Int64() { + t.Fatal("mismatch", i) + } + } +} + +func TestPlot_ecbp1100PolynomialV(t *testing.T) { + t.Skip("This test plots a graph of the ECBP1100 polynomial curve.") + p, err := plot.New() + if err != nil { + panic(err) + } + p.Title.Text = "ECBP1100 Polynomial Curve Function" + p.X.Label.Text = "X" + p.Y.Label.Text = "Y" + + poly := plotter.NewFunction(func(f float64) float64 { + n := big.NewInt(int64(f)) + y := ecbp1100PolynomialV(n) + ff, _ := new(big.Float).SetInt(y).Float64() + return ff + }) + p.Add(poly) + + p.X.Min = 0 + p.X.Max = 30000 + p.Y.Min = 0 + p.Y.Max = 5000 + + p.Y.Label.Text = "Antigravity imposition" + p.X.Label.Text = "Seconds difference between local head and proposed common ancestor" + + if err := p.Save(1000, 1000, "ecbp1100-polynomial.png"); err != nil { + t.Fatal(err) + } +} + +func TestEcbp1100AGSinusoidalA(t *testing.T) { + cases := []struct { + in, out float64 + }{ + {0, 1}, + {25132, 31}, + } + tolerance := 0.0000001 + for i, c := range cases { + if got := ecbp1100AGSinusoidalA(c.in); got < c.out-tolerance || got > c.out+tolerance { + t.Fatalf("%d: in: %0.6f want: %0.6f got: %0.6f", i, c.in, c.out, got) + } + } +} + +func TestDifficultyDelta(t *testing.T) { + t.Skip("A development test to play with difficulty steps.") + parent := &types.Header{ + Number: big.NewInt(1_000_000), + Difficulty: params.DefaultMessNetGenesisBlock().Difficulty, + Time: uint64(time.Now().Unix()), + UncleHash: types.EmptyUncleHash, + } + + data := plotter.XYs{} + + for i := uint64(1); i <= 60; i++ { + nextTime := parent.Time + i + d := ethash.CalcDifficulty(params.MessNetConfig, nextTime, parent) + + rat, _ := new(big.Float).Quo( + new(big.Float).SetInt(d), + new(big.Float).SetInt(parent.Difficulty), + ).Float64() + + t.Log(i, rat) + data = append(data, plotter.XY{X: float64(i), Y: rat}) + } + + p, err := plot.New() + if err != nil { + log.Panic(err) + } + p.Title.Text = "Block Difficulty Delta by Timestamp Offset" + p.X.Label.Text = "Timestamp Offset" + p.Y.Label.Text = "Relative Difficulty (child/parent)" + + dataScatter, _ := plotter.NewScatter(data) + p.Add(dataScatter) + + if err := p.Save(800, 600, "difficulty-adjustments.png"); err != nil { + t.Fatal(err) + } +} + +func TestGenerateChainTargetingHashrate(t *testing.T) { + t.Skip("A development test to play with difficulty steps.") + engine := ethash.NewFaker() + + db := rawdb.NewMemoryDatabase() + genesis := params.DefaultMessNetGenesisBlock() + // genesis.Timestamp = 1 + genesisB := MustCommitGenesis(db, genesis) + + chain, err := NewBlockChain(db, nil, genesis.Config, engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatal(err) + } + defer chain.Stop() + chain.EnableArtificialFinality(true) + + easy, _ := GenerateChain(genesis.Config, genesisB, engine, db, 1000, func(i int, gen *BlockGen) { + gen.OffsetTime(0) + }) + if _, err := chain.InsertChain(easy); err != nil { + t.Fatal(err) + } + + baseDifficulty := chain.CurrentHeader().Difficulty + targetDifficultyRatio := big.NewInt(4) + targetDifficulty := new(big.Int).Mul(baseDifficulty, targetDifficultyRatio) + + data := plotter.XYs{} + + for chain.CurrentHeader().Difficulty.Cmp(targetDifficulty) < 0 { + next, _ := GenerateChain(genesis.Config, chain.CurrentBlock(), engine, db, 1, func(i int, gen *BlockGen) { + gen.OffsetTime(-9) // 8: (=10+8=18>(13+4=17).. // minimum value over stable range + }) + if _, err := chain.InsertChain(next); err != nil { + t.Fatal(err) + } + + // f, _ := new(big.Float).SetInt(next[0].Difficulty()).Float64() + // data = append(data, plotter.XY{X: float64(next[0].NumberU64()), Y: f}) + + rat1, _ := new(big.Float).Quo( + new(big.Float).SetInt(next[0].Difficulty()), + new(big.Float).SetInt(targetDifficulty), + ).Float64() + + // rat, _ := new(big.Float).Quo( + // new(big.Float).SetInt(next[0].Difficulty()), + // new(big.Float).SetInt(targetDifficultyRatio), + // ).Float64() + + data = append(data, plotter.XY{X: float64(next[0].NumberU64()), Y: rat1}) + } + t.Log(chain.CurrentBlock().Number()) + + p, err := plot.New() + if err != nil { + log.Panic(err) + } + p.Title.Text = fmt.Sprintf("Block Difficulty Toward Target: %dx", targetDifficultyRatio.Uint64()) + p.X.Label.Text = "Block Number" + p.Y.Label.Text = "Difficulty" + + dataScatter, _ := plotter.NewScatter(data) + p.Add(dataScatter) + + if err := p.Save(800, 600, "difficulty-toward-target.png"); err != nil { + t.Fatal(err) + } +} + +func runMESSTest(t *testing.T, easyL, hardL, caN int, easyT, hardT int64) (hardHead bool, err error) { + // Generate the original common chain segment and the two competing forks + engine := ethash.NewFaker() + + db := rawdb.NewMemoryDatabase() + genesis := params.DefaultMessNetGenesisBlock() + genesisB := MustCommitGenesis(db, genesis) + + chain, err := NewBlockChain(db, nil, genesis.Config, engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatal(err) + } + defer chain.Stop() + chain.EnableArtificialFinality(yuckyGlobalTestEnableMess) + + easy, _ := GenerateChain(genesis.Config, genesisB, engine, db, easyL, func(i int, b *BlockGen) { + b.SetNonce(types.EncodeNonce(uint64(rand.Int63n(math.MaxInt64)))) + b.OffsetTime(easyT) + }) + commonAncestor := easy[caN-1] + hard, _ := GenerateChain(genesis.Config, commonAncestor, engine, db, hardL, func(i int, b *BlockGen) { + b.SetNonce(types.EncodeNonce(uint64(rand.Int63n(math.MaxInt64)))) + b.OffsetTime(hardT) + }) + + if _, err := chain.InsertChain(easy); err != nil { + t.Fatal(err) + } + _, err = chain.InsertChain(hard) + hardHead = chain.CurrentBlock().Hash() == hard[len(hard)-1].Hash() + return +} + +var yuckyGlobalTestEnableMess = false + +func TestBlockChain_GenerateMESSPlot(t *testing.T) { + t.Skip("This test plots graph of chain acceptance for visualization.") + easyLen := 500 + maxHardLen := 400 + + generatePlot := func(title, fileName string) { + p, err := plot.New() + if err != nil { + log.Panic(err) + } + p.Title.Text = title + p.X.Label.Text = "Block Depth" + p.Y.Label.Text = "Mode Block Time Offset (10 seconds + y)" + + accepteds := plotter.XYs{} + rejecteds := plotter.XYs{} + sides := plotter.XYs{} + + for i := 1; i <= maxHardLen; i++ { + for j := -9; j <= 8; j++ { + fmt.Println("running", i, j) + hardHead, err := runMESSTest(t, easyLen, i, easyLen-i, 0, int64(j)) + point := plotter.XY{X: float64(i), Y: float64(j)} + if err == nil && hardHead { + accepteds = append(accepteds, point) + } else if err == nil && !hardHead { + sides = append(sides, point) + } else if err != nil { + rejecteds = append(rejecteds, point) + } + + if err != nil { + t.Log(err) + } + } + } + + scatterAccept, _ := plotter.NewScatter(accepteds) + scatterReject, _ := plotter.NewScatter(rejecteds) + scatterSide, _ := plotter.NewScatter(sides) + + pixelWidth := vg.Length(1000) + + scatterAccept.Color = color.RGBA{R: 152, G: 236, B: 161, A: 255} + scatterAccept.Shape = draw.BoxGlyph{} + scatterAccept.Radius = vg.Length((float64(pixelWidth) / float64(maxHardLen)) * 2 / 3) + scatterReject.Color = color.RGBA{R: 236, G: 106, B: 94, A: 255} + scatterReject.Shape = draw.BoxGlyph{} + scatterReject.Radius = vg.Length((float64(pixelWidth) / float64(maxHardLen)) * 2 / 3) + scatterSide.Color = color.RGBA{R: 190, G: 197, B: 236, A: 255} + scatterSide.Shape = draw.BoxGlyph{} + scatterSide.Radius = vg.Length((float64(pixelWidth) / float64(maxHardLen)) * 2 / 3) + + p.Add(scatterAccept) + p.Legend.Add("Accepted", scatterAccept) + p.Add(scatterReject) + p.Legend.Add("Rejected", scatterReject) + p.Add(scatterSide) + p.Legend.Add("Sidechained", scatterSide) + + p.Legend.YOffs = -30 + + err = p.Save(pixelWidth, 300, fileName) + if err != nil { + log.Panic(err) + } + } + yuckyGlobalTestEnableMess = true + defer func() { + yuckyGlobalTestEnableMess = false + }() + baseTitle := fmt.Sprintf("Accept/Reject Reorgs: Relative Time (Difficulty) over Proposed Segment Length (%d-block original chain)", easyLen) + generatePlot(baseTitle, "reorgs-MESS.png") + yuckyGlobalTestEnableMess = false + // generatePlot("WITHOUT MESS: "+baseTitle, "reorgs-noMESS.png") +} + +func TestBlockChain_AF_ECBP1100(t *testing.T) { + t.Skip("These have been disused as of the sinusoidal -> cubic change.") + yuckyGlobalTestEnableMess = true + defer func() { + yuckyGlobalTestEnableMess = false + }() + + cases := []struct { + easyLen, hardLen, commonAncestorN int + easyOffset, hardOffset int64 + hardGetsHead, accepted bool + }{ + // INDEX=0 + // Hard has insufficient total difficulty / length and is rejected. + { + 5000, 7500, 2500, + 50, -9, + false, false, + }, + // Hard has sufficient total difficulty / length and is accepted. + { + 1000, 7, 995, + 60, 0, + true, true, + }, + // Hard has sufficient total difficulty / length and is accepted. + { + 1000, 7, 995, + 60, 7, + true, true, + }, + // Hard has sufficient total difficulty / length and is accepted. + { + 1000, 1, 999, + 30, 1, + true, true, + }, + // Hard has sufficient total difficulty / length and is accepted. + { + 500, 3, 497, + 0, -8, + true, true, + }, + // INDEX=5 + // Hard has sufficient total difficulty / length and is accepted. + { + 500, 4, 496, + 0, -9, + true, true, + }, + // Hard has sufficient total difficulty / length and is accepted. + { + 500, 5, 495, + 0, -9, + true, true, + }, + // Hard has sufficient total difficulty / length and is accepted. + { + 500, 6, 494, + 0, -9, + true, true, + }, + // Hard has sufficient total difficulty / length and is accepted. + { + 500, 7, 493, + 0, -9, + true, true, + }, + // Hard has sufficient total difficulty / length and is accepted. + { + 500, 8, 492, + 0, -9, + true, true, + }, + // INDEX=10 + // Hard has sufficient total difficulty / length and is accepted. + { + 500, 9, 491, + 0, -9, + true, true, + }, + // Hard has sufficient total difficulty / length and is accepted. + { + 500, 12, 488, + 0, -9, + true, true, + }, + // Hard has sufficient total difficulty / length and is accepted. + { + 500, 20, 480, + 0, -9, + true, true, + }, + // Hard has sufficient total difficulty / length and is accepted. + { + 500, 40, 460, + 0, -9, + true, true, + }, + // Hard has sufficient total difficulty / length and is accepted. + { + 500, 60, 440, + 0, -9, + true, true, + }, + // // INDEX=15 + // Hard has insufficient total difficulty / length and is rejected. + { + 500, 250, 250, + 0, -9, + false, false, + }, + // Hard has insufficient total difficulty / length and is rejected. + { + 500, 250, 250, + 7, -9, + false, false, + }, + // Hard has insufficient total difficulty / length and is rejected. + { + 500, 300, 200, + 13, -9, + false, false, + }, + // Hard has sufficient total difficulty / length and is accepted. + { + 500, 200, 300, + 47, -9, + true, true, + }, + // Hard has insufficient total difficulty / length and is rejected. + { + 500, 200, 300, + 47, -8, + false, false, + }, + // // INDEX=20 + // Hard has insufficient total difficulty / length and is rejected. + { + 500, 200, 300, + 17, -8, + false, false, + }, + // Hard has insufficient total difficulty / length and is rejected. + { + 500, 200, 300, + 7, -8, + false, false, + }, + // Hard has insufficient total difficulty / length and is rejected. + { + 500, 200, 300, + 0, -8, + false, false, + }, + // Hard has insufficient total difficulty / length and is rejected. + { + 500, 100, 400, + 0, -7, + false, false, + }, + // Hard is accepted, but does not have greater total difficulty, + // and is not set as the chain head. + { + 1000, 1, 900, + 60, -9, + false, true, + }, + // INDEX=25 + // Hard is shorter, but sufficiently heavier chain, is accepted. + { + 500, 100, 390, + 60, -9, + true, true, + }, + } + + for i, c := range cases { + hardHead, err := runMESSTest(t, c.easyLen, c.hardLen, c.commonAncestorN, c.easyOffset, c.hardOffset) + if (err != nil && c.accepted) || (err == nil && !c.accepted) || (hardHead != c.hardGetsHead) { + t.Errorf("case=%d [easy=%d hard=%d ca=%d eo=%d ho=%d] want.accepted=%v want.hardHead=%v got.hardHead=%v err=%v", + i, + c.easyLen, c.hardLen, c.commonAncestorN, c.easyOffset, c.hardOffset, + c.accepted, c.hardGetsHead, hardHead, err) + } + } +} + +func TestBlockChain_AF_Difficulty_Develop(t *testing.T) { + t.Skip("Development version of tests with plotter") + // Generate the original common chain segment and the two competing forks + engine := ethash.NewFaker() + + db := rawdb.NewMemoryDatabase() + genesis := params.DefaultMessNetGenesisBlock() + // genesis.Timestamp = 1 + genesisB := MustCommitGenesis(db, genesis) + + chain, err := NewBlockChain(db, nil, genesis.Config, engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatal(err) + } + defer chain.Stop() + chain.EnableArtificialFinality(true) + + cases := []struct { + easyLen, hardLen, commonAncestorN int + easyOffset, hardOffset int64 + hardGetsHead, accepted bool + }{ + // { + // 1000, 800, 200, + // 10, 1, + // true, true, + // }, + // { + // 1000, 800, 200, + // 60, 1, + // true, true, + // }, + // { + // 10000, 8000, 2000, + // 60, 1, + // true, true, + // }, + // { + // 20000, 18000, 2000, + // 10, 1, + // true, true, + // }, + // { + // 20000, 18000, 2000, + // 60, 1, + // true, true, + // }, + // { + // 10000, 8000, 2000, + // 10, 20, + // true, true, + // }, + + // { + // 1000, 1, 999, + // 10, 1, + // true, true, + // }, + // { + // 1000, 10, 990, + // 10, 1, + // true, true, + // }, + // { + // 1000, 100, 900, + // 10, 1, + // true, true, + // }, + // { + // 1000, 200, 800, + // 10, 1, + // true, true, + // }, + // { + // 1000, 500, 500, + // 10, 1, + // true, true, + // }, + // { + // 1000, 999, 1, + // 10, 1, + // true, true, + // }, + // { + // 5000, 4000, 1000, + // 10, 1, + // true, true, + // }, + + // { + // 10000, 9000, 1000, + // 10, 1, + // true, true, + // }, + // + // { + // 7000, 6500, 500, + // 10, 1, + // true, true, + // }, + + // { + // 100, 90, 10, + // 10, 1, + // true, true, + // }, + + // { + // 1000, 1, 999, + // 10, 1, + // true, true, + // }, + // { + // 1000, 2, 998, + // 10, 1, + // true, true, + // }, + // { + // 1000, 3, 997, + // 10, 1, + // true, true, + // }, + // { + // 1000, 1, 999, + // 10, 8, + // true, true, + // }, + + { + 1000, 50, 950, + 10, 9, + false, false, + }, + { + 1000, 100, 900, + 10, 8, + false, false, + }, + { + 1000, 100, 900, + 10, 7, + false, false, + }, + { + 1000, 50, 950, + 10, 5, + true, true, + }, + { + 1000, 50, 950, + 10, 3, + true, true, + }, + //5 + { + 1000, 100, 900, + 10, 3, + false, false, + }, + { + 1000, 200, 800, + 10, 3, + false, false, + }, + { + 1000, 200, 800, + 10, 1, + false, false, + }, + } + + // poissonTime := func(b *BlockGen, seconds int64) { + // poisson := distuv.Poisson{Lambda: float64(seconds)} + // r := poisson.Rand() + // if r < 1 { + // r = 1 + // } + // if r > float64(seconds) * 1.5 { + // r = float64(seconds) + // } + // chainreader := &fakeChainReader{config: b.config} + // b.header.Time = b.parent.Time() + uint64(r) + // b.header.Difficulty = b.engine.CalcDifficulty(chainreader, b.header.Time, b.parent.Header()) + // for err := b.engine.VerifyHeader(chainreader, b.header, false); + // err != nil && err != consensus.ErrUnknownAncestor && b.header.Time > b.parent.Header().Time; { + // t.Log(err) + // r -= 1 + // b.header.Time = b.parent.Time() + uint64(r) + // b.header.Difficulty = b.engine.CalcDifficulty(chainreader, b.header.Time, b.parent.Header()) + // } + // } + + type ratioComparison struct { + tdRatio float64 + penalty float64 + } + gotRatioComparisons := []ratioComparison{} + + for i, c := range cases { + + if err := chain.Reset(); err != nil { + t.Fatal(err) + } + easy, _ := GenerateChain(genesis.Config, genesisB, engine, db, c.easyLen, func(i int, b *BlockGen) { + b.SetNonce(types.EncodeNonce(uint64(rand.Int63n(math.MaxInt64)))) + // poissonTime(b, c.easyOffset) + b.OffsetTime(c.easyOffset - 10) + }) + commonAncestor := easy[c.commonAncestorN-1] + hard, _ := GenerateChain(genesis.Config, commonAncestor, engine, db, c.hardLen, func(i int, b *BlockGen) { + b.SetNonce(types.EncodeNonce(uint64(rand.Int63n(math.MaxInt64)))) + // poissonTime(b, c.hardOffset) + b.OffsetTime(c.hardOffset - 10) + }) + if _, err := chain.InsertChain(easy); err != nil { + t.Fatal(err) + } + n, err := chain.InsertChain(hard) + hardHead := chain.CurrentBlock().Hash() == hard[len(hard)-1].Hash() + + commons := plotter.XYs{} + easys := plotter.XYs{} + hards := plotter.XYs{} + tdrs := plotter.XYs{} + antigravities := plotter.XYs{} + antigravities2 := plotter.XYs{} + + balance := plotter.XYs{} + + for i := 0; i < c.easyLen; i++ { + td := chain.GetTd(easy[i].Hash(), easy[i].NumberU64()) + point := plotter.XY{X: float64(easy[i].NumberU64()), Y: float64(td.Uint64())} + if i <= c.commonAncestorN { + commons = append(commons, point) + } else { + easys = append(easys, point) + } + } + // td ratios + // for j := 0; j < c.hardLen; j++ { + for j := 0; j < n; j++ { + + td := chain.GetTd(hard[j].Hash(), hard[j].NumberU64()) + if td != nil { + point := plotter.XY{X: float64(hard[j].NumberU64()), Y: float64(td.Uint64())} + hards = append(hards, point) + } + + if commonAncestor.NumberU64() != uint64(c.commonAncestorN) { + t.Fatalf("bad test common=%d easy=%d can=%d", commonAncestor.NumberU64(), c.easyLen, c.commonAncestorN) + } + + ee := c.commonAncestorN + j + easyHeader := easy[ee].Header() + hardHeader := hard[j].Header() + if easyHeader.Number.Uint64() != hardHeader.Number.Uint64() { + t.Fatalf("bad test easyheader=%d hardheader=%d", easyHeader.Number.Uint64(), hardHeader.Number.Uint64()) + } + + /* + HERE LIES THE RUB (IN MY GRAPHS). + + + */ + // y := chain.getTDRatio(commonAncestor.Header(), easyHeader, hardHeader) // <- unit x unit + + // y := chain.getTDRatio(commonAncestor.Header(), easy[c.easyLen-1].Header(), hardHeader) + + y := chain.getTDRatio(commonAncestor.Header(), chain.CurrentHeader(), hardHeader) + + if j == 0 { + t.Logf("case=%d first.hard.tdr=%v", i, y) + } + + ecbp := ecbp1100AGSinusoidalA(float64(hardHeader.Time - commonAncestor.Header().Time)) + + if j == n-1 { + gotRatioComparisons = append(gotRatioComparisons, ratioComparison{ + tdRatio: y, penalty: ecbp, + }) + } + + // Exploring alternative penalty functions. + ecbp2 := ecbp1100AGExpA(float64(hardHeader.Time - commonAncestor.Header().Time)) + // t.Log(y, ecbp, ecbp2) + + tdrs = append(tdrs, plotter.XY{X: float64(hard[j].NumberU64()), Y: y}) + antigravities = append(antigravities, plotter.XY{X: float64(hard[j].NumberU64()), Y: ecbp}) + antigravities2 = append(antigravities2, plotter.XY{X: float64(hard[j].NumberU64()), Y: ecbp2}) + + balance = append(balance, plotter.XY{X: float64(hardHeader.Number.Uint64()), Y: y - ecbp}) + } + scatterCommons, _ := plotter.NewScatter(commons) + scatterEasys, _ := plotter.NewScatter(easys) + scatterHards, _ := plotter.NewScatter(hards) + + scatterTDRs, _ := plotter.NewScatter(tdrs) + scatterAntigravities, _ := plotter.NewScatter(antigravities) + scatterAntigravities2, _ := plotter.NewScatter(antigravities2) + balanceScatter, _ := plotter.NewScatter(balance) + + scatterCommons.Color = color.RGBA{R: 190, G: 197, B: 236, A: 255} + scatterCommons.Shape = draw.CircleGlyph{} + scatterCommons.Radius = 2 + scatterEasys.Color = color.RGBA{R: 152, G: 236, B: 161, A: 255} // green + scatterEasys.Shape = draw.CircleGlyph{} + scatterEasys.Radius = 2 + scatterHards.Color = color.RGBA{R: 236, G: 106, B: 94, A: 255} + scatterHards.Shape = draw.CircleGlyph{} + scatterHards.Radius = 2 + + p, perr := plot.New() + if perr != nil { + log.Panic(perr) + } + p.Add(scatterCommons) + p.Legend.Add("Commons", scatterCommons) + p.Add(scatterEasys) + p.Legend.Add("Easys", scatterEasys) + p.Add(scatterHards) + p.Legend.Add("Hards", scatterHards) + p.Title.Text = fmt.Sprintf("TD easy=%d hard=%d", c.easyOffset, c.hardOffset) + p.Save(1000, 600, fmt.Sprintf("plot-td-%d-%d-%d-%d-%d.png", c.easyLen, c.commonAncestorN, c.hardLen, c.easyOffset, c.hardOffset)) + + p, perr = plot.New() + if perr != nil { + log.Panic(perr) + } + + scatterTDRs.Color = color.RGBA{R: 236, G: 106, B: 94, A: 255} // red + scatterTDRs.Radius = 3 + scatterTDRs.Shape = draw.PyramidGlyph{} + p.Add(scatterTDRs) + p.Legend.Add("TD Ratio", scatterTDRs) + + scatterAntigravities.Color = color.RGBA{R: 190, G: 197, B: 236, A: 255} // blue + scatterAntigravities.Radius = 3 + scatterAntigravities.Shape = draw.PlusGlyph{} + p.Add(scatterAntigravities) + p.Legend.Add("(Anti)Gravity Penalty", scatterAntigravities) + + scatterAntigravities2.Color = color.RGBA{R: 152, G: 236, B: 161, A: 255} // green + scatterAntigravities2.Radius = 3 + scatterAntigravities2.Shape = draw.PlusGlyph{} + // p.Add(scatterAntigravities2) + // p.Legend.Add("(Anti)Gravity Penalty (Alternate)", scatterAntigravities2) + + p.Title.Text = fmt.Sprintf("TD Ratio easy=%d hard=%d", c.easyOffset, c.hardOffset) + p.Save(1000, 600, fmt.Sprintf("plot-td-ratio-%d-%d-%d-%d-%d.png", c.easyLen, c.commonAncestorN, c.hardLen, c.easyOffset, c.hardOffset)) + + p, perr = plot.New() + if perr != nil { + log.Panic(perr) + } + p.Title.Text = fmt.Sprintf("TD Ratio - Antigravity Penalty easy=%d hard=%d", c.easyOffset, c.hardOffset) + balanceScatter.Color = color.RGBA{R: 235, G: 92, B: 236, A: 255} // purple + balanceScatter.Radius = 3 + balanceScatter.Shape = draw.PlusGlyph{} + p.Add(balanceScatter) + p.Legend.Add("TDR - Penalty", balanceScatter) + p.Save(1000, 600, fmt.Sprintf("plot-td-ratio-diff-%d-%d-%d-%d-%d.png", c.easyLen, c.commonAncestorN, c.hardLen, c.easyOffset, c.hardOffset)) + + if (err != nil && c.accepted) || (err == nil && !c.accepted) || (hardHead != c.hardGetsHead) { + compared := gotRatioComparisons[i] + t.Errorf(`case=%d [easy=%d hard=%d ca=%d eo=%d ho=%d] want.accepted=%v want.hardHead=%v got.hardHead=%v err=%v +got.tdr=%v got.pen=%v`, + i, + c.easyLen, c.hardLen, c.commonAncestorN, c.easyOffset, c.hardOffset, + c.accepted, c.hardGetsHead, hardHead, err, compared.tdRatio, compared.penalty) + } + } + +} diff --git a/core/blockchain_insert.go b/core/blockchain_insert.go index 5685b0a4bd..af36cd81cf 100644 --- a/core/blockchain_insert.go +++ b/core/blockchain_insert.go @@ -31,6 +31,7 @@ type insertStats struct { usedGas uint64 lastIndex int startTime mclock.AbsTime + artificialFinality bool } // statsReportLimit is the time limit during import and export after which we @@ -71,6 +72,10 @@ func (st *insertStats) report(chain []*types.Block, index int, dirty common.Stor if st.ignored > 0 { context = append(context, []interface{}{"ignored", st.ignored}...) } + if st.artificialFinality { + context = append(context, []interface{}{"af", st.artificialFinality}...) + } + log.Info("Imported new chain segment", context...) // Bump the stats reported to the next section diff --git a/eth/api.go b/eth/api.go index 6103ed4a04..e856b3e66d 100644 --- a/eth/api.go +++ b/eth/api.go @@ -264,6 +264,15 @@ func (api *PrivateAdminAPI) ImportChain(file string) (bool, error) { return true, nil } +func (api *PrivateAdminAPI) Ecbp1100(blockNr rpc.BlockNumber) (bool, error) { + i := uint64(blockNr.Int64()) + err := api.eth.blockchain.Config().SetECBP1100Transition(&i) + return api.eth.blockchain.IsArtificialFinalityEnabled() && + api.eth.blockchain.Config().IsEnabled( + api.eth.blockchain.Config().GetECBP1100Transition, + api.eth.blockchain.CurrentBlock().Number()), err +} + // PublicDebugAPI is the collection of Ethereum full node APIs exposed // over the public debugging endpoint. type PublicDebugAPI struct { diff --git a/eth/handler.go b/eth/handler.go index 0b300f5d9a..6e8a0ed8cb 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -267,6 +267,10 @@ func (pm *ProtocolManager) Start(maxPeers int) { pm.wg.Add(2) go pm.chainSync.loop() go pm.txsyncLoop64() // TODO(karalabe): Legacy initial tx echange, drop with eth/64. + + // start artificial finality safety loop + pm.wg.Add(1) + go pm.artificialFinalitySafetyLoop() } func (pm *ProtocolManager) Stop() { diff --git a/eth/sync.go b/eth/sync.go index 26badd1e21..7ede248820 100644 --- a/eth/sync.go +++ b/eth/sync.go @@ -28,6 +28,7 @@ import ( "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/params/vars" ) const ( @@ -39,11 +40,60 @@ const ( txsyncPackSize = 100 * 1024 ) +var ( + // minArtificialFinalityPeers defines the minimum number of peers our node must be connected + // to in order to enable artificial finality features. + // A minimum number of peer connections mitigates the risk of lower-powered eclipse attacks. + minArtificialFinalityPeers = defaultMinSyncPeers + + // artificialFinalitySafetyInterval defines the interval at which the local head is checked for staleness. + // If the head is found to be stale across this interval, artificial finality features are disabled. + // This prevents an abandoned victim of an eclipse attack from being forever destitute. + artificialFinalitySafetyInterval = time.Second * time.Duration(30*vars.DurationLimit.Uint64()) +) + type txsync struct { p *peer txs []*types.Transaction } +// artificialFinalitySafetyLoop compares our local head across timer intervals. +// If it changes, assuming the interval is sufficiently long, +// it means we're syncing ok: there has been a steady flow of blocks. +// If it doesn't change, it means that we've stalled syncing for some reason, +// and should disable the permapoint feature in case that's keeping +// us on a dead chain. +func (pm *ProtocolManager) artificialFinalitySafetyLoop() { + defer pm.wg.Done() + + t := time.NewTicker(artificialFinalitySafetyInterval) + defer t.Stop() + + var lastHead uint64 + + for { + select { + case <-t.C: + if pm.blockchain.IsArtificialFinalityEnabled() { + // Get the latest header we have. + n := pm.blockchain.CurrentHeader().Number.Uint64() + // If it has changed, we haven't gone stale or dark. + if lastHead != n { + lastHead = n + continue + } + // Else, it hasn't changed, which means we've been at the same + // header for the whole timer interval time. + pm.blockchain.EnableArtificialFinality(false, "reason", "stale safety interval", "interval", artificialFinalitySafetyInterval) + } else { + lastHead = 0 // reset + } + case <-pm.quitSync: + return + } + } +} + // syncTransactions starts sending all currently pending transactions to the given peer. func (pm *ProtocolManager) syncTransactions(p *peer) { // Assemble the set of transaction to broadcast or announce to the remote @@ -248,6 +298,11 @@ func (cs *chainSyncer) nextSyncOp() *chainSyncOp { } else if minPeers > cs.pm.maxPeers { minPeers = cs.pm.maxPeers } + if cs.pm.peers.Len() < minArtificialFinalityPeers { + if cs.pm.blockchain.IsArtificialFinalityEnabled() { + cs.pm.blockchain.EnableArtificialFinality(false, "reason", "low peers", "peers", cs.pm.peers.Len()) + } + } if cs.pm.peers.Len() < minPeers { return nil } @@ -260,6 +315,12 @@ func (cs *chainSyncer) nextSyncOp() *chainSyncOp { mode, ourTD := cs.modeAndLocalHead() op := peerToSyncOp(mode, peer) if op.td.Cmp(ourTD) <= 0 { + // Enable artificial finality if parameters if should. + if op.mode == downloader.FullSync && + cs.pm.peers.Len() >= minArtificialFinalityPeers && + !cs.pm.blockchain.IsArtificialFinalityEnabled() { + cs.pm.blockchain.EnableArtificialFinality(true, "reason", "synced", "peers", cs.pm.peers.Len()) + } return nil // We're in sync. } return op diff --git a/eth/sync_test.go b/eth/sync_test.go index ac1e5fad1b..1dcb838307 100644 --- a/eth/sync_test.go +++ b/eth/sync_test.go @@ -62,3 +62,64 @@ func testFastSyncDisabling(t *testing.T, protocol int) { t.Fatalf("fast sync not disabled after successful synchronisation") } } + +func TestArtificialFinalityFeatureEnablingDisabling(t *testing.T) { + // Create a full protocol manager, check that fast sync gets disabled + a, _ := newTestProtocolManagerMust(t, downloader.FastSync, 1024, nil, nil) + if atomic.LoadUint32(&a.fastSync) == 1 { + t.Fatalf("fast sync not disabled on non-empty blockchain") + } + + one := uint64(1) + a.blockchain.Config().SetECBP1100Transition(&one) + + oMinAFPeers := minArtificialFinalityPeers + defer func() { + // Clean up after, resetting global default to original valu. + minArtificialFinalityPeers = oMinAFPeers + }() + minArtificialFinalityPeers = 1 + + // Create a full protocol manager, check that fast sync gets disabled + b, _ := newTestProtocolManagerMust(t, downloader.FastSync, 0, nil, nil) + if atomic.LoadUint32(&b.fastSync) == 0 { + t.Fatalf("fast sync disabled on pristine blockchain") + } + b.blockchain.Config().SetECBP1100Transition(&one) + // b.chainSync.forced = true + + io1, io2 := p2p.MsgPipe() + go a.handle(a.newPeer(65, p2p.NewPeer(enode.ID{}, "peer-b", nil), io2, a.txpool.Get)) + go b.handle(b.newPeer(65, p2p.NewPeer(enode.ID{}, "peer-a", nil), io1, b.txpool.Get)) + time.Sleep(250 * time.Millisecond) + + op := peerToSyncOp(downloader.FullSync, b.peers.BestPeer()) + if err := b.doSync(op); err != nil { + t.Fatalf("sync failed: %v", err) + } + + b.chainSync.forced = true + next := b.chainSync.nextSyncOp() + if next != nil { + t.Fatal("non-nil next sync op") + } + if !b.blockchain.Config().IsEnabled(b.blockchain.Config().GetECBP1100Transition, b.blockchain.CurrentBlock().Number()) { + t.Error("AF feature not configured") + } + if !b.blockchain.IsArtificialFinalityEnabled() { + t.Error("AF not enabled") + } + + // Set the value back to default (more than 1). + minArtificialFinalityPeers = oMinAFPeers + + // Next sync op will unset AF because manager only has 1 peer. + b.chainSync.forced = true + next = b.chainSync.nextSyncOp() + if next != nil { + t.Fatal("non-nil next sync op") + } + if b.blockchain.IsArtificialFinalityEnabled() { + t.Error("AF not disabled") + } +} diff --git a/go.mod b/go.mod index 92ddfbb6e5..4d0a1a5279 100755 --- a/go.mod +++ b/go.mod @@ -69,6 +69,7 @@ require ( golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8 golang.org/x/text v0.3.3 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 + gonum.org/v1/plot v0.8.0 gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6 gopkg.in/urfave/cli.v1 v1.20.0 diff --git a/go.sum b/go.sum index 4de62984bd..dd271248eb 100755 --- a/go.sum +++ b/go.sum @@ -11,6 +11,7 @@ cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqCl cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +gioui.org v0.0.0-20200628203458-851255f7a67b/go.mod h1:jiUwifN9cRl/zmco43aAqh0aV+s9GbhG13KcD+gEpkU= github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4= github.com/Azure/azure-pipeline-go v0.2.2 h1:6oiIS9yaG6XCCzhgAgKFfIWyo4LLCiDhZot6ltoThhY= github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc= @@ -41,6 +42,8 @@ github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrU github.com/VictoriaMetrics/fastcache v1.5.7 h1:4y6y0G8PRzszQUYIQHHssv/jgPHAb5qQuuDNdCbyAgw= github.com/VictoriaMetrics/fastcache v1.5.7/go.mod h1:ptDBkNMQI4RtmVo8VS/XwRY6RoTu1dAWCbrk+6WsEM8= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af h1:wVe6/Ea46ZMeNkQjjBW6xcqyQA/j5e0D6GytH95g0gQ= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= @@ -57,6 +60,7 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= @@ -105,6 +109,9 @@ github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc h1:jtW8jbpkO4YirRSyepBOH8E+2HEw6/hKkBvFPwhUN8c= github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= +github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= @@ -112,9 +119,13 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4 github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-fonts/dejavu v0.1.0 h1:JSajPXURYqpr+Cu8U9bt8K+XcACIHWqWrvWCKyeFmVQ= +github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-latex/latex v0.0.0-20200518072620-0806b477ea35 h1:uroDDLmuCK5Pz5J/Ef5vCL6F0sJmAtZFTm0/cF027F4= +github.com/go-latex/latex v0.0.0-20200518072620-0806b477ea35/go.mod h1:PNI+CcWytn/2Z/9f1SGOOYn0eILruVyp0v2/iAs8asQ= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= @@ -129,6 +140,8 @@ github.com/go-test/deep v1.0.5/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -218,6 +231,10 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/jung-kurt/gofpdf v1.16.2 h1:jgbatWHfRlPYiK85qgevsZTHviWXKwB1TTiKdz5PtRc= +github.com/jung-kurt/gofpdf v1.16.2/go.mod h1:1hl7y57EsiPAkLbOwzpzqgx1A30nQCk/YmFV8S2vmK0= github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356 h1:I/yrLt2WilKxlQKCM52clh5rGzTKpVctGT1lH4Dc8Jw= github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= @@ -291,6 +308,7 @@ github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 h1:oYW+YCJ1pachXTQmzR3rNLYGGz4g/UgFcjb28p/viDM= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= +github.com/phpdave11/gofpdi v1.0.7/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -316,6 +334,7 @@ github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00/go.mod h1:gFx+x8UowdsKA9Ac github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521 h1:3hxavr+IHMsQBrYUPQM5v0CgENFktkkbg1sfpgM3h20= github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521/go.mod h1:RvLn4FgxWubrpZHtQLnOf6EwhN2hEMusxZOhcW9H3UQ= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shirou/gopsutil v2.20.5+incompatible h1:tYH07UPoQt0OCQdgWWMgYHy3/a9bcxNpBIysykNIP7I= @@ -386,13 +405,23 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136 h1:A1gGSx58LAGVHUUsOf7IiR0u8Xb6W51gRwfDBhkdcaw= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200618115811-c13761719519 h1:1e2ufUJNM3lCHEY5jIgac/7UTjd6cgJNdatjPdFWf34= +golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -452,6 +481,7 @@ golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -466,8 +496,10 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= @@ -480,12 +512,21 @@ golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.8.1 h1:wGtP3yGpc5mCLOLeTeBdjeui9oZSz5De0eOjMLC/QuQ= +gonum.org/v1/gonum v0.8.1/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0 h1:OE9mWmgKkjJyEmDAAtGMPjXu+YNeGvK9VTSHY6+Qihc= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= +gonum.org/v1/plot v0.8.0 h1:dNgubmltsMoehfn6XgbutHpicbUfbkcGSxkICy1bC4o= +gonum.org/v1/plot v0.8.0/go.mod h1:3GH8dTfoceRTELDnv+4HNwbvM/eMfdDUGHFG2bo3NeE= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -545,3 +586,5 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index c6057f66d2..52cca43a4c 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -181,6 +181,11 @@ web3._extend({ call: 'admin_importChain', params: 1 }), + new web3._extend.Method({ + name: 'ecbp1100', + call: 'admin_ecbp1100', + params: 1 + }), new web3._extend.Method({ name: 'sleepBlocks', call: 'admin_sleepBlocks', diff --git a/params/config_classic.go b/params/config_classic.go index 33cc1c356b..dfaa98f128 100644 --- a/params/config_classic.go +++ b/params/config_classic.go @@ -19,8 +19,10 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/params/types/coregeth" "github.com/ethereum/go-ethereum/params/types/ctypes" + "github.com/ethereum/go-ethereum/params/types/genesisT" ) var ( @@ -73,6 +75,7 @@ var ( ECIP1017EraRounds: big.NewInt(5000000), ECIP1010PauseBlock: big.NewInt(3000000), ECIP1010Length: big.NewInt(2000000), + ECBP1100FBlock: nil, // TODO@ethereumclassic/ECIPS RequireBlockHashes: map[uint64]common.Hash{ 1920000: common.HexToHash("0x94365e3a8c0b35089c1d1195081fe7489b528a84b22199c916180db8b28ade7f"), 2500000: common.HexToHash("0xca12c63534f565899681965528d536c52cb05b7c48e269c2a6cb77ad864d878a"), @@ -82,4 +85,75 @@ var ( DisinflationRateQuotient = big.NewInt(4) // Disinflation rate quotient for ECIP1017 DisinflationRateDivisor = big.NewInt(5) // Disinflation rate divisor for ECIP1017 ExpDiffPeriod = big.NewInt(100000) // Exponential diff period for diff bomb & ECIP1010 + + MessNetConfig = &coregeth.CoreGethChainConfig{ + NetworkID: 1, + Ethash: new(ctypes.EthashConfig), + ChainID: big.NewInt(6161), + + EIP2FBlock: big.NewInt(1), + EIP7FBlock: big.NewInt(1), + + DAOForkBlock: nil, + + EIP150Block: big.NewInt(2), + + EIP155Block: big.NewInt(3), + EIP160FBlock: big.NewInt(3), + + // EIP158~ + EIP161FBlock: big.NewInt(8), + EIP170FBlock: big.NewInt(8), + + // Byzantium eq + EIP100FBlock: big.NewInt(8), + EIP140FBlock: big.NewInt(8), + EIP198FBlock: big.NewInt(8), + EIP211FBlock: big.NewInt(8), + EIP212FBlock: big.NewInt(8), + EIP213FBlock: big.NewInt(8), + EIP214FBlock: big.NewInt(8), + EIP658FBlock: big.NewInt(8), + + // Constantinople eq, aka Agharta + EIP145FBlock: big.NewInt(9), + EIP1014FBlock: big.NewInt(9), + EIP1052FBlock: big.NewInt(9), + + // Istanbul eq, aka Phoenix + // ECIP-1088 + EIP152FBlock: big.NewInt(10), + EIP1108FBlock: big.NewInt(10), + EIP1344FBlock: big.NewInt(10), + EIP1884FBlock: big.NewInt(10), + EIP2028FBlock: big.NewInt(10), + EIP2200FBlock: big.NewInt(10), // RePetersburg (=~ re-1283) + + DisposalBlock: big.NewInt(5), + ECIP1017FBlock: big.NewInt(5), + ECIP1017EraRounds: big.NewInt(5000), + ECIP1010PauseBlock: big.NewInt(3), + ECIP1010Length: big.NewInt(2), + ECBP1100FBlock: big.NewInt(11), + } ) + +func DefaultMessNetGenesisBlock() *genesisT.Genesis { + return &genesisT.Genesis{ + Config: MessNetConfig, + Timestamp: 1598650845, + ExtraData: hexutil.MustDecode("0x4235353535353535353535353535353535353535353535353535353535353535"), + GasLimit: 10485760, + Difficulty: big.NewInt(37103392657464), + Alloc: map[common.Address]genesisT.GenesisAccount{ + common.BytesToAddress([]byte{1}): {Balance: big.NewInt(1)}, // ECRecover + common.BytesToAddress([]byte{2}): {Balance: big.NewInt(1)}, // SHA256 + common.BytesToAddress([]byte{3}): {Balance: big.NewInt(1)}, // RIPEMD + common.BytesToAddress([]byte{4}): {Balance: big.NewInt(1)}, // Identity + common.BytesToAddress([]byte{5}): {Balance: big.NewInt(1)}, // ModExp + common.BytesToAddress([]byte{6}): {Balance: big.NewInt(1)}, // ECAdd + common.BytesToAddress([]byte{7}): {Balance: big.NewInt(1)}, // ECScalarMul + common.BytesToAddress([]byte{8}): {Balance: big.NewInt(1)}, // ECPairing + }, + } +} diff --git a/params/config_mordor.go b/params/config_mordor.go index 623a0cb0f8..4315d558c1 100644 --- a/params/config_mordor.go +++ b/params/config_mordor.go @@ -71,6 +71,7 @@ var ( ECIP1017EraRounds: big.NewInt(2000000), ECIP1010PauseBlock: nil, ECIP1010Length: nil, + ECBP1100FBlock: big.NewInt(2380000), // ETA 29 Sept 2020, ~1500 UTC RequireBlockHashes: map[uint64]common.Hash{ 840013: common.HexToHash("0x2ceada2b191879b71a5bcf2241dd9bc50d6d953f1640e62f9c2cee941dc61c9d"), 840014: common.HexToHash("0x8ec29dd692c8985b82410817bac232fc82805b746538d17bc924624fe74a0fcf"), diff --git a/params/confp/configurator.go b/params/confp/configurator.go index 6266caea3b..942d154be0 100644 --- a/params/confp/configurator.go +++ b/params/confp/configurator.go @@ -21,12 +21,31 @@ import ( "math" "math/big" "reflect" + "regexp" "sort" "strings" "github.com/ethereum/go-ethereum/params/types/ctypes" ) +var ( + // compatibleProtocolNameSchemes define matchable naming schemes used by configuration methods + // that are not incompatible with configuration either having or lacking them. + compatibleProtocolNameSchemes = []string{ + "ECBP", // "Ethereum Classic Best Practice" + "EBP", // "Ethereum Best Practice" + } +) + +func nameSignalsCompatibility(name string) bool { + for _, s := range compatibleProtocolNameSchemes { + if regexp.MustCompile(s).MatchString(name) { + return true + } + } + return false +} + // ConfigCompatError is raised if the locally-stored blockchain is initialised with a // ChainConfig that would alter the past. type ConfigCompatError struct { @@ -133,6 +152,11 @@ func compatible(head *uint64, a, b ctypes.ChainConfigurator) *ConfigCompatError aFns, aNames := Transitions(a) bFns, _ := Transitions(b) for i, afn := range aFns { + // Skip cross-compatible namespaced transition names, assuming + // these will not be enforced as hardforks. + if nameSignalsCompatibility(aNames[i]) { + continue + } if err := func(c1, c2, head *uint64) *ConfigCompatError { if isForkIncompatible(c1, c2, head) { return NewCompatError("incompatible fork value: "+aNames[i], c1, c2) @@ -252,8 +276,13 @@ func Forks(conf ctypes.ChainConfigurator) []uint64 { var forks []uint64 var forksM = make(map[uint64]struct{}) // Will key for uniqueness as fork numbers are appended to slice. - transitions, _ := Transitions(conf) - for _, tr := range transitions { + transitions, names := Transitions(conf) + for i, tr := range transitions { + // Skip cross-compatible namespaced transition names, assuming + // these will not be enforced as hardforks. + if nameSignalsCompatibility(names[i]) { + continue + } // Extract the fork rule block number and aggregate it response := tr() if response == nil || diff --git a/params/types/coregeth/chain_config.go b/params/types/coregeth/chain_config.go index 9ff554b1a5..4f934c7745 100644 --- a/params/types/coregeth/chain_config.go +++ b/params/types/coregeth/chain_config.go @@ -173,6 +173,8 @@ type CoreGethChainConfig struct { ECIP1017EraRounds *big.Int `json:"ecip1017EraRounds,omitempty"` // ECIP1017 era rounds ECIP1080FBlock *big.Int `json:"ecip1080FBlock,omitempty"` + ECBP1100FBlock *big.Int `json:"ecbp1100FBlock,omitempty"` // ECBP1100:MESS artificial finality + DisposalBlock *big.Int `json:"disposalBlock,omitempty"` // Bomb disposal HF block SocialBlock *big.Int `json:"socialBlock,omitempty"` // Ethereum Social Reward block EthersocialBlock *big.Int `json:"ethersocialBlock,omitempty"` // Ethersocial Reward block diff --git a/params/types/coregeth/chain_config_configurator.go b/params/types/coregeth/chain_config_configurator.go index cd20e5da2f..af4ea10f55 100644 --- a/params/types/coregeth/chain_config_configurator.go +++ b/params/types/coregeth/chain_config_configurator.go @@ -381,6 +381,15 @@ func (c *CoreGethChainConfig) SetEIP2537Transition(n *uint64) error { return nil } +func (c *CoreGethChainConfig) GetECBP1100Transition() *uint64 { + return bigNewU64(c.ECBP1100FBlock) +} + +func (c *CoreGethChainConfig) SetECBP1100Transition(n *uint64) error { + c.ECBP1100FBlock = setBig(c.ECBP1100FBlock, n) + return nil +} + func (c *CoreGethChainConfig) IsEnabled(fn func() *uint64, n *big.Int) bool { f := fn() if f == nil || n == nil { diff --git a/params/types/ctypes/configurator_iface.go b/params/types/ctypes/configurator_iface.go index e3ff71a2b1..2abb0e56c5 100644 --- a/params/types/ctypes/configurator_iface.go +++ b/params/types/ctypes/configurator_iface.go @@ -130,6 +130,8 @@ type ProtocolSpecifier interface { SetEIP1706Transition(n *uint64) error GetEIP2537Transition() *uint64 SetEIP2537Transition(n *uint64) error + GetECBP1100Transition() *uint64 + SetECBP1100Transition(n *uint64) error } type Forker interface { diff --git a/params/types/genesisT/genesis.go b/params/types/genesisT/genesis.go index ee5b1096ac..9c97f454a2 100644 --- a/params/types/genesisT/genesis.go +++ b/params/types/genesisT/genesis.go @@ -528,22 +528,30 @@ func (g *Genesis) SetECIP1080Transition(n *uint64) error { return g.Config.SetECIP1080Transition(n) } -func (g Genesis) GetEIP1706Transition() *uint64 { +func (g *Genesis) GetEIP1706Transition() *uint64 { return g.Config.GetEIP1706Transition() } -func (g Genesis) SetEIP1706Transition(n *uint64) error { +func (g *Genesis) SetEIP1706Transition(n *uint64) error { return g.Config.SetEIP1706Transition(n) } -func (g Genesis) GetEIP2537Transition() *uint64 { +func (g *Genesis) GetEIP2537Transition() *uint64 { return g.Config.GetEIP2537Transition() } -func (g Genesis) SetEIP2537Transition(n *uint64) error { +func (g *Genesis) SetEIP2537Transition(n *uint64) error { return g.Config.SetEIP2537Transition(n) } +func (g *Genesis) GetECBP1100Transition() *uint64 { + return g.Config.GetECBP1100Transition() +} + +func (g *Genesis) SetECBP1100Transition(n *uint64) error { + return g.Config.SetECBP1100Transition(n) +} + func (g *Genesis) IsEnabled(fn func() *uint64, n *big.Int) bool { return g.Config.IsEnabled(fn, n) } diff --git a/params/types/goethereum/goethereum.go b/params/types/goethereum/goethereum.go index 35fb77eae4..5e6308df59 100644 --- a/params/types/goethereum/goethereum.go +++ b/params/types/goethereum/goethereum.go @@ -70,6 +70,9 @@ type ChainConfig struct { EIP1706Transition *big.Int `json:"-"` ECIP1080Transition *big.Int `json:"-"` + + // Cache types for use with testing, but will not show up in config API. + ecbp1100Transition *big.Int } // String implements the fmt.Stringer interface. diff --git a/params/types/goethereum/goethereum_configurator.go b/params/types/goethereum/goethereum_configurator.go index 2ae8c64a98..df53e7c99a 100644 --- a/params/types/goethereum/goethereum_configurator.go +++ b/params/types/goethereum/goethereum_configurator.go @@ -391,6 +391,15 @@ func (c *ChainConfig) SetEIP2537Transition(n *uint64) error { return nil } +func (c *ChainConfig) GetECBP1100Transition() *uint64 { + return bigNewU64(c.ecbp1100Transition) +} + +func (c *ChainConfig) SetECBP1100Transition(n *uint64) error { + c.ecbp1100Transition = setBig(c.ecbp1100Transition, n) + return nil +} + func (c *ChainConfig) IsEnabled(fn func() *uint64, n *big.Int) bool { f := fn() if f == nil || n == nil { diff --git a/params/types/multigeth/multigethv0_chain_config_configurator.go b/params/types/multigeth/multigethv0_chain_config_configurator.go index 46e02e63e7..bf3ed07972 100644 --- a/params/types/multigeth/multigethv0_chain_config_configurator.go +++ b/params/types/multigeth/multigethv0_chain_config_configurator.go @@ -416,6 +416,17 @@ func (c *ChainConfig) SetEIP2537Transition(n *uint64) error { return ctypes.ErrUnsupportedConfigFatal } +func (c *ChainConfig) GetECBP1100Transition() *uint64 { + return nil +} + +func (c *ChainConfig) SetECBP1100Transition(n *uint64) error { + if n == nil { + return nil + } + return ctypes.ErrUnsupportedConfigFatal +} + func (c *ChainConfig) IsEnabled(fn func() *uint64, n *big.Int) bool { f := fn() if f == nil || n == nil { diff --git a/params/types/parity/parity_configurator.go b/params/types/parity/parity_configurator.go index 2b135a5b1b..673e1b0be7 100644 --- a/params/types/parity/parity_configurator.go +++ b/params/types/parity/parity_configurator.go @@ -624,6 +624,17 @@ func (spec *ParityChainSpec) SetEIP2537Transition(n *uint64) error { return nil } +func (spec *ParityChainSpec) GetECBP1100Transition() *uint64 { + return nil +} + +func (spec *ParityChainSpec) SetECBP1100Transition(n *uint64) error { + if n == nil { + return nil + } + return ctypes.ErrUnsupportedConfigFatal +} + func (spec *ParityChainSpec) IsEnabled(fn func() *uint64, n *big.Int) bool { f := fn() if f == nil || n == nil {