From bfce9ebee6eccf045d5d843ed91ca88609c3948a Mon Sep 17 00:00:00 2001 From: fudongbai <296179868@qq.com> Date: Sun, 22 Aug 2021 18:28:27 +0800 Subject: [PATCH 01/22] implement block process part of light sync --- cmd/evm/internal/t8ntool/execution.go | 4 +- cmd/utils/flags.go | 15 +- core/blockchain.go | 122 ++++++++++++++-- core/chain_makers.go | 2 +- core/rawdb/accessors_chain.go | 38 +++++ core/rawdb/database.go | 25 ++++ core/rawdb/schema.go | 8 ++ core/rawdb/table.go | 8 ++ core/state/journal.go | 2 +- core/state/snapshot/snapshot.go | 31 +++- core/state/state_object.go | 13 +- core/state/state_test.go | 2 +- core/state/statedb.go | 197 +++++++++++++++++++++++--- core/state/statedb_test.go | 14 +- core/state_processor.go | 174 +++++++++++++++++++++++ core/types/block.go | 33 ++++- eth/backend.go | 3 +- eth/ethconfig/config.go | 2 + eth/ethconfig/gen_config.go | 12 ++ eth/state_accessor.go | 2 +- ethdb/database.go | 6 + node/node.go | 41 ++++++ tests/state_test_util.go | 2 +- 23 files changed, 697 insertions(+), 59 deletions(-) diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index c3f1b16efc..0471037ed8 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -223,7 +223,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, statedb.AddBalance(pre.Env.Coinbase, minerReward) } // Commit block - root, err := statedb.Commit(chainConfig.IsEIP158(vmContext.BlockNumber)) + root, _, err := statedb.Commit(chainConfig.IsEIP158(vmContext.BlockNumber)) if err != nil { fmt.Fprintf(os.Stderr, "Could not commit state: %v", err) return nil, nil, NewError(ErrorEVM, fmt.Errorf("could not commit state: %v", err)) @@ -252,7 +252,7 @@ func MakePreState(db ethdb.Database, accounts core.GenesisAlloc) *state.StateDB } } // Commit and re-open to start with a clean state. - root, _ := statedb.Commit(false) + root, _, _ := statedb.Commit(false) statedb, _ = state.New(root, sdb, nil) return statedb } diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index b4d93fc1f1..7f4eff9bb4 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -125,6 +125,10 @@ var ( Name: "datadir.ancient", Usage: "Data directory for ancient chain segments (default = inside chaindata)", } + DiffFlag = DirectoryFlag{ + Name: "datadir.diff", + Usage: "Data directory for difflayer segments (default = inside chaindata)", + } MinFreeDiskSpaceFlag = DirectoryFlag{ Name: "datadir.minfreedisk", Usage: "Minimum free disk space in MB, once reached triggers auto shut down (default = --cache.gc converted to MB, 0 = disabled)", @@ -425,6 +429,10 @@ var ( Name: "cache.preimages", Usage: "Enable recording the SHA3/keccak preimages of trie keys", } + PersistDiffFlag = cli.BoolFlag{ + Name: "persistdiff", + Usage: "Enable persisting the diff layer", + } // Miner settings MiningEnabledFlag = cli.BoolFlag{ Name: "mine", @@ -1564,7 +1572,12 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { if ctx.GlobalIsSet(AncientFlag.Name) { cfg.DatabaseFreezer = ctx.GlobalString(AncientFlag.Name) } - + if ctx.GlobalIsSet(DiffFlag.Name) { + cfg.DatabaseDiff = ctx.GlobalString(DiffFlag.Name) + } + if ctx.GlobalIsSet(PersistDiffFlag.Name) { + cfg.PersistDiff = ctx.GlobalBool(PersistDiffFlag.Name) + } if gcmode := ctx.GlobalString(GCModeFlag.Name); gcmode != "full" && gcmode != "archive" { Fatalf("--%s must be either 'full' or 'archive'", GCModeFlag.Name) } diff --git a/core/blockchain.go b/core/blockchain.go index be0b0f04ae..bf9d4387ab 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -82,13 +82,16 @@ var ( const ( bodyCacheLimit = 256 blockCacheLimit = 256 + diffLayerCacheLimit = 1024 receiptsCacheLimit = 10000 txLookupCacheLimit = 1024 maxFutureBlocks = 256 maxTimeFutureBlocks = 30 - badBlockLimit = 10 maxBeyondBlocks = 2048 + diffLayerfreezerRecheckInterval = 3 * time.Second + diffLayerfreezerBlockLimit = 864000 // The number of blocks that should be kept in disk. + // BlockChainVersion ensures that an incompatible database forces a resync from scratch. // // Changelog: @@ -188,13 +191,15 @@ type BlockChain struct { currentBlock atomic.Value // Current head of the block chain currentFastBlock atomic.Value // Current head of the fast-sync chain (may be above the block chain!) - stateCache state.Database // State database to reuse between imports (contains state cache) - bodyCache *lru.Cache // Cache for the most recent block bodies - bodyRLPCache *lru.Cache // Cache for the most recent block bodies in RLP encoded format - receiptsCache *lru.Cache // Cache for the most recent receipts per block - blockCache *lru.Cache // Cache for the most recent entire blocks - txLookupCache *lru.Cache // Cache for the most recent transaction lookup data. - futureBlocks *lru.Cache // future blocks are blocks added for later processing + stateCache state.Database // State database to reuse between imports (contains state cache) + bodyCache *lru.Cache // Cache for the most recent block bodies + bodyRLPCache *lru.Cache // Cache for the most recent block bodies in RLP encoded format + receiptsCache *lru.Cache // Cache for the most recent receipts per block + blockCache *lru.Cache // Cache for the most recent entire blocks + txLookupCache *lru.Cache // Cache for the most recent transaction lookup data. + futureBlocks *lru.Cache // future blocks are blocks added for later processing + diffLayerCache *lru.Cache // Cache for the diffLayers + diffQueue *prque.Prque // A Priority queue to store recent diff layer quit chan struct{} // blockchain quit channel wg sync.WaitGroup // chain processing wait group for shutting down @@ -226,6 +231,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par blockCache, _ := lru.New(blockCacheLimit) txLookupCache, _ := lru.New(txLookupCacheLimit) futureBlocks, _ := lru.New(maxFutureBlocks) + diffLayerCache, _ := lru.New(diffLayerCacheLimit) bc := &BlockChain{ chainConfig: chainConfig, @@ -244,10 +250,12 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par bodyRLPCache: bodyRLPCache, receiptsCache: receiptsCache, blockCache: blockCache, + diffLayerCache: diffLayerCache, txLookupCache: txLookupCache, futureBlocks: futureBlocks, engine: engine, vmConfig: vmConfig, + diffQueue: prque.New(nil), } bc.validator = NewBlockValidator(chainConfig, bc, engine) bc.processor = NewStateProcessor(chainConfig, bc, engine) @@ -396,6 +404,10 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par triedb.SaveCachePeriodically(bc.cacheConfig.TrieCleanJournal, bc.cacheConfig.TrieCleanRejournal, bc.quit) }() } + // Need persist and prune diff layer + if bc.db.DiffStore() != nil { + go bc.diffLayerFreeze() + } return bc, nil } @@ -408,6 +420,14 @@ func (bc *BlockChain) CacheReceipts(hash common.Hash, receipts types.Receipts) { bc.receiptsCache.Add(hash, receipts) } +func (bc *BlockChain) CacheDiffLayer(hash common.Hash, num uint64, diffLayer *types.DiffLayer) { + bc.diffLayerCache.Add(hash, diffLayer) + if bc.db.DiffStore() != nil { + // push to priority queue before persisting + bc.diffQueue.Push(diffLayer, -(int64(num))) + } +} + func (bc *BlockChain) CacheBlock(hash common.Hash, block *types.Block) { bc.blockCache.Add(hash, block) } @@ -1506,10 +1526,19 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types. wg.Done() }() // Commit all cached state changes into underlying memory database. - root, err := state.Commit(bc.chainConfig.IsEIP158(block.Number())) + root, diffLayer, err := state.Commit(bc.chainConfig.IsEIP158(block.Number())) if err != nil { return NonStatTy, err } + + // Ensure no empty block body + if diffLayer != nil && block.Header().TxHash != types.EmptyRootHash { + // Filling necessary field + diffLayer.Receipts = receipts + diffLayer.StateRoot = root + diffLayer.Hash = block.Hash() + bc.CacheDiffLayer(diffLayer.Hash, block.Number().Uint64(), diffLayer) + } triedb := bc.stateCache.TrieDB() // If we're running an archive node, always flush @@ -1895,8 +1924,6 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er bc.reportBlock(block, receipts, err) return it.index, err } - bc.CacheReceipts(block.Hash(), receipts) - bc.CacheBlock(block.Hash(), block) // Update the metrics touched during block processing accountReadTimer.Update(statedb.AccountReads) // Account reads are complete, we can mark them storageReadTimer.Update(statedb.StorageReads) // Storage reads are complete, we can mark them @@ -1916,6 +1943,8 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er log.Error("validate state failed", "error", err) return it.index, err } + bc.CacheReceipts(block.Hash(), receipts) + bc.CacheBlock(block.Hash(), block) proctime := time.Since(start) // Update the metrics touched during block validation @@ -2292,6 +2321,77 @@ func (bc *BlockChain) update() { } } +func (bc *BlockChain) diffLayerFreeze() { + recheck := time.Tick(diffLayerfreezerRecheckInterval) + for { + select { + case <-bc.quit: + // Persist all diffLayers when shutdown, it will introduce redundant storage, but it is acceptable. + // If the client been ungracefully shutdown, it will missing all cached diff layers, it is acceptable as well. + var batch ethdb.Batch + for !bc.diffQueue.Empty() { + diff, _ := bc.diffQueue.Pop() + diffLayer := diff.(*types.DiffLayer) + if batch == nil { + batch = bc.db.DiffStore().NewBatch() + } + rawdb.WriteDiffLayer(batch, diffLayer.Hash, diffLayer) + if batch.ValueSize() > ethdb.IdealBatchSize { + if err := batch.Write(); err != nil { + log.Error("Failed to write diff layer", "err", err) + return + } + batch.Reset() + } + } + if batch != nil { + if err := batch.Write(); err != nil { + log.Error("Failed to write diff layer", "err", err) + return + } + batch.Reset() + } + return + case <-recheck: + currentHeight := bc.CurrentBlock().NumberU64() + var batch ethdb.Batch + for !bc.diffQueue.Empty() { + diff, prio := bc.diffQueue.Pop() + diffLayer := diff.(*types.DiffLayer) + + // if the block old enough + if int64(currentHeight)+prio > int64(bc.triesInMemory) { + canonicalHash := bc.GetCanonicalHash(uint64(-prio)) + // on the canonical chain + if canonicalHash == diffLayer.Hash { + if batch == nil { + batch = bc.db.DiffStore().NewBatch() + } + rawdb.WriteDiffLayer(batch, diffLayer.Hash, diffLayer) + staleHash := bc.GetCanonicalHash(uint64(-prio) - diffLayerfreezerBlockLimit) + rawdb.DeleteDiffLayer(batch, staleHash) + } + } else { + bc.diffQueue.Push(diffLayer, prio) + break + } + if batch != nil && batch.ValueSize() > ethdb.IdealBatchSize { + if err := batch.Write(); err != nil { + panic(fmt.Sprintf("Failed to write diff layer, error %v", err)) + } + batch.Reset() + } + } + if batch != nil { + if err := batch.Write(); err != nil { + panic(fmt.Sprintf("Failed to write diff layer, error %v", err)) + } + batch.Reset() + } + } + } +} + // maintainTxIndex is responsible for the construction and deletion of the // transaction index. // diff --git a/core/chain_makers.go b/core/chain_makers.go index 6cb74d51be..9ded01a433 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -223,7 +223,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse block, _, _ := b.engine.FinalizeAndAssemble(chainreader, b.header, statedb, b.txs, b.uncles, b.receipts) // Write state changes to db - root, err := statedb.Commit(config.IsEIP158(b.header.Number)) + root, _, err := statedb.Commit(config.IsEIP158(b.header.Number)) if err != nil { panic(fmt.Sprintf("state write error: %v", err)) } diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index 76132bf37e..6e14cbbd8b 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -447,6 +447,44 @@ func WriteBody(db ethdb.KeyValueWriter, hash common.Hash, number uint64, body *t WriteBodyRLP(db, hash, number, data) } +func WriteDiffLayer(db ethdb.KeyValueWriter, hash common.Hash, layer *types.DiffLayer) { + data, err := rlp.EncodeToBytes(layer) + if err != nil { + log.Crit("Failed to RLP encode diff layer", "err", err) + } + WriteDiffLayerRLP(db, hash, data) +} + +func WriteDiffLayerRLP(db ethdb.KeyValueWriter, hash common.Hash, rlp rlp.RawValue) { + if err := db.Put(diffLayerKey(hash), rlp); err != nil { + log.Crit("Failed to store block body", "err", err) + } +} + +func ReadDiffLayer(db ethdb.Reader, hash common.Hash) *types.DiffLayer { + data := ReadDiffLayerRLP(db, hash) + if len(data) == 0 { + return nil + } + diff := new(types.DiffLayer) + if err := rlp.Decode(bytes.NewReader(data), diff); err != nil { + log.Error("Invalid diff layer RLP", "hash", hash, "err", err) + return nil + } + return diff +} + +func ReadDiffLayerRLP(db ethdb.Reader, hash common.Hash) rlp.RawValue { + data, _ := db.Get(diffLayerKey(hash)) + return data +} + +func DeleteDiffLayer(db ethdb.KeyValueWriter, hash common.Hash) { + if err := db.Delete(diffLayerKey(hash)); err != nil { + log.Crit("Failed to delete diffLayer", "err", err) + } +} + // DeleteBody removes all block body data associated with a hash. func DeleteBody(db ethdb.KeyValueWriter, hash common.Hash, number uint64) { if err := db.Delete(blockBodyKey(number, hash)); err != nil { diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 725972f9ba..5bad47155f 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -36,6 +36,7 @@ import ( type freezerdb struct { ethdb.KeyValueStore ethdb.AncientStore + diffStore ethdb.KeyValueStore } // Close implements io.Closer, closing both the fast key-value store as well as @@ -48,12 +49,28 @@ func (frdb *freezerdb) Close() error { if err := frdb.KeyValueStore.Close(); err != nil { errs = append(errs, err) } + if frdb.diffStore != nil { + if err := frdb.diffStore.Close(); err != nil { + errs = append(errs, err) + } + } if len(errs) != 0 { return fmt.Errorf("%v", errs) } return nil } +func (frdb *freezerdb) DiffStore() ethdb.KeyValueStore { + return frdb.diffStore +} + +func (frdb *freezerdb) SetDiffStore(diff ethdb.KeyValueStore) { + if frdb.diffStore != nil { + frdb.diffStore.Close() + } + frdb.diffStore = diff +} + // Freeze is a helper method used for external testing to trigger and block until // a freeze cycle completes, without having to sleep for a minute to trigger the // automatic background run. @@ -114,6 +131,14 @@ func (db *nofreezedb) Sync() error { return errNotSupported } +func (db *nofreezedb) DiffStore() ethdb.KeyValueStore { + return nil +} + +func (db *nofreezedb) SetDiffStore(diff ethdb.KeyValueStore) { + panic("not implement") +} + // NewDatabase creates a high level database on top of a given key-value data // store without a freezer moving immutable chain segments into cold storage. func NewDatabase(db ethdb.KeyValueStore) ethdb.Database { diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index 2505ce90b9..b4fb99e451 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -90,6 +90,9 @@ var ( SnapshotStoragePrefix = []byte("o") // SnapshotStoragePrefix + account hash + storage hash -> storage trie value CodePrefix = []byte("c") // CodePrefix + code hash -> account code + // difflayer database + diffLayerPrefix = []byte("d") // diffLayerPrefix + hash -> diffLayer + preimagePrefix = []byte("secure-key-") // preimagePrefix + hash -> preimage configPrefix = []byte("ethereum-config-") // config prefix for the db @@ -177,6 +180,11 @@ func blockReceiptsKey(number uint64, hash common.Hash) []byte { return append(append(blockReceiptsPrefix, encodeBlockNumber(number)...), hash.Bytes()...) } +// diffLayerKey = diffLayerKeyPrefix + hash +func diffLayerKey(hash common.Hash) []byte { + return append(append(diffLayerPrefix, hash.Bytes()...)) +} + // txLookupKey = txLookupPrefix + hash func txLookupKey(hash common.Hash) []byte { return append(txLookupPrefix, hash.Bytes()...) diff --git a/core/rawdb/table.go b/core/rawdb/table.go index 323ef6293c..a27f5f8ed7 100644 --- a/core/rawdb/table.go +++ b/core/rawdb/table.go @@ -159,6 +159,14 @@ func (t *table) NewBatch() ethdb.Batch { return &tableBatch{t.db.NewBatch(), t.prefix} } +func (t *table) DiffStore() ethdb.KeyValueStore { + return nil +} + +func (t *table) SetDiffStore(diff ethdb.KeyValueStore) { + panic("not implement") +} + // tableBatch is a wrapper around a database batch that prefixes each key access // with a pre-configured string. type tableBatch struct { diff --git a/core/state/journal.go b/core/state/journal.go index 366e0c9c26..d86823c2ca 100644 --- a/core/state/journal.go +++ b/core/state/journal.go @@ -153,7 +153,7 @@ func (ch createObjectChange) dirtied() *common.Address { func (ch resetObjectChange) revert(s *StateDB) { s.SetStateObject(ch.prev) if !ch.prevdestruct && s.snap != nil { - delete(s.snapDestructs, ch.prev.addrHash) + delete(s.snapDestructs, ch.prev.address) } } diff --git a/core/state/snapshot/snapshot.go b/core/state/snapshot/snapshot.go index 1b0d883439..4bbb126251 100644 --- a/core/state/snapshot/snapshot.go +++ b/core/state/snapshot/snapshot.go @@ -26,6 +26,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" @@ -324,7 +325,7 @@ func (t *Tree) Snapshots(root common.Hash, limits int, nodisk bool) []Snapshot { // Update adds a new snapshot into the tree, if that can be linked to an existing // old parent. It is disallowed to insert a disk layer (the origin of all). -func (t *Tree) Update(blockRoot common.Hash, parentRoot common.Hash, destructs map[common.Hash]struct{}, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) error { +func (t *Tree) Update(blockRoot common.Hash, parentRoot common.Hash, destructs map[common.Address]struct{}, accounts map[common.Address][]byte, storage map[common.Address]map[string][]byte) error { // Reject noop updates to avoid self-loops in the snapshot tree. This is a // special case that can only happen for Clique networks where empty blocks // don't modify the state (0 block subsidy). @@ -339,7 +340,9 @@ func (t *Tree) Update(blockRoot common.Hash, parentRoot common.Hash, destructs m if parent == nil { return fmt.Errorf("parent [%#x] snapshot missing", parentRoot) } - snap := parent.(snapshot).Update(blockRoot, destructs, accounts, storage) + + hashDestructs, hashAccounts, hashStorage := transformSnapData(destructs, accounts, storage) + snap := parent.(snapshot).Update(blockRoot, hashDestructs, hashAccounts, hashStorage) // Save the new snapshot for later t.lock.Lock() @@ -836,3 +839,27 @@ func (t *Tree) DiskRoot() common.Hash { return t.diskRoot() } + +// TODO we can further improve it when the set is very large +func transformSnapData(destructs map[common.Address]struct{}, accounts map[common.Address][]byte, + storage map[common.Address]map[string][]byte) (map[common.Hash]struct{}, map[common.Hash][]byte, + map[common.Hash]map[common.Hash][]byte) { + hasher := crypto.NewKeccakState() + hashDestructs := make(map[common.Hash]struct{}, len(destructs)) + hashAccounts := make(map[common.Hash][]byte, len(accounts)) + hashStorages := make(map[common.Hash]map[common.Hash][]byte, len(storage)) + for addr := range destructs { + hashDestructs[crypto.Keccak256Hash(addr[:])] = struct{}{} + } + for addr, account := range accounts { + hashAccounts[crypto.Keccak256Hash(addr[:])] = account + } + for addr, accountStore := range storage { + hashStorage := make(map[common.Hash][]byte, len(accountStore)) + for k, v := range accountStore { + hashStorage[crypto.HashData(hasher, []byte(k))] = v + } + hashStorages[crypto.Keccak256Hash(addr[:])] = hashStorage + } + return hashDestructs, hashAccounts, hashStorages +} diff --git a/core/state/state_object.go b/core/state/state_object.go index 623d07ac13..298f4305ba 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -234,7 +234,7 @@ func (s *StateObject) GetCommittedState(db Database, key common.Hash) common.Has // 1) resurrect happened, and new slot values were set -- those should // have been handles via pendingStorage above. // 2) we don't have new values, and can deliver empty response back - if _, destructed := s.db.snapDestructs[s.addrHash]; destructed { + if _, destructed := s.db.snapDestructs[s.address]; destructed { return common.Hash{} } enc, err = s.db.snap.Storage(s.addrHash, crypto.Keccak256Hash(key.Bytes())) @@ -345,10 +345,9 @@ func (s *StateObject) updateTrie(db Database) Trie { }(time.Now()) } // The snapshot storage map for the object - var storage map[common.Hash][]byte + var storage map[string][]byte // Insert all the pending updates into the trie tr := s.getTrie(db) - hasher := s.db.hasher usedStorage := make([][]byte, 0, len(s.pendingStorage)) for key, value := range s.pendingStorage { @@ -371,12 +370,12 @@ func (s *StateObject) updateTrie(db Database) Trie { s.db.snapMux.Lock() if storage == nil { // Retrieve the old storage map, if available, create a new one otherwise - if storage = s.db.snapStorage[s.addrHash]; storage == nil { - storage = make(map[common.Hash][]byte) - s.db.snapStorage[s.addrHash] = storage + if storage = s.db.snapStorage[s.address]; storage == nil { + storage = make(map[string][]byte) + s.db.snapStorage[s.address] = storage } } - storage[crypto.HashData(hasher, key[:])] = v // v will be nil if value is 0x00 + storage[string(key[:])] = v // v will be nil if value is 0x00 s.db.snapMux.Unlock() } usedStorage = append(usedStorage, common.CopyBytes(key[:])) // Copy needed for closure diff --git a/core/state/state_test.go b/core/state/state_test.go index 9f003fefb5..77847772c6 100644 --- a/core/state/state_test.go +++ b/core/state/state_test.go @@ -167,7 +167,7 @@ func TestSnapshot2(t *testing.T) { so0.deleted = false state.SetStateObject(so0) - root, _ := state.Commit(false) + root, _, _ := state.Commit(false) state, _ = New(root, state.db, state.snaps) // and one with deleted == true diff --git a/core/state/statedb.go b/core/state/statedb.go index 7940613cd6..15256dda64 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -77,13 +77,17 @@ type StateDB struct { originalRoot common.Hash // The pre-state root, before any changes were made trie Trie hasher crypto.KeccakState + diffLayer *types.DiffLayer + diffTries map[common.Address]Trie + diffCode map[common.Hash][]byte + diffEnabled bool snapMux sync.Mutex snaps *snapshot.Tree snap snapshot.Snapshot - snapDestructs map[common.Hash]struct{} - snapAccounts map[common.Hash][]byte - snapStorage map[common.Hash]map[common.Hash][]byte + snapDestructs map[common.Address]struct{} + snapAccounts map[common.Address][]byte + snapStorage map[common.Address]map[string][]byte // This map holds 'live' objects, which will get modified while processing a state transition. stateObjects map[common.Address]*StateObject @@ -156,9 +160,9 @@ func newStateDB(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, sdb.trie = tr if sdb.snaps != nil { if sdb.snap = sdb.snaps.Snapshot(root); sdb.snap != nil { - sdb.snapDestructs = make(map[common.Hash]struct{}) - sdb.snapAccounts = make(map[common.Hash][]byte) - sdb.snapStorage = make(map[common.Hash]map[common.Hash][]byte) + sdb.snapDestructs = make(map[common.Address]struct{}) + sdb.snapAccounts = make(map[common.Address][]byte) + sdb.snapStorage = make(map[common.Address]map[string][]byte) } } return sdb, nil @@ -186,6 +190,11 @@ func (s *StateDB) StopPrefetcher() { } } +// Mark that the block is processed by diff layer +func (s *StateDB) MarkDiffEnabled() { + s.diffEnabled = true +} + // setError remembers the first non-nil error it is called with. func (s *StateDB) setError(err error) { if s.dbErr == nil { @@ -197,6 +206,19 @@ func (s *StateDB) Error() error { return s.dbErr } +func (s *StateDB) Trie() Trie { + return s.trie +} + +func (s *StateDB) SetDiff(diffLayer *types.DiffLayer, diffTries map[common.Address]Trie, diffCode map[common.Hash][]byte) { + s.diffLayer, s.diffTries, s.diffCode = diffLayer, diffTries, diffCode +} + +func (s *StateDB) SetSnapData(snapDestructs map[common.Address]struct{}, snapAccounts map[common.Address][]byte, + snapStorage map[common.Address]map[string][]byte) { + s.snapDestructs, s.snapAccounts, s.snapStorage = snapDestructs, snapAccounts, snapStorage +} + func (s *StateDB) AddLog(log *types.Log) { s.journal.append(addLogChange{txhash: s.thash}) @@ -683,9 +705,9 @@ func (s *StateDB) createObject(addr common.Address) (newobj, prev *StateObject) var prevdestruct bool if s.snap != nil && prev != nil { - _, prevdestruct = s.snapDestructs[prev.addrHash] + _, prevdestruct = s.snapDestructs[prev.address] if !prevdestruct { - s.snapDestructs[prev.addrHash] = struct{}{} + s.snapDestructs[prev.address] = struct{}{} } } newobj = newObject(s, addr, Account{}) @@ -830,17 +852,17 @@ func (s *StateDB) Copy() *StateDB { state.snaps = s.snaps state.snap = s.snap // deep copy needed - state.snapDestructs = make(map[common.Hash]struct{}) + state.snapDestructs = make(map[common.Address]struct{}) for k, v := range s.snapDestructs { state.snapDestructs[k] = v } - state.snapAccounts = make(map[common.Hash][]byte) + state.snapAccounts = make(map[common.Address][]byte) for k, v := range s.snapAccounts { state.snapAccounts[k] = v } - state.snapStorage = make(map[common.Hash]map[common.Hash][]byte) + state.snapStorage = make(map[common.Address]map[string][]byte) for k, v := range s.snapStorage { - temp := make(map[common.Hash][]byte) + temp := make(map[string][]byte) for kk, vv := range v { temp[kk] = vv } @@ -903,9 +925,9 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) { // transactions within the same block might self destruct and then // ressurrect an account; but the snapshotter needs both events. if s.snap != nil { - s.snapDestructs[obj.addrHash] = struct{}{} // We need to maintain account deletions explicitly (will remain set indefinitely) - delete(s.snapAccounts, obj.addrHash) // Clear out any previously updated account data (may be recreated via a ressurrect) - delete(s.snapStorage, obj.addrHash) // Clear out any previously updated storage data (may be recreated via a ressurrect) + s.snapDestructs[obj.address] = struct{}{} // We need to maintain account deletions explicitly (will remain set indefinitely) + delete(s.snapAccounts, obj.address) // Clear out any previously updated account data (may be recreated via a ressurrect) + delete(s.snapStorage, obj.address) // Clear out any previously updated storage data (may be recreated via a ressurrect) } } else { obj.finalise(true) // Prefetch slots in the background @@ -932,6 +954,9 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) { // It is called in between transactions to get the root hash that // goes into transaction receipts. func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { + if s.diffEnabled { + return s.trie.Hash() + } // Finalise all the dirty storage states and write them into the tries s.Finalise(deleteEmptyObjects) @@ -983,7 +1008,7 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { // at transaction boundary level to ensure we capture state clearing. if s.snap != nil && !obj.deleted { s.snapMux.Lock() - s.snapAccounts[obj.addrHash] = snapshot.SlimAccountRLP(obj.data.Nonce, obj.data.Balance, obj.data.Root, obj.data.CodeHash) + s.snapAccounts[obj.address] = snapshot.SlimAccountRLP(obj.data.Nonce, obj.data.Balance, obj.data.Root, obj.data.CodeHash) s.snapMux.Unlock() } data, err := rlp.EncodeToBytes(obj) @@ -1051,14 +1076,71 @@ func (s *StateDB) clearJournalAndRefund() { s.validRevisions = s.validRevisions[:0] // Snapshots can be created without journal entires } +func (s *StateDB) LightCommit() (common.Hash, *types.DiffLayer, error) { + codeWriter := s.db.TrieDB().DiskDB().NewBatch() + + // Step1 write code + for codeHash, code := range s.diffCode { + rawdb.WriteCode(codeWriter, codeHash, code) + if codeWriter.ValueSize() >= ethdb.IdealBatchSize { + if err := codeWriter.Write(); err != nil { + return common.Hash{}, nil, err + } + codeWriter.Reset() + } + } + if codeWriter.ValueSize() > 0 { + if err := codeWriter.Write(); err != nil { + return common.Hash{}, nil, err + } + } + + // Step2 commit account storage + for account, diff := range s.diffTries { + root, err := diff.Commit(nil) + if err != nil { + return common.Hash{}, nil, err + } + s.db.CacheStorage(crypto.Keccak256Hash(account[:]), root, diff) + } + + // Step3 commit account trie + var account Account + root, err := s.trie.Commit(func(_ [][]byte, _ []byte, leaf []byte, parent common.Hash) error { + if err := rlp.DecodeBytes(leaf, &account); err != nil { + return nil + } + if account.Root != emptyRoot { + s.db.TrieDB().Reference(account.Root, parent) + } + return nil + }) + if err != nil { + return common.Hash{}, nil, err + } + if root != emptyRoot { + s.db.CacheAccount(root, s.trie) + } + + s.snap, s.snapDestructs, s.snapAccounts, s.snapStorage = nil, nil, nil, nil + s.diffTries, s.diffCode = nil, nil + return root, s.diffLayer, nil +} + // Commit writes the state to the underlying in-memory trie database. -func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { +func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, *types.DiffLayer, error) { if s.dbErr != nil { - return common.Hash{}, fmt.Errorf("commit aborted due to earlier error: %v", s.dbErr) + return common.Hash{}, nil, fmt.Errorf("commit aborted due to earlier error: %v", s.dbErr) } // Finalize any pending changes and merge everything into the tries root := s.IntermediateRoot(deleteEmptyObjects) - + if s.diffEnabled { + return s.LightCommit() + } + var diffLayer *types.DiffLayer + if s.snap != nil { + diffLayer = &types.DiffLayer{} + } commitFuncs := []func() error{ func() error { // Commit objects to the trie, measuring the elapsed time @@ -1086,6 +1168,19 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { }() } + if s.snap != nil { + for addr := range s.stateObjectsDirty { + if obj := s.stateObjects[addr]; !obj.deleted { + if obj.code != nil && obj.dirtyCode { + diffLayer.Codes = append(diffLayer.Codes, types.DiffCode{ + Hash: common.BytesToHash(obj.CodeHash()), + Code: obj.code, + }) + } + } + } + } + for addr := range s.stateObjectsDirty { if obj := s.stateObjects[addr]; !obj.deleted { // Write any contract code associated with the state object @@ -1161,7 +1256,12 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { log.Warn("Failed to cap snapshot tree", "root", root, "layers", s.snaps.CapLimit(), "err", err) } } - s.snap, s.snapDestructs, s.snapAccounts, s.snapStorage = nil, nil, nil, nil + } + return nil + }, + func() error { + if s.snap != nil { + diffLayer.Destructs, diffLayer.Accounts, diffLayer.Storages = s.SnapToDiffLayer() } return nil }, @@ -1176,11 +1276,64 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { for i := 0; i < len(commitFuncs); i++ { r := <-commitRes if r != nil { - return common.Hash{}, r + return common.Hash{}, nil, r } } + s.snap, s.snapDestructs, s.snapAccounts, s.snapStorage = nil, nil, nil, nil + return root, diffLayer, nil +} + +func (s *StateDB) DiffLayerToSnap(diffLayer *types.DiffLayer) (map[common.Address]struct{}, map[common.Address][]byte, map[common.Address]map[string][]byte, error) { + snapDestructs := make(map[common.Address]struct{}) + snapAccounts := make(map[common.Address][]byte) + snapStorage := make(map[common.Address]map[string][]byte) - return root, nil + for _, des := range diffLayer.Destructs { + snapDestructs[des] = struct{}{} + } + for _, account := range diffLayer.Accounts { + snapAccounts[account.Account] = account.Blob + } + for _, storage := range diffLayer.Storages { + if len(storage.Keys) != len(storage.Vals) { + return nil, nil, nil, errors.New("invalid diffLayer: length of keys and values mismatch") + } + snapStorage[storage.Account] = make(map[string][]byte, len(storage.Keys)) + n := len(storage.Keys) + for i := 0; i < n; i++ { + snapStorage[storage.Account][storage.Keys[i]] = storage.Vals[i] + } + } + return snapDestructs, snapAccounts, snapStorage, nil +} + +func (s *StateDB) SnapToDiffLayer() ([]common.Address, []types.DiffAccount, []types.DiffStorage) { + destructs := make([]common.Address, 0, len(s.snapDestructs)) + for account := range s.snapDestructs { + destructs = append(destructs, account) + } + accounts := make([]types.DiffAccount, 0, len(s.snapAccounts)) + for accountHash, account := range s.snapAccounts { + accounts = append(accounts, types.DiffAccount{ + Account: accountHash, + Blob: account, + }) + } + storages := make([]types.DiffStorage, 0, len(s.snapStorage)) + for accountHash, storage := range s.snapStorage { + keys := make([]string, 0, len(storage)) + values := make([][]byte, 0, len(storage)) + for k, v := range storage { + keys = append(keys, k) + values = append(values, v) + } + storages = append(storages, types.DiffStorage{ + Account: accountHash, + Keys: keys, + Vals: values, + }) + } + return destructs, accounts, storages } // PrepareAccessList handles the preparatory steps for executing a state transition with diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index 9524e3730d..2c0b9296ff 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -102,7 +102,7 @@ func TestIntermediateLeaks(t *testing.T) { } // Commit and cross check the databases. - transRoot, err := transState.Commit(false) + transRoot, _, err := transState.Commit(false) if err != nil { t.Fatalf("failed to commit transition state: %v", err) } @@ -110,7 +110,7 @@ func TestIntermediateLeaks(t *testing.T) { t.Errorf("can not commit trie %v to persistent database", transRoot.Hex()) } - finalRoot, err := finalState.Commit(false) + finalRoot, _, err := finalState.Commit(false) if err != nil { t.Fatalf("failed to commit final state: %v", err) } @@ -473,7 +473,7 @@ func (test *snapshotTest) checkEqual(state, checkstate *StateDB) error { func TestTouchDelete(t *testing.T) { s := newStateTest() s.state.GetOrNewStateObject(common.Address{}) - root, _ := s.state.Commit(false) + root, _, _ := s.state.Commit(false) s.state, _ = New(root, s.state.db, s.state.snaps) snapshot := s.state.Snapshot() @@ -675,7 +675,7 @@ func TestDeleteCreateRevert(t *testing.T) { addr := common.BytesToAddress([]byte("so")) state.SetBalance(addr, big.NewInt(1)) - root, _ := state.Commit(false) + root, _, _ := state.Commit(false) state, _ = New(root, state.db, state.snaps) // Simulate self-destructing in one transaction, then create-reverting in another @@ -687,7 +687,7 @@ func TestDeleteCreateRevert(t *testing.T) { state.RevertToSnapshot(id) // Commit the entire state and make sure we don't crash and have the correct state - root, _ = state.Commit(true) + root, _, _ = state.Commit(true) state, _ = New(root, state.db, state.snaps) if state.getStateObject(addr) != nil { @@ -712,7 +712,7 @@ func TestMissingTrieNodes(t *testing.T) { a2 := common.BytesToAddress([]byte("another")) state.SetBalance(a2, big.NewInt(100)) state.SetCode(a2, []byte{1, 2, 4}) - root, _ = state.Commit(false) + root, _, _ = state.Commit(false) t.Logf("root: %x", root) // force-flush state.Database().TrieDB().Cap(0) @@ -736,7 +736,7 @@ func TestMissingTrieNodes(t *testing.T) { } // Modify the state state.SetBalance(addr, big.NewInt(2)) - root, err := state.Commit(false) + root, _, err := state.Commit(false) if err == nil { t.Fatalf("expected error, got root :%x", root) } diff --git a/core/state_processor.go b/core/state_processor.go index 858796b67a..486ca80bbf 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -17,17 +17,24 @@ package core import ( + "bytes" + "errors" "fmt" + "math/big" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/misc" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/state/snapshot" "github.com/ethereum/go-ethereum/core/systemcontracts" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" ) // StateProcessor is a basic Processor, which takes care of transitioning @@ -49,6 +56,173 @@ func NewStateProcessor(config *params.ChainConfig, bc *BlockChain, engine consen } } +type LightStateProcessor struct { + StateProcessor +} + +func (p *LightStateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) { + // TODO fetch differ from somewhere else + var diffLayer *types.DiffLayer + if diffLayer == nil { + return p.StateProcessor.Process(block, statedb, cfg) + } + statedb.MarkDiffEnabled() + fullDiffCode := make(map[common.Hash][]byte, len(diffLayer.Codes)) + diffTries := make(map[common.Address]state.Trie) + diffCode := make(map[common.Hash][]byte) + + snapDestructs, snapAccounts, snapStorage, err := statedb.DiffLayerToSnap(diffLayer) + if err != nil { + return nil, nil, 0, err + } + + for _, c := range diffLayer.Codes { + fullDiffCode[c.Hash] = c.Code + } + + for des := range snapDestructs { + statedb.Trie().TryDelete(des[:]) + } + + // TODO need improve, do it concurrently + for diffAccount, blob := range snapAccounts { + addrHash := crypto.Keccak256Hash(diffAccount[:]) + latestAccount, err := snapshot.FullAccount(blob) + if err != nil { + return nil, nil, 0, err + } + + // fetch previous state + var previousAccount state.Account + enc, err := statedb.Trie().TryGet(diffAccount[:]) + if err != nil { + return nil, nil, 0, err + } + if len(enc) != 0 { + if err := rlp.DecodeBytes(enc, &previousAccount); err != nil { + return nil, nil, 0, err + } + } + if latestAccount.Balance == nil { + latestAccount.Balance = new(big.Int) + } + if previousAccount.Balance == nil { + previousAccount.Balance = new(big.Int) + } + if previousAccount.Root == (common.Hash{}) { + previousAccount.Root = types.EmptyRootHash + } + if len(previousAccount.CodeHash) == 0 { + previousAccount.CodeHash = types.EmptyCodeHash + } + + // skip no change account + if previousAccount.Nonce == latestAccount.Nonce && + bytes.Equal(previousAccount.CodeHash, latestAccount.CodeHash) && + previousAccount.Balance.Cmp(latestAccount.Balance) == 0 && + previousAccount.Root == common.BytesToHash(latestAccount.Root) { + log.Warn("receive redundant account change in diff layer") + delete(snapAccounts, diffAccount) + delete(snapStorage, diffAccount) + continue + } + + // update code + codeHash := common.BytesToHash(latestAccount.CodeHash) + if !bytes.Equal(latestAccount.CodeHash, previousAccount.CodeHash) && + !bytes.Equal(latestAccount.CodeHash, types.EmptyCodeHash) { + if code, exist := fullDiffCode[codeHash]; exist { + if crypto.Keccak256Hash(code) == codeHash { + return nil, nil, 0, errors.New("code and codeHash mismatch") + } + diffCode[codeHash] = code + } else { + rawCode := rawdb.ReadCode(p.bc.db, codeHash) + if len(rawCode) == 0 { + return nil, nil, 0, errors.New("missing code in difflayer") + } + } + } + + //update storage + latestRoot := common.BytesToHash(latestAccount.Root) + if latestRoot != previousAccount.Root && latestRoot != types.EmptyRootHash { + accountTrie, err := statedb.Database().OpenStorageTrie(addrHash, previousAccount.Root) + if err != nil { + return nil, nil, 0, err + } + if storageChange, exist := snapStorage[diffAccount]; exist { + for k, v := range storageChange { + if len(v) != 0 { + accountTrie.TryUpdate([]byte(k), v) + } else { + accountTrie.TryDelete([]byte(k)) + } + } + } else { + return nil, nil, 0, errors.New("missing storage change in difflayer") + } + // check storage root + accountRootHash := accountTrie.Hash() + if latestRoot != accountRootHash { + return nil, nil, 0, errors.New("account storage root mismatch") + } + diffTries[diffAccount] = accountTrie + } else { + delete(snapStorage, diffAccount) + } + + // can't trust the blob, need encode by our-self. + latestStateAccount := state.Account{ + Nonce: latestAccount.Nonce, + Balance: latestAccount.Balance, + Root: common.BytesToHash(latestAccount.Root), + CodeHash: latestAccount.CodeHash, + } + bz, err := rlp.EncodeToBytes(&latestStateAccount) + if err != nil { + return nil, nil, 0, err + } + err = statedb.Trie().TryUpdate(diffAccount[:], bz) + if err != nil { + return nil, nil, 0, err + } + } + + // remove redundant storage change + for account, _ := range snapStorage { + if _, exist := snapAccounts[account]; !exist { + log.Warn("receive redundant storage change in diff layer") + delete(snapStorage, account) + } + } + + // remove redundant code + if len(fullDiffCode) != len(diffLayer.Codes) { + diffLayer.Codes = make([]types.DiffCode, 0, len(diffCode)) + for hash, code := range diffCode { + diffLayer.Codes = append(diffLayer.Codes, types.DiffCode{ + Hash: hash, + Code: code, + }) + } + } + if len(snapAccounts) != len(diffLayer.Accounts) || len(snapStorage) != len(diffLayer.Storages) { + diffLayer.Destructs, diffLayer.Accounts, diffLayer.Storages = statedb.SnapToDiffLayer() + } + statedb.SetDiff(diffLayer, diffTries, diffCode) + statedb.SetSnapData(snapDestructs, snapAccounts, snapStorage) + + var allLogs []*types.Log + var gasUsed uint64 + for _, receipt := range diffLayer.Receipts { + allLogs = append(allLogs, receipt.Logs...) + gasUsed += receipt.GasUsed + } + + return diffLayer.Receipts, allLogs, gasUsed, nil +} + // Process processes the state changes according to the Ethereum rules by running // the transaction messages using the statedb and applying any rewards to both // the processor (coinbase) and any included uncles. diff --git a/core/types/block.go b/core/types/block.go index b33493ef7d..4c0dc1089d 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -26,13 +26,17 @@ import ( "sync/atomic" "time" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/rlp" ) var ( - EmptyRootHash = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") + EmptyRootHash = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") + EmptyCodeHash = crypto.Keccak256(nil) + EmptyUncleHash = rlpHash([]*Header(nil)) ) @@ -366,3 +370,30 @@ func (b *Block) Hash() common.Hash { } type Blocks []*Block + +// journalDestruct is an account deletion entry in a diffLayer's disk journal. +type DiffLayer struct { + Hash common.Hash + StateRoot common.Hash + Receipts Receipts // Receipts are duplicated stored to simplify the logic + Codes []DiffCode + Destructs []common.Address + Accounts []DiffAccount + Storages []DiffStorage +} + +type DiffCode struct { + Hash common.Hash + Code []byte +} + +type DiffAccount struct { + Account common.Address + Blob []byte +} + +type DiffStorage struct { + Account common.Address + Keys []string + Vals [][]byte +} diff --git a/eth/backend.go b/eth/backend.go index b52591fd71..d2059dfe03 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -128,7 +128,8 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { ethashConfig.NotifyFull = config.Miner.NotifyFull // Assemble the Ethereum object - chainDb, err := stack.OpenDatabaseWithFreezer("chaindata", config.DatabaseCache, config.DatabaseHandles, config.DatabaseFreezer, "eth/db/chaindata/", false) + chainDb, err := stack.OpenAndMergeDatabase("chaindata", config.DatabaseCache, config.DatabaseHandles, + config.DatabaseFreezer, config.DatabaseDiff, "eth/db/chaindata/", false, config.PersistDiff) if err != nil { return nil, err } diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 40dece429a..3166a8866a 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -159,6 +159,8 @@ type Config struct { DatabaseHandles int `toml:"-"` DatabaseCache int DatabaseFreezer string + DatabaseDiff string + PersistDiff bool TrieCleanCache int TrieCleanCacheJournal string `toml:",omitempty"` // Disk journal directory for trie cache to survive node restarts diff --git a/eth/ethconfig/gen_config.go b/eth/ethconfig/gen_config.go index fa31b78335..3cc9759787 100644 --- a/eth/ethconfig/gen_config.go +++ b/eth/ethconfig/gen_config.go @@ -40,6 +40,7 @@ func (c Config) MarshalTOML() (interface{}, error) { DatabaseHandles int `toml:"-"` DatabaseCache int DatabaseFreezer string + DatabaseDiff string TrieCleanCache int TrieCleanCacheJournal string `toml:",omitempty"` TrieCleanCacheRejournal time.Duration `toml:",omitempty"` @@ -48,6 +49,7 @@ func (c Config) MarshalTOML() (interface{}, error) { TriesInMemory uint64 `toml:",omitempty"` SnapshotCache int Preimages bool + PersistDiff bool Miner miner.Config Ethash ethash.Config TxPool core.TxPoolConfig @@ -84,6 +86,7 @@ func (c Config) MarshalTOML() (interface{}, error) { enc.DatabaseHandles = c.DatabaseHandles enc.DatabaseCache = c.DatabaseCache enc.DatabaseFreezer = c.DatabaseFreezer + enc.DatabaseDiff = c.DatabaseDiff enc.TrieCleanCache = c.TrieCleanCache enc.TrieCleanCacheJournal = c.TrieCleanCacheJournal enc.TrieCleanCacheRejournal = c.TrieCleanCacheRejournal @@ -92,6 +95,7 @@ func (c Config) MarshalTOML() (interface{}, error) { enc.TriesInMemory = c.TriesInMemory enc.SnapshotCache = c.SnapshotCache enc.Preimages = c.Preimages + enc.PersistDiff = c.PersistDiff enc.Miner = c.Miner enc.Ethash = c.Ethash enc.TxPool = c.TxPool @@ -133,6 +137,8 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { DatabaseHandles *int `toml:"-"` DatabaseCache *int DatabaseFreezer *string + DatabaseDiff *string + PersistDiff *bool TrieCleanCache *int TrieCleanCacheJournal *string `toml:",omitempty"` TrieCleanCacheRejournal *time.Duration `toml:",omitempty"` @@ -224,6 +230,12 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.DatabaseFreezer != nil { c.DatabaseFreezer = *dec.DatabaseFreezer } + if dec.DatabaseDiff != nil { + c.DatabaseDiff = *dec.DatabaseDiff + } + if dec.PersistDiff != nil { + c.PersistDiff = *dec.PersistDiff + } if dec.TrieCleanCache != nil { c.TrieCleanCache = *dec.TrieCleanCache } diff --git a/eth/state_accessor.go b/eth/state_accessor.go index aaabd1d152..e9341d6837 100644 --- a/eth/state_accessor.go +++ b/eth/state_accessor.go @@ -119,7 +119,7 @@ func (eth *Ethereum) stateAtBlock(block *types.Block, reexec uint64, base *state return nil, fmt.Errorf("processing block %d failed: %v", current.NumberU64(), err) } // Finalize the state so any modifications are written to the trie - root, err := statedb.Commit(eth.blockchain.Config().IsEIP158(current.Number())) + root, _, err := statedb.Commit(eth.blockchain.Config().IsEIP158(current.Number())) if err != nil { return nil, err } diff --git a/ethdb/database.go b/ethdb/database.go index 0dc14624b9..40d0e01d57 100644 --- a/ethdb/database.go +++ b/ethdb/database.go @@ -118,11 +118,17 @@ type AncientStore interface { io.Closer } +type DiffStore interface { + DiffStore() KeyValueStore + SetDiffStore(diff KeyValueStore) +} + // Database contains all the methods required by the high level database to not // only access the key-value data store but also the chain freezer. type Database interface { Reader Writer + DiffStore Batcher Iteratee Stater diff --git a/node/node.go b/node/node.go index f2602dee47..3a3331bcaa 100644 --- a/node/node.go +++ b/node/node.go @@ -30,6 +30,7 @@ import ( "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/ethdb/leveldb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p" @@ -578,6 +579,22 @@ func (n *Node) OpenDatabase(name string, cache, handles int, namespace string, r return db, err } +func (n *Node) OpenAndMergeDatabase(name string, cache, handles int, freezer, diff, namespace string, readonly, persistDiff bool) (ethdb.Database, error) { + chainDB, err := n.OpenDatabaseWithFreezer(name, cache, handles, freezer, namespace, readonly) + if err != nil { + return nil, err + } + if persistDiff { + diffStore, err := n.OpenDiffDatabase(name, handles, diff, namespace, readonly) + if err != nil { + chainDB.Close() + return nil, err + } + chainDB.SetDiffStore(diffStore) + } + return chainDB, nil +} + // OpenDatabaseWithFreezer opens an existing database with the given name (or // creates one if no previous can be found) from within the node's data directory, // also attaching a chain freezer to it that moves ancient chain data from the @@ -611,6 +628,30 @@ func (n *Node) OpenDatabaseWithFreezer(name string, cache, handles int, freezer, return db, err } +func (n *Node) OpenDiffDatabase(name string, handles int, diff, namespace string, readonly bool) (*leveldb.Database, error) { + n.lock.Lock() + defer n.lock.Unlock() + if n.state == closedState { + return nil, ErrNodeStopped + } + + var db *leveldb.Database + var err error + if n.config.DataDir == "" { + panic("datadir is missing") + } else { + root := n.ResolvePath(name) + switch { + case diff == "": + diff = filepath.Join(root, "diff") + case !filepath.IsAbs(diff): + diff = n.ResolvePath(diff) + } + db, err = leveldb.New(diff, 0, handles, namespace, readonly) + } + return db, err +} + // ResolvePath returns the absolute path of a resource in the instance directory. func (n *Node) ResolvePath(x string) string { return n.config.ResolvePath(x) diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 19c79b6eed..77d4fd08d4 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -226,7 +226,7 @@ func MakePreState(db ethdb.Database, accounts core.GenesisAlloc, snapshotter boo } } // Commit and re-open to start with a clean state. - root, _ := statedb.Commit(false) + root, _, _ := statedb.Commit(false) var snaps *snapshot.Tree if snapshotter { From 3d8a997e5ac0262674dc770903daaa027e307bab Mon Sep 17 00:00:00 2001 From: fudongbai <296179868@qq.com> Date: Mon, 23 Aug 2021 21:30:53 +0800 Subject: [PATCH 02/22] add difflayer protocol --- cmd/utils/flags.go | 7 ++ core/blockchain.go | 91 ++++++++++----- core/rawdb/accessors_chain.go | 4 +- core/types/block.go | 14 +++ eth/backend.go | 5 + eth/ethconfig/config.go | 1 + eth/fetcher/block_fetcher.go | 13 ++- eth/fetcher/block_fetcher_test.go | 36 +++--- eth/handler.go | 30 ++++- eth/handler_diff.go | 75 +++++++++++++ eth/handler_eth.go | 14 ++- eth/peer.go | 23 ++++ eth/peerset.go | 80 +++++++++++++- eth/protocols/diff/discovery.go | 32 ++++++ eth/protocols/diff/handler.go | 177 ++++++++++++++++++++++++++++++ eth/protocols/diff/handshake.go | 81 ++++++++++++++ eth/protocols/diff/peer.go | 65 +++++++++++ eth/protocols/diff/protocol.go | 120 ++++++++++++++++++++ eth/protocols/diff/tracker.go | 26 +++++ eth/protocols/eth/broadcast.go | 13 ++- eth/protocols/eth/handler.go | 4 + eth/protocols/eth/peer.go | 8 +- les/fetcher.go | 4 +- 23 files changed, 861 insertions(+), 62 deletions(-) create mode 100644 eth/handler_diff.go create mode 100644 eth/protocols/diff/discovery.go create mode 100644 eth/protocols/diff/handler.go create mode 100644 eth/protocols/diff/handshake.go create mode 100644 eth/protocols/diff/peer.go create mode 100644 eth/protocols/diff/protocol.go create mode 100644 eth/protocols/diff/tracker.go diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 7f4eff9bb4..a6eebfcf7b 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -117,6 +117,10 @@ var ( Name: "directbroadcast", Usage: "Enable directly broadcast mined block to all peers", } + LightSyncFlag = cli.BoolFlag{ + Name: "lightsync", + Usage: "Enable difflayer light sync ", + } RangeLimitFlag = cli.BoolFlag{ Name: "rangelimit", Usage: "Enable 5000 blocks limit for range query", @@ -1587,6 +1591,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { if ctx.GlobalIsSet(DirectBroadcastFlag.Name) { cfg.DirectBroadcast = ctx.GlobalBool(DirectBroadcastFlag.Name) } + if ctx.GlobalIsSet(LightSyncFlag.Name) { + cfg.LightSync = ctx.GlobalBool(LightSyncFlag.Name) + } if ctx.GlobalIsSet(RangeLimitFlag.Name) { cfg.RangeLimit = ctx.GlobalBool(RangeLimitFlag.Name) } diff --git a/core/blockchain.go b/core/blockchain.go index bf9d4387ab..fc862701c5 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -80,14 +80,15 @@ var ( ) const ( - bodyCacheLimit = 256 - blockCacheLimit = 256 - diffLayerCacheLimit = 1024 - receiptsCacheLimit = 10000 - txLookupCacheLimit = 1024 - maxFutureBlocks = 256 - maxTimeFutureBlocks = 30 - maxBeyondBlocks = 2048 + bodyCacheLimit = 256 + blockCacheLimit = 256 + diffLayerCacheLimit = 1024 + diffLayerRLPCacheLimit = 256 + receiptsCacheLimit = 10000 + txLookupCacheLimit = 1024 + maxFutureBlocks = 256 + maxTimeFutureBlocks = 30 + maxBeyondBlocks = 2048 diffLayerfreezerRecheckInterval = 3 * time.Second diffLayerfreezerBlockLimit = 864000 // The number of blocks that should be kept in disk. @@ -191,15 +192,16 @@ type BlockChain struct { currentBlock atomic.Value // Current head of the block chain currentFastBlock atomic.Value // Current head of the fast-sync chain (may be above the block chain!) - stateCache state.Database // State database to reuse between imports (contains state cache) - bodyCache *lru.Cache // Cache for the most recent block bodies - bodyRLPCache *lru.Cache // Cache for the most recent block bodies in RLP encoded format - receiptsCache *lru.Cache // Cache for the most recent receipts per block - blockCache *lru.Cache // Cache for the most recent entire blocks - txLookupCache *lru.Cache // Cache for the most recent transaction lookup data. - futureBlocks *lru.Cache // future blocks are blocks added for later processing - diffLayerCache *lru.Cache // Cache for the diffLayers - diffQueue *prque.Prque // A Priority queue to store recent diff layer + stateCache state.Database // State database to reuse between imports (contains state cache) + bodyCache *lru.Cache // Cache for the most recent block bodies + bodyRLPCache *lru.Cache // Cache for the most recent block bodies in RLP encoded format + receiptsCache *lru.Cache // Cache for the most recent receipts per block + blockCache *lru.Cache // Cache for the most recent entire blocks + txLookupCache *lru.Cache // Cache for the most recent transaction lookup data. + futureBlocks *lru.Cache // future blocks are blocks added for later processing + diffLayerCache *lru.Cache // Cache for the diffLayers + diffLayerRLPCache *lru.Cache // Cache for the rlp encoded diffLayers + diffQueue *prque.Prque // A Priority queue to store recent diff layer quit chan struct{} // blockchain quit channel wg sync.WaitGroup // chain processing wait group for shutting down @@ -232,6 +234,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par txLookupCache, _ := lru.New(txLookupCacheLimit) futureBlocks, _ := lru.New(maxFutureBlocks) diffLayerCache, _ := lru.New(diffLayerCacheLimit) + diffLayerRLPCache, _ := lru.New(diffLayerRLPCacheLimit) bc := &BlockChain{ chainConfig: chainConfig, @@ -243,19 +246,20 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par Journal: cacheConfig.TrieCleanJournal, Preimages: cacheConfig.Preimages, }), - triesInMemory: cacheConfig.TriesInMemory, - quit: make(chan struct{}), - shouldPreserve: shouldPreserve, - bodyCache: bodyCache, - bodyRLPCache: bodyRLPCache, - receiptsCache: receiptsCache, - blockCache: blockCache, - diffLayerCache: diffLayerCache, - txLookupCache: txLookupCache, - futureBlocks: futureBlocks, - engine: engine, - vmConfig: vmConfig, - diffQueue: prque.New(nil), + triesInMemory: cacheConfig.TriesInMemory, + quit: make(chan struct{}), + shouldPreserve: shouldPreserve, + bodyCache: bodyCache, + bodyRLPCache: bodyRLPCache, + receiptsCache: receiptsCache, + blockCache: blockCache, + diffLayerCache: diffLayerCache, + diffLayerRLPCache: diffLayerRLPCache, + txLookupCache: txLookupCache, + futureBlocks: futureBlocks, + engine: engine, + vmConfig: vmConfig, + diffQueue: prque.New(nil), } bc.validator = NewBlockValidator(chainConfig, bc, engine) bc.processor = NewStateProcessor(chainConfig, bc, engine) @@ -893,6 +897,33 @@ func (bc *BlockChain) GetBodyRLP(hash common.Hash) rlp.RawValue { return body } +// GetBodyRLP retrieves a diff layer in RLP encoding from the database by hash, +// caching it if found. +func (bc *BlockChain) GetDiffLayerRLP(hash common.Hash) rlp.RawValue { + // Short circuit if the diffLayer's already in the cache, retrieve otherwise + if cached, ok := bc.diffLayerRLPCache.Get(hash); ok { + return cached.(rlp.RawValue) + } + if cached, ok := bc.diffLayerCache.Get(hash); ok { + diff := cached.(*types.DiffLayer) + bz, err := rlp.EncodeToBytes(diff) + if err != nil { + return nil + } + bc.diffLayerRLPCache.Add(hash, bz) + return bz + } + diffStore := bc.db.DiffStore() + if diffStore == nil { + return nil + } + rawData := rawdb.ReadDiffLayerRLP(diffStore, hash) + if len(rawData) != 0 { + bc.diffLayerRLPCache.Add(hash, rawData) + } + return rawData +} + // HasBlock checks if a block is fully present in the database or not. func (bc *BlockChain) HasBlock(hash common.Hash, number uint64) bool { if bc.blockCache.Contains(hash) { diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index 6e14cbbd8b..34f16ebb7a 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -461,7 +461,7 @@ func WriteDiffLayerRLP(db ethdb.KeyValueWriter, hash common.Hash, rlp rlp.RawVal } } -func ReadDiffLayer(db ethdb.Reader, hash common.Hash) *types.DiffLayer { +func ReadDiffLayer(db ethdb.KeyValueReader, hash common.Hash) *types.DiffLayer { data := ReadDiffLayerRLP(db, hash) if len(data) == 0 { return nil @@ -474,7 +474,7 @@ func ReadDiffLayer(db ethdb.Reader, hash common.Hash) *types.DiffLayer { return diff } -func ReadDiffLayerRLP(db ethdb.Reader, hash common.Hash) rlp.RawValue { +func ReadDiffLayerRLP(db ethdb.KeyValueReader, hash common.Hash) rlp.RawValue { data, _ := db.Get(diffLayerKey(hash)) return data } diff --git a/core/types/block.go b/core/types/block.go index 4c0dc1089d..7b8d9dc41b 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -19,6 +19,7 @@ package types import ( "encoding/binary" + "errors" "fmt" "io" "math/big" @@ -373,6 +374,7 @@ type Blocks []*Block // journalDestruct is an account deletion entry in a diffLayer's disk journal. type DiffLayer struct { + DiffHash common.Hash `rlp:"_"` Hash common.Hash StateRoot common.Hash Receipts Receipts // Receipts are duplicated stored to simplify the logic @@ -382,6 +384,18 @@ type DiffLayer struct { Storages []DiffStorage } +func (d *DiffLayer) Validate() error { + if d.Hash == (common.Hash{}) || d.StateRoot == (common.Hash{}) { + return errors.New("hash can't be empty") + } + for _, storage := range d.Storages { + if len(storage.Keys) != len(storage.Vals) { + return errors.New("the length of keys and values mismatch in storage") + } + } + return nil +} + type DiffCode struct { Hash common.Hash Code []byte diff --git a/eth/backend.go b/eth/backend.go index d2059dfe03..2c6d9b87c1 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -26,6 +26,8 @@ import ( "sync/atomic" "time" + "github.com/ethereum/go-ethereum/eth/protocols/diff" + "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -233,6 +235,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { Checkpoint: checkpoint, Whitelist: config.Whitelist, DirectBroadcast: config.DirectBroadcast, + LightSync: config.LightSync, }); err != nil { return nil, err } @@ -538,6 +541,8 @@ func (s *Ethereum) Protocols() []p2p.Protocol { if s.config.SnapshotCache > 0 { protos = append(protos, snap.MakeProtocols((*snapHandler)(s.handler), s.snapDialCandidates)...) } + // diff protocol can still open without snap protocol + protos = append(protos, diff.MakeProtocols((*diffHandler)(s.handler), s.snapDialCandidates)...) return protos } diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 3166a8866a..b36498df58 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -133,6 +133,7 @@ type Config struct { NoPruning bool // Whether to disable pruning and flush everything to disk DirectBroadcast bool + LightSync bool // Whether support light sync RangeLimit bool TxLookupLimit uint64 `toml:",omitempty"` // The maximum number of blocks from head whose tx indices are reserved. diff --git a/eth/fetcher/block_fetcher.go b/eth/fetcher/block_fetcher.go index 4819e9eab5..4007fee650 100644 --- a/eth/fetcher/block_fetcher.go +++ b/eth/fetcher/block_fetcher.go @@ -82,6 +82,9 @@ type headerRequesterFn func(common.Hash) error // bodyRequesterFn is a callback type for sending a body retrieval request. type bodyRequesterFn func([]common.Hash) error +// DiffRequesterFn is a callback type for sending a diff layer retrieval request. +type DiffRequesterFn func([]common.Hash) error + // headerVerifierFn is a callback type to verify a block's header for fast propagation. type headerVerifierFn func(header *types.Header) error @@ -112,6 +115,8 @@ type blockAnnounce struct { fetchHeader headerRequesterFn // Fetcher function to retrieve the header of an announced block fetchBodies bodyRequesterFn // Fetcher function to retrieve the body of an announced block + fetchDiffs DiffRequesterFn // Fetcher function to retrieve the diff layer of an announced block + } // headerFilterTask represents a batch of headers needing fetcher filtering. @@ -246,7 +251,7 @@ func (f *BlockFetcher) Stop() { // Notify announces the fetcher of the potential availability of a new block in // the network. func (f *BlockFetcher) Notify(peer string, hash common.Hash, number uint64, time time.Time, - headerFetcher headerRequesterFn, bodyFetcher bodyRequesterFn) error { + headerFetcher headerRequesterFn, bodyFetcher bodyRequesterFn, diffFetcher DiffRequesterFn) error { block := &blockAnnounce{ hash: hash, number: number, @@ -254,6 +259,7 @@ func (f *BlockFetcher) Notify(peer string, hash common.Hash, number uint64, time origin: peer, fetchHeader: headerFetcher, fetchBodies: bodyFetcher, + fetchDiffs: diffFetcher, } select { case f.notify <- block: @@ -481,10 +487,15 @@ func (f *BlockFetcher) loop() { // Create a closure of the fetch and schedule in on a new thread fetchHeader, hashes := f.fetching[hashes[0]].fetchHeader, hashes + fetchDiff := f.fetching[hashes[0]].fetchDiffs + gopool.Submit(func() { if f.fetchingHook != nil { f.fetchingHook(hashes) } + if fetchDiff != nil { + fetchDiff(hashes) + } for _, hash := range hashes { headerFetchMeter.Mark(1) fetchHeader(hash) // Suboptimal, but protocol doesn't allow batch header retrievals diff --git a/eth/fetcher/block_fetcher_test.go b/eth/fetcher/block_fetcher_test.go index a6eef71da0..9a40d4cb3e 100644 --- a/eth/fetcher/block_fetcher_test.go +++ b/eth/fetcher/block_fetcher_test.go @@ -343,7 +343,7 @@ func testSequentialAnnouncements(t *testing.T, light bool) { } } for i := len(hashes) - 2; i >= 0; i-- { - tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher) + tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher, nil) verifyImportEvent(t, imported, true) } verifyImportDone(t, imported) @@ -392,9 +392,9 @@ func testConcurrentAnnouncements(t *testing.T, light bool) { } } for i := len(hashes) - 2; i >= 0; i-- { - tester.fetcher.Notify("first", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), firstHeaderWrapper, firstBodyFetcher) - tester.fetcher.Notify("second", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout+time.Millisecond), secondHeaderWrapper, secondBodyFetcher) - tester.fetcher.Notify("second", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout-time.Millisecond), secondHeaderWrapper, secondBodyFetcher) + tester.fetcher.Notify("first", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), firstHeaderWrapper, firstBodyFetcher, nil) + tester.fetcher.Notify("second", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout+time.Millisecond), secondHeaderWrapper, secondBodyFetcher, nil) + tester.fetcher.Notify("second", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout-time.Millisecond), secondHeaderWrapper, secondBodyFetcher, nil) verifyImportEvent(t, imported, true) } verifyImportDone(t, imported) @@ -441,7 +441,7 @@ func testOverlappingAnnouncements(t *testing.T, light bool) { } for i := len(hashes) - 2; i >= 0; i-- { - tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher) + tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher, nil) select { case <-imported: case <-time.After(time.Second): @@ -488,7 +488,7 @@ func testPendingDeduplication(t *testing.T, light bool) { } // Announce the same block many times until it's fetched (wait for any pending ops) for checkNonExist() { - tester.fetcher.Notify("repeater", hashes[0], 1, time.Now().Add(-arriveTimeout), headerWrapper, bodyFetcher) + tester.fetcher.Notify("repeater", hashes[0], 1, time.Now().Add(-arriveTimeout), headerWrapper, bodyFetcher, nil) time.Sleep(time.Millisecond) } time.Sleep(delay) @@ -532,12 +532,12 @@ func testRandomArrivalImport(t *testing.T, light bool) { } for i := len(hashes) - 1; i >= 0; i-- { if i != skip { - tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher) + tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher, nil) time.Sleep(time.Millisecond) } } // Finally announce the skipped entry and check full import - tester.fetcher.Notify("valid", hashes[skip], uint64(len(hashes)-skip-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher) + tester.fetcher.Notify("valid", hashes[skip], uint64(len(hashes)-skip-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher, nil) verifyImportCount(t, imported, len(hashes)-1) verifyChainHeight(t, tester, uint64(len(hashes)-1)) } @@ -560,7 +560,7 @@ func TestQueueGapFill(t *testing.T) { for i := len(hashes) - 1; i >= 0; i-- { if i != skip { - tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher) + tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher, nil) time.Sleep(time.Millisecond) } } @@ -593,7 +593,7 @@ func TestImportDeduplication(t *testing.T) { tester.fetcher.importedHook = func(header *types.Header, block *types.Block) { imported <- block } // Announce the duplicating block, wait for retrieval, and also propagate directly - tester.fetcher.Notify("valid", hashes[0], 1, time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher) + tester.fetcher.Notify("valid", hashes[0], 1, time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher, nil) <-fetching tester.fetcher.Enqueue("valid", blocks[hashes[0]]) @@ -669,14 +669,14 @@ func testDistantAnnouncementDiscarding(t *testing.T, light bool) { tester.fetcher.fetchingHook = func(hashes []common.Hash) { fetching <- struct{}{} } // Ensure that a block with a lower number than the threshold is discarded - tester.fetcher.Notify("lower", hashes[low], blocks[hashes[low]].NumberU64(), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher) + tester.fetcher.Notify("lower", hashes[low], blocks[hashes[low]].NumberU64(), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher, nil) select { case <-time.After(50 * time.Millisecond): case <-fetching: t.Fatalf("fetcher requested stale header") } // Ensure that a block with a higher number than the threshold is discarded - tester.fetcher.Notify("higher", hashes[high], blocks[hashes[high]].NumberU64(), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher) + tester.fetcher.Notify("higher", hashes[high], blocks[hashes[high]].NumberU64(), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher, nil) select { case <-time.After(50 * time.Millisecond): case <-fetching: @@ -712,7 +712,7 @@ func testInvalidNumberAnnouncement(t *testing.T, light bool) { } } // Announce a block with a bad number, check for immediate drop - tester.fetcher.Notify("bad", hashes[0], 2, time.Now().Add(-arriveTimeout), badHeaderFetcher, badBodyFetcher) + tester.fetcher.Notify("bad", hashes[0], 2, time.Now().Add(-arriveTimeout), badHeaderFetcher, badBodyFetcher, nil) verifyImportEvent(t, imported, false) tester.lock.RLock() @@ -726,7 +726,7 @@ func testInvalidNumberAnnouncement(t *testing.T, light bool) { goodHeaderFetcher := tester.makeHeaderFetcher("good", blocks, -gatherSlack) goodBodyFetcher := tester.makeBodyFetcher("good", blocks, 0) // Make sure a good announcement passes without a drop - tester.fetcher.Notify("good", hashes[0], 1, time.Now().Add(-arriveTimeout), goodHeaderFetcher, goodBodyFetcher) + tester.fetcher.Notify("good", hashes[0], 1, time.Now().Add(-arriveTimeout), goodHeaderFetcher, goodBodyFetcher, nil) verifyImportEvent(t, imported, true) tester.lock.RLock() @@ -765,7 +765,7 @@ func TestEmptyBlockShortCircuit(t *testing.T) { } // Iteratively announce blocks until all are imported for i := len(hashes) - 2; i >= 0; i-- { - tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher) + tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher, nil) // All announces should fetch the header verifyFetchingEvent(t, fetching, true) @@ -808,9 +808,9 @@ func TestHashMemoryExhaustionAttack(t *testing.T) { // Feed the tester a huge hashset from the attacker, and a limited from the valid peer for i := 0; i < len(attack); i++ { if i < maxQueueDist { - tester.fetcher.Notify("valid", hashes[len(hashes)-2-i], uint64(i+1), time.Now(), validHeaderFetcher, validBodyFetcher) + tester.fetcher.Notify("valid", hashes[len(hashes)-2-i], uint64(i+1), time.Now(), validHeaderFetcher, validBodyFetcher, nil) } - tester.fetcher.Notify("attacker", attack[i], 1 /* don't distance drop */, time.Now(), attackerHeaderFetcher, attackerBodyFetcher) + tester.fetcher.Notify("attacker", attack[i], 1 /* don't distance drop */, time.Now(), attackerHeaderFetcher, attackerBodyFetcher, nil) } if count := atomic.LoadInt32(&announces); count != hashLimit+maxQueueDist { t.Fatalf("queued announce count mismatch: have %d, want %d", count, hashLimit+maxQueueDist) @@ -820,7 +820,7 @@ func TestHashMemoryExhaustionAttack(t *testing.T) { // Feed the remaining valid hashes to ensure DOS protection state remains clean for i := len(hashes) - maxQueueDist - 2; i >= 0; i-- { - tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), validHeaderFetcher, validBodyFetcher) + tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), validHeaderFetcher, validBodyFetcher, nil) verifyImportEvent(t, imported, true) } verifyImportDone(t, imported) diff --git a/eth/handler.go b/eth/handler.go index f42109753d..b75f20e7e3 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -24,6 +24,8 @@ import ( "sync/atomic" "time" + "github.com/ethereum/go-ethereum/eth/protocols/diff" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/forkid" @@ -81,6 +83,7 @@ type handlerConfig struct { TxPool txPool // Transaction pool to propagate from Network uint64 // Network identifier to adfvertise Sync downloader.SyncMode // Whether to fast or full sync + LightSync bool // Whether to light sync BloomCache uint64 // Megabytes to alloc for fast sync bloom EventMux *event.TypeMux // Legacy event mux, deprecate for `feed` Checkpoint *params.TrustedCheckpoint // Hard coded checkpoint for sync challenges @@ -96,6 +99,7 @@ type handler struct { snapSync uint32 // Flag whether fast sync should operate on top of the snap protocol acceptTxs uint32 // Flag whether we're considered synchronised (enables transaction processing) directBroadcast bool + lightSync bool // Flag whether light sync should operate on top of the diff protocol checkpointNumber uint64 // Block number for the sync progress validator to cross reference checkpointHash common.Hash // Block hash for the sync progress validator to cross reference @@ -143,6 +147,7 @@ func newHandler(config *handlerConfig) (*handler, error) { peers: newPeerSet(), whitelist: config.Whitelist, directBroadcast: config.DirectBroadcast, + lightSync: config.LightSync, txsyncCh: make(chan *txsync), quitSync: make(chan struct{}), } @@ -246,6 +251,11 @@ func (h *handler) runEthPeer(peer *eth.Peer, handler eth.Handler) error { peer.Log().Error("Snapshot extension barrier failed", "err", err) return err } + diff, err := h.peers.waitDiffExtension(peer) + if err != nil { + peer.Log().Error("Diff extension barrier failed", "err", err) + return err + } // TODO(karalabe): Not sure why this is needed if !h.chainSync.handlePeerEvent(peer) { return p2p.DiscQuitting @@ -286,7 +296,7 @@ func (h *handler) runEthPeer(peer *eth.Peer, handler eth.Handler) error { peer.Log().Debug("Ethereum peer connected", "name", peer.Name()) // Register the peer locally - if err := h.peers.registerPeer(peer, snap); err != nil { + if err := h.peers.registerPeer(peer, snap, diff); err != nil { peer.Log().Error("Ethereum peer registration failed", "err", err) return err } @@ -357,6 +367,21 @@ func (h *handler) runSnapExtension(peer *snap.Peer, handler snap.Handler) error return handler(peer) } +// runDiffExtension registers a `diff` peer into the joint eth/diff peerset and +// starts handling inbound messages. As `diff` is only a satellite protocol to +// `eth`, all subsystem registrations and lifecycle management will be done by +// the main `eth` handler to prevent strange races. +func (h *handler) runDiffExtension(peer *diff.Peer, handler diff.Handler) error { + h.peerWG.Add(1) + defer h.peerWG.Done() + + if err := h.peers.registerDiffExtension(peer); err != nil { + peer.Log().Error("Diff extension registration failed", "err", err) + return err + } + return handler(peer) +} + // removePeer unregisters a peer from the downloader and fetchers, removes it from // the set of tracked peers and closes the network connection to it. func (h *handler) removePeer(id string) { @@ -453,8 +478,9 @@ func (h *handler) BroadcastBlock(block *types.Block, propagate bool) { } else { transfer = peers[:int(math.Sqrt(float64(len(peers))))] } + diff := h.chain.GetDiffLayerRLP(block.Hash()) for _, peer := range transfer { - peer.AsyncSendNewBlock(block, td) + peer.AsyncSendNewBlock(block, diff, td) } log.Trace("Propagated block", "hash", hash, "recipients", len(transfer), "duration", common.PrettyDuration(time.Since(block.ReceivedAt))) return diff --git a/eth/handler_diff.go b/eth/handler_diff.go new file mode 100644 index 0000000000..d0352a4c57 --- /dev/null +++ b/eth/handler_diff.go @@ -0,0 +1,75 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package eth + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/eth/protocols/diff" + "github.com/ethereum/go-ethereum/p2p/enode" +) + +// diffHandler implements the diff.Backend interface to handle the various network +// packets that are sent as replies or broadcasts. +type diffHandler handler + +func (h *diffHandler) Chain() *core.BlockChain { return h.chain } + +// RunPeer is invoked when a peer joins on the `diff` protocol. +func (h *diffHandler) RunPeer(peer *diff.Peer, hand diff.Handler) error { + if err := peer.Handshake(h.lightSync); err != nil { + return err + } + return (*handler)(h).runDiffExtension(peer, hand) +} + +// PeerInfo retrieves all known `diff` information about a peer. +func (h *diffHandler) PeerInfo(id enode.ID) interface{} { + if p := h.peers.peer(id.String()); p != nil { + if p.diffExt != nil { + return p.diffExt.info() + } + } + return nil +} + +// Handle is invoked from a peer's message handler when it receives a new remote +// message that the handler couldn't consume and serve itself. +func (h *diffHandler) Handle(peer *diff.Peer, packet diff.Packet) error { + // DeliverSnapPacket is invoked from a peer's message handler when it transmits a + // data packet for the local node to consume. + switch packet := packet.(type) { + case *diff.DiffLayersPacket: + diffs, _, err := packet.Unpack() + if err != nil { + return err + } + for _, d := range diffs { + if d != nil { + if err := d.Validate(); err != nil { + return err + } + } + } + // TODO, we need rateLimit here + + default: + return fmt.Errorf("unexpected snap packet type: %T", packet) + } + return nil +} diff --git a/eth/handler_eth.go b/eth/handler_eth.go index 3ff9f2245b..15305d4ae5 100644 --- a/eth/handler_eth.go +++ b/eth/handler_eth.go @@ -23,6 +23,8 @@ import ( "sync/atomic" "time" + "github.com/ethereum/go-ethereum/eth/fetcher" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" @@ -98,7 +100,6 @@ func (h *ethHandler) Handle(peer *eth.Peer, packet eth.Packet) error { case *eth.PooledTransactionsPacket: return h.txFetcher.Enqueue(peer.ID(), *packet, true) - default: return fmt.Errorf("unexpected eth packet type: %T", packet) } @@ -191,8 +192,17 @@ func (h *ethHandler) handleBlockAnnounces(peer *eth.Peer, hashes []common.Hash, unknownNumbers = append(unknownNumbers, numbers[i]) } } + // self support light sync + var diffFetcher fetcher.DiffRequesterFn + if h.lightSync { + // the peer support diff protocol + if ep := h.peers.peer(peer.ID()); ep != nil && ep.diffExt != nil { + diffFetcher = ep.diffExt.RequestDiffLayers + } + } + for i := 0; i < len(unknownHashes); i++ { - h.blockFetcher.Notify(peer.ID(), unknownHashes[i], unknownNumbers[i], time.Now(), peer.RequestOneHeader, peer.RequestBodies) + h.blockFetcher.Notify(peer.ID(), unknownHashes[i], unknownNumbers[i], time.Now(), peer.RequestOneHeader, peer.RequestBodies, diffFetcher) } return nil } diff --git a/eth/peer.go b/eth/peer.go index 1cea9c640e..581a94b585 100644 --- a/eth/peer.go +++ b/eth/peer.go @@ -21,6 +21,8 @@ import ( "sync" "time" + "github.com/ethereum/go-ethereum/eth/protocols/diff" + "github.com/ethereum/go-ethereum/eth/protocols/eth" "github.com/ethereum/go-ethereum/eth/protocols/snap" ) @@ -37,6 +39,7 @@ type ethPeerInfo struct { type ethPeer struct { *eth.Peer snapExt *snapPeer // Satellite `snap` connection + diffExt *diffPeer syncDrop *time.Timer // Connection dropper if `eth` sync progress isn't validated in time snapWait chan struct{} // Notification channel for snap connections @@ -60,11 +63,31 @@ type snapPeerInfo struct { Version uint `json:"version"` // Snapshot protocol version negotiated } +// diffPeerInfo represents a short summary of the `diff` sub-protocol metadata known +// about a connected peer. +type diffPeerInfo struct { + Version uint `json:"version"` // diff protocol version negotiated + LightSync bool `json:"light_sync"` +} + // snapPeer is a wrapper around snap.Peer to maintain a few extra metadata. type snapPeer struct { *snap.Peer } +// diffPeer is a wrapper around diff.Peer to maintain a few extra metadata. +type diffPeer struct { + *diff.Peer +} + +// info gathers and returns some `diff` protocol metadata known about a peer. +func (p *diffPeer) info() *diffPeerInfo { + return &diffPeerInfo{ + Version: p.Version(), + LightSync: p.LightSync(), + } +} + // info gathers and returns some `snap` protocol metadata known about a peer. func (p *snapPeer) info() *snapPeerInfo { return &snapPeerInfo{ diff --git a/eth/peerset.go b/eth/peerset.go index 1e864a8e46..e6d3e8a763 100644 --- a/eth/peerset.go +++ b/eth/peerset.go @@ -21,6 +21,8 @@ import ( "math/big" "sync" + "github.com/ethereum/go-ethereum/eth/protocols/diff" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/eth/protocols/eth" "github.com/ethereum/go-ethereum/eth/protocols/snap" @@ -43,6 +45,10 @@ var ( // errSnapWithoutEth is returned if a peer attempts to connect only on the // snap protocol without advertizing the eth main protocol. errSnapWithoutEth = errors.New("peer connected on snap without compatible eth support") + + // errDiffWithoutEth is returned if a peer attempts to connect only on the + // diff protocol without advertizing the eth main protocol. + errDiffWithoutEth = errors.New("peer connected on diff without compatible eth support") ) // peerSet represents the collection of active peers currently participating in @@ -54,6 +60,9 @@ type peerSet struct { snapWait map[string]chan *snap.Peer // Peers connected on `eth` waiting for their snap extension snapPend map[string]*snap.Peer // Peers connected on the `snap` protocol, but not yet on `eth` + diffWait map[string]chan *diff.Peer // Peers connected on `eth` waiting for their diff extension + diffPend map[string]*diff.Peer // Peers connected on the `diff` protocol, but not yet on `eth` + lock sync.RWMutex closed bool } @@ -64,6 +73,8 @@ func newPeerSet() *peerSet { peers: make(map[string]*ethPeer), snapWait: make(map[string]chan *snap.Peer), snapPend: make(map[string]*snap.Peer), + diffWait: make(map[string]chan *diff.Peer), + diffPend: make(map[string]*diff.Peer), } } @@ -97,6 +108,36 @@ func (ps *peerSet) registerSnapExtension(peer *snap.Peer) error { return nil } +// registerDiffExtension unblocks an already connected `eth` peer waiting for its +// `diff` extension, or if no such peer exists, tracks the extension for the time +// being until the `eth` main protocol starts looking for it. +func (ps *peerSet) registerDiffExtension(peer *diff.Peer) error { + // Reject the peer if it advertises `diff` without `eth` as `diff` is only a + // satellite protocol meaningful with the chain selection of `eth` + if !peer.RunningCap(eth.ProtocolName, eth.ProtocolVersions) { + return errDiffWithoutEth + } + // Ensure nobody can double connect + ps.lock.Lock() + defer ps.lock.Unlock() + + id := peer.ID() + if _, ok := ps.peers[id]; ok { + return errPeerAlreadyRegistered // avoid connections with the same id as existing ones + } + if _, ok := ps.diffPend[id]; ok { + return errPeerAlreadyRegistered // avoid connections with the same id as pending ones + } + // Inject the peer into an `eth` counterpart is available, otherwise save for later + if wait, ok := ps.diffWait[id]; ok { + delete(ps.diffWait, id) + wait <- peer + return nil + } + ps.diffPend[id] = peer + return nil +} + // waitExtensions blocks until all satellite protocols are connected and tracked // by the peerset. func (ps *peerSet) waitSnapExtension(peer *eth.Peer) (*snap.Peer, error) { @@ -131,9 +172,43 @@ func (ps *peerSet) waitSnapExtension(peer *eth.Peer) (*snap.Peer, error) { return <-wait, nil } +// waitDiffExtension blocks until all satellite protocols are connected and tracked +// by the peerset. +func (ps *peerSet) waitDiffExtension(peer *eth.Peer) (*diff.Peer, error) { + // If the peer does not support a compatible `diff`, don't wait + if !peer.RunningCap(diff.ProtocolName, diff.ProtocolVersions) { + return nil, nil + } + // Ensure nobody can double connect + ps.lock.Lock() + + id := peer.ID() + if _, ok := ps.peers[id]; ok { + ps.lock.Unlock() + return nil, errPeerAlreadyRegistered // avoid connections with the same id as existing ones + } + if _, ok := ps.diffWait[id]; ok { + ps.lock.Unlock() + return nil, errPeerAlreadyRegistered // avoid connections with the same id as pending ones + } + // If `diff` already connected, retrieve the peer from the pending set + if diff, ok := ps.diffPend[id]; ok { + delete(ps.diffPend, id) + + ps.lock.Unlock() + return diff, nil + } + // Otherwise wait for `diff` to connect concurrently + wait := make(chan *diff.Peer) + ps.diffWait[id] = wait + ps.lock.Unlock() + + return <-wait, nil +} + // registerPeer injects a new `eth` peer into the working set, or returns an error // if the peer is already known. -func (ps *peerSet) registerPeer(peer *eth.Peer, ext *snap.Peer) error { +func (ps *peerSet) registerPeer(peer *eth.Peer, ext *snap.Peer, diffExt *diff.Peer) error { // Start tracking the new peer ps.lock.Lock() defer ps.lock.Unlock() @@ -152,6 +227,9 @@ func (ps *peerSet) registerPeer(peer *eth.Peer, ext *snap.Peer) error { eth.snapExt = &snapPeer{ext} ps.snapPeers++ } + if diffExt != nil { + eth.diffExt = &diffPeer{diffExt} + } ps.peers[id] = eth return nil } diff --git a/eth/protocols/diff/discovery.go b/eth/protocols/diff/discovery.go new file mode 100644 index 0000000000..6a6ff066db --- /dev/null +++ b/eth/protocols/diff/discovery.go @@ -0,0 +1,32 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package diff + +import ( + "github.com/ethereum/go-ethereum/rlp" +) + +// enrEntry is the ENR entry which advertises `diff` protocol on the discovery. +type enrEntry struct { + // Ignore additional fields (for forward compatibility). + Rest []rlp.RawValue `rlp:"tail"` +} + +// ENRKey implements enr.Entry. +func (e enrEntry) ENRKey() string { + return "diff" +} diff --git a/eth/protocols/diff/handler.go b/eth/protocols/diff/handler.go new file mode 100644 index 0000000000..b6532cf684 --- /dev/null +++ b/eth/protocols/diff/handler.go @@ -0,0 +1,177 @@ +package diff + +import ( + "fmt" + "time" + + "github.com/ethereum/go-ethereum/metrics" + + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/enr" + + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/rlp" +) + +const ( + // softResponseLimit is the target maximum size of replies to data retrievals. + softResponseLimit = 2 * 1024 * 1024 + + // maxDiffLayerServe is the maximum number of diff layers to serve. + maxDiffLayerServe = 1024 +) + +// Handler is a callback to invoke from an outside runner after the boilerplate +// exchanges have passed. +type Handler func(peer *Peer) error + +type Backend interface { + // Chain retrieves the blockchain object to serve data. + Chain() *core.BlockChain + + // RunPeer is invoked when a peer joins on the `eth` protocol. The handler + // should do any peer maintenance work, handshakes and validations. If all + // is passed, control should be given back to the `handler` to process the + // inbound messages going forward. + RunPeer(peer *Peer, handler Handler) error + + PeerInfo(id enode.ID) interface{} + + Handle(peer *Peer, packet Packet) error +} + +// MakeProtocols constructs the P2P protocol definitions for `diff`. +func MakeProtocols(backend Backend, dnsdisc enode.Iterator) []p2p.Protocol { + // Filter the discovery iterator for nodes advertising diff support. + dnsdisc = enode.Filter(dnsdisc, func(n *enode.Node) bool { + var diff enrEntry + return n.Load(&diff) == nil + }) + + protocols := make([]p2p.Protocol, len(ProtocolVersions)) + for i, version := range ProtocolVersions { + version := version // Closure + + protocols[i] = p2p.Protocol{ + Name: ProtocolName, + Version: version, + Length: protocolLengths[version], + Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error { + return backend.RunPeer(newPeer(version, p, rw), func(peer *Peer) error { + return handle(backend, peer) + }) + }, + NodeInfo: func() interface{} { + return nodeInfo(backend.Chain()) + }, + PeerInfo: func(id enode.ID) interface{} { + return backend.PeerInfo(id) + }, + Attributes: []enr.Entry{&enrEntry{}}, + DialCandidates: dnsdisc, + } + } + return protocols +} + +// handle is the callback invoked to manage the life cycle of a `diff` peer. +// When this function terminates, the peer is disconnected. +func handle(backend Backend, peer *Peer) error { + for { + if err := handleMessage(backend, peer); err != nil { + peer.Log().Debug("Message handling failed in `diff`", "err", err) + return err + } + } +} + +// handleMessage is invoked whenever an inbound message is received from a +// remote peer on the `diff` protocol. The remote connection is torn down upon +// returning any error. +func handleMessage(backend Backend, peer *Peer) error { + // Read the next message from the remote peer, and ensure it's fully consumed + msg, err := peer.rw.ReadMsg() + if err != nil { + return err + } + if msg.Size > maxMessageSize { + return fmt.Errorf("%w: %v > %v", errMsgTooLarge, msg.Size, maxMessageSize) + } + defer msg.Discard() + start := time.Now() + // Track the emount of time it takes to serve the request and run the handler + if metrics.Enabled { + h := fmt.Sprintf("%s/%s/%d/%#02x", p2p.HandleHistName, ProtocolName, peer.Version(), msg.Code) + defer func(start time.Time) { + sampler := func() metrics.Sample { + return metrics.ResettingSample( + metrics.NewExpDecaySample(1028, 0.015), + ) + } + metrics.GetOrRegisterHistogramLazy(h, nil, sampler).Update(time.Since(start).Microseconds()) + }(start) + } + // Handle the message depending on its contents + switch { + case msg.Code == GetDiffLayerMsg: + res := new(GetDiffLayersPacket) + if err := msg.Decode(res); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + diffs := answerDiffLayersQuery(backend, res) + + p2p.Send(peer.rw, FullDiffLayerMsg, &FullDiffLayersPacket{ + RequestId: res.RequestId, + DiffLayersPacket: diffs, + }) + return nil + + case msg.Code == DiffLayerMsg: + // A batch of trie nodes arrived to one of our previous requests + res := new(DiffLayersPacket) + if err := msg.Decode(res); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + return backend.Handle(peer, res) + case msg.Code == FullDiffLayerMsg: + // A batch of trie nodes arrived to one of our previous requests + res := new(FullDiffLayersPacket) + if err := msg.Decode(res); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + requestTracker.Fulfil(peer.id, peer.version, FullDiffLayerMsg, res.RequestId) + return backend.Handle(peer, &res.DiffLayersPacket) + default: + return fmt.Errorf("%w: %v", errInvalidMsgCode, msg.Code) + } +} + +func answerDiffLayersQuery(backend Backend, query *GetDiffLayersPacket) []rlp.RawValue { + // Gather blocks until the fetch or network limits is reached + var ( + bytes int + diffLayers []rlp.RawValue + ) + // Need avoid transfer huge package + for lookups, hash := range query.BlockHashes { + if bytes >= softResponseLimit || len(diffLayers) >= maxDiffLayerServe || + lookups >= 2*maxDiffLayerServe { + break + } + if data := backend.Chain().GetDiffLayerRLP(hash); len(data) != 0 { + diffLayers = append(diffLayers, data) + bytes += len(data) + } + } + return diffLayers +} + +// NodeInfo represents a short summary of the `diff` sub-protocol metadata +// known about the host peer. +type NodeInfo struct{} + +// nodeInfo retrieves some `diff` protocol metadata about the running host node. +func nodeInfo(_ *core.BlockChain) *NodeInfo { + return &NodeInfo{} +} diff --git a/eth/protocols/diff/handshake.go b/eth/protocols/diff/handshake.go new file mode 100644 index 0000000000..0d96c5294e --- /dev/null +++ b/eth/protocols/diff/handshake.go @@ -0,0 +1,81 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package diff + +import ( + "fmt" + "time" + + "github.com/ethereum/go-ethereum/common/gopool" + "github.com/ethereum/go-ethereum/p2p" +) + +const ( + // handshakeTimeout is the maximum allowed time for the `diff` handshake to + // complete before dropping the connection.= as malicious. + handshakeTimeout = 5 * time.Second +) + +// Handshake executes the diff protocol handshake, +func (p *Peer) Handshake(lightSync bool) error { + // Send out own handshake in a new thread + errc := make(chan error, 2) + + var cap DiffCapPacket // safe to read after two values have been received from errc + + gopool.Submit(func() { + errc <- p2p.Send(p.rw, DiffCapMsg, &DiffCapPacket{ + LightSync: lightSync, + }) + }) + gopool.Submit(func() { + errc <- p.readCap(&cap) + }) + timeout := time.NewTimer(handshakeTimeout) + defer timeout.Stop() + for i := 0; i < 2; i++ { + select { + case err := <-errc: + if err != nil { + return err + } + case <-timeout.C: + return p2p.DiscReadTimeout + } + } + p.lightSync = cap.LightSync + return nil +} + +// readStatus reads the remote handshake message. +func (p *Peer) readCap(cap *DiffCapPacket) error { + msg, err := p.rw.ReadMsg() + if err != nil { + return err + } + if msg.Code != DiffCapMsg { + return fmt.Errorf("%w: first msg has code %x (!= %x)", errNoCapMsg, msg.Code, DiffCapMsg) + } + if msg.Size > maxMessageSize { + return fmt.Errorf("%w: %v > %v", errMsgTooLarge, msg.Size, maxMessageSize) + } + // Decode the handshake and make sure everything matches + if err := msg.Decode(cap); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + return nil +} diff --git a/eth/protocols/diff/peer.go b/eth/protocols/diff/peer.go new file mode 100644 index 0000000000..c599fadef9 --- /dev/null +++ b/eth/protocols/diff/peer.go @@ -0,0 +1,65 @@ +package diff + +import ( + "math/rand" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p" +) + +// Peer is a collection of relevant information we have about a `diff` peer. +type Peer struct { + id string // Unique ID for the peer, cached + lightSync bool // whether the peer can light sync + + *p2p.Peer // The embedded P2P package peer + rw p2p.MsgReadWriter // Input/output streams for diff + version uint // Protocol version negotiated + logger log.Logger // Contextual logger with the peer id injected +} + +// newPeer create a wrapper for a network connection and negotiated protocol +// version. +func newPeer(version uint, p *p2p.Peer, rw p2p.MsgReadWriter) *Peer { + id := p.ID().String() + return &Peer{ + id: id, + Peer: p, + rw: rw, + lightSync: false, + version: version, + logger: log.New("peer", id[:8]), + } +} + +// ID retrieves the peer's unique identifier. +func (p *Peer) ID() string { + return p.id +} + +// Version retrieves the peer's negoatiated `diff` protocol version. +func (p *Peer) Version() uint { + return p.version +} + +func (p *Peer) LightSync() bool { + return p.lightSync +} + +// Log overrides the P2P logget with the higher level one containing only the id. +func (p *Peer) Log() log.Logger { + return p.logger +} + +// RequestDiffLayers fetches a batch of diff layers corresponding to the hashes +// specified. +func (p *Peer) RequestDiffLayers(hashes []common.Hash) error { + id := rand.Uint64() + + requestTracker.Track(p.id, p.version, GetDiffLayerMsg, DiffLayerMsg, id) + return p2p.Send(p.rw, GetDiffLayerMsg, GetDiffLayersPacket{ + RequestId: id, + BlockHashes: hashes, + }) +} diff --git a/eth/protocols/diff/protocol.go b/eth/protocols/diff/protocol.go new file mode 100644 index 0000000000..e403d242ea --- /dev/null +++ b/eth/protocols/diff/protocol.go @@ -0,0 +1,120 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package diff + +import ( + "errors" + "fmt" + + "golang.org/x/crypto/sha3" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rlp" +) + +// Constants to match up protocol versions and messages +const ( + diff1 = 1 +) + +// ProtocolName is the official short name of the `diff` protocol used during +// devp2p capability negotiation. +const ProtocolName = "diff" + +// ProtocolVersions are the supported versions of the `diff` protocol (first +// is primary). +var ProtocolVersions = []uint{diff1} + +// protocolLengths are the number of implemented message corresponding to +// different protocol versions. +var protocolLengths = map[uint]uint64{diff1: 4} + +// maxMessageSize is the maximum cap on the size of a protocol message. +const maxMessageSize = 10 * 1024 * 1024 + +const ( + DiffCapMsg = 0x00 + GetDiffLayerMsg = 0x01 + DiffLayerMsg = 0x02 + FullDiffLayerMsg = 0x03 +) + +var ( + errMsgTooLarge = errors.New("message too long") + errDecode = errors.New("invalid message") + errInvalidMsgCode = errors.New("invalid message code") + errBadRequest = errors.New("bad request") + errNoCapMsg = errors.New("miss cap message during handshake") +) + +// Packet represents a p2p message in the `diff` protocol. +type Packet interface { + Name() string // Name returns a string corresponding to the message type. + Kind() byte // Kind returns the message type. +} + +type GetDiffLayersPacket struct { + RequestId uint64 + BlockHashes []common.Hash +} + +func (p *DiffLayersPacket) Unpack() ([]*types.DiffLayer, []common.Hash, error) { + diffLayers := make([]*types.DiffLayer, 0, len(*p)) + diffHashes := make([]common.Hash, 0, len(*p)) + hasher := sha3.NewLegacyKeccak256() + for _, rawData := range *p { + var diff types.DiffLayer + err := rlp.DecodeBytes(rawData, &diff) + if err != nil { + return nil, nil, fmt.Errorf("%w: diff layer %v", errDecode, err) + } + diffLayers = append(diffLayers, &diff) + _, err = hasher.Write(rawData) + if err != nil { + return nil, nil, err + } + var diffHash common.Hash + hasher.Sum(diffHash[:0]) + hasher.Reset() + diff.DiffHash = diffHash + diffHashes = append(diffHashes) + } + return diffLayers, diffHashes, nil +} + +type DiffCapPacket struct { + LightSync bool +} +type DiffLayersPacket []rlp.RawValue + +type FullDiffLayersPacket struct { + RequestId uint64 + DiffLayersPacket +} + +func (*GetDiffLayersPacket) Name() string { return "GetDiffLayers" } +func (*GetDiffLayersPacket) Kind() byte { return GetDiffLayerMsg } + +func (*DiffLayersPacket) Name() string { return "DiffLayers" } +func (*DiffLayersPacket) Kind() byte { return DiffLayerMsg } + +func (*FullDiffLayersPacket) Name() string { return "FullDiffLayers" } +func (*FullDiffLayersPacket) Kind() byte { return FullDiffLayerMsg } + +func (*DiffCapPacket) Name() string { return "DiffCap" } +func (*DiffCapPacket) Kind() byte { return DiffCapMsg } diff --git a/eth/protocols/diff/tracker.go b/eth/protocols/diff/tracker.go new file mode 100644 index 0000000000..754c41258b --- /dev/null +++ b/eth/protocols/diff/tracker.go @@ -0,0 +1,26 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package diff + +import ( + "time" + + "github.com/ethereum/go-ethereum/p2p/tracker" +) + +// requestTracker is a singleton tracker for request times. +var requestTracker = tracker.New(ProtocolName, time.Minute) diff --git a/eth/protocols/eth/broadcast.go b/eth/protocols/eth/broadcast.go index e0ee2a1cfa..a9cf525161 100644 --- a/eth/protocols/eth/broadcast.go +++ b/eth/protocols/eth/broadcast.go @@ -19,6 +19,11 @@ package eth import ( "math/big" + "github.com/ethereum/go-ethereum/eth/protocols/diff" + "github.com/ethereum/go-ethereum/p2p" + + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/gopool" "github.com/ethereum/go-ethereum/core/types" @@ -33,8 +38,9 @@ const ( // blockPropagation is a block propagation event, waiting for its turn in the // broadcast queue. type blockPropagation struct { - block *types.Block - td *big.Int + block *types.Block + diffLayer rlp.RawValue + td *big.Int } // broadcastBlocks is a write loop that multiplexes blocks and block accouncements @@ -47,6 +53,9 @@ func (p *Peer) broadcastBlocks() { if err := p.SendNewBlock(prop.block, prop.td); err != nil { return } + if len(prop.diffLayer) != 0 { + p2p.Send(p.rw, diff.DiffLayerMsg, &diff.DiffLayersPacket{prop.diffLayer}) + } p.Log().Trace("Propagated block", "number", prop.block.Number(), "hash", prop.block.Hash(), "td", prop.td) case block := <-p.queuedBlockAnns: diff --git a/eth/protocols/eth/handler.go b/eth/protocols/eth/handler.go index 6bbaa2f555..4903100345 100644 --- a/eth/protocols/eth/handler.go +++ b/eth/protocols/eth/handler.go @@ -57,6 +57,10 @@ const ( // containing 200+ transactions nowadays, the practical limit will always // be softResponseLimit. maxReceiptsServe = 1024 + + // TODO this may exceed soft response limit? Please do test + // maxDiffLayerServe is the maximum number of diff layers to serve. + maxDiffLayerServe = 1024 ) // Handler is a callback to invoke from an outside runner after the boilerplate diff --git a/eth/protocols/eth/peer.go b/eth/protocols/eth/peer.go index e619c183ba..3199fc809b 100644 --- a/eth/protocols/eth/peer.go +++ b/eth/protocols/eth/peer.go @@ -54,6 +54,10 @@ const ( // dropping broadcasts. Similarly to block propagations, there's no point to queue // above some healthy uncle limit, so use that. maxQueuedBlockAnns = 4 + + // maxQueuedDiffLayers is the maximum number of diffLayers to queue up before + // dropping broadcasts. + maxQueuedDiffLayers = 4 ) // max is a helper function which returns the larger of the two given integers. @@ -333,9 +337,9 @@ func (p *Peer) SendNewBlock(block *types.Block, td *big.Int) error { // AsyncSendNewBlock queues an entire block for propagation to a remote peer. If // the peer's broadcast queue is full, the event is silently dropped. -func (p *Peer) AsyncSendNewBlock(block *types.Block, td *big.Int) { +func (p *Peer) AsyncSendNewBlock(block *types.Block, diff rlp.RawValue, td *big.Int) { select { - case p.queuedBlocks <- &blockPropagation{block: block, td: td}: + case p.queuedBlocks <- &blockPropagation{block: block, diffLayer: diff, td: td}: // Mark all the block hash as known, but ensure we don't overflow our limits for p.knownBlocks.Cardinality() >= maxKnownBlocks { p.knownBlocks.Pop() diff --git a/les/fetcher.go b/les/fetcher.go index fc4c5e386a..ed2e72a330 100644 --- a/les/fetcher.go +++ b/les/fetcher.go @@ -339,7 +339,7 @@ func (f *lightFetcher) mainloop() { log.Debug("Trigger light sync", "peer", peerid, "local", localHead.Number, "localhash", localHead.Hash(), "remote", data.Number, "remotehash", data.Hash) continue } - f.fetcher.Notify(peerid.String(), data.Hash, data.Number, time.Now(), f.requestHeaderByHash(peerid), nil) + f.fetcher.Notify(peerid.String(), data.Hash, data.Number, time.Now(), f.requestHeaderByHash(peerid), nil, nil) log.Debug("Trigger header retrieval", "peer", peerid, "number", data.Number, "hash", data.Hash) } // Keep collecting announces from trusted server even we are syncing. @@ -355,7 +355,7 @@ func (f *lightFetcher) mainloop() { continue } p := agreed[rand.Intn(len(agreed))] - f.fetcher.Notify(p.String(), data.Hash, data.Number, time.Now(), f.requestHeaderByHash(p), nil) + f.fetcher.Notify(p.String(), data.Hash, data.Number, time.Now(), f.requestHeaderByHash(p), nil, nil) log.Debug("Trigger trusted header retrieval", "number", data.Number, "hash", data.Hash) } } From b782a7bd04b2eeaef50f1f79c91f361b3c6d6dc2 Mon Sep 17 00:00:00 2001 From: fudongbai <296179868@qq.com> Date: Thu, 26 Aug 2021 14:25:07 +0800 Subject: [PATCH 03/22] handle difflayer and refine light processor --- cmd/utils/flags.go | 7 +- core/block_validator.go | 3 - core/blockchain.go | 341 +++++++++++++++++++++++++++------ core/blockchain_test.go | 2 +- core/rawdb/accessors_chain.go | 20 +- core/state/statedb.go | 31 +-- core/state_processor.go | 89 ++++++--- core/types.go | 2 +- core/types/block.go | 12 +- eth/backend.go | 9 +- eth/handler.go | 10 +- eth/handler_diff.go | 11 +- eth/handler_eth.go | 3 +- eth/peer.go | 1 - eth/peerset.go | 3 +- eth/protocols/diff/handler.go | 5 +- eth/protocols/diff/peer.go | 60 +++++- eth/protocols/diff/protocol.go | 10 +- eth/protocols/eth/broadcast.go | 13 +- eth/protocols/eth/handler.go | 4 - eth/protocols/eth/peer.go | 4 +- eth/state_accessor.go | 2 +- 22 files changed, 472 insertions(+), 170 deletions(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index a6eebfcf7b..9b5d2b14a6 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -118,8 +118,9 @@ var ( Usage: "Enable directly broadcast mined block to all peers", } LightSyncFlag = cli.BoolFlag{ - Name: "lightsync", - Usage: "Enable difflayer light sync ", + Name: "lightsync", + Usage: "Enable difflayer light sync, Please note that enable lightsync will improve the syncing speed, " + + "but will degrade the security to light client level", } RangeLimitFlag = cli.BoolFlag{ Name: "rangelimit", @@ -435,7 +436,7 @@ var ( } PersistDiffFlag = cli.BoolFlag{ Name: "persistdiff", - Usage: "Enable persisting the diff layer", + Usage: "Enable persistence of the diff layer", } // Miner settings MiningEnabledFlag = cli.BoolFlag{ diff --git a/core/block_validator.go b/core/block_validator.go index 92be755199..7ef440b4b7 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -17,9 +17,7 @@ package core import ( - "encoding/json" "fmt" - "os" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core/state" @@ -133,7 +131,6 @@ func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateD }, func() error { if root := statedb.IntermediateRoot(v.config.IsEIP158(header.Number)); header.Root != root { - statedb.IterativeDump(true, true, true, json.NewEncoder(os.Stdout)) return fmt.Errorf("invalid merkle root (remote: %x local: %x)", header.Root, root) } else { return nil diff --git a/core/blockchain.go b/core/blockchain.go index fc862701c5..65ff1deb9b 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -90,8 +90,12 @@ const ( maxTimeFutureBlocks = 30 maxBeyondBlocks = 2048 - diffLayerfreezerRecheckInterval = 3 * time.Second - diffLayerfreezerBlockLimit = 864000 // The number of blocks that should be kept in disk. + diffLayerFreezerRecheckInterval = 3 * time.Second + diffLayerFreezerBlockLimit = 864000 // The number of diff layers that should be kept in disk. + diffLayerPruneRecheckInterval = 1 * time.Second // The interval to prune unverified diff layers + maxDiffQueueDist = 64 // Maximum allowed distance from the chain head to queue diffLayers + maxDiffLimit = 128 // Maximum number of unique diff layers a peer may have delivered + maxDiffForkDist = 11 // Maximum allowed backward distance from the chain head // BlockChainVersion ensures that an incompatible database forces a resync from scratch. // @@ -135,6 +139,11 @@ type CacheConfig struct { SnapshotWait bool // Wait for snapshot construction on startup. TODO(karalabe): This is a dirty hack for testing, nuke it } +// To avoid cycle import +type PeerIDer interface { + ID() string +} + // defaultCacheConfig are the default caching values if none are specified by the // user (also used during testing). var defaultCacheConfig = &CacheConfig{ @@ -146,6 +155,8 @@ var defaultCacheConfig = &CacheConfig{ SnapshotWait: true, } +type BlockChainOption func(*BlockChain) *BlockChain + // BlockChain represents the canonical chain given a database with a genesis // block. The Blockchain manages chain imports, reverts, chain reorganisations. // @@ -192,16 +203,26 @@ type BlockChain struct { currentBlock atomic.Value // Current head of the block chain currentFastBlock atomic.Value // Current head of the fast-sync chain (may be above the block chain!) - stateCache state.Database // State database to reuse between imports (contains state cache) - bodyCache *lru.Cache // Cache for the most recent block bodies - bodyRLPCache *lru.Cache // Cache for the most recent block bodies in RLP encoded format - receiptsCache *lru.Cache // Cache for the most recent receipts per block - blockCache *lru.Cache // Cache for the most recent entire blocks - txLookupCache *lru.Cache // Cache for the most recent transaction lookup data. - futureBlocks *lru.Cache // future blocks are blocks added for later processing - diffLayerCache *lru.Cache // Cache for the diffLayers - diffLayerRLPCache *lru.Cache // Cache for the rlp encoded diffLayers - diffQueue *prque.Prque // A Priority queue to store recent diff layer + stateCache state.Database // State database to reuse between imports (contains state cache) + bodyCache *lru.Cache // Cache for the most recent block bodies + bodyRLPCache *lru.Cache // Cache for the most recent block bodies in RLP encoded format + receiptsCache *lru.Cache // Cache for the most recent receipts per block + blockCache *lru.Cache // Cache for the most recent entire blocks + txLookupCache *lru.Cache // Cache for the most recent transaction lookup data. + futureBlocks *lru.Cache // future blocks are blocks added for later processing + + // trusted diff layers + diffLayerCache *lru.Cache // Cache for the diffLayers + diffLayerRLPCache *lru.Cache // Cache for the rlp encoded diffLayers + diffQueue *prque.Prque // A Priority queue to store recent diff layer + + // untrusted diff layers + diffMux sync.RWMutex + receivedDiffLayers map[common.Hash]map[common.Hash]*types.DiffLayer // map[blockHash] map[DiffHash]Diff + diffHashToBlockHash map[common.Hash]common.Hash // map[diffHash]blockHash + diffHashToPeers map[common.Hash]map[string]struct{} // map[diffHash]map[pid] + diffNumToBlockHashes map[uint64]map[common.Hash]struct{} // map[number]map[blockHash] + diffPeersToDiffHashes map[string]map[common.Hash]struct{} // map[pid]map[diffHash] quit chan struct{} // blockchain quit channel wg sync.WaitGroup // chain processing wait group for shutting down @@ -220,12 +241,15 @@ type BlockChain struct { // NewBlockChain returns a fully initialised block chain using information // available in the database. It initialises the default Ethereum Validator and // Processor. -func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *params.ChainConfig, engine consensus.Engine, vmConfig vm.Config, shouldPreserve func(block *types.Block) bool, txLookupLimit *uint64) (*BlockChain, error) { +func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *params.ChainConfig, engine consensus.Engine, + vmConfig vm.Config, shouldPreserve func(block *types.Block) bool, txLookupLimit *uint64, + options ...BlockChainOption) (*BlockChain, error) { if cacheConfig == nil { cacheConfig = defaultCacheConfig } if cacheConfig.TriesInMemory != 128 { - log.Warn("TriesInMemory isn't the default value(128), you need specify exact same TriesInMemory when prune data", "triesInMemory", cacheConfig.TriesInMemory) + log.Warn("TriesInMemory isn't the default value(128), you need specify exact same TriesInMemory when prune data", + "triesInMemory", cacheConfig.TriesInMemory) } bodyCache, _ := lru.New(bodyCacheLimit) bodyRLPCache, _ := lru.New(bodyCacheLimit) @@ -246,20 +270,25 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par Journal: cacheConfig.TrieCleanJournal, Preimages: cacheConfig.Preimages, }), - triesInMemory: cacheConfig.TriesInMemory, - quit: make(chan struct{}), - shouldPreserve: shouldPreserve, - bodyCache: bodyCache, - bodyRLPCache: bodyRLPCache, - receiptsCache: receiptsCache, - blockCache: blockCache, - diffLayerCache: diffLayerCache, - diffLayerRLPCache: diffLayerRLPCache, - txLookupCache: txLookupCache, - futureBlocks: futureBlocks, - engine: engine, - vmConfig: vmConfig, - diffQueue: prque.New(nil), + triesInMemory: cacheConfig.TriesInMemory, + quit: make(chan struct{}), + shouldPreserve: shouldPreserve, + bodyCache: bodyCache, + bodyRLPCache: bodyRLPCache, + receiptsCache: receiptsCache, + blockCache: blockCache, + diffLayerCache: diffLayerCache, + diffLayerRLPCache: diffLayerRLPCache, + txLookupCache: txLookupCache, + futureBlocks: futureBlocks, + engine: engine, + vmConfig: vmConfig, + diffQueue: prque.New(nil), + receivedDiffLayers: make(map[common.Hash]map[common.Hash]*types.DiffLayer), + diffHashToBlockHash: make(map[common.Hash]common.Hash), + diffHashToPeers: make(map[common.Hash]map[string]struct{}), + diffNumToBlockHashes: make(map[uint64]map[common.Hash]struct{}), + diffPeersToDiffHashes: make(map[string]map[common.Hash]struct{}), } bc.validator = NewBlockValidator(chainConfig, bc, engine) bc.processor = NewStateProcessor(chainConfig, bc, engine) @@ -387,6 +416,10 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par } bc.snaps, _ = snapshot.New(bc.db, bc.stateCache.TrieDB(), bc.cacheConfig.SnapshotLimit, int(bc.cacheConfig.TriesInMemory), head.Root(), !bc.cacheConfig.SnapshotWait, true, recover) } + // do options before start any routine + for _, option := range options { + bc = option(bc) + } // Take ownership of this particular state go bc.update() if txLookupLimit != nil { @@ -410,8 +443,10 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par } // Need persist and prune diff layer if bc.db.DiffStore() != nil { - go bc.diffLayerFreeze() + go bc.trustedDiffLayerFreezeLoop() } + go bc.untrustedDiffLayerPruneLoop() + return bc, nil } @@ -420,19 +455,19 @@ func (bc *BlockChain) GetVMConfig() *vm.Config { return &bc.vmConfig } -func (bc *BlockChain) CacheReceipts(hash common.Hash, receipts types.Receipts) { +func (bc *BlockChain) cacheReceipts(hash common.Hash, receipts types.Receipts) { bc.receiptsCache.Add(hash, receipts) } -func (bc *BlockChain) CacheDiffLayer(hash common.Hash, num uint64, diffLayer *types.DiffLayer) { - bc.diffLayerCache.Add(hash, diffLayer) +func (bc *BlockChain) cacheDiffLayer(diffLayer *types.DiffLayer) { + bc.diffLayerCache.Add(diffLayer.BlockHash, diffLayer) if bc.db.DiffStore() != nil { // push to priority queue before persisting - bc.diffQueue.Push(diffLayer, -(int64(num))) + bc.diffQueue.Push(diffLayer, -(int64(diffLayer.Number))) } } -func (bc *BlockChain) CacheBlock(hash common.Hash, block *types.Block) { +func (bc *BlockChain) cacheBlock(hash common.Hash, block *types.Block) { bc.blockCache.Add(hash, block) } @@ -897,29 +932,41 @@ func (bc *BlockChain) GetBodyRLP(hash common.Hash) rlp.RawValue { return body } -// GetBodyRLP retrieves a diff layer in RLP encoding from the database by hash, -// caching it if found. -func (bc *BlockChain) GetDiffLayerRLP(hash common.Hash) rlp.RawValue { +// GetDiffLayerRLP retrieves a diff layer in RLP encoding from the cache or database by blockHash +func (bc *BlockChain) GetDiffLayerRLP(blockHash common.Hash) rlp.RawValue { // Short circuit if the diffLayer's already in the cache, retrieve otherwise - if cached, ok := bc.diffLayerRLPCache.Get(hash); ok { + if cached, ok := bc.diffLayerRLPCache.Get(blockHash); ok { return cached.(rlp.RawValue) } - if cached, ok := bc.diffLayerCache.Get(hash); ok { + if cached, ok := bc.diffLayerCache.Get(blockHash); ok { diff := cached.(*types.DiffLayer) bz, err := rlp.EncodeToBytes(diff) if err != nil { return nil } - bc.diffLayerRLPCache.Add(hash, bz) + bc.diffLayerRLPCache.Add(blockHash, bz) return bz } + + // fallback to untrusted sources. + diff := bc.GetDiffLayer(blockHash, "") + if diff != nil { + bz, err := rlp.EncodeToBytes(diff) + if err != nil { + return nil + } + // No need to cache untrusted data + return bz + } + + // fallback to disk diffStore := bc.db.DiffStore() if diffStore == nil { return nil } - rawData := rawdb.ReadDiffLayerRLP(diffStore, hash) + rawData := rawdb.ReadDiffLayerRLP(diffStore, blockHash) if len(rawData) != 0 { - bc.diffLayerRLPCache.Add(hash, rawData) + bc.diffLayerRLPCache.Add(blockHash, rawData) } return rawData } @@ -1566,9 +1613,9 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types. if diffLayer != nil && block.Header().TxHash != types.EmptyRootHash { // Filling necessary field diffLayer.Receipts = receipts - diffLayer.StateRoot = root - diffLayer.Hash = block.Hash() - bc.CacheDiffLayer(diffLayer.Hash, block.Number().Uint64(), diffLayer) + diffLayer.BlockHash = block.Hash() + diffLayer.Number = block.NumberU64() + bc.cacheDiffLayer(diffLayer) } triedb := bc.stateCache.TrieDB() @@ -1945,12 +1992,11 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er } // Enable prefetching to pull in trie node paths while processing transactions statedb.StartPrefetcher("chain") - activeState = statedb - statedb.TryPreload(block, signer) //Process block using the parent state as reference point substart := time.Now() - receipts, logs, usedGas, err := bc.processor.Process(block, statedb, bc.vmConfig) + statedb, receipts, logs, usedGas, err := bc.processor.Process(block, statedb, bc.vmConfig) + activeState = statedb if err != nil { bc.reportBlock(block, receipts, err) return it.index, err @@ -1969,13 +2015,15 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er // Validate the state using the default validator substart = time.Now() - if err := bc.validator.ValidateState(block, statedb, receipts, usedGas); err != nil { - bc.reportBlock(block, receipts, err) - log.Error("validate state failed", "error", err) - return it.index, err + if !statedb.IsLightProcessed() { + if err := bc.validator.ValidateState(block, statedb, receipts, usedGas); err != nil { + log.Error("validate state failed", "error", err) + return it.index, err + } } - bc.CacheReceipts(block.Hash(), receipts) - bc.CacheBlock(block.Hash(), block) + bc.reportBlock(block, receipts, err) + bc.cacheReceipts(block.Hash(), receipts) + bc.cacheBlock(block.Hash(), block) proctime := time.Since(start) // Update the metrics touched during block validation @@ -2352,8 +2400,10 @@ func (bc *BlockChain) update() { } } -func (bc *BlockChain) diffLayerFreeze() { - recheck := time.Tick(diffLayerfreezerRecheckInterval) +func (bc *BlockChain) trustedDiffLayerFreezeLoop() { + recheck := time.Tick(diffLayerFreezerRecheckInterval) + bc.wg.Add(1) + defer bc.wg.Done() for { select { case <-bc.quit: @@ -2366,7 +2416,7 @@ func (bc *BlockChain) diffLayerFreeze() { if batch == nil { batch = bc.db.DiffStore().NewBatch() } - rawdb.WriteDiffLayer(batch, diffLayer.Hash, diffLayer) + rawdb.WriteDiffLayer(batch, diffLayer.BlockHash, diffLayer) if batch.ValueSize() > ethdb.IdealBatchSize { if err := batch.Write(); err != nil { log.Error("Failed to write diff layer", "err", err) @@ -2376,6 +2426,7 @@ func (bc *BlockChain) diffLayerFreeze() { } } if batch != nil { + // flush data if err := batch.Write(); err != nil { log.Error("Failed to write diff layer", "err", err) return @@ -2394,12 +2445,12 @@ func (bc *BlockChain) diffLayerFreeze() { if int64(currentHeight)+prio > int64(bc.triesInMemory) { canonicalHash := bc.GetCanonicalHash(uint64(-prio)) // on the canonical chain - if canonicalHash == diffLayer.Hash { + if canonicalHash == diffLayer.BlockHash { if batch == nil { batch = bc.db.DiffStore().NewBatch() } - rawdb.WriteDiffLayer(batch, diffLayer.Hash, diffLayer) - staleHash := bc.GetCanonicalHash(uint64(-prio) - diffLayerfreezerBlockLimit) + rawdb.WriteDiffLayer(batch, diffLayer.BlockHash, diffLayer) + staleHash := bc.GetCanonicalHash(uint64(-prio) - diffLayerFreezerBlockLimit) rawdb.DeleteDiffLayer(batch, staleHash) } } else { @@ -2423,6 +2474,170 @@ func (bc *BlockChain) diffLayerFreeze() { } } +func (bc *BlockChain) GetDiffLayer(blockHash common.Hash, pid string) *types.DiffLayer { + bc.diffMux.RLock() + defer bc.diffMux.RUnlock() + if diffs, exist := bc.receivedDiffLayers[blockHash]; exist && len(diffs) != 0 { + if len(diffs) == 1 { + // return the only one diff layer + for _, diff := range diffs { + return diff + } + } else { + // pick the one from exact same peer if we know where the block comes from + if pid != "" { + if diffHashes, exist := bc.diffPeersToDiffHashes[pid]; exist { + for diff := range diffs { + if _, overlap := diffHashes[diff]; overlap { + return bc.receivedDiffLayers[blockHash][diff] + } + } + } + } + // Do not find overlap, do random pick + for _, diff := range diffs { + return diff + } + } + } + return nil +} + +func (bc *BlockChain) removeDiffLayers(diffHash common.Hash) { + bc.diffMux.Lock() + defer bc.diffMux.Unlock() + + // Untrusted peers + pids := bc.diffHashToPeers[diffHash] + invalidDiffHashes := make(map[common.Hash]struct{}) + if pids != nil { + for pid := range pids { + invaliDiffHashesPeer := bc.diffPeersToDiffHashes[pid] + if invaliDiffHashesPeer != nil { + for invaliDiffHash := range invaliDiffHashesPeer { + invalidDiffHashes[invaliDiffHash] = struct{}{} + } + } + delete(bc.diffPeersToDiffHashes, pid) + } + } + for invalidDiffHash := range invalidDiffHashes { + delete(bc.diffHashToPeers, invalidDiffHash) + affectedBlockHash := bc.diffHashToBlockHash[invalidDiffHash] + if diffs, exist := bc.receivedDiffLayers[affectedBlockHash]; exist { + delete(diffs, invalidDiffHash) + if len(diffs) == 0 { + delete(bc.receivedDiffLayers, affectedBlockHash) + } + } + delete(bc.diffHashToBlockHash, invalidDiffHash) + } +} + +func (bc *BlockChain) untrustedDiffLayerPruneLoop() { + recheck := time.Tick(diffLayerPruneRecheckInterval) + bc.wg.Add(1) + defer bc.wg.Done() + for { + select { + case <-bc.quit: + return + case <-recheck: + bc.pruneDiffLayer() + } + } +} + +func (bc *BlockChain) pruneDiffLayer() { + currentHeight := bc.CurrentBlock().NumberU64() + bc.diffMux.Lock() + defer bc.diffMux.Unlock() + sortNumbers := make([]uint64, 0, len(bc.diffNumToBlockHashes)) + for number := range bc.diffNumToBlockHashes { + sortNumbers = append(sortNumbers, number) + } + sort.Slice(sortNumbers, func(i, j int) bool { + return sortNumbers[i] <= sortNumbers[j] + }) + staleBlockHashes := make(map[common.Hash]struct{}) + for _, number := range sortNumbers { + if number < currentHeight-maxDiffForkDist { + affectedHashes := bc.diffNumToBlockHashes[number] + if affectedHashes != nil { + for affectedHash := range affectedHashes { + staleBlockHashes[affectedHash] = struct{}{} + } + delete(bc.diffNumToBlockHashes, number) + } + } else { + break + } + } + staleDiffHashes := make(map[common.Hash]struct{}, 0) + for blockHash := range staleBlockHashes { + if diffHashes, exist := bc.receivedDiffLayers[blockHash]; exist { + for diffHash := range diffHashes { + staleDiffHashes[diffHash] = struct{}{} + delete(bc.diffHashToBlockHash, diffHash) + delete(bc.diffHashToPeers, diffHash) + } + } + delete(bc.receivedDiffLayers, blockHash) + } + for diffHash := range staleDiffHashes { + for p, diffHashes := range bc.diffPeersToDiffHashes { + delete(diffHashes, diffHash) + if len(diffHash) == 0 { + delete(bc.diffPeersToDiffHashes, p) + } + } + } +} + +// Process received diff layers +func (bc *BlockChain) HandleDiffLayer(diffLayer *types.DiffLayer, pid string) error { + // Basic check + currentHeight := bc.CurrentBlock().NumberU64() + if diffLayer.Number > currentHeight && diffLayer.Number-currentHeight > maxDiffQueueDist { + return errors.New("diff layers too new from current") + } + if diffLayer.Number < currentHeight && currentHeight-diffLayer.Number > maxDiffForkDist { + return errors.New("diff layers too old from current") + } + + bc.diffMux.Lock() + defer bc.diffMux.Unlock() + + if len(bc.diffPeersToDiffHashes[pid]) > maxDiffLimit { + return errors.New("too many accumulated diffLayers") + } + if _, exist := bc.diffPeersToDiffHashes[pid]; exist { + if _, alreadyHas := bc.diffPeersToDiffHashes[pid][diffLayer.DiffHash]; alreadyHas { + return nil + } + } else { + bc.diffPeersToDiffHashes[pid] = make(map[common.Hash]struct{}) + } + bc.diffPeersToDiffHashes[pid][diffLayer.DiffHash] = struct{}{} + if _, exist := bc.diffNumToBlockHashes[diffLayer.Number]; !exist { + bc.diffNumToBlockHashes[diffLayer.Number] = make(map[common.Hash]struct{}) + } + bc.diffNumToBlockHashes[diffLayer.Number][diffLayer.BlockHash] = struct{}{} + + if _, exist := bc.diffHashToPeers[diffLayer.DiffHash]; !exist { + bc.diffHashToPeers[diffLayer.DiffHash] = make(map[string]struct{}) + } + bc.diffHashToPeers[diffLayer.DiffHash][pid] = struct{}{} + + if _, exist := bc.receivedDiffLayers[diffLayer.BlockHash]; !exist { + bc.receivedDiffLayers[diffLayer.BlockHash] = make(map[common.Hash]*types.DiffLayer) + } + bc.receivedDiffLayers[diffLayer.BlockHash][diffLayer.DiffHash] = diffLayer + bc.diffHashToBlockHash[diffLayer.DiffHash] = diffLayer.BlockHash + + return nil +} + // maintainTxIndex is responsible for the construction and deletion of the // transaction index. // @@ -2672,3 +2887,9 @@ func (bc *BlockChain) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscript func (bc *BlockChain) SubscribeBlockProcessingEvent(ch chan<- bool) event.Subscription { return bc.scope.Track(bc.blockProcFeed.Subscribe(ch)) } + +//Options +func EnableLightProcessor(bc *BlockChain) *BlockChain { + bc.processor = NewLightStateProcessor(bc.Config(), bc, bc.engine) + return bc +} diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 9395a379f5..8bc1009be3 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -151,7 +151,7 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error { if err != nil { return err } - receipts, _, usedGas, err := blockchain.processor.Process(block, statedb, vm.Config{}) + statedb, receipts, _, usedGas, err := blockchain.processor.Process(block, statedb, vm.Config{}) if err != nil { blockchain.reportBlock(block, receipts, err) return err diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index 34f16ebb7a..6489a600fb 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -455,32 +455,32 @@ func WriteDiffLayer(db ethdb.KeyValueWriter, hash common.Hash, layer *types.Diff WriteDiffLayerRLP(db, hash, data) } -func WriteDiffLayerRLP(db ethdb.KeyValueWriter, hash common.Hash, rlp rlp.RawValue) { - if err := db.Put(diffLayerKey(hash), rlp); err != nil { - log.Crit("Failed to store block body", "err", err) +func WriteDiffLayerRLP(db ethdb.KeyValueWriter, blockHash common.Hash, rlp rlp.RawValue) { + if err := db.Put(diffLayerKey(blockHash), rlp); err != nil { + log.Crit("Failed to store diff layer", "err", err) } } -func ReadDiffLayer(db ethdb.KeyValueReader, hash common.Hash) *types.DiffLayer { - data := ReadDiffLayerRLP(db, hash) +func ReadDiffLayer(db ethdb.KeyValueReader, blockHash common.Hash) *types.DiffLayer { + data := ReadDiffLayerRLP(db, blockHash) if len(data) == 0 { return nil } diff := new(types.DiffLayer) if err := rlp.Decode(bytes.NewReader(data), diff); err != nil { - log.Error("Invalid diff layer RLP", "hash", hash, "err", err) + log.Error("Invalid diff layer RLP", "hash", blockHash, "err", err) return nil } return diff } -func ReadDiffLayerRLP(db ethdb.KeyValueReader, hash common.Hash) rlp.RawValue { - data, _ := db.Get(diffLayerKey(hash)) +func ReadDiffLayerRLP(db ethdb.KeyValueReader, blockHash common.Hash) rlp.RawValue { + data, _ := db.Get(diffLayerKey(blockHash)) return data } -func DeleteDiffLayer(db ethdb.KeyValueWriter, hash common.Hash) { - if err := db.Delete(diffLayerKey(hash)); err != nil { +func DeleteDiffLayer(db ethdb.KeyValueWriter, blockHash common.Hash) { + if err := db.Delete(diffLayerKey(blockHash)); err != nil { log.Crit("Failed to delete diffLayer", "err", err) } } diff --git a/core/state/statedb.go b/core/state/statedb.go index 15256dda64..1830d00a6c 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -72,15 +72,15 @@ func (n *proofList) Delete(key []byte) error { // * Contracts // * Accounts type StateDB struct { - db Database - prefetcher *triePrefetcher - originalRoot common.Hash // The pre-state root, before any changes were made - trie Trie - hasher crypto.KeccakState - diffLayer *types.DiffLayer - diffTries map[common.Address]Trie - diffCode map[common.Hash][]byte - diffEnabled bool + db Database + prefetcher *triePrefetcher + originalRoot common.Hash // The pre-state root, before any changes were made + trie Trie + hasher crypto.KeccakState + diffLayer *types.DiffLayer + diffTries map[common.Address]Trie + diffCode map[common.Hash][]byte + lightProcessed bool snapMux sync.Mutex snaps *snapshot.Tree @@ -191,8 +191,12 @@ func (s *StateDB) StopPrefetcher() { } // Mark that the block is processed by diff layer -func (s *StateDB) MarkDiffEnabled() { - s.diffEnabled = true +func (s *StateDB) MarkLightProcessed() { + s.lightProcessed = true +} + +func (s *StateDB) IsLightProcessed() bool { + return s.lightProcessed } // setError remembers the first non-nil error it is called with. @@ -954,7 +958,7 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) { // It is called in between transactions to get the root hash that // goes into transaction receipts. func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { - if s.diffEnabled { + if s.lightProcessed { return s.trie.Hash() } // Finalise all the dirty storage states and write them into the tries @@ -1134,7 +1138,7 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, *types.DiffLayer } // Finalize any pending changes and merge everything into the tries root := s.IntermediateRoot(deleteEmptyObjects) - if s.diffEnabled { + if s.lightProcessed { return s.LightCommit() } var diffLayer *types.DiffLayer @@ -1295,6 +1299,7 @@ func (s *StateDB) DiffLayerToSnap(diffLayer *types.DiffLayer) (map[common.Addres snapAccounts[account.Account] = account.Blob } for _, storage := range diffLayer.Storages { + // should never happen if len(storage.Keys) != len(storage.Vals) { return nil, nil, nil, errors.New("invalid diffLayer: length of keys and values mismatch") } diff --git a/core/state_processor.go b/core/state_processor.go index 486ca80bbf..99a9003bc6 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -21,6 +21,8 @@ import ( "errors" "fmt" "math/big" + "math/rand" + "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" @@ -37,6 +39,8 @@ import ( "github.com/ethereum/go-ethereum/rlp" ) +const fullProcessCheck = 21 // On light sync mode, will do full process every fullProcessCheck randomly + // StateProcessor is a basic Processor, which takes care of transitioning // state from one point to another. // @@ -57,16 +61,50 @@ func NewStateProcessor(config *params.ChainConfig, bc *BlockChain, engine consen } type LightStateProcessor struct { + randomGenerator *rand.Rand StateProcessor } -func (p *LightStateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) { - // TODO fetch differ from somewhere else - var diffLayer *types.DiffLayer - if diffLayer == nil { - return p.StateProcessor.Process(block, statedb, cfg) +func NewLightStateProcessor(config *params.ChainConfig, bc *BlockChain, engine consensus.Engine) *LightStateProcessor { + randomGenerator := rand.New(rand.NewSource(int64(time.Now().Nanosecond()))) + return &LightStateProcessor{ + randomGenerator: randomGenerator, + StateProcessor: *NewStateProcessor(config, bc, engine), + } +} + +func (p *LightStateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (*state.StateDB, types.Receipts, []*types.Log, uint64, error) { + // random fallback to full process + if check := p.randomGenerator.Int63n(fullProcessCheck); check != 0 { + var pid string + if peer, ok := block.ReceivedFrom.(PeerIDer); ok { + pid = peer.ID() + } + diffLayer := p.bc.GetDiffLayer(block.Hash(), pid) + if diffLayer != nil { + receipts, logs, gasUsed, err := p.LightProcess(diffLayer, block, statedb, cfg) + if err == nil { + return statedb, receipts, logs, gasUsed, nil + } else { + p.bc.removeDiffLayers(diffLayer.DiffHash) + // prepare new statedb + statedb.StopPrefetcher() + parent := p.bc.GetHeader(block.ParentHash(), block.NumberU64()-1) + statedb, err = state.New(parent.Root, p.bc.stateCache, p.bc.snaps) + if err != nil { + return statedb, nil, nil, 0, err + } + // Enable prefetching to pull in trie node paths while processing transactions + statedb.StartPrefetcher("chain") + } + } } - statedb.MarkDiffEnabled() + // fallback to full process + return p.StateProcessor.Process(block, statedb, cfg) +} + +func (p *LightStateProcessor) LightProcess(diffLayer *types.DiffLayer, block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) { + statedb.MarkLightProcessed() fullDiffCode := make(map[common.Hash][]byte, len(diffLayer.Codes)) diffTries := make(map[common.Address]state.Trie) diffCode := make(map[common.Hash][]byte) @@ -132,7 +170,7 @@ func (p *LightStateProcessor) Process(block *types.Block, statedb *state.StateDB if !bytes.Equal(latestAccount.CodeHash, previousAccount.CodeHash) && !bytes.Equal(latestAccount.CodeHash, types.EmptyCodeHash) { if code, exist := fullDiffCode[codeHash]; exist { - if crypto.Keccak256Hash(code) == codeHash { + if crypto.Keccak256Hash(code) != codeHash { return nil, nil, 0, errors.New("code and codeHash mismatch") } diffCode[codeHash] = code @@ -188,6 +226,18 @@ func (p *LightStateProcessor) Process(block *types.Block, statedb *state.StateDB return nil, nil, 0, err } } + var allLogs []*types.Log + var gasUsed uint64 + for _, receipt := range diffLayer.Receipts { + allLogs = append(allLogs, receipt.Logs...) + gasUsed += receipt.GasUsed + } + + // Do validate in advance so that we can fall back to full process + if err := p.bc.validator.ValidateState(block, statedb, diffLayer.Receipts, gasUsed); err != nil { + log.Error("validate state failed during light sync", "error", err) + return nil, nil, 0, err + } // remove redundant storage change for account, _ := range snapStorage { @@ -207,18 +257,12 @@ func (p *LightStateProcessor) Process(block *types.Block, statedb *state.StateDB }) } } + + statedb.SetSnapData(snapDestructs, snapAccounts, snapStorage) if len(snapAccounts) != len(diffLayer.Accounts) || len(snapStorage) != len(diffLayer.Storages) { diffLayer.Destructs, diffLayer.Accounts, diffLayer.Storages = statedb.SnapToDiffLayer() } statedb.SetDiff(diffLayer, diffTries, diffCode) - statedb.SetSnapData(snapDestructs, snapAccounts, snapStorage) - - var allLogs []*types.Log - var gasUsed uint64 - for _, receipt := range diffLayer.Receipts { - allLogs = append(allLogs, receipt.Logs...) - gasUsed += receipt.GasUsed - } return diffLayer.Receipts, allLogs, gasUsed, nil } @@ -230,13 +274,15 @@ func (p *LightStateProcessor) Process(block *types.Block, statedb *state.StateDB // Process returns the receipts and logs accumulated during the process and // returns the amount of gas that was used in the process. If any of the // transactions failed to execute due to insufficient gas it will return an error. -func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) { +func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (*state.StateDB, types.Receipts, []*types.Log, uint64, error) { var ( usedGas = new(uint64) header = block.Header() allLogs []*types.Log gp = new(GasPool).AddGas(block.GasLimit()) ) + signer := types.MakeSigner(p.bc.chainConfig, block.Number()) + statedb.TryPreload(block, signer) var receipts = make([]*types.Receipt, 0) // Mutate the block and state according to any hard-fork specs if p.config.DAOForkSupport && p.config.DAOForkBlock != nil && p.config.DAOForkBlock.Cmp(block.Number()) == 0 { @@ -253,11 +299,10 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg commonTxs := make([]*types.Transaction, 0, len(block.Transactions())) // usually do have two tx, one for validator set contract, another for system reward contract. systemTxs := make([]*types.Transaction, 0, 2) - signer := types.MakeSigner(p.config, header.Number) for i, tx := range block.Transactions() { if isPoSA { if isSystemTx, err := posa.IsSystemTransaction(tx, block.Header()); err != nil { - return nil, nil, 0, err + return statedb, nil, nil, 0, err } else if isSystemTx { systemTxs = append(systemTxs, tx) continue @@ -266,12 +311,12 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg msg, err := tx.AsMessage(signer) if err != nil { - return nil, nil, 0, err + return statedb, nil, nil, 0, err } statedb.Prepare(tx.Hash(), block.Hash(), i) receipt, err := applyTransaction(msg, p.config, p.bc, nil, gp, statedb, header, tx, usedGas, vmenv) if err != nil { - return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) + return statedb, nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) } commonTxs = append(commonTxs, tx) @@ -281,13 +326,13 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg // Finalize the block, applying any consensus engine specific extras (e.g. block rewards) err := p.engine.Finalize(p.bc, header, statedb, &commonTxs, block.Uncles(), &receipts, &systemTxs, usedGas) if err != nil { - return receipts, allLogs, *usedGas, err + return statedb, receipts, allLogs, *usedGas, err } for _, receipt := range receipts { allLogs = append(allLogs, receipt.Logs...) } - return receipts, allLogs, *usedGas, nil + return statedb, receipts, allLogs, *usedGas, nil } func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (*types.Receipt, error) { diff --git a/core/types.go b/core/types.go index 4c5b74a498..49bd58e086 100644 --- a/core/types.go +++ b/core/types.go @@ -47,5 +47,5 @@ type Processor interface { // Process processes the state changes according to the Ethereum rules by running // the transaction messages using the statedb and applying any rewards to both // the processor (coinbase) and any included uncles. - Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) + Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (*state.StateDB, types.Receipts, []*types.Log, uint64, error) } diff --git a/core/types/block.go b/core/types/block.go index 7b8d9dc41b..7f2a0785cb 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -27,10 +27,9 @@ import ( "sync/atomic" "time" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/rlp" ) @@ -372,11 +371,10 @@ func (b *Block) Hash() common.Hash { type Blocks []*Block -// journalDestruct is an account deletion entry in a diffLayer's disk journal. type DiffLayer struct { DiffHash common.Hash `rlp:"_"` - Hash common.Hash - StateRoot common.Hash + BlockHash common.Hash + Number uint64 Receipts Receipts // Receipts are duplicated stored to simplify the logic Codes []DiffCode Destructs []common.Address @@ -385,8 +383,8 @@ type DiffLayer struct { } func (d *DiffLayer) Validate() error { - if d.Hash == (common.Hash{}) || d.StateRoot == (common.Hash{}) { - return errors.New("hash can't be empty") + if d.BlockHash == (common.Hash{}) || d.DiffHash == (common.Hash{}) { + return errors.New("both BlockHash and DiffHash can't be empty") } for _, storage := range d.Storages { if len(storage.Keys) != len(storage.Vals) { diff --git a/eth/backend.go b/eth/backend.go index 2c6d9b87c1..36db5ac5ba 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -26,8 +26,6 @@ import ( "sync/atomic" "time" - "github.com/ethereum/go-ethereum/eth/protocols/diff" - "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -44,6 +42,7 @@ import ( "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/eth/filters" "github.com/ethereum/go-ethereum/eth/gasprice" + "github.com/ethereum/go-ethereum/eth/protocols/diff" "github.com/ethereum/go-ethereum/eth/protocols/eth" "github.com/ethereum/go-ethereum/eth/protocols/snap" "github.com/ethereum/go-ethereum/ethdb" @@ -200,7 +199,11 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { Preimages: config.Preimages, } ) - eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, chainConfig, eth.engine, vmConfig, eth.shouldPreserve, &config.TxLookupLimit) + bcOps := make([]core.BlockChainOption, 0) + if config.LightSync { + bcOps = append(bcOps, core.EnableLightProcessor) + } + eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, chainConfig, eth.engine, vmConfig, eth.shouldPreserve, &config.TxLookupLimit, bcOps...) if err != nil { return nil, err } diff --git a/eth/handler.go b/eth/handler.go index b75f20e7e3..d169b4e58b 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -24,14 +24,13 @@ import ( "sync/atomic" "time" - "github.com/ethereum/go-ethereum/eth/protocols/diff" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/forkid" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/fetcher" + "github.com/ethereum/go-ethereum/eth/protocols/diff" "github.com/ethereum/go-ethereum/eth/protocols/eth" "github.com/ethereum/go-ethereum/eth/protocols/snap" "github.com/ethereum/go-ethereum/ethdb" @@ -39,6 +38,7 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" ) @@ -480,8 +480,12 @@ func (h *handler) BroadcastBlock(block *types.Block, propagate bool) { } diff := h.chain.GetDiffLayerRLP(block.Hash()) for _, peer := range transfer { - peer.AsyncSendNewBlock(block, diff, td) + if len(diff) != 0 && peer.diffExt != nil { + peer.diffExt.AsyncSendDiffLayer([]rlp.RawValue{diff}) + } + peer.AsyncSendNewBlock(block, td) } + log.Trace("Propagated block", "hash", hash, "recipients", len(transfer), "duration", common.PrettyDuration(time.Since(block.ReceivedAt))) return } diff --git a/eth/handler_diff.go b/eth/handler_diff.go index d0352a4c57..be05877506 100644 --- a/eth/handler_diff.go +++ b/eth/handler_diff.go @@ -55,7 +55,7 @@ func (h *diffHandler) Handle(peer *diff.Peer, packet diff.Packet) error { // data packet for the local node to consume. switch packet := packet.(type) { case *diff.DiffLayersPacket: - diffs, _, err := packet.Unpack() + diffs, err := packet.Unpack() if err != nil { return err } @@ -66,10 +66,15 @@ func (h *diffHandler) Handle(peer *diff.Peer, packet diff.Packet) error { } } } - // TODO, we need rateLimit here + for _, diff := range diffs { + err := h.chain.HandleDiffLayer(diff, peer.ID()) + if err != nil { + return err + } + } default: - return fmt.Errorf("unexpected snap packet type: %T", packet) + return fmt.Errorf("unexpected diff packet type: %T", packet) } return nil } diff --git a/eth/handler_eth.go b/eth/handler_eth.go index 15305d4ae5..802df1b64a 100644 --- a/eth/handler_eth.go +++ b/eth/handler_eth.go @@ -23,11 +23,10 @@ import ( "sync/atomic" "time" - "github.com/ethereum/go-ethereum/eth/fetcher" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/fetcher" "github.com/ethereum/go-ethereum/eth/protocols/eth" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p/enode" diff --git a/eth/peer.go b/eth/peer.go index 581a94b585..89139f0b53 100644 --- a/eth/peer.go +++ b/eth/peer.go @@ -22,7 +22,6 @@ import ( "time" "github.com/ethereum/go-ethereum/eth/protocols/diff" - "github.com/ethereum/go-ethereum/eth/protocols/eth" "github.com/ethereum/go-ethereum/eth/protocols/snap" ) diff --git a/eth/peerset.go b/eth/peerset.go index e6d3e8a763..b5ac95c1d6 100644 --- a/eth/peerset.go +++ b/eth/peerset.go @@ -21,9 +21,8 @@ import ( "math/big" "sync" - "github.com/ethereum/go-ethereum/eth/protocols/diff" - "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/eth/protocols/diff" "github.com/ethereum/go-ethereum/eth/protocols/eth" "github.com/ethereum/go-ethereum/eth/protocols/snap" "github.com/ethereum/go-ethereum/p2p" diff --git a/eth/protocols/diff/handler.go b/eth/protocols/diff/handler.go index b6532cf684..eadc0030df 100644 --- a/eth/protocols/diff/handler.go +++ b/eth/protocols/diff/handler.go @@ -4,13 +4,11 @@ import ( "fmt" "time" + "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/metrics" - "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enr" - - "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/rlp" ) @@ -59,6 +57,7 @@ func MakeProtocols(backend Backend, dnsdisc enode.Iterator) []p2p.Protocol { Length: protocolLengths[version], Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error { return backend.RunPeer(newPeer(version, p, rw), func(peer *Peer) error { + defer peer.Close() return handle(backend, peer) }) }, diff --git a/eth/protocols/diff/peer.go b/eth/protocols/diff/peer.go index c599fadef9..f0c4952e65 100644 --- a/eth/protocols/diff/peer.go +++ b/eth/protocols/diff/peer.go @@ -6,30 +6,53 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/rlp" ) +const maxQueuedDiffLayers = 12 + // Peer is a collection of relevant information we have about a `diff` peer. type Peer struct { - id string // Unique ID for the peer, cached - lightSync bool // whether the peer can light sync + id string // Unique ID for the peer, cached + lightSync bool // whether the peer can light sync + queuedDiffLayers chan []rlp.RawValue // Queue of diff layers to broadcast to the peer *p2p.Peer // The embedded P2P package peer rw p2p.MsgReadWriter // Input/output streams for diff version uint // Protocol version negotiated logger log.Logger // Contextual logger with the peer id injected + term chan struct{} // Termination channel to stop the broadcasters } // newPeer create a wrapper for a network connection and negotiated protocol // version. func newPeer(version uint, p *p2p.Peer, rw p2p.MsgReadWriter) *Peer { id := p.ID().String() - return &Peer{ - id: id, - Peer: p, - rw: rw, - lightSync: false, - version: version, - logger: log.New("peer", id[:8]), + peer := &Peer{ + id: id, + Peer: p, + rw: rw, + lightSync: false, + version: version, + logger: log.New("peer", id[:8]), + queuedDiffLayers: make(chan []rlp.RawValue, maxQueuedDiffLayers), + term: make(chan struct{}), + } + go peer.broadcastDiffLayers() + return peer +} + +func (p *Peer) broadcastDiffLayers() { + for { + select { + case prop := <-p.queuedDiffLayers: + if err := p.SendDiffLayers(prop); err != nil { + p.Log().Error("Failed to propagated diff layer", "err", err) + return + } + case <-p.term: + return + } } } @@ -52,6 +75,13 @@ func (p *Peer) Log() log.Logger { return p.logger } +// Close signals the broadcast goroutine to terminate. Only ever call this if +// you created the peer yourself via NewPeer. Otherwise let whoever created it +// clean it up! +func (p *Peer) Close() { + close(p.term) +} + // RequestDiffLayers fetches a batch of diff layers corresponding to the hashes // specified. func (p *Peer) RequestDiffLayers(hashes []common.Hash) error { @@ -63,3 +93,15 @@ func (p *Peer) RequestDiffLayers(hashes []common.Hash) error { BlockHashes: hashes, }) } + +func (p *Peer) SendDiffLayers(diffs []rlp.RawValue) error { + return p2p.Send(p.rw, DiffLayerMsg, diffs) +} + +func (p *Peer) AsyncSendDiffLayer(diffLayers []rlp.RawValue) { + select { + case p.queuedDiffLayers <- diffLayers: + default: + p.Log().Debug("Dropping diff layers propagation") + } +} diff --git a/eth/protocols/diff/protocol.go b/eth/protocols/diff/protocol.go index e403d242ea..e4bf2f4a08 100644 --- a/eth/protocols/diff/protocol.go +++ b/eth/protocols/diff/protocol.go @@ -73,28 +73,26 @@ type GetDiffLayersPacket struct { BlockHashes []common.Hash } -func (p *DiffLayersPacket) Unpack() ([]*types.DiffLayer, []common.Hash, error) { +func (p *DiffLayersPacket) Unpack() ([]*types.DiffLayer, error) { diffLayers := make([]*types.DiffLayer, 0, len(*p)) - diffHashes := make([]common.Hash, 0, len(*p)) hasher := sha3.NewLegacyKeccak256() for _, rawData := range *p { var diff types.DiffLayer err := rlp.DecodeBytes(rawData, &diff) if err != nil { - return nil, nil, fmt.Errorf("%w: diff layer %v", errDecode, err) + return nil, fmt.Errorf("%w: diff layer %v", errDecode, err) } diffLayers = append(diffLayers, &diff) _, err = hasher.Write(rawData) if err != nil { - return nil, nil, err + return nil, err } var diffHash common.Hash hasher.Sum(diffHash[:0]) hasher.Reset() diff.DiffHash = diffHash - diffHashes = append(diffHashes) } - return diffLayers, diffHashes, nil + return diffLayers, nil } type DiffCapPacket struct { diff --git a/eth/protocols/eth/broadcast.go b/eth/protocols/eth/broadcast.go index a9cf525161..e0ee2a1cfa 100644 --- a/eth/protocols/eth/broadcast.go +++ b/eth/protocols/eth/broadcast.go @@ -19,11 +19,6 @@ package eth import ( "math/big" - "github.com/ethereum/go-ethereum/eth/protocols/diff" - "github.com/ethereum/go-ethereum/p2p" - - "github.com/ethereum/go-ethereum/rlp" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/gopool" "github.com/ethereum/go-ethereum/core/types" @@ -38,9 +33,8 @@ const ( // blockPropagation is a block propagation event, waiting for its turn in the // broadcast queue. type blockPropagation struct { - block *types.Block - diffLayer rlp.RawValue - td *big.Int + block *types.Block + td *big.Int } // broadcastBlocks is a write loop that multiplexes blocks and block accouncements @@ -53,9 +47,6 @@ func (p *Peer) broadcastBlocks() { if err := p.SendNewBlock(prop.block, prop.td); err != nil { return } - if len(prop.diffLayer) != 0 { - p2p.Send(p.rw, diff.DiffLayerMsg, &diff.DiffLayersPacket{prop.diffLayer}) - } p.Log().Trace("Propagated block", "number", prop.block.Number(), "hash", prop.block.Hash(), "td", prop.td) case block := <-p.queuedBlockAnns: diff --git a/eth/protocols/eth/handler.go b/eth/protocols/eth/handler.go index 4903100345..6bbaa2f555 100644 --- a/eth/protocols/eth/handler.go +++ b/eth/protocols/eth/handler.go @@ -57,10 +57,6 @@ const ( // containing 200+ transactions nowadays, the practical limit will always // be softResponseLimit. maxReceiptsServe = 1024 - - // TODO this may exceed soft response limit? Please do test - // maxDiffLayerServe is the maximum number of diff layers to serve. - maxDiffLayerServe = 1024 ) // Handler is a callback to invoke from an outside runner after the boilerplate diff --git a/eth/protocols/eth/peer.go b/eth/protocols/eth/peer.go index 3199fc809b..f09760a2ec 100644 --- a/eth/protocols/eth/peer.go +++ b/eth/protocols/eth/peer.go @@ -337,9 +337,9 @@ func (p *Peer) SendNewBlock(block *types.Block, td *big.Int) error { // AsyncSendNewBlock queues an entire block for propagation to a remote peer. If // the peer's broadcast queue is full, the event is silently dropped. -func (p *Peer) AsyncSendNewBlock(block *types.Block, diff rlp.RawValue, td *big.Int) { +func (p *Peer) AsyncSendNewBlock(block *types.Block, td *big.Int) { select { - case p.queuedBlocks <- &blockPropagation{block: block, diffLayer: diff, td: td}: + case p.queuedBlocks <- &blockPropagation{block: block, td: td}: // Mark all the block hash as known, but ensure we don't overflow our limits for p.knownBlocks.Cardinality() >= maxKnownBlocks { p.knownBlocks.Pop() diff --git a/eth/state_accessor.go b/eth/state_accessor.go index e9341d6837..461dc491fb 100644 --- a/eth/state_accessor.go +++ b/eth/state_accessor.go @@ -114,7 +114,7 @@ func (eth *Ethereum) stateAtBlock(block *types.Block, reexec uint64, base *state if current = eth.blockchain.GetBlockByNumber(next); current == nil { return nil, fmt.Errorf("block #%d not found", next) } - _, _, _, err := eth.blockchain.Processor().Process(current, statedb, vm.Config{}) + statedb, _, _, _, err := eth.blockchain.Processor().Process(current, statedb, vm.Config{}) if err != nil { return nil, fmt.Errorf("processing block %d failed: %v", current.NumberU64(), err) } From 172b26e194863dfb40378b11ec92893ea7015ca3 Mon Sep 17 00:00:00 2001 From: fudongbai <296179868@qq.com> Date: Tue, 31 Aug 2021 16:49:55 +0800 Subject: [PATCH 04/22] add testcase for diff protocol --- cmd/geth/main.go | 2 + core/blockchain.go | 47 ++--- core/blockchain_diff_test.go | 263 ++++++++++++++++++++++++++++ core/rawdb/database.go | 5 +- core/state_processor.go | 9 +- core/types/block.go | 50 +++++- eth/handler_diff_test.go | 203 +++++++++++++++++++++ eth/protocols/diff/handler.go | 8 +- eth/protocols/diff/handler_test.go | 192 ++++++++++++++++++++ eth/protocols/diff/peer.go | 4 +- eth/protocols/diff/peer_test.go | 61 +++++++ eth/protocols/diff/protocol.go | 7 +- eth/protocols/diff/protocol_test.go | 131 ++++++++++++++ 13 files changed, 945 insertions(+), 37 deletions(-) create mode 100644 core/blockchain_diff_test.go create mode 100644 eth/handler_diff_test.go create mode 100644 eth/protocols/diff/handler_test.go create mode 100644 eth/protocols/diff/peer_test.go create mode 100644 eth/protocols/diff/protocol_test.go diff --git a/cmd/geth/main.go b/cmd/geth/main.go index d383d99b7e..a0540c9362 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -65,6 +65,7 @@ var ( utils.ExternalSignerFlag, utils.NoUSBFlag, utils.DirectBroadcastFlag, + utils.LightSyncFlag, utils.RangeLimitFlag, utils.USBFlag, utils.SmartCardDaemonPathFlag, @@ -114,6 +115,7 @@ var ( utils.CacheGCFlag, utils.CacheSnapshotFlag, utils.CachePreimagesFlag, + utils.PersistDiffFlag, utils.ListenPortFlag, utils.MaxPeersFlag, utils.MaxPendingPeersFlag, diff --git a/core/blockchain.go b/core/blockchain.go index 65ff1deb9b..11f3590434 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -93,7 +93,7 @@ const ( diffLayerFreezerRecheckInterval = 3 * time.Second diffLayerFreezerBlockLimit = 864000 // The number of diff layers that should be kept in disk. diffLayerPruneRecheckInterval = 1 * time.Second // The interval to prune unverified diff layers - maxDiffQueueDist = 64 // Maximum allowed distance from the chain head to queue diffLayers + maxDiffQueueDist = 128 // Maximum allowed distance from the chain head to queue diffLayers maxDiffLimit = 128 // Maximum number of unique diff layers a peer may have delivered maxDiffForkDist = 11 // Maximum allowed backward distance from the chain head @@ -218,7 +218,7 @@ type BlockChain struct { // untrusted diff layers diffMux sync.RWMutex - receivedDiffLayers map[common.Hash]map[common.Hash]*types.DiffLayer // map[blockHash] map[DiffHash]Diff + blockHashToDiffLayers map[common.Hash]map[common.Hash]*types.DiffLayer // map[blockHash] map[DiffHash]Diff diffHashToBlockHash map[common.Hash]common.Hash // map[diffHash]blockHash diffHashToPeers map[common.Hash]map[string]struct{} // map[diffHash]map[pid] diffNumToBlockHashes map[uint64]map[common.Hash]struct{} // map[number]map[blockHash] @@ -284,7 +284,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par engine: engine, vmConfig: vmConfig, diffQueue: prque.New(nil), - receivedDiffLayers: make(map[common.Hash]map[common.Hash]*types.DiffLayer), + blockHashToDiffLayers: make(map[common.Hash]map[common.Hash]*types.DiffLayer), diffHashToBlockHash: make(map[common.Hash]common.Hash), diffHashToPeers: make(map[common.Hash]map[string]struct{}), diffNumToBlockHashes: make(map[uint64]map[common.Hash]struct{}), @@ -944,12 +944,12 @@ func (bc *BlockChain) GetDiffLayerRLP(blockHash common.Hash) rlp.RawValue { if err != nil { return nil } - bc.diffLayerRLPCache.Add(blockHash, bz) + bc.diffLayerRLPCache.Add(blockHash, rlp.RawValue(bz)) return bz } // fallback to untrusted sources. - diff := bc.GetDiffLayer(blockHash, "") + diff := bc.GetUnTrustedDiffLayer(blockHash, "") if diff != nil { bz, err := rlp.EncodeToBytes(diff) if err != nil { @@ -966,7 +966,7 @@ func (bc *BlockChain) GetDiffLayerRLP(blockHash common.Hash) rlp.RawValue { } rawData := rawdb.ReadDiffLayerRLP(diffStore, blockHash) if len(rawData) != 0 { - bc.diffLayerRLPCache.Add(blockHash, rawData) + bc.diffLayerRLPCache.Add(blockHash, rlp.RawValue(rawData)) } return rawData } @@ -2018,10 +2018,10 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er if !statedb.IsLightProcessed() { if err := bc.validator.ValidateState(block, statedb, receipts, usedGas); err != nil { log.Error("validate state failed", "error", err) + bc.reportBlock(block, receipts, err) return it.index, err } } - bc.reportBlock(block, receipts, err) bc.cacheReceipts(block.Hash(), receipts) bc.cacheBlock(block.Hash(), block) proctime := time.Since(start) @@ -2442,7 +2442,7 @@ func (bc *BlockChain) trustedDiffLayerFreezeLoop() { diffLayer := diff.(*types.DiffLayer) // if the block old enough - if int64(currentHeight)+prio > int64(bc.triesInMemory) { + if int64(currentHeight)+prio >= int64(bc.triesInMemory) { canonicalHash := bc.GetCanonicalHash(uint64(-prio)) // on the canonical chain if canonicalHash == diffLayer.BlockHash { @@ -2474,10 +2474,10 @@ func (bc *BlockChain) trustedDiffLayerFreezeLoop() { } } -func (bc *BlockChain) GetDiffLayer(blockHash common.Hash, pid string) *types.DiffLayer { +func (bc *BlockChain) GetUnTrustedDiffLayer(blockHash common.Hash, pid string) *types.DiffLayer { bc.diffMux.RLock() defer bc.diffMux.RUnlock() - if diffs, exist := bc.receivedDiffLayers[blockHash]; exist && len(diffs) != 0 { + if diffs, exist := bc.blockHashToDiffLayers[blockHash]; exist && len(diffs) != 0 { if len(diffs) == 1 { // return the only one diff layer for _, diff := range diffs { @@ -2489,7 +2489,7 @@ func (bc *BlockChain) GetDiffLayer(blockHash common.Hash, pid string) *types.Dif if diffHashes, exist := bc.diffPeersToDiffHashes[pid]; exist { for diff := range diffs { if _, overlap := diffHashes[diff]; overlap { - return bc.receivedDiffLayers[blockHash][diff] + return bc.blockHashToDiffLayers[blockHash][diff] } } } @@ -2524,10 +2524,10 @@ func (bc *BlockChain) removeDiffLayers(diffHash common.Hash) { for invalidDiffHash := range invalidDiffHashes { delete(bc.diffHashToPeers, invalidDiffHash) affectedBlockHash := bc.diffHashToBlockHash[invalidDiffHash] - if diffs, exist := bc.receivedDiffLayers[affectedBlockHash]; exist { + if diffs, exist := bc.blockHashToDiffLayers[affectedBlockHash]; exist { delete(diffs, invalidDiffHash) if len(diffs) == 0 { - delete(bc.receivedDiffLayers, affectedBlockHash) + delete(bc.blockHashToDiffLayers, affectedBlockHash) } } delete(bc.diffHashToBlockHash, invalidDiffHash) @@ -2575,19 +2575,19 @@ func (bc *BlockChain) pruneDiffLayer() { } staleDiffHashes := make(map[common.Hash]struct{}, 0) for blockHash := range staleBlockHashes { - if diffHashes, exist := bc.receivedDiffLayers[blockHash]; exist { + if diffHashes, exist := bc.blockHashToDiffLayers[blockHash]; exist { for diffHash := range diffHashes { staleDiffHashes[diffHash] = struct{}{} delete(bc.diffHashToBlockHash, diffHash) delete(bc.diffHashToPeers, diffHash) } } - delete(bc.receivedDiffLayers, blockHash) + delete(bc.blockHashToDiffLayers, blockHash) } for diffHash := range staleDiffHashes { for p, diffHashes := range bc.diffPeersToDiffHashes { delete(diffHashes, diffHash) - if len(diffHash) == 0 { + if len(diffHashes) == 0 { delete(bc.diffPeersToDiffHashes, p) } } @@ -2599,17 +2599,20 @@ func (bc *BlockChain) HandleDiffLayer(diffLayer *types.DiffLayer, pid string) er // Basic check currentHeight := bc.CurrentBlock().NumberU64() if diffLayer.Number > currentHeight && diffLayer.Number-currentHeight > maxDiffQueueDist { - return errors.New("diff layers too new from current") + log.Error("diff layers too new from current", "pid", pid) + return nil } if diffLayer.Number < currentHeight && currentHeight-diffLayer.Number > maxDiffForkDist { - return errors.New("diff layers too old from current") + log.Error("diff layers too old from current", "pid", pid) + return nil } bc.diffMux.Lock() defer bc.diffMux.Unlock() if len(bc.diffPeersToDiffHashes[pid]) > maxDiffLimit { - return errors.New("too many accumulated diffLayers") + log.Error("too many accumulated diffLayers", "pid", pid) + return nil } if _, exist := bc.diffPeersToDiffHashes[pid]; exist { if _, alreadyHas := bc.diffPeersToDiffHashes[pid][diffLayer.DiffHash]; alreadyHas { @@ -2629,10 +2632,10 @@ func (bc *BlockChain) HandleDiffLayer(diffLayer *types.DiffLayer, pid string) er } bc.diffHashToPeers[diffLayer.DiffHash][pid] = struct{}{} - if _, exist := bc.receivedDiffLayers[diffLayer.BlockHash]; !exist { - bc.receivedDiffLayers[diffLayer.BlockHash] = make(map[common.Hash]*types.DiffLayer) + if _, exist := bc.blockHashToDiffLayers[diffLayer.BlockHash]; !exist { + bc.blockHashToDiffLayers[diffLayer.BlockHash] = make(map[common.Hash]*types.DiffLayer) } - bc.receivedDiffLayers[diffLayer.BlockHash][diffLayer.DiffHash] = diffLayer + bc.blockHashToDiffLayers[diffLayer.BlockHash][diffLayer.DiffHash] = diffLayer bc.diffHashToBlockHash[diffLayer.DiffHash] = diffLayer.BlockHash return nil diff --git a/core/blockchain_diff_test.go b/core/blockchain_diff_test.go new file mode 100644 index 0000000000..d44ba0465b --- /dev/null +++ b/core/blockchain_diff_test.go @@ -0,0 +1,263 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Tests that abnormal program termination (i.e.crash) and restart doesn't leave +// the database in some strange state with gaps in the chain, nor with block data +// dangling in the future. + +package core + +import ( + "math/big" + "testing" + "time" + + "golang.org/x/crypto/sha3" + + "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/state/snapshot" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/ethdb/memorydb" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" +) + +var ( + // testKey is a private key to use for funding a tester account. + testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + // testAddr is the Ethereum address of the tester account. + testAddr = crypto.PubkeyToAddress(testKey.PublicKey) +) + +// testBackend is a mock implementation of the live Ethereum message handler. Its +// purpose is to allow testing the request/reply workflows and wire serialization +// in the `eth` protocol without actually doing any data processing. +type testBackend struct { + db ethdb.Database + chain *BlockChain +} + +// newTestBackend creates an empty chain and wraps it into a mock backend. +func newTestBackend(blocks int, light bool) *testBackend { + return newTestBackendWithGenerator(blocks, light) +} + +// newTestBackend creates a chain with a number of explicitly defined blocks and +// wraps it into a mock backend. +func newTestBackendWithGenerator(blocks int, lightProcess bool) *testBackend { + signer := types.HomesteadSigner{} + // Create a database pre-initialize with a genesis block + db := rawdb.NewMemoryDatabase() + db.SetDiffStore(memorydb.New()) + (&Genesis{ + Config: params.TestChainConfig, + Alloc: GenesisAlloc{testAddr: {Balance: big.NewInt(100000000000000000)}}, + }).MustCommit(db) + + chain, _ := NewBlockChain(db, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, nil) + generator := func(i int, block *BlockGen) { + // The chain maker doesn't have access to a chain, so the difficulty will be + // lets unset (nil). Set it here to the correct value. + block.SetCoinbase(testAddr) + + // We want to simulate an empty middle block, having the same state as the + // first one. The last is needs a state change again to force a reorg. + tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddr), common.Address{0x01}, big.NewInt(1), params.TxGas, big.NewInt(1), nil), signer, testKey) + if err != nil { + panic(err) + } + block.AddTxWithChain(chain, tx) + } + bs, _ := GenerateChain(params.TestChainConfig, chain.Genesis(), ethash.NewFaker(), db, blocks, generator) + if _, err := chain.InsertChain(bs); err != nil { + panic(err) + } + if lightProcess { + EnableLightProcessor(chain) + } + + return &testBackend{ + db: db, + chain: chain, + } +} + +// close tears down the transaction pool and chain behind the mock backend. +func (b *testBackend) close() { + b.chain.Stop() +} + +func (b *testBackend) Chain() *BlockChain { return b.chain } + +func rawDataToDiffLayer(data rlp.RawValue) (*types.DiffLayer, error) { + var diff types.DiffLayer + hasher := sha3.NewLegacyKeccak256() + err := rlp.DecodeBytes(data, &diff) + if err != nil { + return nil, err + } + hasher.Write(data) + var diffHash common.Hash + hasher.Sum(diffHash[:0]) + hasher.Reset() + diff.DiffHash = diffHash + return &diff, nil +} + +func TestProcessDiffLayer(t *testing.T) { + t.Parallel() + + blockNum := maxDiffLimit - 1 + fullBackend := newTestBackend(blockNum, false) + falseDiff := 5 + defer fullBackend.close() + + lightBackend := newTestBackend(0, true) + defer lightBackend.close() + for i := 1; i <= blockNum-falseDiff; i++ { + block := fullBackend.chain.GetBlockByNumber(uint64(i)) + if block == nil { + t.Fatal("block should not be nil") + } + blockHash := block.Hash() + rawDiff := fullBackend.chain.GetDiffLayerRLP(blockHash) + diff, err := rawDataToDiffLayer(rawDiff) + if err != nil { + t.Errorf("failed to decode rawdata %v", err) + } + lightBackend.Chain().HandleDiffLayer(diff, "testpid") + _, err = lightBackend.chain.insertChain([]*types.Block{block}, true) + if err != nil { + t.Errorf("failed to insert block %v", err) + } + } + currentBlock := lightBackend.chain.CurrentBlock() + nextBlock := fullBackend.chain.GetBlockByNumber(currentBlock.NumberU64() + 1) + rawDiff := fullBackend.chain.GetDiffLayerRLP(nextBlock.Hash()) + diff, _ := rawDataToDiffLayer(rawDiff) + latestAccount, _ := snapshot.FullAccount(diff.Accounts[0].Blob) + latestAccount.Balance = big.NewInt(0) + bz, _ := rlp.EncodeToBytes(&latestAccount) + diff.Accounts[0].Blob = bz + + lightBackend.Chain().HandleDiffLayer(diff, "testpid") + + _, err := lightBackend.chain.insertChain([]*types.Block{nextBlock}, true) + if err != nil { + t.Errorf("failed to process block %v", err) + } + + // the diff cache should be cleared + if len(lightBackend.chain.diffPeersToDiffHashes) != 0 { + t.Errorf("the size of diffPeersToDiffHashes should be 0, but get %d", len(lightBackend.chain.diffPeersToDiffHashes)) + } + if len(lightBackend.chain.diffHashToPeers) != 0 { + t.Errorf("the size of diffHashToPeers should be 0, but get %d", len(lightBackend.chain.diffHashToPeers)) + } + if len(lightBackend.chain.diffHashToBlockHash) != 0 { + t.Errorf("the size of diffHashToBlockHash should be 0, but get %d", len(lightBackend.chain.diffHashToBlockHash)) + } + if len(lightBackend.chain.blockHashToDiffLayers) != 0 { + t.Errorf("the size of blockHashToDiffLayers should be 0, but get %d", len(lightBackend.chain.blockHashToDiffLayers)) + } +} + +func TestFreezeDiffLayer(t *testing.T) { + t.Parallel() + + blockNum := 1024 + fullBackend := newTestBackend(blockNum, true) + defer fullBackend.close() + if fullBackend.chain.diffQueue.Size() != blockNum { + t.Errorf("size of diff queue is wrong, expected: %d, get: %d", blockNum, fullBackend.chain.diffQueue.Size()) + } + time.Sleep(diffLayerFreezerRecheckInterval + 1*time.Second) + if fullBackend.chain.diffQueue.Size() != int(fullBackend.chain.triesInMemory) { + t.Errorf("size of diff queue is wrong, expected: %d, get: %d", blockNum, fullBackend.chain.diffQueue.Size()) + } + + block := fullBackend.chain.GetBlockByNumber(uint64(blockNum / 2)) + diffStore := fullBackend.chain.db.DiffStore() + rawData := rawdb.ReadDiffLayerRLP(diffStore, block.Hash()) + if len(rawData) == 0 { + t.Error("do not find diff layer in db") + } +} + +func TestPruneDiffLayer(t *testing.T) { + t.Parallel() + + blockNum := 1024 + fullBackend := newTestBackend(blockNum, true) + defer fullBackend.close() + + anotherFullBackend := newTestBackend(2*blockNum, true) + defer anotherFullBackend.close() + + for num := uint64(1); num < uint64(blockNum); num++ { + header := fullBackend.chain.GetHeaderByNumber(num) + rawDiff := fullBackend.chain.GetDiffLayerRLP(header.Hash()) + diff, _ := rawDataToDiffLayer(rawDiff) + fullBackend.Chain().HandleDiffLayer(diff, "testpid1") + fullBackend.Chain().HandleDiffLayer(diff, "testpid2") + + } + fullBackend.chain.pruneDiffLayer() + if len(fullBackend.chain.diffNumToBlockHashes) != maxDiffForkDist { + t.Error("unexpected size of diffNumToBlockHashes") + } + if len(fullBackend.chain.diffPeersToDiffHashes) != 2 { + t.Error("unexpected size of diffPeersToDiffHashes") + } + if len(fullBackend.chain.blockHashToDiffLayers) != maxDiffForkDist { + t.Error("unexpected size of diffNumToBlockHashes") + } + if len(fullBackend.chain.diffHashToBlockHash) != maxDiffForkDist { + t.Error("unexpected size of diffHashToBlockHash") + } + if len(fullBackend.chain.diffHashToPeers) != maxDiffForkDist { + t.Error("unexpected size of diffHashToPeers") + } + + blocks := make([]*types.Block, 0, blockNum) + for i := blockNum + 1; i <= 2*blockNum; i++ { + b := anotherFullBackend.chain.GetBlockByNumber(uint64(i)) + blocks = append(blocks, b) + } + fullBackend.chain.insertChain(blocks, true) + fullBackend.chain.pruneDiffLayer() + if len(fullBackend.chain.diffNumToBlockHashes) != 0 { + t.Error("unexpected size of diffNumToBlockHashes") + } + if len(fullBackend.chain.diffPeersToDiffHashes) != 0 { + t.Error("unexpected size of diffPeersToDiffHashes") + } + if len(fullBackend.chain.blockHashToDiffLayers) != 0 { + t.Error("unexpected size of diffNumToBlockHashes") + } + if len(fullBackend.chain.diffHashToBlockHash) != 0 { + t.Error("unexpected size of diffHashToBlockHash") + } + if len(fullBackend.chain.diffHashToPeers) != 0 { + t.Error("unexpected size of diffHashToPeers") + } + +} diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 5bad47155f..82d5df06ce 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -94,6 +94,7 @@ func (frdb *freezerdb) Freeze(threshold uint64) error { // nofreezedb is a database wrapper that disables freezer data retrievals. type nofreezedb struct { ethdb.KeyValueStore + diffStore ethdb.KeyValueStore } // HasAncient returns an error as we don't have a backing chain freezer. @@ -132,11 +133,11 @@ func (db *nofreezedb) Sync() error { } func (db *nofreezedb) DiffStore() ethdb.KeyValueStore { - return nil + return db.diffStore } func (db *nofreezedb) SetDiffStore(diff ethdb.KeyValueStore) { - panic("not implement") + db.diffStore = diff } // NewDatabase creates a high level database on top of a given key-value data diff --git a/core/state_processor.go b/core/state_processor.go index 99a9003bc6..5ad7a7908e 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -80,12 +80,19 @@ func (p *LightStateProcessor) Process(block *types.Block, statedb *state.StateDB if peer, ok := block.ReceivedFrom.(PeerIDer); ok { pid = peer.ID() } - diffLayer := p.bc.GetDiffLayer(block.Hash(), pid) + diffLayer := p.bc.GetUnTrustedDiffLayer(block.Hash(), pid) if diffLayer != nil { + if err := diffLayer.Receipts.DeriveFields(p.bc.chainConfig, block.Hash(), block.NumberU64(), block.Transactions()); err != nil { + log.Error("Failed to derive block receipts fields", "hash", block.Hash(), "number", block.NumberU64(), "err", err) + // fallback to full process + return p.StateProcessor.Process(block, statedb, cfg) + } receipts, logs, gasUsed, err := p.LightProcess(diffLayer, block, statedb, cfg) if err == nil { + log.Info("do light process success at block", "num", block.NumberU64()) return statedb, receipts, logs, gasUsed, nil } else { + log.Error("do light process err at block\n", "num", block.NumberU64(), "err", err) p.bc.removeDiffLayers(diffLayer.DiffHash) // prepare new statedb statedb.StopPrefetcher() diff --git a/core/types/block.go b/core/types/block.go index 7f2a0785cb..a577e60516 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -372,7 +372,6 @@ func (b *Block) Hash() common.Hash { type Blocks []*Block type DiffLayer struct { - DiffHash common.Hash `rlp:"_"` BlockHash common.Hash Number uint64 Receipts Receipts // Receipts are duplicated stored to simplify the logic @@ -380,11 +379,56 @@ type DiffLayer struct { Destructs []common.Address Accounts []DiffAccount Storages []DiffStorage + + DiffHash common.Hash +} + +type extDiffLayer struct { + BlockHash common.Hash + Number uint64 + Receipts []*ReceiptForStorage // Receipts are duplicated stored to simplify the logic + Codes []DiffCode + Destructs []common.Address + Accounts []DiffAccount + Storages []DiffStorage +} + +// DecodeRLP decodes the Ethereum +func (d *DiffLayer) DecodeRLP(s *rlp.Stream) error { + var ed extDiffLayer + if err := s.Decode(&ed); err != nil { + return err + } + d.BlockHash, d.Number, d.Codes, d.Destructs, d.Accounts, d.Storages = + ed.BlockHash, ed.Number, ed.Codes, ed.Destructs, ed.Accounts, ed.Storages + + d.Receipts = make([]*Receipt, len(ed.Receipts)) + for i, storageReceipt := range ed.Receipts { + d.Receipts[i] = (*Receipt)(storageReceipt) + } + return nil +} + +// EncodeRLP serializes b into the Ethereum RLP block format. +func (d *DiffLayer) EncodeRLP(w io.Writer) error { + storageReceipts := make([]*ReceiptForStorage, len(d.Receipts)) + for i, receipt := range d.Receipts { + storageReceipts[i] = (*ReceiptForStorage)(receipt) + } + return rlp.Encode(w, extDiffLayer{ + BlockHash: d.BlockHash, + Number: d.Number, + Receipts: storageReceipts, + Codes: d.Codes, + Destructs: d.Destructs, + Accounts: d.Accounts, + Storages: d.Storages, + }) } func (d *DiffLayer) Validate() error { - if d.BlockHash == (common.Hash{}) || d.DiffHash == (common.Hash{}) { - return errors.New("both BlockHash and DiffHash can't be empty") + if d.BlockHash == (common.Hash{}) { + return errors.New("blockHash can't be empty") } for _, storage := range d.Storages { if len(storage.Keys) != len(storage.Vals) { diff --git a/eth/handler_diff_test.go b/eth/handler_diff_test.go new file mode 100644 index 0000000000..0861736c5e --- /dev/null +++ b/eth/handler_diff_test.go @@ -0,0 +1,203 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package eth + +import ( + "crypto/rand" + "math/big" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core" + "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/eth/downloader" + "github.com/ethereum/go-ethereum/eth/protocols/diff" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" +) + +// testBackend is a mock implementation of the live Ethereum message handler. Its +// purpose is to allow testing the request/reply workflows and wire serialization +// in the `eth` protocol without actually doing any data processing. +type testBackend struct { + db ethdb.Database + chain *core.BlockChain + txpool *core.TxPool + + handler *handler +} + +// newTestBackend creates an empty chain and wraps it into a mock backend. +func newTestBackend(blocks int) *testBackend { + return newTestBackendWithGenerator(blocks) +} + +// newTestBackend creates a chain with a number of explicitly defined blocks and +// wraps it into a mock backend. +func newTestBackendWithGenerator(blocks int) *testBackend { + signer := types.HomesteadSigner{} + // Create a database pre-initialize with a genesis block + db := rawdb.NewMemoryDatabase() + (&core.Genesis{ + Config: params.TestChainConfig, + Alloc: core.GenesisAlloc{testAddr: {Balance: big.NewInt(100000000000000000)}}, + }).MustCommit(db) + + chain, _ := core.NewBlockChain(db, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, nil) + generator := func(i int, block *core.BlockGen) { + // The chain maker doesn't have access to a chain, so the difficulty will be + // lets unset (nil). Set it here to the correct value. + block.SetCoinbase(testAddr) + + // We want to simulate an empty middle block, having the same state as the + // first one. The last is needs a state change again to force a reorg. + tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddr), common.Address{0x01}, big.NewInt(1), params.TxGas, big.NewInt(1), nil), signer, testKey) + if err != nil { + panic(err) + } + block.AddTxWithChain(chain, tx) + } + bs, _ := core.GenerateChain(params.TestChainConfig, chain.Genesis(), ethash.NewFaker(), db, blocks, generator) + if _, err := chain.InsertChain(bs); err != nil { + panic(err) + } + txpool := newTestTxPool() + + handler, _ := newHandler(&handlerConfig{ + Database: db, + Chain: chain, + TxPool: txpool, + Network: 1, + Sync: downloader.FullSync, + BloomCache: 1, + }) + handler.Start(100) + + txconfig := core.DefaultTxPoolConfig + txconfig.Journal = "" // Don't litter the disk with test journals + + return &testBackend{ + db: db, + chain: chain, + txpool: core.NewTxPool(txconfig, params.TestChainConfig, chain), + handler: handler, + } +} + +// close tears down the transaction pool and chain behind the mock backend. +func (b *testBackend) close() { + b.txpool.Stop() + b.chain.Stop() + b.handler.Stop() +} + +func (b *testBackend) Chain() *core.BlockChain { return b.chain } + +func (b *testBackend) RunPeer(peer *diff.Peer, handler diff.Handler) error { + // Normally the backend would do peer mainentance and handshakes. All that + // is omitted and we will just give control back to the handler. + return handler(peer) +} +func (b *testBackend) PeerInfo(enode.ID) interface{} { panic("not implemented") } + +func (b *testBackend) Handle(*diff.Peer, diff.Packet) error { + panic("data processing tests should be done in the handler package") +} + +type testPeer struct { + *diff.Peer + + net p2p.MsgReadWriter // Network layer reader/writer to simulate remote messaging + app *p2p.MsgPipeRW // Application layer reader/writer to simulate the local side +} + +// newTestPeer creates a new peer registered at the given data backend. +func newTestPeer(name string, version uint, backend *testBackend) (*testPeer, <-chan error) { + // Create a message pipe to communicate through + app, net := p2p.MsgPipe() + + // Start the peer on a new thread + var id enode.ID + rand.Read(id[:]) + + peer := diff.NewPeer(version, p2p.NewPeer(id, name, nil), net) + errc := make(chan error, 1) + go func() { + errc <- backend.RunPeer(peer, func(peer *diff.Peer) error { + + return diff.Handle((*diffHandler)(backend.handler), peer) + }) + }() + return &testPeer{app: app, net: net, Peer: peer}, errc +} + +// close terminates the local side of the peer, notifying the remote protocol +// manager of termination. +func (p *testPeer) close() { + p.Peer.Close() + p.app.Close() +} + +func TestHandleDiffLayer(t *testing.T) { + t.Parallel() + + blockNum := 1024 + waitInterval := 100 * time.Millisecond + backend := newTestBackend(blockNum) + defer backend.close() + + peer, _ := newTestPeer("peer", diff.Diff1, backend) + defer peer.close() + + tests := []struct { + DiffLayer *types.DiffLayer + Valid bool + }{ + {DiffLayer: &types.DiffLayer{ + BlockHash: common.Hash{0x1}, + Number: 1025, + }, Valid: true}, + {DiffLayer: &types.DiffLayer{ + BlockHash: common.Hash{0x2}, + Number: 2025, + }, Valid: false}, + {DiffLayer: &types.DiffLayer{ + BlockHash: common.Hash{0x3}, + Number: 500, + }, Valid: false}, + } + + for _, tt := range tests { + bz, _ := rlp.EncodeToBytes(tt.DiffLayer) + + p2p.Send(peer.app, diff.DiffLayerMsg, diff.DiffLayersPacket{rlp.RawValue(bz)}) + } + time.Sleep(waitInterval) + for idx, tt := range tests { + diff := backend.chain.GetUnTrustedDiffLayer(tt.DiffLayer.BlockHash, "") + if (tt.Valid && diff == nil) || (!tt.Valid && diff != nil) { + t.Errorf("test: %d, diff layer handle failed", idx) + } + } +} diff --git a/eth/protocols/diff/handler.go b/eth/protocols/diff/handler.go index eadc0030df..cf6828b18b 100644 --- a/eth/protocols/diff/handler.go +++ b/eth/protocols/diff/handler.go @@ -56,9 +56,9 @@ func MakeProtocols(backend Backend, dnsdisc enode.Iterator) []p2p.Protocol { Version: version, Length: protocolLengths[version], Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error { - return backend.RunPeer(newPeer(version, p, rw), func(peer *Peer) error { + return backend.RunPeer(NewPeer(version, p, rw), func(peer *Peer) error { defer peer.Close() - return handle(backend, peer) + return Handle(backend, peer) }) }, NodeInfo: func() interface{} { @@ -74,9 +74,9 @@ func MakeProtocols(backend Backend, dnsdisc enode.Iterator) []p2p.Protocol { return protocols } -// handle is the callback invoked to manage the life cycle of a `diff` peer. +// Handle is the callback invoked to manage the life cycle of a `diff` peer. // When this function terminates, the peer is disconnected. -func handle(backend Backend, peer *Peer) error { +func Handle(backend Backend, peer *Peer) error { for { if err := handleMessage(backend, peer); err != nil { peer.Log().Debug("Message handling failed in `diff`", "err", err) diff --git a/eth/protocols/diff/handler_test.go b/eth/protocols/diff/handler_test.go new file mode 100644 index 0000000000..fcdca07b9f --- /dev/null +++ b/eth/protocols/diff/handler_test.go @@ -0,0 +1,192 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package diff + +import ( + "math/big" + "math/rand" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core" + "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/crypto" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" +) + +var ( + // testKey is a private key to use for funding a tester account. + testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + + // testAddr is the Ethereum address of the tester account. + testAddr = crypto.PubkeyToAddress(testKey.PublicKey) +) + +// testBackend is a mock implementation of the live Ethereum message handler. Its +// purpose is to allow testing the request/reply workflows and wire serialization +// in the `eth` protocol without actually doing any data processing. +type testBackend struct { + db ethdb.Database + chain *core.BlockChain + txpool *core.TxPool +} + +// newTestBackend creates an empty chain and wraps it into a mock backend. +func newTestBackend(blocks int) *testBackend { + return newTestBackendWithGenerator(blocks) +} + +// newTestBackend creates a chain with a number of explicitly defined blocks and +// wraps it into a mock backend. +func newTestBackendWithGenerator(blocks int) *testBackend { + signer := types.HomesteadSigner{} + // Create a database pre-initialize with a genesis block + db := rawdb.NewMemoryDatabase() + (&core.Genesis{ + Config: params.TestChainConfig, + Alloc: core.GenesisAlloc{testAddr: {Balance: big.NewInt(100000000000000000)}}, + }).MustCommit(db) + + chain, _ := core.NewBlockChain(db, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, nil) + generator := func(i int, block *core.BlockGen) { + // The chain maker doesn't have access to a chain, so the difficulty will be + // lets unset (nil). Set it here to the correct value. + block.SetCoinbase(testAddr) + + // We want to simulate an empty middle block, having the same state as the + // first one. The last is needs a state change again to force a reorg. + tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddr), common.Address{0x01}, big.NewInt(1), params.TxGas, big.NewInt(1), nil), signer, testKey) + if err != nil { + panic(err) + } + block.AddTxWithChain(chain, tx) + } + bs, _ := core.GenerateChain(params.TestChainConfig, chain.Genesis(), ethash.NewFaker(), db, blocks, generator) + if _, err := chain.InsertChain(bs); err != nil { + panic(err) + } + txconfig := core.DefaultTxPoolConfig + txconfig.Journal = "" // Don't litter the disk with test journals + + return &testBackend{ + db: db, + chain: chain, + txpool: core.NewTxPool(txconfig, params.TestChainConfig, chain), + } +} + +// close tears down the transaction pool and chain behind the mock backend. +func (b *testBackend) close() { + b.txpool.Stop() + b.chain.Stop() +} + +func (b *testBackend) Chain() *core.BlockChain { return b.chain } + +func (b *testBackend) RunPeer(peer *Peer, handler Handler) error { + // Normally the backend would do peer mainentance and handshakes. All that + // is omitted and we will just give control back to the handler. + return handler(peer) +} +func (b *testBackend) PeerInfo(enode.ID) interface{} { panic("not implemented") } + +func (b *testBackend) Handle(*Peer, Packet) error { + panic("data processing tests should be done in the handler package") +} + +func TestGetDiffLayers(t *testing.T) { testGetDiffLayers(t, Diff1) } + +func testGetDiffLayers(t *testing.T, protocol uint) { + t.Parallel() + + blockNum := 2048 + backend := newTestBackend(blockNum) + defer backend.close() + + peer, _ := newTestPeer("peer", protocol, backend) + defer peer.close() + + foundDiffBlockHashes := make([]common.Hash, 0) + foundDiffPackets := make([]FullDiffLayersPacket, 0) + foundDiffRlps := make([]rlp.RawValue, 0) + missDiffBlockHashes := make([]common.Hash, 0) + missDiffPackets := make([]FullDiffLayersPacket, 0) + + for i := 0; i < 100; i++ { + number := uint64(rand.Int63n(1024)) + if number == 0 { + continue + } + foundHash := backend.chain.GetCanonicalHash(number + 1024) + missHash := backend.chain.GetCanonicalHash(number) + foundRlp := backend.chain.GetDiffLayerRLP(foundHash) + + if len(foundHash) == 0 { + t.Fatalf("Faild to fond rlp encoded diff layer %v", foundHash) + } + foundDiffPackets = append(foundDiffPackets, FullDiffLayersPacket{ + RequestId: uint64(i), + DiffLayersPacket: []rlp.RawValue{foundRlp}, + }) + foundDiffRlps = append(foundDiffRlps, foundRlp) + + missDiffPackets = append(missDiffPackets, FullDiffLayersPacket{ + RequestId: uint64(i), + DiffLayersPacket: []rlp.RawValue{}, + }) + + missDiffBlockHashes = append(missDiffBlockHashes, missHash) + foundDiffBlockHashes = append(foundDiffBlockHashes, foundHash) + } + + for idx, blockHash := range foundDiffBlockHashes { + p2p.Send(peer.app, GetDiffLayerMsg, GetDiffLayersPacket{RequestId: uint64(idx), BlockHashes: []common.Hash{blockHash}}) + if err := p2p.ExpectMsg(peer.app, FullDiffLayerMsg, foundDiffPackets[idx]); err != nil { + t.Errorf("test %d: diff layer mismatch: %v", idx, err) + } + } + + for idx, blockHash := range missDiffBlockHashes { + p2p.Send(peer.app, GetDiffLayerMsg, GetDiffLayersPacket{RequestId: uint64(idx), BlockHashes: []common.Hash{blockHash}}) + if err := p2p.ExpectMsg(peer.app, FullDiffLayerMsg, missDiffPackets[idx]); err != nil { + t.Errorf("test %d: diff layer mismatch: %v", idx, err) + } + } + + p2p.Send(peer.app, GetDiffLayerMsg, GetDiffLayersPacket{RequestId: 111, BlockHashes: foundDiffBlockHashes}) + if err := p2p.ExpectMsg(peer.app, FullDiffLayerMsg, FullDiffLayersPacket{ + 111, + foundDiffRlps, + }); err != nil { + t.Errorf("test: diff layer mismatch: %v", err) + } + + p2p.Send(peer.app, GetDiffLayerMsg, GetDiffLayersPacket{RequestId: 111, BlockHashes: missDiffBlockHashes}) + if err := p2p.ExpectMsg(peer.app, FullDiffLayerMsg, FullDiffLayersPacket{ + 111, + nil, + }); err != nil { + t.Errorf("test: diff layer mismatch: %v", err) + } +} diff --git a/eth/protocols/diff/peer.go b/eth/protocols/diff/peer.go index f0c4952e65..c55a7e1581 100644 --- a/eth/protocols/diff/peer.go +++ b/eth/protocols/diff/peer.go @@ -24,9 +24,9 @@ type Peer struct { term chan struct{} // Termination channel to stop the broadcasters } -// newPeer create a wrapper for a network connection and negotiated protocol +// NewPeer create a wrapper for a network connection and negotiated protocol // version. -func newPeer(version uint, p *p2p.Peer, rw p2p.MsgReadWriter) *Peer { +func NewPeer(version uint, p *p2p.Peer, rw p2p.MsgReadWriter) *Peer { id := p.ID().String() peer := &Peer{ id: id, diff --git a/eth/protocols/diff/peer_test.go b/eth/protocols/diff/peer_test.go new file mode 100644 index 0000000000..015b86f134 --- /dev/null +++ b/eth/protocols/diff/peer_test.go @@ -0,0 +1,61 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// This file contains some shares testing functionality, common to multiple +// different files and modules being tested. + +package diff + +import ( + "crypto/rand" + + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/enode" +) + +// testPeer is a simulated peer to allow testing direct network calls. +type testPeer struct { + *Peer + + net p2p.MsgReadWriter // Network layer reader/writer to simulate remote messaging + app *p2p.MsgPipeRW // Application layer reader/writer to simulate the local side +} + +// newTestPeer creates a new peer registered at the given data backend. +func newTestPeer(name string, version uint, backend Backend) (*testPeer, <-chan error) { + // Create a message pipe to communicate through + app, net := p2p.MsgPipe() + + // Start the peer on a new thread + var id enode.ID + rand.Read(id[:]) + + peer := NewPeer(version, p2p.NewPeer(id, name, nil), net) + errc := make(chan error, 1) + go func() { + errc <- backend.RunPeer(peer, func(peer *Peer) error { + return Handle(backend, peer) + }) + }() + return &testPeer{app: app, net: net, Peer: peer}, errc +} + +// close terminates the local side of the peer, notifying the remote protocol +// manager of termination. +func (p *testPeer) close() { + p.Peer.Close() + p.app.Close() +} diff --git a/eth/protocols/diff/protocol.go b/eth/protocols/diff/protocol.go index e4bf2f4a08..881ff828b8 100644 --- a/eth/protocols/diff/protocol.go +++ b/eth/protocols/diff/protocol.go @@ -29,7 +29,7 @@ import ( // Constants to match up protocol versions and messages const ( - diff1 = 1 + Diff1 = 1 ) // ProtocolName is the official short name of the `diff` protocol used during @@ -38,11 +38,11 @@ const ProtocolName = "diff" // ProtocolVersions are the supported versions of the `diff` protocol (first // is primary). -var ProtocolVersions = []uint{diff1} +var ProtocolVersions = []uint{Diff1} // protocolLengths are the number of implemented message corresponding to // different protocol versions. -var protocolLengths = map[uint]uint64{diff1: 4} +var protocolLengths = map[uint]uint64{Diff1: 4} // maxMessageSize is the maximum cap on the size of a protocol message. const maxMessageSize = 10 * 1024 * 1024 @@ -98,6 +98,7 @@ func (p *DiffLayersPacket) Unpack() ([]*types.DiffLayer, error) { type DiffCapPacket struct { LightSync bool } + type DiffLayersPacket []rlp.RawValue type FullDiffLayersPacket struct { diff --git a/eth/protocols/diff/protocol_test.go b/eth/protocols/diff/protocol_test.go new file mode 100644 index 0000000000..05657eca7f --- /dev/null +++ b/eth/protocols/diff/protocol_test.go @@ -0,0 +1,131 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package diff + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rlp" +) + +// Tests that the custom union field encoder and decoder works correctly. +func TestDiffLayersPacketDataEncodeDecode(t *testing.T) { + // Create a "random" hash for testing + var hash common.Hash + for i := range hash { + hash[i] = byte(i) + } + + testDiffLayers := []*types.DiffLayer{ + { + BlockHash: common.HexToHash("0x1e9624dcd0874958723aa3dae1fe299861e93ef32b980143d798c428bdd7a20a"), + Number: 10479133, + Receipts: []*types.Receipt{{ + GasUsed: 100, + TransactionIndex: 1, + }}, + Codes: []types.DiffCode{{ + Hash: common.HexToHash("0xaece2dbf80a726206cf4df299afa09f9d8f3dcd85ff39bb4b3f0402a3a6af2f5"), + Code: []byte{1, 2, 3, 4}, + }}, + Destructs: []common.Address{ + common.HexToAddress("0x0205bb28ece9289d3fb8eb0c9e999bbd5be2b931"), + }, + Accounts: []types.DiffAccount{{ + Account: common.HexToAddress("0x18b2a687610328590bc8f2e5fedde3b582a49cda"), + Blob: []byte{2, 3, 4, 5}, + }}, + Storages: []types.DiffStorage{{ + Account: common.HexToAddress("0x18b2a687610328590bc8f2e5fedde3b582a49cda"), + Keys: []string{"abc"}, + Vals: [][]byte{{1, 2, 3}}, + }}, + }, + } + // Assemble some table driven tests + tests := []struct { + diffLayers []*types.DiffLayer + fail bool + }{ + {fail: false, diffLayers: testDiffLayers}, + } + // Iterate over each of the tests and try to encode and then decode + for i, tt := range tests { + originPacket := make([]rlp.RawValue, 0) + for _, diff := range tt.diffLayers { + bz, err := rlp.EncodeToBytes(diff) + assert.NoError(t, err) + originPacket = append(originPacket, bz) + } + + bz, err := rlp.EncodeToBytes(DiffLayersPacket(originPacket)) + if err != nil && !tt.fail { + t.Fatalf("test %d: failed to encode packet: %v", i, err) + } else if err == nil && tt.fail { + t.Fatalf("test %d: encode should have failed", i) + } + if !tt.fail { + packet := new(DiffLayersPacket) + if err := rlp.DecodeBytes(bz, packet); err != nil { + t.Fatalf("test %d: failed to decode packet: %v", i, err) + } + diffLayers, err := packet.Unpack() + assert.NoError(t, err) + + if len(diffLayers) != len(tt.diffLayers) { + t.Fatalf("test %d: encode length mismatch: have %+v, want %+v", i, len(diffLayers), len(tt.diffLayers)) + } + expectedPacket := make([]rlp.RawValue, 0) + for _, diff := range diffLayers { + bz, err := rlp.EncodeToBytes(diff) + assert.NoError(t, err) + expectedPacket = append(expectedPacket, bz) + } + for i := 0; i < len(expectedPacket); i++ { + if !bytes.Equal(expectedPacket[i], originPacket[i]) { + t.Fatalf("test %d: data change during encode and decode", i) + } + } + } + } +} + +func TestDiffMessages(t *testing.T) { + + for i, tc := range []struct { + message interface{} + want []byte + }{ + { + DiffCapPacket{true}, + common.FromHex("c101"), + }, + { + GetDiffLayersPacket{1111, []common.Hash{common.HexToHash("0xaece2dbf80a726206cf4df299afa09f9d8f3dcd85ff39bb4b3f0402a3a6af2f5")}}, + common.FromHex("e5820457e1a0aece2dbf80a726206cf4df299afa09f9d8f3dcd85ff39bb4b3f0402a3a6af2f5"), + }, + } { + if have, _ := rlp.EncodeToBytes(tc.message); !bytes.Equal(have, tc.want) { + t.Errorf("test %d, type %T, have\n\t%x\nwant\n\t%x", i, tc.message, have, tc.want) + } + } +} From c598aae597271b33ef3f50c4c4071b548e96b13b Mon Sep 17 00:00:00 2001 From: fudongbai <296179868@qq.com> Date: Thu, 2 Sep 2021 16:28:18 +0800 Subject: [PATCH 05/22] make it faster --- core/state/statedb.go | 2 +- core/state_processor.go | 251 ++++++++++++++++++++++++++-------------- 2 files changed, 163 insertions(+), 90 deletions(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index 1830d00a6c..b949ff468a 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -39,7 +39,7 @@ import ( ) const ( - preLoadLimit = 64 + preLoadLimit = 128 defaultNumOfSlots = 100 ) diff --git a/core/state_processor.go b/core/state_processor.go index 5ad7a7908e..551ebe1559 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -22,6 +22,8 @@ import ( "fmt" "math/big" "math/rand" + "runtime" + "sync" "time" "github.com/ethereum/go-ethereum/common" @@ -39,7 +41,10 @@ import ( "github.com/ethereum/go-ethereum/rlp" ) -const fullProcessCheck = 21 // On light sync mode, will do full process every fullProcessCheck randomly +const ( + fullProcessCheck = 21 // On light sync mode, will do full process every fullProcessCheck randomly + minNumberOfAccountPerTask = 10 +) // StateProcessor is a basic Processor, which takes care of transitioning // state from one point to another. @@ -128,111 +133,179 @@ func (p *LightStateProcessor) LightProcess(diffLayer *types.DiffLayer, block *ty for des := range snapDestructs { statedb.Trie().TryDelete(des[:]) } + threads := 1 + if len(snapAccounts)/runtime.NumCPU() > minNumberOfAccountPerTask { + threads = runtime.NumCPU() + } - // TODO need improve, do it concurrently - for diffAccount, blob := range snapAccounts { - addrHash := crypto.Keccak256Hash(diffAccount[:]) - latestAccount, err := snapshot.FullAccount(blob) - if err != nil { - return nil, nil, 0, err - } + iteAccounts := make([]common.Address, 0, len(snapAccounts)) + for diffAccount, _ := range snapAccounts { + iteAccounts = append(iteAccounts, diffAccount) + } - // fetch previous state - var previousAccount state.Account - enc, err := statedb.Trie().TryGet(diffAccount[:]) - if err != nil { - return nil, nil, 0, err - } - if len(enc) != 0 { - if err := rlp.DecodeBytes(enc, &previousAccount); err != nil { - return nil, nil, 0, err - } - } - if latestAccount.Balance == nil { - latestAccount.Balance = new(big.Int) - } - if previousAccount.Balance == nil { - previousAccount.Balance = new(big.Int) - } - if previousAccount.Root == (common.Hash{}) { - previousAccount.Root = types.EmptyRootHash - } - if len(previousAccount.CodeHash) == 0 { - previousAccount.CodeHash = types.EmptyCodeHash + errChan := make(chan error, threads) + exitChan := make(chan struct{}, 0) + var snapMux sync.RWMutex + var stateMux, diffMux sync.Mutex + for i := 0; i < threads; i++ { + start := i * len(iteAccounts) / threads + end := (i + 1) * len(iteAccounts) / threads + if i+1 == threads { + end = len(iteAccounts) } + go func(start, end int) { + for index := start; index < end; index++ { + select { + // fast fail + case <-exitChan: + return + default: + } + diffAccount := iteAccounts[index] + snapMux.RLock() + blob := snapAccounts[diffAccount] + snapMux.RUnlock() + addrHash := crypto.Keccak256Hash(diffAccount[:]) + latestAccount, err := snapshot.FullAccount(blob) + if err != nil { + errChan <- err + return + } - // skip no change account - if previousAccount.Nonce == latestAccount.Nonce && - bytes.Equal(previousAccount.CodeHash, latestAccount.CodeHash) && - previousAccount.Balance.Cmp(latestAccount.Balance) == 0 && - previousAccount.Root == common.BytesToHash(latestAccount.Root) { - log.Warn("receive redundant account change in diff layer") - delete(snapAccounts, diffAccount) - delete(snapStorage, diffAccount) - continue - } + // fetch previous state + var previousAccount state.Account + stateMux.Lock() + enc, err := statedb.Trie().TryGet(diffAccount[:]) + stateMux.Unlock() + if err != nil { + errChan <- err + return + } + if len(enc) != 0 { + if err := rlp.DecodeBytes(enc, &previousAccount); err != nil { + errChan <- err + return + } + } + if latestAccount.Balance == nil { + latestAccount.Balance = new(big.Int) + } + if previousAccount.Balance == nil { + previousAccount.Balance = new(big.Int) + } + if previousAccount.Root == (common.Hash{}) { + previousAccount.Root = types.EmptyRootHash + } + if len(previousAccount.CodeHash) == 0 { + previousAccount.CodeHash = types.EmptyCodeHash + } - // update code - codeHash := common.BytesToHash(latestAccount.CodeHash) - if !bytes.Equal(latestAccount.CodeHash, previousAccount.CodeHash) && - !bytes.Equal(latestAccount.CodeHash, types.EmptyCodeHash) { - if code, exist := fullDiffCode[codeHash]; exist { - if crypto.Keccak256Hash(code) != codeHash { - return nil, nil, 0, errors.New("code and codeHash mismatch") + // skip no change account + if previousAccount.Nonce == latestAccount.Nonce && + bytes.Equal(previousAccount.CodeHash, latestAccount.CodeHash) && + previousAccount.Balance.Cmp(latestAccount.Balance) == 0 && + previousAccount.Root == common.BytesToHash(latestAccount.Root) { + log.Warn("receive redundant account change in diff layer", "account", diffAccount, "num", block.NumberU64()) + snapMux.Lock() + delete(snapAccounts, diffAccount) + delete(snapStorage, diffAccount) + snapMux.Unlock() + continue } - diffCode[codeHash] = code - } else { - rawCode := rawdb.ReadCode(p.bc.db, codeHash) - if len(rawCode) == 0 { - return nil, nil, 0, errors.New("missing code in difflayer") + + // update code + codeHash := common.BytesToHash(latestAccount.CodeHash) + if !bytes.Equal(latestAccount.CodeHash, previousAccount.CodeHash) && + !bytes.Equal(latestAccount.CodeHash, types.EmptyCodeHash) { + if code, exist := fullDiffCode[codeHash]; exist { + if crypto.Keccak256Hash(code) != codeHash { + errChan <- err + return + } + diffMux.Lock() + diffCode[codeHash] = code + diffMux.Unlock() + } else { + rawCode := rawdb.ReadCode(p.bc.db, codeHash) + if len(rawCode) == 0 { + errChan <- err + return + } + } } - } - } - //update storage - latestRoot := common.BytesToHash(latestAccount.Root) - if latestRoot != previousAccount.Root && latestRoot != types.EmptyRootHash { - accountTrie, err := statedb.Database().OpenStorageTrie(addrHash, previousAccount.Root) - if err != nil { - return nil, nil, 0, err - } - if storageChange, exist := snapStorage[diffAccount]; exist { - for k, v := range storageChange { - if len(v) != 0 { - accountTrie.TryUpdate([]byte(k), v) + //update storage + latestRoot := common.BytesToHash(latestAccount.Root) + if latestRoot != previousAccount.Root && latestRoot != types.EmptyRootHash { + accountTrie, err := statedb.Database().OpenStorageTrie(addrHash, previousAccount.Root) + if err != nil { + errChan <- err + return + } + snapMux.RLock() + storageChange, exist := snapStorage[diffAccount] + snapMux.RUnlock() + + if exist { + for k, v := range storageChange { + if len(v) != 0 { + accountTrie.TryUpdate([]byte(k), v) + } else { + accountTrie.TryDelete([]byte(k)) + } + } } else { - accountTrie.TryDelete([]byte(k)) + errChan <- errors.New("missing storage change in difflayer") + return + } + // check storage root + accountRootHash := accountTrie.Hash() + if latestRoot != accountRootHash { + errChan <- errors.New("account storage root mismatch") + return } + diffMux.Lock() + diffTries[diffAccount] = accountTrie + diffMux.Unlock() + } else { + snapMux.Lock() + delete(snapStorage, diffAccount) + snapMux.Unlock() + } + + // can't trust the blob, need encode by our-self. + latestStateAccount := state.Account{ + Nonce: latestAccount.Nonce, + Balance: latestAccount.Balance, + Root: common.BytesToHash(latestAccount.Root), + CodeHash: latestAccount.CodeHash, + } + bz, err := rlp.EncodeToBytes(&latestStateAccount) + if err != nil { + errChan <- err + return + } + stateMux.Lock() + err = statedb.Trie().TryUpdate(diffAccount[:], bz) + stateMux.Unlock() + if err != nil { + errChan <- err + return } - } else { - return nil, nil, 0, errors.New("missing storage change in difflayer") - } - // check storage root - accountRootHash := accountTrie.Hash() - if latestRoot != accountRootHash { - return nil, nil, 0, errors.New("account storage root mismatch") } - diffTries[diffAccount] = accountTrie - } else { - delete(snapStorage, diffAccount) - } + errChan <- nil + return + }(start, end) + } - // can't trust the blob, need encode by our-self. - latestStateAccount := state.Account{ - Nonce: latestAccount.Nonce, - Balance: latestAccount.Balance, - Root: common.BytesToHash(latestAccount.Root), - CodeHash: latestAccount.CodeHash, - } - bz, err := rlp.EncodeToBytes(&latestStateAccount) - if err != nil { - return nil, nil, 0, err - } - err = statedb.Trie().TryUpdate(diffAccount[:], bz) + for i := 0; i < threads; i++ { + err := <-errChan if err != nil { + close(exitChan) return nil, nil, 0, err } } + var allLogs []*types.Log var gasUsed uint64 for _, receipt := range diffLayer.Receipts { From d1600918a333d891bd21e3c04fd55e2c3dea5be1 Mon Sep 17 00:00:00 2001 From: fudongbai <296179868@qq.com> Date: Thu, 2 Sep 2021 18:26:46 +0800 Subject: [PATCH 06/22] allow validator to light sync --- consensus/consensus.go | 1 + consensus/parlia/parlia.go | 22 ++++++++++++++++++++++ core/state/statedb.go | 1 + core/state_processor.go | 13 +++++++++---- 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/consensus/consensus.go b/consensus/consensus.go index fc161390f9..04a9924383 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -141,4 +141,5 @@ type PoSA interface { IsSystemContract(to *common.Address) bool EnoughDistance(chain ChainReader, header *types.Header) bool IsLocalBlock(header *types.Header) bool + AllowLightProcess(chain ChainReader, currentHeader *types.Header) bool } diff --git a/consensus/parlia/parlia.go b/consensus/parlia/parlia.go index 14304fe2bc..a9df24848b 100644 --- a/consensus/parlia/parlia.go +++ b/consensus/parlia/parlia.go @@ -882,6 +882,28 @@ func (p *Parlia) EnoughDistance(chain consensus.ChainReader, header *types.Heade return snap.enoughDistance(p.val, header) } +func (p *Parlia) AllowLightProcess(chain consensus.ChainReader, currentHeader *types.Header) bool { + snap, err := p.snapshot(chain, currentHeader.Number.Uint64()-1, currentHeader.ParentHash, nil) + if err != nil { + return true + } + + idx := snap.indexOfVal(p.val) + if idx < 0 { + return true + } + validators := snap.validators() + + validatorNum := int64(len(validators)) + // It is not allowed if the only two validators + if validatorNum <= 2 { + return false + } + + offset := (int64(snap.Number) + 2) % validatorNum + return validators[offset] == p.val +} + func (p *Parlia) IsLocalBlock(header *types.Header) bool { return p.val == header.Coinbase } diff --git a/core/state/statedb.go b/core/state/statedb.go index b949ff468a..382d73d81a 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -1012,6 +1012,7 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { // at transaction boundary level to ensure we capture state clearing. if s.snap != nil && !obj.deleted { s.snapMux.Lock() + // It is possible to add unnecessary change, but it is fine. s.snapAccounts[obj.address] = snapshot.SlimAccountRLP(obj.data.Nonce, obj.data.Balance, obj.data.Root, obj.data.CodeHash) s.snapMux.Unlock() } diff --git a/core/state_processor.go b/core/state_processor.go index 551ebe1559..a510206b4e 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -79,8 +79,12 @@ func NewLightStateProcessor(config *params.ChainConfig, bc *BlockChain, engine c } func (p *LightStateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (*state.StateDB, types.Receipts, []*types.Log, uint64, error) { + allowLightProcess := true + if posa, ok := p.engine.(consensus.PoSA); ok { + allowLightProcess = posa.AllowLightProcess(p.bc, block.Header()) + } // random fallback to full process - if check := p.randomGenerator.Int63n(fullProcessCheck); check != 0 { + if check := p.randomGenerator.Int63n(fullProcessCheck); allowLightProcess && check != 0 { var pid string if peer, ok := block.ReceivedFrom.(PeerIDer); ok { pid = peer.ID() @@ -139,7 +143,7 @@ func (p *LightStateProcessor) LightProcess(diffLayer *types.DiffLayer, block *ty } iteAccounts := make([]common.Address, 0, len(snapAccounts)) - for diffAccount, _ := range snapAccounts { + for diffAccount := range snapAccounts { iteAccounts = append(iteAccounts, diffAccount) } @@ -205,7 +209,8 @@ func (p *LightStateProcessor) LightProcess(diffLayer *types.DiffLayer, block *ty bytes.Equal(previousAccount.CodeHash, latestAccount.CodeHash) && previousAccount.Balance.Cmp(latestAccount.Balance) == 0 && previousAccount.Root == common.BytesToHash(latestAccount.Root) { - log.Warn("receive redundant account change in diff layer", "account", diffAccount, "num", block.NumberU64()) + // It is normal to receive redundant message since the collected message is redundant. + log.Debug("receive redundant account change in diff layer", "account", diffAccount, "num", block.NumberU64()) snapMux.Lock() delete(snapAccounts, diffAccount) delete(snapStorage, diffAccount) @@ -320,7 +325,7 @@ func (p *LightStateProcessor) LightProcess(diffLayer *types.DiffLayer, block *ty } // remove redundant storage change - for account, _ := range snapStorage { + for account := range snapStorage { if _, exist := snapAccounts[account]; !exist { log.Warn("receive redundant storage change in diff layer") delete(snapStorage, account) From 6f39765eb5104c6196fecc2f3f3e929cd8ef7dff Mon Sep 17 00:00:00 2001 From: fudongbai <296179868@qq.com> Date: Thu, 2 Sep 2021 19:32:52 +0800 Subject: [PATCH 07/22] change into diff sync --- cmd/geth/main.go | 2 +- cmd/utils/flags.go | 10 +++++----- eth/backend.go | 4 ++-- eth/ethconfig/config.go | 2 +- eth/handler.go | 6 +++--- eth/handler_diff.go | 2 +- eth/handler_eth.go | 2 +- eth/peer.go | 8 ++++---- eth/protocols/diff/handshake.go | 4 ++-- eth/protocols/diff/peer.go | 8 ++++---- eth/protocols/diff/protocol.go | 2 +- 11 files changed, 25 insertions(+), 25 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index a0540c9362..3c2f5ff219 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -65,7 +65,7 @@ var ( utils.ExternalSignerFlag, utils.NoUSBFlag, utils.DirectBroadcastFlag, - utils.LightSyncFlag, + utils.DiffSyncFlag, utils.RangeLimitFlag, utils.USBFlag, utils.SmartCardDaemonPathFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 9b5d2b14a6..e7648a00cd 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -117,9 +117,9 @@ var ( Name: "directbroadcast", Usage: "Enable directly broadcast mined block to all peers", } - LightSyncFlag = cli.BoolFlag{ - Name: "lightsync", - Usage: "Enable difflayer light sync, Please note that enable lightsync will improve the syncing speed, " + + DiffSyncFlag = cli.BoolFlag{ + Name: "diffsync", + Usage: "Enable difflayer light sync, Please note that enable diffsync will improve the syncing speed, " + "but will degrade the security to light client level", } RangeLimitFlag = cli.BoolFlag{ @@ -1592,8 +1592,8 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { if ctx.GlobalIsSet(DirectBroadcastFlag.Name) { cfg.DirectBroadcast = ctx.GlobalBool(DirectBroadcastFlag.Name) } - if ctx.GlobalIsSet(LightSyncFlag.Name) { - cfg.LightSync = ctx.GlobalBool(LightSyncFlag.Name) + if ctx.GlobalIsSet(DiffSyncFlag.Name) { + cfg.DiffSync = ctx.GlobalBool(DiffSyncFlag.Name) } if ctx.GlobalIsSet(RangeLimitFlag.Name) { cfg.RangeLimit = ctx.GlobalBool(RangeLimitFlag.Name) diff --git a/eth/backend.go b/eth/backend.go index 36db5ac5ba..6be178371a 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -200,7 +200,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { } ) bcOps := make([]core.BlockChainOption, 0) - if config.LightSync { + if config.DiffSync { bcOps = append(bcOps, core.EnableLightProcessor) } eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, chainConfig, eth.engine, vmConfig, eth.shouldPreserve, &config.TxLookupLimit, bcOps...) @@ -238,7 +238,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { Checkpoint: checkpoint, Whitelist: config.Whitelist, DirectBroadcast: config.DirectBroadcast, - LightSync: config.LightSync, + DiffSync: config.DiffSync, }); err != nil { return nil, err } diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index b36498df58..e5f01b550a 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -133,7 +133,7 @@ type Config struct { NoPruning bool // Whether to disable pruning and flush everything to disk DirectBroadcast bool - LightSync bool // Whether support light sync + DiffSync bool // Whether support diff sync RangeLimit bool TxLookupLimit uint64 `toml:",omitempty"` // The maximum number of blocks from head whose tx indices are reserved. diff --git a/eth/handler.go b/eth/handler.go index d169b4e58b..90d2e96159 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -83,7 +83,7 @@ type handlerConfig struct { TxPool txPool // Transaction pool to propagate from Network uint64 // Network identifier to adfvertise Sync downloader.SyncMode // Whether to fast or full sync - LightSync bool // Whether to light sync + DiffSync bool // Whether to diff sync BloomCache uint64 // Megabytes to alloc for fast sync bloom EventMux *event.TypeMux // Legacy event mux, deprecate for `feed` Checkpoint *params.TrustedCheckpoint // Hard coded checkpoint for sync challenges @@ -99,7 +99,7 @@ type handler struct { snapSync uint32 // Flag whether fast sync should operate on top of the snap protocol acceptTxs uint32 // Flag whether we're considered synchronised (enables transaction processing) directBroadcast bool - lightSync bool // Flag whether light sync should operate on top of the diff protocol + diffSync bool // Flag whether light sync should operate on top of the diff protocol checkpointNumber uint64 // Block number for the sync progress validator to cross reference checkpointHash common.Hash // Block hash for the sync progress validator to cross reference @@ -147,7 +147,7 @@ func newHandler(config *handlerConfig) (*handler, error) { peers: newPeerSet(), whitelist: config.Whitelist, directBroadcast: config.DirectBroadcast, - lightSync: config.LightSync, + diffSync: config.DiffSync, txsyncCh: make(chan *txsync), quitSync: make(chan struct{}), } diff --git a/eth/handler_diff.go b/eth/handler_diff.go index be05877506..ea310c38c2 100644 --- a/eth/handler_diff.go +++ b/eth/handler_diff.go @@ -32,7 +32,7 @@ func (h *diffHandler) Chain() *core.BlockChain { return h.chain } // RunPeer is invoked when a peer joins on the `diff` protocol. func (h *diffHandler) RunPeer(peer *diff.Peer, hand diff.Handler) error { - if err := peer.Handshake(h.lightSync); err != nil { + if err := peer.Handshake(h.diffSync); err != nil { return err } return (*handler)(h).runDiffExtension(peer, hand) diff --git a/eth/handler_eth.go b/eth/handler_eth.go index 802df1b64a..d9fcfb2058 100644 --- a/eth/handler_eth.go +++ b/eth/handler_eth.go @@ -193,7 +193,7 @@ func (h *ethHandler) handleBlockAnnounces(peer *eth.Peer, hashes []common.Hash, } // self support light sync var diffFetcher fetcher.DiffRequesterFn - if h.lightSync { + if h.diffSync { // the peer support diff protocol if ep := h.peers.peer(peer.ID()); ep != nil && ep.diffExt != nil { diffFetcher = ep.diffExt.RequestDiffLayers diff --git a/eth/peer.go b/eth/peer.go index 89139f0b53..2fb6fabf26 100644 --- a/eth/peer.go +++ b/eth/peer.go @@ -65,8 +65,8 @@ type snapPeerInfo struct { // diffPeerInfo represents a short summary of the `diff` sub-protocol metadata known // about a connected peer. type diffPeerInfo struct { - Version uint `json:"version"` // diff protocol version negotiated - LightSync bool `json:"light_sync"` + Version uint `json:"version"` // diff protocol version negotiated + DiffSync bool `json:"diff_sync"` } // snapPeer is a wrapper around snap.Peer to maintain a few extra metadata. @@ -82,8 +82,8 @@ type diffPeer struct { // info gathers and returns some `diff` protocol metadata known about a peer. func (p *diffPeer) info() *diffPeerInfo { return &diffPeerInfo{ - Version: p.Version(), - LightSync: p.LightSync(), + Version: p.Version(), + DiffSync: p.DiffSync(), } } diff --git a/eth/protocols/diff/handshake.go b/eth/protocols/diff/handshake.go index 0d96c5294e..21cf3b4e43 100644 --- a/eth/protocols/diff/handshake.go +++ b/eth/protocols/diff/handshake.go @@ -39,7 +39,7 @@ func (p *Peer) Handshake(lightSync bool) error { gopool.Submit(func() { errc <- p2p.Send(p.rw, DiffCapMsg, &DiffCapPacket{ - LightSync: lightSync, + DiffSync: lightSync, }) }) gopool.Submit(func() { @@ -57,7 +57,7 @@ func (p *Peer) Handshake(lightSync bool) error { return p2p.DiscReadTimeout } } - p.lightSync = cap.LightSync + p.diffSync = cap.DiffSync return nil } diff --git a/eth/protocols/diff/peer.go b/eth/protocols/diff/peer.go index c55a7e1581..0f02152baa 100644 --- a/eth/protocols/diff/peer.go +++ b/eth/protocols/diff/peer.go @@ -14,7 +14,7 @@ const maxQueuedDiffLayers = 12 // Peer is a collection of relevant information we have about a `diff` peer. type Peer struct { id string // Unique ID for the peer, cached - lightSync bool // whether the peer can light sync + diffSync bool // whether the peer can diff sync queuedDiffLayers chan []rlp.RawValue // Queue of diff layers to broadcast to the peer *p2p.Peer // The embedded P2P package peer @@ -32,7 +32,7 @@ func NewPeer(version uint, p *p2p.Peer, rw p2p.MsgReadWriter) *Peer { id: id, Peer: p, rw: rw, - lightSync: false, + diffSync: false, version: version, logger: log.New("peer", id[:8]), queuedDiffLayers: make(chan []rlp.RawValue, maxQueuedDiffLayers), @@ -66,8 +66,8 @@ func (p *Peer) Version() uint { return p.version } -func (p *Peer) LightSync() bool { - return p.lightSync +func (p *Peer) DiffSync() bool { + return p.diffSync } // Log overrides the P2P logget with the higher level one containing only the id. diff --git a/eth/protocols/diff/protocol.go b/eth/protocols/diff/protocol.go index 881ff828b8..650ba4f51e 100644 --- a/eth/protocols/diff/protocol.go +++ b/eth/protocols/diff/protocol.go @@ -96,7 +96,7 @@ func (p *DiffLayersPacket) Unpack() ([]*types.DiffLayer, error) { } type DiffCapPacket struct { - LightSync bool + DiffSync bool } type DiffLayersPacket []rlp.RawValue From a9e5a0000cb6bd4fba5b71adfc9ca1a0dc01984c Mon Sep 17 00:00:00 2001 From: kyrie-yl <83150977+kyrie-yl@users.noreply.github.com> Date: Thu, 2 Sep 2021 20:09:59 +0800 Subject: [PATCH 08/22] ligth sync: download difflayer (#2) * ligth sync: download difflayer Signed-off-by: kyrie-yl * download diff layer: fix according to the comments Signed-off-by: kyrie-yl * download diff layer: update Signed-off-by: kyrie-yl * download diff layer: fix accroding comments Signed-off-by: kyrie-yl Co-authored-by: kyrie-yl --- eth/downloader/downloader.go | 54 +++++++++++++++++++++++++++---- eth/downloader/downloader_test.go | 4 +-- eth/handler.go | 6 +++- eth/peerset.go | 8 +++++ 4 files changed, 62 insertions(+), 10 deletions(-) diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index 3dee90c424..ec0d0eb0d9 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -161,10 +161,10 @@ type Downloader struct { quitLock sync.Mutex // Lock to prevent double closes // Testing hooks - syncInitHook func(uint64, uint64) // Method to call upon initiating a new sync run - bodyFetchHook func([]*types.Header) // Method to call upon starting a block body fetch - receiptFetchHook func([]*types.Header) // Method to call upon starting a receipt fetch - chainInsertHook func([]*fetchResult) // Method to call upon inserting a chain of blocks (possibly in multiple invocations) + syncInitHook func(uint64, uint64) // Method to call upon initiating a new sync run + bodyFetchHook func([]*types.Header, ...interface{}) // Method to call upon starting a block body fetch + receiptFetchHook func([]*types.Header, ...interface{}) // Method to call upon starting a receipt fetch + chainInsertHook func([]*fetchResult) // Method to call upon inserting a chain of blocks (possibly in multiple invocations) } // LightChain encapsulates functions required to synchronise a light chain. @@ -220,8 +220,43 @@ type BlockChain interface { Snapshots() *snapshot.Tree } +type DownloadOption func(downloader *Downloader) *Downloader + +type IDiffPeer interface { + RequestDiffLayers([]common.Hash) error +} + +type IPeerSet interface { + GetDiffPeer(string) IDiffPeer +} + +func DiffBodiesFetchOption(peers IPeerSet) DownloadOption { + return func(dl *Downloader) *Downloader { + var hook = func(headers []*types.Header, args ...interface{}) { + if len(args) < 2 { + return + } + if mode, ok := args[0].(SyncMode); ok { + if mode == FullSync { + if peerID, ok := args[1].(string); ok { + if ep := peers.GetDiffPeer(peerID); ep != nil { + hashes := make([]common.Hash, 0, len(headers)) + for _, header := range headers { + hashes = append(hashes, header.Hash()) + } + ep.RequestDiffLayers(hashes) + } + } + } + } + } + dl.bodyFetchHook = hook + return dl + } +} + // New creates a new downloader to fetch hashes and blocks from remote peers. -func New(checkpoint uint64, stateDb ethdb.Database, stateBloom *trie.SyncBloom, mux *event.TypeMux, chain BlockChain, lightchain LightChain, dropPeer peerDropFn) *Downloader { +func New(checkpoint uint64, stateDb ethdb.Database, stateBloom *trie.SyncBloom, mux *event.TypeMux, chain BlockChain, lightchain LightChain, dropPeer peerDropFn, options ...DownloadOption) *Downloader { if lightchain == nil { lightchain = chain } @@ -252,6 +287,11 @@ func New(checkpoint uint64, stateDb ethdb.Database, stateBloom *trie.SyncBloom, }, trackStateReq: make(chan *stateReq), } + for _, option := range options { + if dl != nil { + dl = option(dl) + } + } go dl.qosTuner() go dl.stateFetcher() return dl @@ -1363,7 +1403,7 @@ func (d *Downloader) fetchReceipts(from uint64) error { // - kind: textual label of the type being downloaded to display in log messages func (d *Downloader) fetchParts(deliveryCh chan dataPack, deliver func(dataPack) (int, error), wakeCh chan bool, expire func() map[string]int, pending func() int, inFlight func() bool, reserve func(*peerConnection, int) (*fetchRequest, bool, bool), - fetchHook func([]*types.Header), fetch func(*peerConnection, *fetchRequest) error, cancel func(*fetchRequest), capacity func(*peerConnection) int, + fetchHook func([]*types.Header, ...interface{}), fetch func(*peerConnection, *fetchRequest) error, cancel func(*fetchRequest), capacity func(*peerConnection) int, idle func() ([]*peerConnection, int), setIdle func(*peerConnection, int, time.Time), kind string) error { // Create a ticker to detect expired retrieval tasks @@ -1512,7 +1552,7 @@ func (d *Downloader) fetchParts(deliveryCh chan dataPack, deliver func(dataPack) } // Fetch the chunk and make sure any errors return the hashes to the queue if fetchHook != nil { - fetchHook(request.Headers) + fetchHook(request.Headers, d.getMode(), peer.id) } if err := fetch(peer, request); err != nil { // Although we could try and make an attempt to fix this, this error really diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go index 794160993b..66f6872025 100644 --- a/eth/downloader/downloader_test.go +++ b/eth/downloader/downloader_test.go @@ -921,10 +921,10 @@ func testEmptyShortCircuit(t *testing.T, protocol uint, mode SyncMode) { // Instrument the downloader to signal body requests bodiesHave, receiptsHave := int32(0), int32(0) - tester.downloader.bodyFetchHook = func(headers []*types.Header) { + tester.downloader.bodyFetchHook = func(headers []*types.Header, _ ...interface{}) { atomic.AddInt32(&bodiesHave, int32(len(headers))) } - tester.downloader.receiptFetchHook = func(headers []*types.Header) { + tester.downloader.receiptFetchHook = func(headers []*types.Header, _ ...interface{}) { atomic.AddInt32(&receiptsHave, int32(len(headers))) } // Synchronise with the peer and make sure all blocks were retrieved diff --git a/eth/handler.go b/eth/handler.go index 90d2e96159..03867c3920 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -192,7 +192,11 @@ func newHandler(config *handlerConfig) (*handler, error) { if atomic.LoadUint32(&h.fastSync) == 1 && atomic.LoadUint32(&h.snapSync) == 0 { h.stateBloom = trie.NewSyncBloom(config.BloomCache, config.Database) } - h.downloader = downloader.New(h.checkpointNumber, config.Database, h.stateBloom, h.eventMux, h.chain, nil, h.removePeer) + var downloadOptions []downloader.DownloadOption + if h.diffSync { + downloadOptions = append(downloadOptions, downloader.DiffBodiesFetchOption(h.peers)) + } + h.downloader = downloader.New(h.checkpointNumber, config.Database, h.stateBloom, h.eventMux, h.chain, nil, h.removePeer, downloadOptions...) // Construct the fetcher (short sync) validator := func(header *types.Header) error { diff --git a/eth/peerset.go b/eth/peerset.go index b5ac95c1d6..f0955f34c6 100644 --- a/eth/peerset.go +++ b/eth/peerset.go @@ -22,6 +22,7 @@ import ( "sync" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/protocols/diff" "github.com/ethereum/go-ethereum/eth/protocols/eth" "github.com/ethereum/go-ethereum/eth/protocols/snap" @@ -205,6 +206,13 @@ func (ps *peerSet) waitDiffExtension(peer *eth.Peer) (*diff.Peer, error) { return <-wait, nil } +func (ps *peerSet) GetDiffPeer(pid string) downloader.IDiffPeer { + if p := ps.peer(pid); p != nil && p.diffExt != nil { + return p.diffExt + } + return nil +} + // registerPeer injects a new `eth` peer into the working set, or returns an error // if the peer is already known. func (ps *peerSet) registerPeer(peer *eth.Peer, ext *snap.Peer, diffExt *diff.Peer) error { From 66ee50de6c5286802a08cfa2c824dd4895ca9cad Mon Sep 17 00:00:00 2001 From: fudongbai <296179868@qq.com> Date: Thu, 2 Sep 2021 20:18:25 +0800 Subject: [PATCH 09/22] update light sync to diff sync --- cmd/utils/flags.go | 2 +- core/state_processor.go | 4 ++-- eth/handler.go | 2 +- eth/handler_eth.go | 2 +- eth/protocols/diff/handshake.go | 4 ++-- eth/protocols/eth/peer.go | 4 ---- 6 files changed, 7 insertions(+), 11 deletions(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index e7648a00cd..52cdc007b3 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -119,7 +119,7 @@ var ( } DiffSyncFlag = cli.BoolFlag{ Name: "diffsync", - Usage: "Enable difflayer light sync, Please note that enable diffsync will improve the syncing speed, " + + Usage: "Enable difflayer sync, Please note that enable diffsync will improve the syncing speed, " + "but will degrade the security to light client level", } RangeLimitFlag = cli.BoolFlag{ diff --git a/core/state_processor.go b/core/state_processor.go index a510206b4e..c281263975 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -42,7 +42,7 @@ import ( ) const ( - fullProcessCheck = 21 // On light sync mode, will do full process every fullProcessCheck randomly + fullProcessCheck = 21 // On diff sync mode, will do full process every fullProcessCheck randomly minNumberOfAccountPerTask = 10 ) @@ -320,7 +320,7 @@ func (p *LightStateProcessor) LightProcess(diffLayer *types.DiffLayer, block *ty // Do validate in advance so that we can fall back to full process if err := p.bc.validator.ValidateState(block, statedb, diffLayer.Receipts, gasUsed); err != nil { - log.Error("validate state failed during light sync", "error", err) + log.Error("validate state failed during diff sync", "error", err) return nil, nil, 0, err } diff --git a/eth/handler.go b/eth/handler.go index 03867c3920..d6f8b75150 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -99,7 +99,7 @@ type handler struct { snapSync uint32 // Flag whether fast sync should operate on top of the snap protocol acceptTxs uint32 // Flag whether we're considered synchronised (enables transaction processing) directBroadcast bool - diffSync bool // Flag whether light sync should operate on top of the diff protocol + diffSync bool // Flag whether diff sync should operate on top of the diff protocol checkpointNumber uint64 // Block number for the sync progress validator to cross reference checkpointHash common.Hash // Block hash for the sync progress validator to cross reference diff --git a/eth/handler_eth.go b/eth/handler_eth.go index d9fcfb2058..d2cf83fbfe 100644 --- a/eth/handler_eth.go +++ b/eth/handler_eth.go @@ -191,7 +191,7 @@ func (h *ethHandler) handleBlockAnnounces(peer *eth.Peer, hashes []common.Hash, unknownNumbers = append(unknownNumbers, numbers[i]) } } - // self support light sync + // self support diff sync var diffFetcher fetcher.DiffRequesterFn if h.diffSync { // the peer support diff protocol diff --git a/eth/protocols/diff/handshake.go b/eth/protocols/diff/handshake.go index 21cf3b4e43..0ab4fddfa8 100644 --- a/eth/protocols/diff/handshake.go +++ b/eth/protocols/diff/handshake.go @@ -31,7 +31,7 @@ const ( ) // Handshake executes the diff protocol handshake, -func (p *Peer) Handshake(lightSync bool) error { +func (p *Peer) Handshake(diffSync bool) error { // Send out own handshake in a new thread errc := make(chan error, 2) @@ -39,7 +39,7 @@ func (p *Peer) Handshake(lightSync bool) error { gopool.Submit(func() { errc <- p2p.Send(p.rw, DiffCapMsg, &DiffCapPacket{ - DiffSync: lightSync, + DiffSync: diffSync, }) }) gopool.Submit(func() { diff --git a/eth/protocols/eth/peer.go b/eth/protocols/eth/peer.go index f09760a2ec..e619c183ba 100644 --- a/eth/protocols/eth/peer.go +++ b/eth/protocols/eth/peer.go @@ -54,10 +54,6 @@ const ( // dropping broadcasts. Similarly to block propagations, there's no point to queue // above some healthy uncle limit, so use that. maxQueuedBlockAnns = 4 - - // maxQueuedDiffLayers is the maximum number of diffLayers to queue up before - // dropping broadcasts. - maxQueuedDiffLayers = 4 ) // max is a helper function which returns the larger of the two given integers. From ca156fc7fe3289886449bf9110195c85d15329ea Mon Sep 17 00:00:00 2001 From: fudongbai <296179868@qq.com> Date: Sat, 4 Sep 2021 23:11:55 +0800 Subject: [PATCH 10/22] raise the max diff limit --- core/blockchain.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 11f3590434..fbb3a51af9 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -93,8 +93,8 @@ const ( diffLayerFreezerRecheckInterval = 3 * time.Second diffLayerFreezerBlockLimit = 864000 // The number of diff layers that should be kept in disk. diffLayerPruneRecheckInterval = 1 * time.Second // The interval to prune unverified diff layers - maxDiffQueueDist = 128 // Maximum allowed distance from the chain head to queue diffLayers - maxDiffLimit = 128 // Maximum number of unique diff layers a peer may have delivered + maxDiffQueueDist = 2048 // Maximum allowed distance from the chain head to queue diffLayers + maxDiffLimit = 2048 // Maximum number of unique diff layers a peer may have delivered maxDiffForkDist = 11 // Maximum allowed backward distance from the chain head // BlockChainVersion ensures that an incompatible database forces a resync from scratch. From 01fe925ad5850eb7392d6b4698affb077381ab2d Mon Sep 17 00:00:00 2001 From: fudongbai <296179868@qq.com> Date: Mon, 6 Sep 2021 09:57:52 +0800 Subject: [PATCH 11/22] add switcher of snap protocol --- cmd/geth/main.go | 1 + cmd/geth/usage.go | 1 + cmd/utils/flags.go | 10 ++++++++++ eth/backend.go | 2 +- eth/ethconfig/config.go | 9 +++++---- node/config.go | 3 +++ 6 files changed, 21 insertions(+), 5 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 3c2f5ff219..2f56e6c5d2 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -65,6 +65,7 @@ var ( utils.ExternalSignerFlag, utils.NoUSBFlag, utils.DirectBroadcastFlag, + utils.DisableSnapProtocolFlag, utils.DiffSyncFlag, utils.RangeLimitFlag, utils.USBFlag, diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 1450c29e84..2a208c827b 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -40,6 +40,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{ utils.KeyStoreDirFlag, utils.NoUSBFlag, utils.DirectBroadcastFlag, + utils.DisableSnapProtocolFlag, utils.RangeLimitFlag, utils.SmartCardDaemonPathFlag, utils.NetworkIdFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 52cdc007b3..9e92f00950 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -117,6 +117,10 @@ var ( Name: "directbroadcast", Usage: "Enable directly broadcast mined block to all peers", } + DisableSnapProtocolFlag = cli.BoolFlag{ + Name: "disablesnapprotocol", + Usage: "Disable snap protocol", + } DiffSyncFlag = cli.BoolFlag{ Name: "diffsync", Usage: "Enable difflayer sync, Please note that enable diffsync will improve the syncing speed, " + @@ -1284,6 +1288,9 @@ func SetNodeConfig(ctx *cli.Context, cfg *node.Config) { if ctx.GlobalIsSet(DirectBroadcastFlag.Name) { cfg.DirectBroadcast = ctx.GlobalBool(DirectBroadcastFlag.Name) } + if ctx.GlobalIsSet(DisableSnapProtocolFlag.Name) { + cfg.DisableSnapProtocol = ctx.GlobalBool(DisableSnapProtocolFlag.Name) + } if ctx.GlobalIsSet(RangeLimitFlag.Name) { cfg.RangeLimit = ctx.GlobalBool(RangeLimitFlag.Name) } @@ -1592,6 +1599,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { if ctx.GlobalIsSet(DirectBroadcastFlag.Name) { cfg.DirectBroadcast = ctx.GlobalBool(DirectBroadcastFlag.Name) } + if ctx.GlobalIsSet(DisableSnapProtocolFlag.Name) { + cfg.DisableSnapProtocol = ctx.GlobalBool(DisableSnapProtocolFlag.Name) + } if ctx.GlobalIsSet(DiffSyncFlag.Name) { cfg.DiffSync = ctx.GlobalBool(DiffSyncFlag.Name) } diff --git a/eth/backend.go b/eth/backend.go index 6be178371a..f77147f9d9 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -541,7 +541,7 @@ func (s *Ethereum) BloomIndexer() *core.ChainIndexer { return s.bloomIndexer } // network protocols to start. func (s *Ethereum) Protocols() []p2p.Protocol { protos := eth.MakeProtocols((*ethHandler)(s.handler), s.networkID, s.ethDialCandidates) - if s.config.SnapshotCache > 0 { + if !s.config.DisableSnapProtocol && s.config.SnapshotCache > 0 { protos = append(protos, snap.MakeProtocols((*snapHandler)(s.handler), s.snapDialCandidates)...) } // diff protocol can still open without snap protocol diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index e5f01b550a..bded76d493 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -131,10 +131,11 @@ type Config struct { EthDiscoveryURLs []string SnapDiscoveryURLs []string - NoPruning bool // Whether to disable pruning and flush everything to disk - DirectBroadcast bool - DiffSync bool // Whether support diff sync - RangeLimit bool + NoPruning bool // Whether to disable pruning and flush everything to disk + DirectBroadcast bool + DisableSnapProtocol bool //Whether disable snap protocol + DiffSync bool // Whether support diff sync + RangeLimit bool TxLookupLimit uint64 `toml:",omitempty"` // The maximum number of blocks from head whose tx indices are reserved. diff --git a/node/config.go b/node/config.go index 23461c1916..527cd36415 100644 --- a/node/config.go +++ b/node/config.go @@ -98,6 +98,9 @@ type Config struct { // DirectBroadcast enable directly broadcast mined block to all peers DirectBroadcast bool `toml:",omitempty"` + // DisableSnapProtocol disable the snap protocol + DisableSnapProtocol bool `toml:",omitempty"` + // RangeLimit enable 5000 blocks limit when handle range query RangeLimit bool `toml:",omitempty"` From 296f3201b5ccfe6cd74ab75c42d107f4139ab2b5 Mon Sep 17 00:00:00 2001 From: fudongbai <296179868@qq.com> Date: Mon, 6 Sep 2021 10:18:06 +0800 Subject: [PATCH 12/22] fix test case --- eth/handler_diff_test.go | 2 +- eth/tracers/tracers_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/eth/handler_diff_test.go b/eth/handler_diff_test.go index 0861736c5e..f6b967631a 100644 --- a/eth/handler_diff_test.go +++ b/eth/handler_diff_test.go @@ -180,7 +180,7 @@ func TestHandleDiffLayer(t *testing.T) { }, Valid: true}, {DiffLayer: &types.DiffLayer{ BlockHash: common.Hash{0x2}, - Number: 2025, + Number: 3073, }, Valid: false}, {DiffLayer: &types.DiffLayer{ BlockHash: common.Hash{0x3}, diff --git a/eth/tracers/tracers_test.go b/eth/tracers/tracers_test.go index cdffdc6024..0bc4cd81e6 100644 --- a/eth/tracers/tracers_test.go +++ b/eth/tracers/tracers_test.go @@ -357,7 +357,7 @@ func BenchmarkTransactionTrace(b *testing.B) { //DisableReturnData: true, }) evm := vm.NewEVM(context, txContext, statedb, params.AllEthashProtocolChanges, vm.Config{Debug: true, Tracer: tracer}) - msg, err := tx.AsMessage(signer, nil) + msg, err := tx.AsMessage(signer) if err != nil { b.Fatalf("failed to prepare transaction for tracing: %v", err) } From 2041edc36ce86e801c84cb17617c821fd0490fd0 Mon Sep 17 00:00:00 2001 From: fudongbai <296179868@qq.com> Date: Mon, 6 Sep 2021 14:56:08 +0800 Subject: [PATCH 13/22] make commit concurrently --- core/blockchain_diff_test.go | 4 +- core/state/statedb.go | 160 ++++++++++++++++++++++++++--------- core/state_processor.go | 2 +- 3 files changed, 121 insertions(+), 45 deletions(-) diff --git a/core/blockchain_diff_test.go b/core/blockchain_diff_test.go index d44ba0465b..7df2612b35 100644 --- a/core/blockchain_diff_test.go +++ b/core/blockchain_diff_test.go @@ -117,15 +117,15 @@ func rawDataToDiffLayer(data rlp.RawValue) (*types.DiffLayer, error) { hasher.Write(data) var diffHash common.Hash hasher.Sum(diffHash[:0]) - hasher.Reset() diff.DiffHash = diffHash + hasher.Reset() return &diff, nil } func TestProcessDiffLayer(t *testing.T) { t.Parallel() - blockNum := maxDiffLimit - 1 + blockNum := 128 fullBackend := newTestBackend(blockNum, false) falseDiff := 5 defer fullBackend.close() diff --git a/core/state/statedb.go b/core/state/statedb.go index 382d73d81a..886f181bf5 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -39,8 +39,9 @@ import ( ) const ( - preLoadLimit = 128 - defaultNumOfSlots = 100 + preLoadLimit = 128 + defaultNumOfSlots = 100 + minNumberOfAccountPerTask = 5 ) type revision struct { @@ -558,7 +559,7 @@ func (s *StateDB) TryPreload(block *types.Block, signer types.Signer) { accounts[*tx.To()] = true } } - for account, _ := range accounts { + for account := range accounts { accountsSlice = append(accountsSlice, account) } if len(accountsSlice) >= preLoadLimit && len(accountsSlice) > runtime.NumCPU() { @@ -1081,52 +1082,127 @@ func (s *StateDB) clearJournalAndRefund() { s.validRevisions = s.validRevisions[:0] // Snapshots can be created without journal entires } -func (s *StateDB) LightCommit() (common.Hash, *types.DiffLayer, error) { +func (s *StateDB) LightCommit(root common.Hash) (common.Hash, *types.DiffLayer, error) { codeWriter := s.db.TrieDB().DiskDB().NewBatch() - // Step1 write code - for codeHash, code := range s.diffCode { - rawdb.WriteCode(codeWriter, codeHash, code) - if codeWriter.ValueSize() >= ethdb.IdealBatchSize { - if err := codeWriter.Write(); err != nil { - return common.Hash{}, nil, err + commitFuncs := []func() error{ + func() error { + for codeHash, code := range s.diffCode { + rawdb.WriteCode(codeWriter, codeHash, code) + if codeWriter.ValueSize() >= ethdb.IdealBatchSize { + if err := codeWriter.Write(); err != nil { + return err + } + codeWriter.Reset() + } + } + if codeWriter.ValueSize() > 0 { + if err := codeWriter.Write(); err != nil { + return err + } + } + return nil + }, + func() error { + tasks := make(chan func()) + taskResults := make(chan error, len(s.diffTries)) + tasksNum := 0 + finishCh := make(chan struct{}) + defer close(finishCh) + threads := 1 + if len(s.diffTries)/runtime.NumCPU() > minNumberOfAccountPerTask { + threads = runtime.NumCPU() + } + for i := 0; i < threads; i++ { + go func() { + for { + select { + case task := <-tasks: + task() + case <-finishCh: + return + } + } + }() } - codeWriter.Reset() - } - } - if codeWriter.ValueSize() > 0 { - if err := codeWriter.Write(); err != nil { - return common.Hash{}, nil, err - } - } - // Step2 commit account storage - for account, diff := range s.diffTries { - root, err := diff.Commit(nil) - if err != nil { - return common.Hash{}, nil, err - } - s.db.CacheStorage(crypto.Keccak256Hash(account[:]), root, diff) - } + for account, diff := range s.diffTries { + tmpAccount := account + tmpDiff := diff + tasks <- func() { + root, err := tmpDiff.Commit(nil) + if err != nil { + taskResults <- err + return + } + s.db.CacheStorage(crypto.Keccak256Hash(tmpAccount[:]), root, tmpDiff) + taskResults <- nil + return + } + tasksNum++ + } + + for i := 0; i < tasksNum; i++ { + err := <-taskResults + if err != nil { + return err + } + } - // Step3 commit account trie - var account Account - root, err := s.trie.Commit(func(_ [][]byte, _ []byte, leaf []byte, parent common.Hash) error { - if err := rlp.DecodeBytes(leaf, &account); err != nil { + // commit account trie + var account Account + root, err := s.trie.Commit(func(_ [][]byte, _ []byte, leaf []byte, parent common.Hash) error { + if err := rlp.DecodeBytes(leaf, &account); err != nil { + return nil + } + if account.Root != emptyRoot { + s.db.TrieDB().Reference(account.Root, parent) + } + return nil + }) + if err != nil { + return err + } + if root != emptyRoot { + s.db.CacheAccount(root, s.trie) + } return nil - } - if account.Root != emptyRoot { - s.db.TrieDB().Reference(account.Root, parent) - } - return nil - }) - if err != nil { - return common.Hash{}, nil, err + }, + func() error { + if s.snap != nil { + if metrics.EnabledExpensive { + defer func(start time.Time) { s.SnapshotCommits += time.Since(start) }(time.Now()) + } + // Only update if there's a state transition (skip empty Clique blocks) + if parent := s.snap.Root(); parent != root { + if err := s.snaps.Update(root, parent, s.snapDestructs, s.snapAccounts, s.snapStorage); err != nil { + log.Warn("Failed to update snapshot tree", "from", parent, "to", root, "err", err) + } + // Keep n diff layers in the memory + // - head layer is paired with HEAD state + // - head-1 layer is paired with HEAD-1 state + // - head-(n-1) layer(bottom-most diff layer) is paired with HEAD-(n-1)state + if err := s.snaps.Cap(root, s.snaps.CapLimit()); err != nil { + log.Warn("Failed to cap snapshot tree", "root", root, "layers", s.snaps.CapLimit(), "err", err) + } + } + } + return nil + }, + } + commitRes := make(chan error, len(commitFuncs)) + for _, f := range commitFuncs { + tmpFunc := f + go func() { + commitRes <- tmpFunc() + }() } - if root != emptyRoot { - s.db.CacheAccount(root, s.trie) + for i := 0; i < len(commitFuncs); i++ { + r := <-commitRes + if r != nil { + return common.Hash{}, nil, r + } } - s.snap, s.snapDestructs, s.snapAccounts, s.snapStorage = nil, nil, nil, nil s.diffTries, s.diffCode = nil, nil return root, s.diffLayer, nil @@ -1140,7 +1216,7 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, *types.DiffLayer // Finalize any pending changes and merge everything into the tries root := s.IntermediateRoot(deleteEmptyObjects) if s.lightProcessed { - return s.LightCommit() + return s.LightCommit(root) } var diffLayer *types.DiffLayer if s.snap != nil { diff --git a/core/state_processor.go b/core/state_processor.go index c281263975..ecb28a23ea 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -43,7 +43,7 @@ import ( const ( fullProcessCheck = 21 // On diff sync mode, will do full process every fullProcessCheck randomly - minNumberOfAccountPerTask = 10 + minNumberOfAccountPerTask = 5 ) // StateProcessor is a basic Processor, which takes care of transitioning From 5ce6bbef9b9036c9e16f57dbe28bc06abf1bb207 Mon Sep 17 00:00:00 2001 From: fudongbai <296179868@qq.com> Date: Mon, 6 Sep 2021 16:39:14 +0800 Subject: [PATCH 14/22] remove peer for diff cache when peer closed --- consensus/parlia/parlia.go | 2 +- core/blockchain.go | 46 ++++++++++- core/blockchain_diff_test.go | 8 +- core/state_processor.go | 16 +++- eth/downloader/downloader.go | 6 +- eth/handler.go | 6 +- eth/handler_diff.go | 53 +++++++++---- eth/protocols/diff/handler.go | 9 ++- eth/protocols/diff/peer.go | 2 +- eth/protocols/diff/protocol.go | 1 + eth/protocols/diff/tracker.go | 141 ++++++++++++++++++++++++++++++++- 11 files changed, 251 insertions(+), 39 deletions(-) diff --git a/consensus/parlia/parlia.go b/consensus/parlia/parlia.go index a9df24848b..0e4285412d 100644 --- a/consensus/parlia/parlia.go +++ b/consensus/parlia/parlia.go @@ -895,7 +895,7 @@ func (p *Parlia) AllowLightProcess(chain consensus.ChainReader, currentHeader *t validators := snap.validators() validatorNum := int64(len(validators)) - // It is not allowed if the only two validators + // It is not allowed if only two validators if validatorNum <= 2 { return false } diff --git a/core/blockchain.go b/core/blockchain.go index fbb3a51af9..84a229f629 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -94,8 +94,9 @@ const ( diffLayerFreezerBlockLimit = 864000 // The number of diff layers that should be kept in disk. diffLayerPruneRecheckInterval = 1 * time.Second // The interval to prune unverified diff layers maxDiffQueueDist = 2048 // Maximum allowed distance from the chain head to queue diffLayers - maxDiffLimit = 2048 // Maximum number of unique diff layers a peer may have delivered + maxDiffLimit = 2048 // Maximum number of unique diff layers a peer may have responded maxDiffForkDist = 11 // Maximum allowed backward distance from the chain head + maxDiffLimitForBroadcast = 128 // Maximum number of unique diff layers a peer may have broadcasted // BlockChainVersion ensures that an incompatible database forces a resync from scratch. // @@ -2534,6 +2535,34 @@ func (bc *BlockChain) removeDiffLayers(diffHash common.Hash) { } } +func (bc *BlockChain) RemoveDiffPeer(pid string) { + bc.diffMux.Lock() + defer bc.diffMux.Unlock() + if invaliDiffHashes := bc.diffPeersToDiffHashes[pid]; invaliDiffHashes != nil { + for invalidDiffHash := range invaliDiffHashes { + lastDiffHash := false + if peers, ok := bc.diffHashToPeers[invalidDiffHash]; ok { + delete(peers, pid) + if len(peers) == 0 { + lastDiffHash = true + delete(bc.diffHashToPeers, invalidDiffHash) + } + } + if lastDiffHash { + affectedBlockHash := bc.diffHashToBlockHash[invalidDiffHash] + if diffs, exist := bc.blockHashToDiffLayers[affectedBlockHash]; exist { + delete(diffs, invalidDiffHash) + if len(diffs) == 0 { + delete(bc.blockHashToDiffLayers, affectedBlockHash) + } + } + delete(bc.diffHashToBlockHash, invalidDiffHash) + } + } + delete(bc.diffPeersToDiffHashes, pid) + } +} + func (bc *BlockChain) untrustedDiffLayerPruneLoop() { recheck := time.Tick(diffLayerPruneRecheckInterval) bc.wg.Add(1) @@ -2595,7 +2624,7 @@ func (bc *BlockChain) pruneDiffLayer() { } // Process received diff layers -func (bc *BlockChain) HandleDiffLayer(diffLayer *types.DiffLayer, pid string) error { +func (bc *BlockChain) HandleDiffLayer(diffLayer *types.DiffLayer, pid string, fulfilled bool) error { // Basic check currentHeight := bc.CurrentBlock().NumberU64() if diffLayer.Number > currentHeight && diffLayer.Number-currentHeight > maxDiffQueueDist { @@ -2610,6 +2639,13 @@ func (bc *BlockChain) HandleDiffLayer(diffLayer *types.DiffLayer, pid string) er bc.diffMux.Lock() defer bc.diffMux.Unlock() + if !fulfilled { + if len(bc.diffPeersToDiffHashes[pid]) > maxDiffLimitForBroadcast { + log.Error("too many accumulated diffLayers", "pid", pid) + return nil + } + } + if len(bc.diffPeersToDiffHashes[pid]) > maxDiffLimit { log.Error("too many accumulated diffLayers", "pid", pid) return nil @@ -2618,12 +2654,14 @@ func (bc *BlockChain) HandleDiffLayer(diffLayer *types.DiffLayer, pid string) er if _, alreadyHas := bc.diffPeersToDiffHashes[pid][diffLayer.DiffHash]; alreadyHas { return nil } - } else { - bc.diffPeersToDiffHashes[pid] = make(map[common.Hash]struct{}) } + bc.diffPeersToDiffHashes[pid] = make(map[common.Hash]struct{}) bc.diffPeersToDiffHashes[pid][diffLayer.DiffHash] = struct{}{} if _, exist := bc.diffNumToBlockHashes[diffLayer.Number]; !exist { bc.diffNumToBlockHashes[diffLayer.Number] = make(map[common.Hash]struct{}) + } + if len(bc.diffNumToBlockHashes[diffLayer.Number]) > 4 { + } bc.diffNumToBlockHashes[diffLayer.Number][diffLayer.BlockHash] = struct{}{} diff --git a/core/blockchain_diff_test.go b/core/blockchain_diff_test.go index 7df2612b35..14c5426bf6 100644 --- a/core/blockchain_diff_test.go +++ b/core/blockchain_diff_test.go @@ -143,7 +143,7 @@ func TestProcessDiffLayer(t *testing.T) { if err != nil { t.Errorf("failed to decode rawdata %v", err) } - lightBackend.Chain().HandleDiffLayer(diff, "testpid") + lightBackend.Chain().HandleDiffLayer(diff, "testpid", true) _, err = lightBackend.chain.insertChain([]*types.Block{block}, true) if err != nil { t.Errorf("failed to insert block %v", err) @@ -158,7 +158,7 @@ func TestProcessDiffLayer(t *testing.T) { bz, _ := rlp.EncodeToBytes(&latestAccount) diff.Accounts[0].Blob = bz - lightBackend.Chain().HandleDiffLayer(diff, "testpid") + lightBackend.Chain().HandleDiffLayer(diff, "testpid", true) _, err := lightBackend.chain.insertChain([]*types.Block{nextBlock}, true) if err != nil { @@ -216,8 +216,8 @@ func TestPruneDiffLayer(t *testing.T) { header := fullBackend.chain.GetHeaderByNumber(num) rawDiff := fullBackend.chain.GetDiffLayerRLP(header.Hash()) diff, _ := rawDataToDiffLayer(rawDiff) - fullBackend.Chain().HandleDiffLayer(diff, "testpid1") - fullBackend.Chain().HandleDiffLayer(diff, "testpid2") + fullBackend.Chain().HandleDiffLayer(diff, "testpid1", true) + fullBackend.Chain().HandleDiffLayer(diff, "testpid2", true) } fullBackend.chain.pruneDiffLayer() diff --git a/core/state_processor.go b/core/state_processor.go index ecb28a23ea..aca43b4609 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -44,6 +44,7 @@ import ( const ( fullProcessCheck = 21 // On diff sync mode, will do full process every fullProcessCheck randomly minNumberOfAccountPerTask = 5 + diffLayerTimeout = 50 ) // StateProcessor is a basic Processor, which takes care of transitioning @@ -82,14 +83,25 @@ func (p *LightStateProcessor) Process(block *types.Block, statedb *state.StateDB allowLightProcess := true if posa, ok := p.engine.(consensus.PoSA); ok { allowLightProcess = posa.AllowLightProcess(p.bc, block.Header()) + log.Error("===debug, allow to light process?", "allow", allowLightProcess) } // random fallback to full process - if check := p.randomGenerator.Int63n(fullProcessCheck); allowLightProcess && check != 0 { + if check := p.randomGenerator.Int63n(fullProcessCheck); allowLightProcess && check != 0 && len(block.Transactions()) != 0 { var pid string if peer, ok := block.ReceivedFrom.(PeerIDer); ok { pid = peer.ID() } - diffLayer := p.bc.GetUnTrustedDiffLayer(block.Hash(), pid) + var diffLayer *types.DiffLayer + //TODO This is just for debug + for tried := 0; tried < diffLayerTimeout; tried++ { + // wait a bit for the diff layer + diffLayer = p.bc.GetUnTrustedDiffLayer(block.Hash(), pid) + if diffLayer != nil { + log.Error("===debug find it", "idx", tried) + break + } + time.Sleep(time.Millisecond) + } if diffLayer != nil { if err := diffLayer.Receipts.DeriveFields(p.bc.chainConfig, block.Hash(), block.NumberU64(), block.Transactions()); err != nil { log.Error("Failed to derive block receipts fields", "hash", block.Hash(), "number", block.NumberU64(), "err", err) diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index ec0d0eb0d9..a1cc1068ae 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -161,10 +161,10 @@ type Downloader struct { quitLock sync.Mutex // Lock to prevent double closes // Testing hooks - syncInitHook func(uint64, uint64) // Method to call upon initiating a new sync run + syncInitHook func(uint64, uint64) // Method to call upon initiating a new sync run bodyFetchHook func([]*types.Header, ...interface{}) // Method to call upon starting a block body fetch receiptFetchHook func([]*types.Header, ...interface{}) // Method to call upon starting a receipt fetch - chainInsertHook func([]*fetchResult) // Method to call upon inserting a chain of blocks (possibly in multiple invocations) + chainInsertHook func([]*fetchResult) // Method to call upon inserting a chain of blocks (possibly in multiple invocations) } // LightChain encapsulates functions required to synchronise a light chain. @@ -230,7 +230,7 @@ type IPeerSet interface { GetDiffPeer(string) IDiffPeer } -func DiffBodiesFetchOption(peers IPeerSet) DownloadOption { +func EnableDiffFetchOp(peers IPeerSet) DownloadOption { return func(dl *Downloader) *Downloader { var hook = func(headers []*types.Header, args ...interface{}) { if len(args) < 2 { diff --git a/eth/handler.go b/eth/handler.go index d6f8b75150..e4980b939b 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -194,7 +194,7 @@ func newHandler(config *handlerConfig) (*handler, error) { } var downloadOptions []downloader.DownloadOption if h.diffSync { - downloadOptions = append(downloadOptions, downloader.DiffBodiesFetchOption(h.peers)) + downloadOptions = append(downloadOptions, downloader.EnableDiffFetchOp(h.peers)) } h.downloader = downloader.New(h.checkpointNumber, config.Database, h.stateBloom, h.eventMux, h.chain, nil, h.removePeer, downloadOptions...) @@ -485,7 +485,9 @@ func (h *handler) BroadcastBlock(block *types.Block, propagate bool) { diff := h.chain.GetDiffLayerRLP(block.Hash()) for _, peer := range transfer { if len(diff) != 0 && peer.diffExt != nil { - peer.diffExt.AsyncSendDiffLayer([]rlp.RawValue{diff}) + // difflayer should send before block + log.Error("===debug Broadcast block", "number", block.Number(), "hash", hash) + peer.diffExt.SendDiffLayers([]rlp.RawValue{diff}) } peer.AsyncSendNewBlock(block, td) } diff --git a/eth/handler_diff.go b/eth/handler_diff.go index ea310c38c2..ab9363e6e5 100644 --- a/eth/handler_diff.go +++ b/eth/handler_diff.go @@ -19,6 +19,8 @@ package eth import ( "fmt" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/eth/protocols/diff" "github.com/ethereum/go-ethereum/p2p/enode" @@ -35,6 +37,7 @@ func (h *diffHandler) RunPeer(peer *diff.Peer, hand diff.Handler) error { if err := peer.Handshake(h.diffSync); err != nil { return err } + defer h.chain.RemoveDiffPeer(peer.ID()) return (*handler)(h).runDiffExtension(peer, hand) } @@ -55,26 +58,42 @@ func (h *diffHandler) Handle(peer *diff.Peer, packet diff.Packet) error { // data packet for the local node to consume. switch packet := packet.(type) { case *diff.DiffLayersPacket: - diffs, err := packet.Unpack() - if err != nil { - return err - } - for _, d := range diffs { - if d != nil { - if err := d.Validate(); err != nil { - return err - } - } - } - for _, diff := range diffs { - err := h.chain.HandleDiffLayer(diff, peer.ID()) - if err != nil { - return err - } - } + return h.handleDiffLayerPackage(packet, peer.ID(), false) + + case *diff.FullDiffLayersPacket: + return h.handleDiffLayerPackage(&packet.DiffLayersPacket, peer.ID(), true) default: return fmt.Errorf("unexpected diff packet type: %T", packet) } return nil } + +func (h *diffHandler) handleDiffLayerPackage(packet *diff.DiffLayersPacket, pid string, fulfilled bool) error { + diffs, err := packet.Unpack() + + if err != nil { + log.Error("====unpack err", "number", diffs[0].Number, "hash", diffs[0].BlockHash, "err", err) + return err + } + if len(diffs) > 0 { + log.Error("====debug receive difflayer", "number", diffs[0].Number, "hash", diffs[0].BlockHash) + + } else { + log.Error("====debug receive difflayer length 0") + } + for _, d := range diffs { + if d != nil { + if err := d.Validate(); err != nil { + return err + } + } + } + for _, diff := range diffs { + err := h.chain.HandleDiffLayer(diff, pid, fulfilled) + if err != nil { + return err + } + } + return nil +} diff --git a/eth/protocols/diff/handler.go b/eth/protocols/diff/handler.go index cf6828b18b..d07035fe9b 100644 --- a/eth/protocols/diff/handler.go +++ b/eth/protocols/diff/handler.go @@ -20,6 +20,8 @@ const ( maxDiffLayerServe = 1024 ) +var requestTracker = NewTracker(time.Minute) + // Handler is a callback to invoke from an outside runner after the boilerplate // exchanges have passed. type Handler func(peer *Peer) error @@ -139,8 +141,11 @@ func handleMessage(backend Backend, peer *Peer) error { if err := msg.Decode(res); err != nil { return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) } - requestTracker.Fulfil(peer.id, peer.version, FullDiffLayerMsg, res.RequestId) - return backend.Handle(peer, &res.DiffLayersPacket) + if fulfilled := requestTracker.Fulfil(peer.id, peer.version, FullDiffLayerMsg, res.RequestId); fulfilled { + return backend.Handle(peer, res) + } else { + return fmt.Errorf("%w: %v", errUnexpectedMsg, msg.Code) + } default: return fmt.Errorf("%w: %v", errInvalidMsgCode, msg.Code) } diff --git a/eth/protocols/diff/peer.go b/eth/protocols/diff/peer.go index 0f02152baa..f110cab69e 100644 --- a/eth/protocols/diff/peer.go +++ b/eth/protocols/diff/peer.go @@ -87,7 +87,7 @@ func (p *Peer) Close() { func (p *Peer) RequestDiffLayers(hashes []common.Hash) error { id := rand.Uint64() - requestTracker.Track(p.id, p.version, GetDiffLayerMsg, DiffLayerMsg, id) + requestTracker.Track(p.id, p.version, GetDiffLayerMsg, FullDiffLayerMsg, id) return p2p.Send(p.rw, GetDiffLayerMsg, GetDiffLayersPacket{ RequestId: id, BlockHashes: hashes, diff --git a/eth/protocols/diff/protocol.go b/eth/protocols/diff/protocol.go index 650ba4f51e..02474632a5 100644 --- a/eth/protocols/diff/protocol.go +++ b/eth/protocols/diff/protocol.go @@ -58,6 +58,7 @@ var ( errMsgTooLarge = errors.New("message too long") errDecode = errors.New("invalid message") errInvalidMsgCode = errors.New("invalid message code") + errUnexpectedMsg = errors.New("unexpected message code") errBadRequest = errors.New("bad request") errNoCapMsg = errors.New("miss cap message during handshake") ) diff --git a/eth/protocols/diff/tracker.go b/eth/protocols/diff/tracker.go index 754c41258b..7ee49e6ce2 100644 --- a/eth/protocols/diff/tracker.go +++ b/eth/protocols/diff/tracker.go @@ -17,10 +17,145 @@ package diff import ( + "container/list" + "fmt" + "sync" "time" - "github.com/ethereum/go-ethereum/p2p/tracker" + "github.com/ethereum/go-ethereum/log" ) -// requestTracker is a singleton tracker for request times. -var requestTracker = tracker.New(ProtocolName, time.Minute) +const ( + // maxTrackedPackets is a huge number to act as a failsafe on the number of + // pending requests the node will track. It should never be hit unless an + // attacker figures out a way to spin requests. + maxTrackedPackets = 10000 +) + +// request tracks sent network requests which have not yet received a response. +type request struct { + peer string + version uint // Protocol version + + reqCode uint64 // Protocol message code of the request + resCode uint64 // Protocol message code of the expected response + + time time.Time // Timestamp when the request was made + expire *list.Element // Expiration marker to untrack it +} + +type Tracker struct { + timeout time.Duration // Global timeout after which to drop a tracked packet + + pending map[uint64]*request // Currently pending requests + expire *list.List // Linked list tracking the expiration order + wake *time.Timer // Timer tracking the expiration of the next item + + lock sync.Mutex // Lock protecting from concurrent updates +} + +func NewTracker(timeout time.Duration) *Tracker { + return &Tracker{ + timeout: timeout, + pending: make(map[uint64]*request), + expire: list.New(), + } +} + +// Track adds a network request to the tracker to wait for a response to arrive +// or until the request it cancelled or times out. +func (t *Tracker) Track(peer string, version uint, reqCode uint64, resCode uint64, id uint64) { + t.lock.Lock() + defer t.lock.Unlock() + + // If there's a duplicate request, we've just random-collided (or more probably, + // we have a bug), report it. We could also add a metric, but we're not really + // expecting ourselves to be buggy, so a noisy warning should be enough. + if _, ok := t.pending[id]; ok { + log.Error("Network request id collision", "version", version, "code", reqCode, "id", id) + return + } + // If we have too many pending requests, bail out instead of leaking memory + if pending := len(t.pending); pending >= maxTrackedPackets { + log.Error("Request tracker exceeded allowance", "pending", pending, "peer", peer, "version", version, "code", reqCode) + return + } + // Id doesn't exist yet, start tracking it + t.pending[id] = &request{ + peer: peer, + version: version, + reqCode: reqCode, + resCode: resCode, + time: time.Now(), + expire: t.expire.PushBack(id), + } + + // If we've just inserted the first item, start the expiration timer + if t.wake == nil { + t.wake = time.AfterFunc(t.timeout, t.clean) + } +} + +// clean is called automatically when a preset time passes without a response +// being dleivered for the first network request. +func (t *Tracker) clean() { + t.lock.Lock() + defer t.lock.Unlock() + + // Expire anything within a certain threshold (might be no items at all if + // we raced with the delivery) + for t.expire.Len() > 0 { + // Stop iterating if the next pending request is still alive + var ( + head = t.expire.Front() + id = head.Value.(uint64) + req = t.pending[id] + ) + if time.Since(req.time) < t.timeout+5*time.Millisecond { + break + } + // Nope, dead, drop it + t.expire.Remove(head) + delete(t.pending, id) + } + t.schedule() +} + +// schedule starts a timer to trigger on the expiration of the first network +// packet. +func (t *Tracker) schedule() { + if t.expire.Len() == 0 { + t.wake = nil + return + } + t.wake = time.AfterFunc(time.Until(t.pending[t.expire.Front().Value.(uint64)].time.Add(t.timeout)), t.clean) +} + +// Fulfil fills a pending request, if any is available. +func (t *Tracker) Fulfil(peer string, version uint, code uint64, id uint64) bool { + t.lock.Lock() + defer t.lock.Unlock() + + // If it's a non existing request, track as stale response + req, ok := t.pending[id] + if !ok { + return false + } + // If the response is funky, it might be some active attack + if req.peer != peer || req.version != version || req.resCode != code { + log.Warn("Network response id collision", + "have", fmt.Sprintf("%s:/%d:%d", peer, version, code), + "want", fmt.Sprintf("%s:/%d:%d", peer, req.version, req.resCode), + ) + return false + } + // Everything matches, mark the request serviced + t.expire.Remove(req.expire) + delete(t.pending, id) + if req.expire.Prev() == nil { + if t.wake.Stop() { + t.schedule() + } + } + return true +} From c6e62e118e804864dd45f1f90e5e996b2418ae1e Mon Sep 17 00:00:00 2001 From: fudongbai <296179868@qq.com> Date: Tue, 7 Sep 2021 23:59:06 +0800 Subject: [PATCH 15/22] consensus tuning --- consensus/parlia/parlia.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/consensus/parlia/parlia.go b/consensus/parlia/parlia.go index 0e4285412d..275ad2b913 100644 --- a/consensus/parlia/parlia.go +++ b/consensus/parlia/parlia.go @@ -55,7 +55,8 @@ const ( validatorBytesLength = common.AddressLength wiggleTime = uint64(1) // second, Random delay (per signer) to allow concurrent signers - initialBackOffTime = uint64(1) // second + // TODO this is a hardfork change, just for tuning so far, recover it late + initialBackOffTime = uint64(2) // second systemRewardPercent = 4 // it means 1/2^4 = 1/16 percentage of gas fee incoming will be distributed to system @@ -799,6 +800,10 @@ func (p *Parlia) Delay(chain consensus.ChainReader, header *types.Header) *time. return nil } delay := p.delayForRamanujanFork(snap, header) + // The blocking time should be no more than half of epoch + if delay > time.Duration(p.config.Period)*time.Second*4/5 { + delay = time.Duration(p.config.Period) * time.Second * 4 / 5 + } return &delay } From 443f9a44d4914eb65b87acfcd289182db78bd168 Mon Sep 17 00:00:00 2001 From: fudongbai <296179868@qq.com> Date: Wed, 8 Sep 2021 14:04:02 +0800 Subject: [PATCH 16/22] add test code --- cmd/utils/flags.go | 2 +- consensus/parlia/parlia.go | 23 ++++++----------------- core/blockchain.go | 8 +++----- core/state/statedb.go | 16 +++++++++++++--- core/state_processor.go | 28 +++++++++++++++++----------- eth/handler.go | 1 - eth/handler_diff.go | 9 --------- eth/protocols/diff/protocol.go | 1 + 8 files changed, 41 insertions(+), 47 deletions(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 9e92f00950..15db979823 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -123,7 +123,7 @@ var ( } DiffSyncFlag = cli.BoolFlag{ Name: "diffsync", - Usage: "Enable difflayer sync, Please note that enable diffsync will improve the syncing speed, " + + Usage: "Enable diffy sync, Please note that enable diffsync will improve the syncing speed, " + "but will degrade the security to light client level", } RangeLimitFlag = cli.BoolFlag{ diff --git a/consensus/parlia/parlia.go b/consensus/parlia/parlia.go index 275ad2b913..9e599cdbd8 100644 --- a/consensus/parlia/parlia.go +++ b/consensus/parlia/parlia.go @@ -55,8 +55,7 @@ const ( validatorBytesLength = common.AddressLength wiggleTime = uint64(1) // second, Random delay (per signer) to allow concurrent signers - // TODO this is a hardfork change, just for tuning so far, recover it late - initialBackOffTime = uint64(2) // second + initialBackOffTime = uint64(1) // second systemRewardPercent = 4 // it means 1/2^4 = 1/16 percentage of gas fee incoming will be distributed to system @@ -800,9 +799,9 @@ func (p *Parlia) Delay(chain consensus.ChainReader, header *types.Header) *time. return nil } delay := p.delayForRamanujanFork(snap, header) - // The blocking time should be no more than half of epoch - if delay > time.Duration(p.config.Period)*time.Second*4/5 { - delay = time.Duration(p.config.Period) * time.Second * 4 / 5 + // The blocking time should be no more than half of period + if delay > time.Duration(p.config.Period)*time.Second/2 { + delay = time.Duration(p.config.Period) * time.Second / 2 } return &delay } @@ -894,19 +893,9 @@ func (p *Parlia) AllowLightProcess(chain consensus.ChainReader, currentHeader *t } idx := snap.indexOfVal(p.val) - if idx < 0 { - return true - } - validators := snap.validators() - - validatorNum := int64(len(validators)) - // It is not allowed if only two validators - if validatorNum <= 2 { - return false - } + // validator is not allowed to diff sync + return idx < 0 - offset := (int64(snap.Number) + 2) % validatorNum - return validators[offset] == p.val } func (p *Parlia) IsLocalBlock(header *types.Header) bool { diff --git a/core/blockchain.go b/core/blockchain.go index 84a229f629..fbf7fc00f6 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -2654,14 +2654,12 @@ func (bc *BlockChain) HandleDiffLayer(diffLayer *types.DiffLayer, pid string, fu if _, alreadyHas := bc.diffPeersToDiffHashes[pid][diffLayer.DiffHash]; alreadyHas { return nil } + } else { + bc.diffPeersToDiffHashes[pid] = make(map[common.Hash]struct{}) } - bc.diffPeersToDiffHashes[pid] = make(map[common.Hash]struct{}) bc.diffPeersToDiffHashes[pid][diffLayer.DiffHash] = struct{}{} if _, exist := bc.diffNumToBlockHashes[diffLayer.Number]; !exist { bc.diffNumToBlockHashes[diffLayer.Number] = make(map[common.Hash]struct{}) - } - if len(bc.diffNumToBlockHashes[diffLayer.Number]) > 4 { - } bc.diffNumToBlockHashes[diffLayer.Number][diffLayer.BlockHash] = struct{}{} @@ -2929,7 +2927,7 @@ func (bc *BlockChain) SubscribeBlockProcessingEvent(ch chan<- bool) event.Subscr return bc.scope.Track(bc.blockProcFeed.Subscribe(ch)) } -//Options +// Options func EnableLightProcessor(bc *BlockChain) *BlockChain { bc.processor = NewLightStateProcessor(bc.Config(), bc, bc.engine) return bc diff --git a/core/state/statedb.go b/core/state/statedb.go index 886f181bf5..b55171e324 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -1109,9 +1109,11 @@ func (s *StateDB) LightCommit(root common.Hash) (common.Hash, *types.DiffLayer, tasksNum := 0 finishCh := make(chan struct{}) defer close(finishCh) - threads := 1 - if len(s.diffTries)/runtime.NumCPU() > minNumberOfAccountPerTask { + threads := len(s.diffTries) / minNumberOfAccountPerTask + if threads > runtime.NumCPU() { threads = runtime.NumCPU() + } else if threads == 0 { + threads = 1 } for i := 0; i < threads; i++ { go func() { @@ -1230,7 +1232,15 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, *types.DiffLayer tasksNum := 0 finishCh := make(chan struct{}) defer close(finishCh) - for i := 0; i < runtime.NumCPU(); i++ { + + threads := len(s.stateObjectsDirty) / minNumberOfAccountPerTask + if threads > runtime.NumCPU() { + threads = runtime.NumCPU() + } else if threads == 0 { + threads = 1 + } + + for i := 0; i < threads; i++ { go func() { codeWriter := s.db.TrieDB().DiskDB().NewBatch() for { diff --git a/core/state_processor.go b/core/state_processor.go index aca43b4609..dde81664f9 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -44,7 +44,9 @@ import ( const ( fullProcessCheck = 21 // On diff sync mode, will do full process every fullProcessCheck randomly minNumberOfAccountPerTask = 5 - diffLayerTimeout = 50 + recentTime = 2048 * 3 + recentDiffLayerTimeout = 20 + farDiffLayerTimeout = 2 ) // StateProcessor is a basic Processor, which takes care of transitioning @@ -83,7 +85,6 @@ func (p *LightStateProcessor) Process(block *types.Block, statedb *state.StateDB allowLightProcess := true if posa, ok := p.engine.(consensus.PoSA); ok { allowLightProcess = posa.AllowLightProcess(p.bc, block.Header()) - log.Error("===debug, allow to light process?", "allow", allowLightProcess) } // random fallback to full process if check := p.randomGenerator.Int63n(fullProcessCheck); allowLightProcess && check != 0 && len(block.Transactions()) != 0 { @@ -92,12 +93,14 @@ func (p *LightStateProcessor) Process(block *types.Block, statedb *state.StateDB pid = peer.ID() } var diffLayer *types.DiffLayer - //TODO This is just for debug + var diffLayerTimeout = recentDiffLayerTimeout + if time.Now().Unix()-int64(block.Time()) > recentTime { + diffLayerTimeout = farDiffLayerTimeout + } for tried := 0; tried < diffLayerTimeout; tried++ { // wait a bit for the diff layer diffLayer = p.bc.GetUnTrustedDiffLayer(block.Hash(), pid) if diffLayer != nil { - log.Error("===debug find it", "idx", tried) break } time.Sleep(time.Millisecond) @@ -108,12 +111,13 @@ func (p *LightStateProcessor) Process(block *types.Block, statedb *state.StateDB // fallback to full process return p.StateProcessor.Process(block, statedb, cfg) } - receipts, logs, gasUsed, err := p.LightProcess(diffLayer, block, statedb, cfg) + + receipts, logs, gasUsed, err := p.LightProcess(diffLayer, block, statedb) if err == nil { log.Info("do light process success at block", "num", block.NumberU64()) return statedb, receipts, logs, gasUsed, nil } else { - log.Error("do light process err at block\n", "num", block.NumberU64(), "err", err) + log.Error("do light process err at block", "num", block.NumberU64(), "err", err) p.bc.removeDiffLayers(diffLayer.DiffHash) // prepare new statedb statedb.StopPrefetcher() @@ -131,7 +135,7 @@ func (p *LightStateProcessor) Process(block *types.Block, statedb *state.StateDB return p.StateProcessor.Process(block, statedb, cfg) } -func (p *LightStateProcessor) LightProcess(diffLayer *types.DiffLayer, block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) { +func (p *LightStateProcessor) LightProcess(diffLayer *types.DiffLayer, block *types.Block, statedb *state.StateDB) (types.Receipts, []*types.Log, uint64, error) { statedb.MarkLightProcessed() fullDiffCode := make(map[common.Hash][]byte, len(diffLayer.Codes)) diffTries := make(map[common.Address]state.Trie) @@ -149,9 +153,11 @@ func (p *LightStateProcessor) LightProcess(diffLayer *types.DiffLayer, block *ty for des := range snapDestructs { statedb.Trie().TryDelete(des[:]) } - threads := 1 - if len(snapAccounts)/runtime.NumCPU() > minNumberOfAccountPerTask { + threads := len(snapAccounts) / minNumberOfAccountPerTask + if threads > runtime.NumCPU() { threads = runtime.NumCPU() + } else if threads == 0 { + threads = 1 } iteAccounts := make([]common.Address, 0, len(snapAccounts)) @@ -236,7 +242,7 @@ func (p *LightStateProcessor) LightProcess(diffLayer *types.DiffLayer, block *ty !bytes.Equal(latestAccount.CodeHash, types.EmptyCodeHash) { if code, exist := fullDiffCode[codeHash]; exist { if crypto.Keccak256Hash(code) != codeHash { - errChan <- err + errChan <- fmt.Errorf("code and code hash mismatch, account %s", diffAccount.String()) return } diffMux.Lock() @@ -245,7 +251,7 @@ func (p *LightStateProcessor) LightProcess(diffLayer *types.DiffLayer, block *ty } else { rawCode := rawdb.ReadCode(p.bc.db, codeHash) if len(rawCode) == 0 { - errChan <- err + errChan <- fmt.Errorf("missing code, account %s", diffAccount.String()) return } } diff --git a/eth/handler.go b/eth/handler.go index e4980b939b..0fd0e5d49f 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -486,7 +486,6 @@ func (h *handler) BroadcastBlock(block *types.Block, propagate bool) { for _, peer := range transfer { if len(diff) != 0 && peer.diffExt != nil { // difflayer should send before block - log.Error("===debug Broadcast block", "number", block.Number(), "hash", hash) peer.diffExt.SendDiffLayers([]rlp.RawValue{diff}) } peer.AsyncSendNewBlock(block, td) diff --git a/eth/handler_diff.go b/eth/handler_diff.go index ab9363e6e5..97474f5f18 100644 --- a/eth/handler_diff.go +++ b/eth/handler_diff.go @@ -19,8 +19,6 @@ package eth import ( "fmt" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/eth/protocols/diff" "github.com/ethereum/go-ethereum/p2p/enode" @@ -73,15 +71,8 @@ func (h *diffHandler) handleDiffLayerPackage(packet *diff.DiffLayersPacket, pid diffs, err := packet.Unpack() if err != nil { - log.Error("====unpack err", "number", diffs[0].Number, "hash", diffs[0].BlockHash, "err", err) return err } - if len(diffs) > 0 { - log.Error("====debug receive difflayer", "number", diffs[0].Number, "hash", diffs[0].BlockHash) - - } else { - log.Error("====debug receive difflayer length 0") - } for _, d := range diffs { if d != nil { if err := d.Validate(); err != nil { diff --git a/eth/protocols/diff/protocol.go b/eth/protocols/diff/protocol.go index 02474632a5..7506496234 100644 --- a/eth/protocols/diff/protocol.go +++ b/eth/protocols/diff/protocol.go @@ -98,6 +98,7 @@ func (p *DiffLayersPacket) Unpack() ([]*types.DiffLayer, error) { type DiffCapPacket struct { DiffSync bool + Extra rlp.RawValue // for extension } type DiffLayersPacket []rlp.RawValue From 1fbecfa04b0b3cacb48b11b4fc9238c76f75e9f1 Mon Sep 17 00:00:00 2001 From: fudongbai <296179868@qq.com> Date: Fri, 10 Sep 2021 18:45:06 +0800 Subject: [PATCH 17/22] remove extra message --- eth/protocols/diff/handshake.go | 1 + eth/protocols/diff/protocol.go | 2 ++ 2 files changed, 3 insertions(+) diff --git a/eth/protocols/diff/handshake.go b/eth/protocols/diff/handshake.go index 0ab4fddfa8..4198ea88a1 100644 --- a/eth/protocols/diff/handshake.go +++ b/eth/protocols/diff/handshake.go @@ -40,6 +40,7 @@ func (p *Peer) Handshake(diffSync bool) error { gopool.Submit(func() { errc <- p2p.Send(p.rw, DiffCapMsg, &DiffCapPacket{ DiffSync: diffSync, + Extra: defaultExtra, }) }) gopool.Submit(func() { diff --git a/eth/protocols/diff/protocol.go b/eth/protocols/diff/protocol.go index 7506496234..f1b2ce750b 100644 --- a/eth/protocols/diff/protocol.go +++ b/eth/protocols/diff/protocol.go @@ -54,6 +54,8 @@ const ( FullDiffLayerMsg = 0x03 ) +var defaultExtra = []byte{0x00} + var ( errMsgTooLarge = errors.New("message too long") errDecode = errors.New("invalid message") From 92e21cc33d5e2d9b1a142013b244849ed8739978 Mon Sep 17 00:00:00 2001 From: guagualvcha <296179868@qq.com> Date: Sun, 26 Sep 2021 12:13:34 +0800 Subject: [PATCH 18/22] fix testcase and lint make diff block configable wait code write fix testcase resolve comments resolve comment --- cmd/faucet/faucet.go | 4 +- cmd/geth/main.go | 1 + cmd/utils/flags.go | 8 +++ consensus/parlia/parlia.go | 6 +- consensus/parlia/ramanujanfork.go | 2 +- consensus/parlia/snapshot.go | 2 +- core/blockchain.go | 47 ++++++++------- core/blockchain_diff_test.go | 2 +- core/blockchain_test.go | 4 +- core/rawdb/freezer_table_test.go | 60 +------------------ core/state/database.go | 1 - core/state/snapshot/disklayer_test.go | 8 +-- core/state/snapshot/iterator_test.go | 86 +++++++++++++-------------- core/state/snapshot/snapshot.go | 12 ++-- core/state/snapshot/snapshot_test.go | 24 ++++---- core/state/statedb.go | 17 +++--- core/state/sync_test.go | 2 +- core/state_prefetcher.go | 9 --- core/state_processor.go | 3 +- core/vm/contracts_lightclient_test.go | 2 +- core/vm/lightclient/types.go | 2 +- eth/backend.go | 3 + eth/ethconfig/config.go | 2 + eth/ethconfig/gen_config.go | 6 ++ eth/handler.go | 2 +- eth/handler_diff.go | 7 +-- eth/protocols/diff/handler.go | 3 +- eth/protocols/diff/protocol.go | 1 - eth/protocols/diff/protocol_test.go | 4 +- ethclient/ethclient_test.go | 70 +--------------------- les/peer.go | 2 +- light/trie.go | 12 +--- miner/worker.go | 19 ------ node/node.go | 18 +++--- rlp/typecache.go | 10 ---- 35 files changed, 159 insertions(+), 302 deletions(-) diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go index 5aab7598a1..fe6d44b4a0 100644 --- a/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -139,7 +139,7 @@ func main() { log.Crit("Length of bep2eContracts, bep2eSymbols, bep2eAmounts mismatch") } - bep2eInfos := make(map[string]bep2eInfo, 0) + bep2eInfos := make(map[string]bep2eInfo, len(symbols)) for idx, s := range symbols { n, ok := big.NewInt(0).SetString(bep2eNumAmounts[idx], 10) if !ok { @@ -148,7 +148,7 @@ func main() { amountStr := big.NewFloat(0).Quo(big.NewFloat(0).SetInt(n), big.NewFloat(0).SetInt64(params.Ether)).String() bep2eInfos[s] = bep2eInfo{ - Contract: common.HexToAddress(contracts[idx]), + Contract: common.HexToAddreparlia.goss(contracts[idx]), Amount: *n, AmountStr: amountStr, } diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 2f56e6c5d2..8be8d20bf4 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -117,6 +117,7 @@ var ( utils.CacheSnapshotFlag, utils.CachePreimagesFlag, utils.PersistDiffFlag, + utils.DiffBlockFlag, utils.ListenPortFlag, utils.MaxPeersFlag, utils.MaxPendingPeersFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 15db979823..c4dbd84911 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -442,6 +442,11 @@ var ( Name: "persistdiff", Usage: "Enable persistence of the diff layer", } + DiffBlockFlag = cli.Uint64Flag{ + Name: "diffblock", + Usage: "The number of blocks should be persisted in db (default = 864000 )", + Value: uint64(864000), + } // Miner settings MiningEnabledFlag = cli.BoolFlag{ Name: "mine", @@ -1590,6 +1595,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { if ctx.GlobalIsSet(PersistDiffFlag.Name) { cfg.PersistDiff = ctx.GlobalBool(PersistDiffFlag.Name) } + if ctx.GlobalIsSet(DiffBlockFlag.Name) { + cfg.DiffBlock = ctx.GlobalUint64(DiffBlockFlag.Name) + } if gcmode := ctx.GlobalString(GCModeFlag.Name); gcmode != "full" && gcmode != "archive" { Fatalf("--%s must be either 'full' or 'archive'", GCModeFlag.Name) } diff --git a/consensus/parlia/parlia.go b/consensus/parlia/parlia.go index 9e599cdbd8..729c6f3730 100644 --- a/consensus/parlia/parlia.go +++ b/consensus/parlia/parlia.go @@ -800,8 +800,9 @@ func (p *Parlia) Delay(chain consensus.ChainReader, header *types.Header) *time. } delay := p.delayForRamanujanFork(snap, header) // The blocking time should be no more than half of period - if delay > time.Duration(p.config.Period)*time.Second/2 { - delay = time.Duration(p.config.Period) * time.Second / 2 + half := time.Duration(p.config.Period) * time.Second / 2 + if delay > half { + delay = half } return &delay } @@ -895,7 +896,6 @@ func (p *Parlia) AllowLightProcess(chain consensus.ChainReader, currentHeader *t idx := snap.indexOfVal(p.val) // validator is not allowed to diff sync return idx < 0 - } func (p *Parlia) IsLocalBlock(header *types.Header) bool { diff --git a/consensus/parlia/ramanujanfork.go b/consensus/parlia/ramanujanfork.go index 903c678f42..9b702ca6c5 100644 --- a/consensus/parlia/ramanujanfork.go +++ b/consensus/parlia/ramanujanfork.go @@ -21,7 +21,7 @@ func (p *Parlia) delayForRamanujanFork(snap *Snapshot, header *types.Header) tim if header.Difficulty.Cmp(diffNoTurn) == 0 { // It's not our turn explicitly to sign, delay it a bit wiggle := time.Duration(len(snap.Validators)/2+1) * wiggleTimeBeforeFork - delay += time.Duration(fixedBackOffTimeBeforeFork) + time.Duration(rand.Int63n(int64(wiggle))) + delay += fixedBackOffTimeBeforeFork + time.Duration(rand.Int63n(int64(wiggle))) } return delay } diff --git a/consensus/parlia/snapshot.go b/consensus/parlia/snapshot.go index dc83d92c8d..95aaf861de 100644 --- a/consensus/parlia/snapshot.go +++ b/consensus/parlia/snapshot.go @@ -256,7 +256,7 @@ func (s *Snapshot) enoughDistance(validator common.Address, header *types.Header if validator == header.Coinbase { return false } - offset := (int64(s.Number) + 1) % int64(validatorNum) + offset := (int64(s.Number) + 1) % validatorNum if int64(idx) >= offset { return int64(idx)-offset >= validatorNum-2 } else { diff --git a/core/blockchain.go b/core/blockchain.go index fbf7fc00f6..78ee92dced 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -91,7 +91,6 @@ const ( maxBeyondBlocks = 2048 diffLayerFreezerRecheckInterval = 3 * time.Second - diffLayerFreezerBlockLimit = 864000 // The number of diff layers that should be kept in disk. diffLayerPruneRecheckInterval = 1 * time.Second // The interval to prune unverified diff layers maxDiffQueueDist = 2048 // Maximum allowed distance from the chain head to queue diffLayers maxDiffLimit = 2048 // Maximum number of unique diff layers a peer may have responded @@ -213,9 +212,11 @@ type BlockChain struct { futureBlocks *lru.Cache // future blocks are blocks added for later processing // trusted diff layers - diffLayerCache *lru.Cache // Cache for the diffLayers - diffLayerRLPCache *lru.Cache // Cache for the rlp encoded diffLayers - diffQueue *prque.Prque // A Priority queue to store recent diff layer + diffLayerCache *lru.Cache // Cache for the diffLayers + diffLayerRLPCache *lru.Cache // Cache for the rlp encoded diffLayers + diffQueue *prque.Prque // A Priority queue to store recent diff layer + diffQueueBuffer chan *types.DiffLayer + diffLayerFreezerBlockLimit uint64 // untrusted diff layers diffMux sync.RWMutex @@ -285,6 +286,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par engine: engine, vmConfig: vmConfig, diffQueue: prque.New(nil), + diffQueueBuffer: make(chan *types.DiffLayer), blockHashToDiffLayers: make(map[common.Hash]map[common.Hash]*types.DiffLayer), diffHashToBlockHash: make(map[common.Hash]common.Hash), diffHashToPeers: make(map[common.Hash]map[string]struct{}), @@ -444,7 +446,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par } // Need persist and prune diff layer if bc.db.DiffStore() != nil { - go bc.trustedDiffLayerFreezeLoop() + go bc.trustedDiffLayerLoop() } go bc.untrustedDiffLayerPruneLoop() @@ -464,7 +466,7 @@ func (bc *BlockChain) cacheDiffLayer(diffLayer *types.DiffLayer) { bc.diffLayerCache.Add(diffLayer.BlockHash, diffLayer) if bc.db.DiffStore() != nil { // push to priority queue before persisting - bc.diffQueue.Push(diffLayer, -(int64(diffLayer.Number))) + bc.diffQueueBuffer <- diffLayer } } @@ -967,7 +969,7 @@ func (bc *BlockChain) GetDiffLayerRLP(blockHash common.Hash) rlp.RawValue { } rawData := rawdb.ReadDiffLayerRLP(diffStore, blockHash) if len(rawData) != 0 { - bc.diffLayerRLPCache.Add(blockHash, rlp.RawValue(rawData)) + bc.diffLayerRLPCache.Add(blockHash, rawData) } return rawData } @@ -2009,8 +2011,6 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er storageUpdateTimer.Update(statedb.StorageUpdates) // Storage updates are complete, we can mark them snapshotAccountReadTimer.Update(statedb.SnapshotAccountReads) // Account reads are complete, we can mark them snapshotStorageReadTimer.Update(statedb.SnapshotStorageReads) // Storage reads are complete, we can mark them - trieproc := statedb.SnapshotAccountReads + statedb.AccountReads + statedb.AccountUpdates - trieproc += statedb.SnapshotStorageReads + statedb.StorageReads + statedb.StorageUpdates blockExecutionTimer.Update(time.Since(substart)) @@ -2401,12 +2401,14 @@ func (bc *BlockChain) update() { } } -func (bc *BlockChain) trustedDiffLayerFreezeLoop() { +func (bc *BlockChain) trustedDiffLayerLoop() { recheck := time.Tick(diffLayerFreezerRecheckInterval) bc.wg.Add(1) defer bc.wg.Done() for { select { + case diff := <-bc.diffQueueBuffer: + bc.diffQueue.Push(diff, -(int64(diff.Number))) case <-bc.quit: // Persist all diffLayers when shutdown, it will introduce redundant storage, but it is acceptable. // If the client been ungracefully shutdown, it will missing all cached diff layers, it is acceptable as well. @@ -2451,7 +2453,7 @@ func (bc *BlockChain) trustedDiffLayerFreezeLoop() { batch = bc.db.DiffStore().NewBatch() } rawdb.WriteDiffLayer(batch, diffLayer.BlockHash, diffLayer) - staleHash := bc.GetCanonicalHash(uint64(-prio) - diffLayerFreezerBlockLimit) + staleHash := bc.GetCanonicalHash(uint64(-prio) - bc.diffLayerFreezerBlockLimit) rawdb.DeleteDiffLayer(batch, staleHash) } } else { @@ -2511,16 +2513,12 @@ func (bc *BlockChain) removeDiffLayers(diffHash common.Hash) { // Untrusted peers pids := bc.diffHashToPeers[diffHash] invalidDiffHashes := make(map[common.Hash]struct{}) - if pids != nil { - for pid := range pids { - invaliDiffHashesPeer := bc.diffPeersToDiffHashes[pid] - if invaliDiffHashesPeer != nil { - for invaliDiffHash := range invaliDiffHashesPeer { - invalidDiffHashes[invaliDiffHash] = struct{}{} - } - } - delete(bc.diffPeersToDiffHashes, pid) + for pid := range pids { + invaliDiffHashesPeer := bc.diffPeersToDiffHashes[pid] + for invaliDiffHash := range invaliDiffHashesPeer { + invalidDiffHashes[invaliDiffHash] = struct{}{} } + delete(bc.diffPeersToDiffHashes, pid) } for invalidDiffHash := range invalidDiffHashes { delete(bc.diffHashToPeers, invalidDiffHash) @@ -2602,7 +2600,7 @@ func (bc *BlockChain) pruneDiffLayer() { break } } - staleDiffHashes := make(map[common.Hash]struct{}, 0) + staleDiffHashes := make(map[common.Hash]struct{}) for blockHash := range staleBlockHashes { if diffHashes, exist := bc.blockHashToDiffLayers[blockHash]; exist { for diffHash := range diffHashes { @@ -2932,3 +2930,10 @@ func EnableLightProcessor(bc *BlockChain) *BlockChain { bc.processor = NewLightStateProcessor(bc.Config(), bc, bc.engine) return bc } + +func EnablePersistDiff(limit uint64) BlockChainOption { + return func(chain *BlockChain) *BlockChain { + chain.diffLayerFreezerBlockLimit = limit + return chain + } +} diff --git a/core/blockchain_diff_test.go b/core/blockchain_diff_test.go index 14c5426bf6..717b4039ed 100644 --- a/core/blockchain_diff_test.go +++ b/core/blockchain_diff_test.go @@ -72,7 +72,7 @@ func newTestBackendWithGenerator(blocks int, lightProcess bool) *testBackend { Alloc: GenesisAlloc{testAddr: {Balance: big.NewInt(100000000000000000)}}, }).MustCommit(db) - chain, _ := NewBlockChain(db, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, nil) + chain, _ := NewBlockChain(db, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, nil, EnablePersistDiff(860000)) generator := func(i int, block *BlockGen) { // The chain maker doesn't have access to a chain, so the difficulty will be // lets unset (nil). Set it here to the correct value. diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 8bc1009be3..4314bd45a9 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -1769,7 +1769,7 @@ func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommon } lastPrunedIndex := len(blocks) - TestTriesInMemory - 1 - lastPrunedBlock := blocks[lastPrunedIndex] + lastPrunedBlock := blocks[lastPrunedIndex-1] firstNonPrunedBlock := blocks[len(blocks)-TestTriesInMemory] // Verify pruning of lastPrunedBlock @@ -2420,7 +2420,7 @@ func TestSideImportPrunedBlocks(t *testing.T) { } lastPrunedIndex := len(blocks) - TestTriesInMemory - 1 - lastPrunedBlock := blocks[lastPrunedIndex] + lastPrunedBlock := blocks[lastPrunedIndex-1] // Verify pruning of lastPrunedBlock if chain.HasBlockAndState(lastPrunedBlock.Hash(), lastPrunedBlock.NumberU64()) { diff --git a/core/rawdb/freezer_table_test.go b/core/rawdb/freezer_table_test.go index 0df28f236d..8e52b20088 100644 --- a/core/rawdb/freezer_table_test.go +++ b/core/rawdb/freezer_table_test.go @@ -18,13 +18,10 @@ package rawdb import ( "bytes" - "encoding/binary" "fmt" - "io/ioutil" "math/rand" "os" "path/filepath" - "sync" "testing" "time" @@ -528,7 +525,6 @@ func TestOffset(t *testing.T) { f.Append(4, getChunk(20, 0xbb)) f.Append(5, getChunk(20, 0xaa)) - f.DumpIndex(0, 100) f.Close() } // Now crop it. @@ -575,7 +571,6 @@ func TestOffset(t *testing.T) { if err != nil { t.Fatal(err) } - f.DumpIndex(0, 100) // It should allow writing item 6 f.Append(numDeleted+2, getChunk(20, 0x99)) @@ -640,55 +635,6 @@ func TestOffset(t *testing.T) { // 1. have data files d0, d1, d2, d3 // 2. remove d2,d3 // -// However, all 'normal' failure modes arising due to failing to sync() or save a file -// should be handled already, and the case described above can only (?) happen if an -// external process/user deletes files from the filesystem. - -// TestAppendTruncateParallel is a test to check if the Append/truncate operations are -// racy. -// -// The reason why it's not a regular fuzzer, within tests/fuzzers, is that it is dependent -// on timing rather than 'clever' input -- there's no determinism. -func TestAppendTruncateParallel(t *testing.T) { - dir, err := ioutil.TempDir("", "freezer") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) - - f, err := newCustomTable(dir, "tmp", metrics.NilMeter{}, metrics.NilMeter{}, metrics.NilGauge{}, 8, true) - if err != nil { - t.Fatal(err) - } - - fill := func(mark uint64) []byte { - data := make([]byte, 8) - binary.LittleEndian.PutUint64(data, mark) - return data - } - - for i := 0; i < 5000; i++ { - f.truncate(0) - data0 := fill(0) - f.Append(0, data0) - data1 := fill(1) - - var wg sync.WaitGroup - wg.Add(2) - go func() { - f.truncate(0) - wg.Done() - }() - go func() { - f.Append(1, data1) - wg.Done() - }() - wg.Wait() - - if have, err := f.Retrieve(0); err == nil { - if !bytes.Equal(have, data0) { - t.Fatalf("have %x want %x", have, data0) - } - } - } -} +// However, all 'normal' failure modes arising due to failing to sync() or save a file should be +// handled already, and the case described above can only (?) happen if an external process/user +// deletes files from the filesystem. diff --git a/core/state/database.go b/core/state/database.go index ce37e73837..0bcde2d5a9 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -243,7 +243,6 @@ func (db *cachingDB) CacheStorage(addrHash common.Hash, root common.Hash, t Trie triesArray := [3]*triePair{{root: root, trie: tr.ResetCopy()}, nil, nil} db.storageTrieCache.Add(addrHash, triesArray) } - return } func (db *cachingDB) Purge() { diff --git a/core/state/snapshot/disklayer_test.go b/core/state/snapshot/disklayer_test.go index ccde2fc094..362edba90d 100644 --- a/core/state/snapshot/disklayer_test.go +++ b/core/state/snapshot/disklayer_test.go @@ -121,7 +121,7 @@ func TestDiskMerge(t *testing.T) { base.Storage(conNukeCache, conNukeCacheSlot) // Modify or delete some accounts, flatten everything onto disk - if err := snaps.Update(diffRoot, baseRoot, map[common.Hash]struct{}{ + if err := snaps.update(diffRoot, baseRoot, map[common.Hash]struct{}{ accDelNoCache: {}, accDelCache: {}, conNukeNoCache: {}, @@ -344,7 +344,7 @@ func TestDiskPartialMerge(t *testing.T) { assertStorage(conNukeCache, conNukeCacheSlot, conNukeCacheSlot[:]) // Modify or delete some accounts, flatten everything onto disk - if err := snaps.Update(diffRoot, baseRoot, map[common.Hash]struct{}{ + if err := snaps.update(diffRoot, baseRoot, map[common.Hash]struct{}{ accDelNoCache: {}, accDelCache: {}, conNukeNoCache: {}, @@ -466,7 +466,7 @@ func TestDiskGeneratorPersistence(t *testing.T) { }, } // Modify or delete some accounts, flatten everything onto disk - if err := snaps.Update(diffRoot, baseRoot, nil, map[common.Hash][]byte{ + if err := snaps.update(diffRoot, baseRoot, nil, map[common.Hash][]byte{ accTwo: accTwo[:], }, nil); err != nil { t.Fatalf("failed to update snapshot tree: %v", err) @@ -484,7 +484,7 @@ func TestDiskGeneratorPersistence(t *testing.T) { } // Test scenario 2, the disk layer is fully generated // Modify or delete some accounts, flatten everything onto disk - if err := snaps.Update(diffTwoRoot, diffRoot, nil, map[common.Hash][]byte{ + if err := snaps.update(diffTwoRoot, diffRoot, nil, map[common.Hash][]byte{ accThree: accThree.Bytes(), }, map[common.Hash]map[common.Hash][]byte{ accThree: {accThreeSlot: accThreeSlot.Bytes()}, diff --git a/core/state/snapshot/iterator_test.go b/core/state/snapshot/iterator_test.go index 2c7e876e08..2a27b01577 100644 --- a/core/state/snapshot/iterator_test.go +++ b/core/state/snapshot/iterator_test.go @@ -221,13 +221,13 @@ func TestAccountIteratorTraversal(t *testing.T) { }, } // Stack three diff layers on top with various overlaps - snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, + snaps.update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, randomAccountSet("0xaa", "0xee", "0xff", "0xf0"), nil) - snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, + snaps.update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, randomAccountSet("0xbb", "0xdd", "0xf0"), nil) - snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), nil, + snaps.update(common.HexToHash("0x04"), common.HexToHash("0x03"), nil, randomAccountSet("0xcc", "0xf0", "0xff"), nil) // Verify the single and multi-layer iterators @@ -268,13 +268,13 @@ func TestStorageIteratorTraversal(t *testing.T) { }, } // Stack three diff layers on top with various overlaps - snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, + snaps.update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, randomAccountSet("0xaa"), randomStorageSet([]string{"0xaa"}, [][]string{{"0x01", "0x02", "0x03"}}, nil)) - snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, + snaps.update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, randomAccountSet("0xaa"), randomStorageSet([]string{"0xaa"}, [][]string{{"0x04", "0x05", "0x06"}}, nil)) - snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), nil, + snaps.update(common.HexToHash("0x04"), common.HexToHash("0x03"), nil, randomAccountSet("0xaa"), randomStorageSet([]string{"0xaa"}, [][]string{{"0x01", "0x02", "0x03"}}, nil)) // Verify the single and multi-layer iterators @@ -353,14 +353,14 @@ func TestAccountIteratorTraversalValues(t *testing.T) { } } // Assemble a stack of snapshots from the account layers - snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, a, nil) - snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, b, nil) - snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), nil, c, nil) - snaps.Update(common.HexToHash("0x05"), common.HexToHash("0x04"), nil, d, nil) - snaps.Update(common.HexToHash("0x06"), common.HexToHash("0x05"), nil, e, nil) - snaps.Update(common.HexToHash("0x07"), common.HexToHash("0x06"), nil, f, nil) - snaps.Update(common.HexToHash("0x08"), common.HexToHash("0x07"), nil, g, nil) - snaps.Update(common.HexToHash("0x09"), common.HexToHash("0x08"), nil, h, nil) + snaps.update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, a, nil) + snaps.update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, b, nil) + snaps.update(common.HexToHash("0x04"), common.HexToHash("0x03"), nil, c, nil) + snaps.update(common.HexToHash("0x05"), common.HexToHash("0x04"), nil, d, nil) + snaps.update(common.HexToHash("0x06"), common.HexToHash("0x05"), nil, e, nil) + snaps.update(common.HexToHash("0x07"), common.HexToHash("0x06"), nil, f, nil) + snaps.update(common.HexToHash("0x08"), common.HexToHash("0x07"), nil, g, nil) + snaps.update(common.HexToHash("0x09"), common.HexToHash("0x08"), nil, h, nil) it, _ := snaps.AccountIterator(common.HexToHash("0x09"), common.Hash{}) head := snaps.Snapshot(common.HexToHash("0x09")) @@ -452,14 +452,14 @@ func TestStorageIteratorTraversalValues(t *testing.T) { } } // Assemble a stack of snapshots from the account layers - snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, randomAccountSet("0xaa"), wrapStorage(a)) - snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, randomAccountSet("0xaa"), wrapStorage(b)) - snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), nil, randomAccountSet("0xaa"), wrapStorage(c)) - snaps.Update(common.HexToHash("0x05"), common.HexToHash("0x04"), nil, randomAccountSet("0xaa"), wrapStorage(d)) - snaps.Update(common.HexToHash("0x06"), common.HexToHash("0x05"), nil, randomAccountSet("0xaa"), wrapStorage(e)) - snaps.Update(common.HexToHash("0x07"), common.HexToHash("0x06"), nil, randomAccountSet("0xaa"), wrapStorage(e)) - snaps.Update(common.HexToHash("0x08"), common.HexToHash("0x07"), nil, randomAccountSet("0xaa"), wrapStorage(g)) - snaps.Update(common.HexToHash("0x09"), common.HexToHash("0x08"), nil, randomAccountSet("0xaa"), wrapStorage(h)) + snaps.update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, randomAccountSet("0xaa"), wrapStorage(a)) + snaps.update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, randomAccountSet("0xaa"), wrapStorage(b)) + snaps.update(common.HexToHash("0x04"), common.HexToHash("0x03"), nil, randomAccountSet("0xaa"), wrapStorage(c)) + snaps.update(common.HexToHash("0x05"), common.HexToHash("0x04"), nil, randomAccountSet("0xaa"), wrapStorage(d)) + snaps.update(common.HexToHash("0x06"), common.HexToHash("0x05"), nil, randomAccountSet("0xaa"), wrapStorage(e)) + snaps.update(common.HexToHash("0x07"), common.HexToHash("0x06"), nil, randomAccountSet("0xaa"), wrapStorage(e)) + snaps.update(common.HexToHash("0x08"), common.HexToHash("0x07"), nil, randomAccountSet("0xaa"), wrapStorage(g)) + snaps.update(common.HexToHash("0x09"), common.HexToHash("0x08"), nil, randomAccountSet("0xaa"), wrapStorage(h)) it, _ := snaps.StorageIterator(common.HexToHash("0x09"), common.HexToHash("0xaa"), common.Hash{}) head := snaps.Snapshot(common.HexToHash("0x09")) @@ -522,7 +522,7 @@ func TestAccountIteratorLargeTraversal(t *testing.T) { }, } for i := 1; i < 128; i++ { - snaps.Update(common.HexToHash(fmt.Sprintf("0x%02x", i+1)), common.HexToHash(fmt.Sprintf("0x%02x", i)), nil, makeAccounts(200), nil) + snaps.update(common.HexToHash(fmt.Sprintf("0x%02x", i+1)), common.HexToHash(fmt.Sprintf("0x%02x", i)), nil, makeAccounts(200), nil) } // Iterate the entire stack and ensure everything is hit only once head := snaps.Snapshot(common.HexToHash("0x80")) @@ -566,13 +566,13 @@ func TestAccountIteratorFlattening(t *testing.T) { }, } // Create a stack of diffs on top - snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, + snaps.update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, randomAccountSet("0xaa", "0xee", "0xff", "0xf0"), nil) - snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, + snaps.update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, randomAccountSet("0xbb", "0xdd", "0xf0"), nil) - snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), nil, + snaps.update(common.HexToHash("0x04"), common.HexToHash("0x03"), nil, randomAccountSet("0xcc", "0xf0", "0xff"), nil) // Create an iterator and flatten the data from underneath it @@ -597,13 +597,13 @@ func TestAccountIteratorSeek(t *testing.T) { base.root: base, }, } - snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, + snaps.update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, randomAccountSet("0xaa", "0xee", "0xff", "0xf0"), nil) - snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, + snaps.update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, randomAccountSet("0xbb", "0xdd", "0xf0"), nil) - snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), nil, + snaps.update(common.HexToHash("0x04"), common.HexToHash("0x03"), nil, randomAccountSet("0xcc", "0xf0", "0xff"), nil) // Account set is now @@ -661,13 +661,13 @@ func TestStorageIteratorSeek(t *testing.T) { }, } // Stack three diff layers on top with various overlaps - snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, + snaps.update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, randomAccountSet("0xaa"), randomStorageSet([]string{"0xaa"}, [][]string{{"0x01", "0x03", "0x05"}}, nil)) - snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, + snaps.update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, randomAccountSet("0xaa"), randomStorageSet([]string{"0xaa"}, [][]string{{"0x02", "0x05", "0x06"}}, nil)) - snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), nil, + snaps.update(common.HexToHash("0x04"), common.HexToHash("0x03"), nil, randomAccountSet("0xaa"), randomStorageSet([]string{"0xaa"}, [][]string{{"0x01", "0x05", "0x08"}}, nil)) // Account set is now @@ -724,17 +724,17 @@ func TestAccountIteratorDeletions(t *testing.T) { }, } // Stack three diff layers on top with various overlaps - snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), + snaps.update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, randomAccountSet("0x11", "0x22", "0x33"), nil) deleted := common.HexToHash("0x22") destructed := map[common.Hash]struct{}{ deleted: {}, } - snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), + snaps.update(common.HexToHash("0x03"), common.HexToHash("0x02"), destructed, randomAccountSet("0x11", "0x33"), nil) - snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), + snaps.update(common.HexToHash("0x04"), common.HexToHash("0x03"), nil, randomAccountSet("0x33", "0x44", "0x55"), nil) // The output should be 11,33,44,55 @@ -770,10 +770,10 @@ func TestStorageIteratorDeletions(t *testing.T) { }, } // Stack three diff layers on top with various overlaps - snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, + snaps.update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, randomAccountSet("0xaa"), randomStorageSet([]string{"0xaa"}, [][]string{{"0x01", "0x03", "0x05"}}, nil)) - snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, + snaps.update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, randomAccountSet("0xaa"), randomStorageSet([]string{"0xaa"}, [][]string{{"0x02", "0x04", "0x06"}}, [][]string{{"0x01", "0x03"}})) // The output should be 02,04,05,06 @@ -790,14 +790,14 @@ func TestStorageIteratorDeletions(t *testing.T) { destructed := map[common.Hash]struct{}{ common.HexToHash("0xaa"): {}, } - snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), destructed, nil, nil) + snaps.update(common.HexToHash("0x04"), common.HexToHash("0x03"), destructed, nil, nil) it, _ = snaps.StorageIterator(common.HexToHash("0x04"), common.HexToHash("0xaa"), common.Hash{}) verifyIterator(t, 0, it, verifyStorage) it.Release() // Re-insert the slots of the same account - snaps.Update(common.HexToHash("0x05"), common.HexToHash("0x04"), nil, + snaps.update(common.HexToHash("0x05"), common.HexToHash("0x04"), nil, randomAccountSet("0xaa"), randomStorageSet([]string{"0xaa"}, [][]string{{"0x07", "0x08", "0x09"}}, nil)) // The output should be 07,08,09 @@ -806,7 +806,7 @@ func TestStorageIteratorDeletions(t *testing.T) { it.Release() // Destruct the whole storage but re-create the account in the same layer - snaps.Update(common.HexToHash("0x06"), common.HexToHash("0x05"), destructed, randomAccountSet("0xaa"), randomStorageSet([]string{"0xaa"}, [][]string{{"0x11", "0x12"}}, nil)) + snaps.update(common.HexToHash("0x06"), common.HexToHash("0x05"), destructed, randomAccountSet("0xaa"), randomStorageSet([]string{"0xaa"}, [][]string{{"0x11", "0x12"}}, nil)) it, _ = snaps.StorageIterator(common.HexToHash("0x06"), common.HexToHash("0xaa"), common.Hash{}) verifyIterator(t, 2, it, verifyStorage) // The output should be 11,12 it.Release() @@ -848,7 +848,7 @@ func BenchmarkAccountIteratorTraversal(b *testing.B) { }, } for i := 1; i <= 100; i++ { - snaps.Update(common.HexToHash(fmt.Sprintf("0x%02x", i+1)), common.HexToHash(fmt.Sprintf("0x%02x", i)), nil, makeAccounts(200), nil) + snaps.update(common.HexToHash(fmt.Sprintf("0x%02x", i+1)), common.HexToHash(fmt.Sprintf("0x%02x", i)), nil, makeAccounts(200), nil) } // We call this once before the benchmark, so the creation of // sorted accountlists are not included in the results. @@ -943,9 +943,9 @@ func BenchmarkAccountIteratorLargeBaselayer(b *testing.B) { base.root: base, }, } - snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, makeAccounts(2000), nil) + snaps.update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, makeAccounts(2000), nil) for i := 2; i <= 100; i++ { - snaps.Update(common.HexToHash(fmt.Sprintf("0x%02x", i+1)), common.HexToHash(fmt.Sprintf("0x%02x", i)), nil, makeAccounts(20), nil) + snaps.update(common.HexToHash(fmt.Sprintf("0x%02x", i+1)), common.HexToHash(fmt.Sprintf("0x%02x", i)), nil, makeAccounts(20), nil) } // We call this once before the benchmark, so the creation of // sorted accountlists are not included in the results. diff --git a/core/state/snapshot/snapshot.go b/core/state/snapshot/snapshot.go index 4bbb126251..46d1b06def 100644 --- a/core/state/snapshot/snapshot.go +++ b/core/state/snapshot/snapshot.go @@ -60,7 +60,6 @@ var ( snapshotDirtyStorageWriteMeter = metrics.NewRegisteredMeter("state/snapshot/dirty/storage/write", nil) snapshotDirtyAccountHitDepthHist = metrics.NewRegisteredHistogram("state/snapshot/dirty/account/hit/depth", nil, metrics.NewExpDecaySample(1028, 0.015)) - snapshotDirtyStorageHitDepthHist = metrics.NewRegisteredHistogram("state/snapshot/dirty/storage/hit/depth", nil, metrics.NewExpDecaySample(1028, 0.015)) snapshotFlushAccountItemMeter = metrics.NewRegisteredMeter("state/snapshot/flush/account/item", nil) snapshotFlushAccountSizeMeter = metrics.NewRegisteredMeter("state/snapshot/flush/account/size", nil) @@ -323,9 +322,14 @@ func (t *Tree) Snapshots(root common.Hash, limits int, nodisk bool) []Snapshot { return ret } +func (t *Tree) Update(blockRoot common.Hash, parentRoot common.Hash, destructs map[common.Address]struct{}, accounts map[common.Address][]byte, storage map[common.Address]map[string][]byte) error { + hashDestructs, hashAccounts, hashStorage := transformSnapData(destructs, accounts, storage) + return t.update(blockRoot, parentRoot, hashDestructs, hashAccounts, hashStorage) +} + // Update adds a new snapshot into the tree, if that can be linked to an existing // old parent. It is disallowed to insert a disk layer (the origin of all). -func (t *Tree) Update(blockRoot common.Hash, parentRoot common.Hash, destructs map[common.Address]struct{}, accounts map[common.Address][]byte, storage map[common.Address]map[string][]byte) error { +func (t *Tree) update(blockRoot common.Hash, parentRoot common.Hash, destructs map[common.Hash]struct{}, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) error { // Reject noop updates to avoid self-loops in the snapshot tree. This is a // special case that can only happen for Clique networks where empty blocks // don't modify the state (0 block subsidy). @@ -340,9 +344,7 @@ func (t *Tree) Update(blockRoot common.Hash, parentRoot common.Hash, destructs m if parent == nil { return fmt.Errorf("parent [%#x] snapshot missing", parentRoot) } - - hashDestructs, hashAccounts, hashStorage := transformSnapData(destructs, accounts, storage) - snap := parent.(snapshot).Update(blockRoot, hashDestructs, hashAccounts, hashStorage) + snap := parent.(snapshot).Update(blockRoot, destructs, accounts, storage) // Save the new snapshot for later t.lock.Lock() diff --git a/core/state/snapshot/snapshot_test.go b/core/state/snapshot/snapshot_test.go index 4b787cfe2e..f8ced63665 100644 --- a/core/state/snapshot/snapshot_test.go +++ b/core/state/snapshot/snapshot_test.go @@ -105,7 +105,7 @@ func TestDiskLayerExternalInvalidationFullFlatten(t *testing.T) { accounts := map[common.Hash][]byte{ common.HexToHash("0xa1"): randomAccount(), } - if err := snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, accounts, nil); err != nil { + if err := snaps.update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, accounts, nil); err != nil { t.Fatalf("failed to create a diff layer: %v", err) } if n := len(snaps.layers); n != 2 { @@ -149,10 +149,10 @@ func TestDiskLayerExternalInvalidationPartialFlatten(t *testing.T) { accounts := map[common.Hash][]byte{ common.HexToHash("0xa1"): randomAccount(), } - if err := snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, accounts, nil); err != nil { + if err := snaps.update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, accounts, nil); err != nil { t.Fatalf("failed to create a diff layer: %v", err) } - if err := snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, accounts, nil); err != nil { + if err := snaps.update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, accounts, nil); err != nil { t.Fatalf("failed to create a diff layer: %v", err) } if n := len(snaps.layers); n != 3 { @@ -197,13 +197,13 @@ func TestDiffLayerExternalInvalidationPartialFlatten(t *testing.T) { accounts := map[common.Hash][]byte{ common.HexToHash("0xa1"): randomAccount(), } - if err := snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, accounts, nil); err != nil { + if err := snaps.update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, accounts, nil); err != nil { t.Fatalf("failed to create a diff layer: %v", err) } - if err := snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, accounts, nil); err != nil { + if err := snaps.update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, accounts, nil); err != nil { t.Fatalf("failed to create a diff layer: %v", err) } - if err := snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), nil, accounts, nil); err != nil { + if err := snaps.update(common.HexToHash("0x04"), common.HexToHash("0x03"), nil, accounts, nil); err != nil { t.Fatalf("failed to create a diff layer: %v", err) } if n := len(snaps.layers); n != 4 { @@ -257,12 +257,12 @@ func TestPostCapBasicDataAccess(t *testing.T) { }, } // The lowest difflayer - snaps.Update(common.HexToHash("0xa1"), common.HexToHash("0x01"), nil, setAccount("0xa1"), nil) - snaps.Update(common.HexToHash("0xa2"), common.HexToHash("0xa1"), nil, setAccount("0xa2"), nil) - snaps.Update(common.HexToHash("0xb2"), common.HexToHash("0xa1"), nil, setAccount("0xb2"), nil) + snaps.update(common.HexToHash("0xa1"), common.HexToHash("0x01"), nil, setAccount("0xa1"), nil) + snaps.update(common.HexToHash("0xa2"), common.HexToHash("0xa1"), nil, setAccount("0xa2"), nil) + snaps.update(common.HexToHash("0xb2"), common.HexToHash("0xa1"), nil, setAccount("0xb2"), nil) - snaps.Update(common.HexToHash("0xa3"), common.HexToHash("0xa2"), nil, setAccount("0xa3"), nil) - snaps.Update(common.HexToHash("0xb3"), common.HexToHash("0xb2"), nil, setAccount("0xb3"), nil) + snaps.update(common.HexToHash("0xa3"), common.HexToHash("0xa2"), nil, setAccount("0xa3"), nil) + snaps.update(common.HexToHash("0xb3"), common.HexToHash("0xb2"), nil, setAccount("0xb3"), nil) // checkExist verifies if an account exiss in a snapshot checkExist := func(layer *diffLayer, key string) error { @@ -357,7 +357,7 @@ func TestSnaphots(t *testing.T) { ) for i := 0; i < 129; i++ { head = makeRoot(uint64(i + 2)) - snaps.Update(head, last, nil, setAccount(fmt.Sprintf("%d", i+2)), nil) + snaps.update(head, last, nil, setAccount(fmt.Sprintf("%d", i+2)), nil) last = head snaps.Cap(head, 128) // 130 layers (128 diffs + 1 accumulator + 1 disk) } diff --git a/core/state/statedb.go b/core/state/statedb.go index b55171e324..1b55382779 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -577,10 +577,8 @@ func (s *StateDB) TryPreload(block *types.Block, signer types.Signer) { } for i := 0; i < runtime.NumCPU(); i++ { objs := <-objsChan - if objs != nil { - for _, obj := range objs { - s.SetStateObject(obj) - } + for _, obj := range objs { + s.SetStateObject(obj) } } } @@ -1038,7 +1036,7 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { if s.trie == nil { tr, err := s.db.OpenTrie(s.originalRoot) if err != nil { - panic(fmt.Sprintf("Failed to open trie tree")) + panic("Failed to open trie tree") } s.trie = tr } @@ -1139,7 +1137,6 @@ func (s *StateDB) LightCommit(root common.Hash) (common.Hash, *types.DiffLayer, } s.db.CacheStorage(crypto.Keccak256Hash(tmpAccount[:]), root, tmpDiff) taskResults <- nil - return } tasksNum++ } @@ -1231,7 +1228,6 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, *types.DiffLayer taskResults := make(chan error, len(s.stateObjectsDirty)) tasksNum := 0 finishCh := make(chan struct{}) - defer close(finishCh) threads := len(s.stateObjectsDirty) / minNumberOfAccountPerTask if threads > runtime.NumCPU() { @@ -1239,8 +1235,9 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, *types.DiffLayer } else if threads == 0 { threads = 1 } - + wg := sync.WaitGroup{} for i := 0; i < threads; i++ { + wg.Add(1) go func() { codeWriter := s.db.TrieDB().DiskDB().NewBatch() for { @@ -1253,6 +1250,7 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, *types.DiffLayer log.Crit("Failed to commit dirty codes", "error", err) } } + wg.Done() return } } @@ -1293,9 +1291,11 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, *types.DiffLayer for i := 0; i < tasksNum; i++ { err := <-taskResults if err != nil { + close(finishCh) return err } } + close(finishCh) if len(s.stateObjectsDirty) > 0 { s.stateObjectsDirty = make(map[common.Address]struct{}, len(s.stateObjectsDirty)/2) @@ -1326,6 +1326,7 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, *types.DiffLayer if root != emptyRoot { s.db.CacheAccount(root, s.trie) } + wg.Wait() return nil }, func() error { diff --git a/core/state/sync_test.go b/core/state/sync_test.go index a13fcf56a3..24cae59004 100644 --- a/core/state/sync_test.go +++ b/core/state/sync_test.go @@ -69,7 +69,7 @@ func makeTestState() (Database, common.Hash, []*testAccount) { state.updateStateObject(obj) accounts = append(accounts, acc) } - root, _ := state.Commit(false) + root, _, _ := state.Commit(false) // Return the generated state return db, root, accounts diff --git a/core/state_prefetcher.go b/core/state_prefetcher.go index 05394321f7..dacd8df404 100644 --- a/core/state_prefetcher.go +++ b/core/state_prefetcher.go @@ -35,15 +35,6 @@ type statePrefetcher struct { engine consensus.Engine // Consensus engine used for block rewards } -// newStatePrefetcher initialises a new statePrefetcher. -func newStatePrefetcher(config *params.ChainConfig, bc *BlockChain, engine consensus.Engine) *statePrefetcher { - return &statePrefetcher{ - config: config, - bc: bc, - engine: engine, - } -} - // Prefetch processes the state changes according to the Ethereum rules by running // the transaction messages using the statedb, but any changes are discarded. The // only goal is to pre-cache transaction signatures and state trie nodes. diff --git a/core/state_processor.go b/core/state_processor.go index dde81664f9..0677a50dce 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -166,7 +166,7 @@ func (p *LightStateProcessor) LightProcess(diffLayer *types.DiffLayer, block *ty } errChan := make(chan error, threads) - exitChan := make(chan struct{}, 0) + exitChan := make(chan struct{}) var snapMux sync.RWMutex var stateMux, diffMux sync.Mutex for i := 0; i < threads; i++ { @@ -317,7 +317,6 @@ func (p *LightStateProcessor) LightProcess(diffLayer *types.DiffLayer, block *ty } } errChan <- nil - return }(start, end) } diff --git a/core/vm/contracts_lightclient_test.go b/core/vm/contracts_lightclient_test.go index cf17c03559..e1634be809 100644 --- a/core/vm/contracts_lightclient_test.go +++ b/core/vm/contracts_lightclient_test.go @@ -10,7 +10,7 @@ import ( ) const ( - testHeight uint64 = 66848226 + testHeight uint64 = 66848226 ) func TestTmHeaderValidateAndMerkleProofValidate(t *testing.T) { diff --git a/core/vm/lightclient/types.go b/core/vm/lightclient/types.go index 406d428e74..6006fe04d4 100644 --- a/core/vm/lightclient/types.go +++ b/core/vm/lightclient/types.go @@ -103,7 +103,7 @@ func (cs ConsensusState) EncodeConsensusState() ([]byte, error) { copy(encodingBytes[pos:pos+chainIDLength], cs.ChainID) pos += chainIDLength - binary.BigEndian.PutUint64(encodingBytes[pos:pos+heightLength], uint64(cs.Height)) + binary.BigEndian.PutUint64(encodingBytes[pos:pos+heightLength], cs.Height) pos += heightLength copy(encodingBytes[pos:pos+appHashLength], cs.AppHash) diff --git a/eth/backend.go b/eth/backend.go index f77147f9d9..bdae4b4235 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -203,6 +203,9 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { if config.DiffSync { bcOps = append(bcOps, core.EnableLightProcessor) } + if config.PersistDiff { + bcOps = append(bcOps, core.EnablePersistDiff(config.DiffBlock)) + } eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, chainConfig, eth.engine, vmConfig, eth.shouldPreserve, &config.TxLookupLimit, bcOps...) if err != nil { return nil, err diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index bded76d493..bf143ba02c 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -80,6 +80,7 @@ var Defaults = Config{ TrieTimeout: 60 * time.Minute, TriesInMemory: 128, SnapshotCache: 102, + DiffBlock: uint64(864000), Miner: miner.Config{ GasFloor: 8000000, GasCeil: 8000000, @@ -163,6 +164,7 @@ type Config struct { DatabaseFreezer string DatabaseDiff string PersistDiff bool + DiffBlock uint64 TrieCleanCache int TrieCleanCacheJournal string `toml:",omitempty"` // Disk journal directory for trie cache to survive node restarts diff --git a/eth/ethconfig/gen_config.go b/eth/ethconfig/gen_config.go index 3cc9759787..258ade2293 100644 --- a/eth/ethconfig/gen_config.go +++ b/eth/ethconfig/gen_config.go @@ -50,6 +50,7 @@ func (c Config) MarshalTOML() (interface{}, error) { SnapshotCache int Preimages bool PersistDiff bool + DiffBlock uint64 `toml:",omitempty"` Miner miner.Config Ethash ethash.Config TxPool core.TxPoolConfig @@ -96,6 +97,7 @@ func (c Config) MarshalTOML() (interface{}, error) { enc.SnapshotCache = c.SnapshotCache enc.Preimages = c.Preimages enc.PersistDiff = c.PersistDiff + enc.DiffBlock = c.DiffBlock enc.Miner = c.Miner enc.Ethash = c.Ethash enc.TxPool = c.TxPool @@ -139,6 +141,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { DatabaseFreezer *string DatabaseDiff *string PersistDiff *bool + DiffBlock *uint64 `toml:",omitempty"` TrieCleanCache *int TrieCleanCacheJournal *string `toml:",omitempty"` TrieCleanCacheRejournal *time.Duration `toml:",omitempty"` @@ -236,6 +239,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.PersistDiff != nil { c.PersistDiff = *dec.PersistDiff } + if dec.DiffBlock != nil { + c.DiffBlock = *dec.DiffBlock + } if dec.TrieCleanCache != nil { c.TrieCleanCache = *dec.TrieCleanCache } diff --git a/eth/handler.go b/eth/handler.go index 0fd0e5d49f..41b459d2d5 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -478,7 +478,7 @@ func (h *handler) BroadcastBlock(block *types.Block, propagate bool) { // Send the block to a subset of our peers var transfer []*ethPeer if h.directBroadcast { - transfer = peers[:int(len(peers))] + transfer = peers[:] } else { transfer = peers[:int(math.Sqrt(float64(len(peers))))] } diff --git a/eth/handler_diff.go b/eth/handler_diff.go index 97474f5f18..c996105f0f 100644 --- a/eth/handler_diff.go +++ b/eth/handler_diff.go @@ -41,10 +41,8 @@ func (h *diffHandler) RunPeer(peer *diff.Peer, hand diff.Handler) error { // PeerInfo retrieves all known `diff` information about a peer. func (h *diffHandler) PeerInfo(id enode.ID) interface{} { - if p := h.peers.peer(id.String()); p != nil { - if p.diffExt != nil { - return p.diffExt.info() - } + if p := h.peers.peer(id.String()); p != nil && p.diffExt != nil { + return p.diffExt.info() } return nil } @@ -64,7 +62,6 @@ func (h *diffHandler) Handle(peer *diff.Peer, packet diff.Packet) error { default: return fmt.Errorf("unexpected diff packet type: %T", packet) } - return nil } func (h *diffHandler) handleDiffLayerPackage(packet *diff.DiffLayersPacket, pid string, fulfilled bool) error { diff --git a/eth/protocols/diff/handler.go b/eth/protocols/diff/handler.go index d07035fe9b..8678ff7f65 100644 --- a/eth/protocols/diff/handler.go +++ b/eth/protocols/diff/handler.go @@ -143,9 +143,8 @@ func handleMessage(backend Backend, peer *Peer) error { } if fulfilled := requestTracker.Fulfil(peer.id, peer.version, FullDiffLayerMsg, res.RequestId); fulfilled { return backend.Handle(peer, res) - } else { - return fmt.Errorf("%w: %v", errUnexpectedMsg, msg.Code) } + return fmt.Errorf("%w: %v", errUnexpectedMsg, msg.Code) default: return fmt.Errorf("%w: %v", errInvalidMsgCode, msg.Code) } diff --git a/eth/protocols/diff/protocol.go b/eth/protocols/diff/protocol.go index f1b2ce750b..4467d0b327 100644 --- a/eth/protocols/diff/protocol.go +++ b/eth/protocols/diff/protocol.go @@ -61,7 +61,6 @@ var ( errDecode = errors.New("invalid message") errInvalidMsgCode = errors.New("invalid message code") errUnexpectedMsg = errors.New("unexpected message code") - errBadRequest = errors.New("bad request") errNoCapMsg = errors.New("miss cap message during handshake") ) diff --git a/eth/protocols/diff/protocol_test.go b/eth/protocols/diff/protocol_test.go index 05657eca7f..eda96adf1b 100644 --- a/eth/protocols/diff/protocol_test.go +++ b/eth/protocols/diff/protocol_test.go @@ -116,8 +116,8 @@ func TestDiffMessages(t *testing.T) { want []byte }{ { - DiffCapPacket{true}, - common.FromHex("c101"), + DiffCapPacket{true, defaultExtra}, + common.FromHex("c20100"), }, { GetDiffLayersPacket{1111, []common.Hash{common.HexToHash("0xaece2dbf80a726206cf4df299afa09f9d8f3dcd85ff39bb4b3f0402a3a6af2f5")}}, diff --git a/ethclient/ethclient_test.go b/ethclient/ethclient_test.go index 9fa5bf87a4..341acf978f 100644 --- a/ethclient/ethclient_test.go +++ b/ethclient/ethclient_test.go @@ -17,7 +17,6 @@ package ethclient import ( - "bytes" "context" "errors" "fmt" @@ -262,9 +261,7 @@ func TestEthClient(t *testing.T) { "TestCallContract": { func(t *testing.T) { testCallContract(t, client) }, }, - "TestAtFunctions": { - func(t *testing.T) { testAtFunctions(t, client) }, - }, + // DO not have TestAtFunctions now, because we do not have pending block now } t.Parallel() @@ -490,69 +487,6 @@ func testCallContract(t *testing.T, client *rpc.Client) { } } -func testAtFunctions(t *testing.T, client *rpc.Client) { - ec := NewClient(client) - // send a transaction for some interesting pending status - sendTransaction(ec) - time.Sleep(100 * time.Millisecond) - // Check pending transaction count - pending, err := ec.PendingTransactionCount(context.Background()) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if pending != 1 { - t.Fatalf("unexpected pending, wanted 1 got: %v", pending) - } - // Query balance - balance, err := ec.BalanceAt(context.Background(), testAddr, nil) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - penBalance, err := ec.PendingBalanceAt(context.Background(), testAddr) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if balance.Cmp(penBalance) == 0 { - t.Fatalf("unexpected balance: %v %v", balance, penBalance) - } - // NonceAt - nonce, err := ec.NonceAt(context.Background(), testAddr, nil) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - penNonce, err := ec.PendingNonceAt(context.Background(), testAddr) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if penNonce != nonce+1 { - t.Fatalf("unexpected nonce: %v %v", nonce, penNonce) - } - // StorageAt - storage, err := ec.StorageAt(context.Background(), testAddr, common.Hash{}, nil) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - penStorage, err := ec.PendingStorageAt(context.Background(), testAddr, common.Hash{}) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if !bytes.Equal(storage, penStorage) { - t.Fatalf("unexpected storage: %v %v", storage, penStorage) - } - // CodeAt - code, err := ec.CodeAt(context.Background(), testAddr, nil) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - penCode, err := ec.PendingCodeAt(context.Background(), testAddr) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if !bytes.Equal(code, penCode) { - t.Fatalf("unexpected code: %v %v", code, penCode) - } -} - func sendTransaction(ec *Client) error { // Retrieve chainID chainID, err := ec.ChainID(context.Background()) @@ -560,7 +494,7 @@ func sendTransaction(ec *Client) error { return err } // Create transaction - tx := types.NewTransaction(0, common.Address{1}, big.NewInt(1), 22000, big.NewInt(1), nil) + tx := types.NewTransaction(0, common.Address{1}, big.NewInt(1), 23000, big.NewInt(100000), nil) signer := types.LatestSignerForChainID(chainID) signature, err := crypto.Sign(signer.Hash(tx).Bytes(), testKey) if err != nil { diff --git a/les/peer.go b/les/peer.go index 5cdd557a90..e09b3bc130 100644 --- a/les/peer.go +++ b/les/peer.go @@ -1054,7 +1054,7 @@ func (p *clientPeer) Handshake(td *big.Int, head common.Hash, headNum uint64, ge // If local ethereum node is running in archive mode, advertise ourselves we have // all version state data. Otherwise only recent state is available. - stateRecent := uint64(server.handler.blockchain.TriesInMemory() - blockSafetyMargin) + stateRecent := server.handler.blockchain.TriesInMemory() - blockSafetyMargin if server.archiveMode { stateRecent = 0 } diff --git a/light/trie.go b/light/trie.go index e189634e1c..3896b73c4d 100644 --- a/light/trie.go +++ b/light/trie.go @@ -95,17 +95,11 @@ func (db *odrDatabase) TrieDB() *trie.Database { return nil } -func (db *odrDatabase) CacheAccount(_ common.Hash, _ state.Trie) { - return -} +func (db *odrDatabase) CacheAccount(_ common.Hash, _ state.Trie) {} -func (db *odrDatabase) CacheStorage(_ common.Hash, _ common.Hash, _ state.Trie) { - return -} +func (db *odrDatabase) CacheStorage(_ common.Hash, _ common.Hash, _ state.Trie) {} -func (db *odrDatabase) Purge() { - return -} +func (db *odrDatabase) Purge() {} type odrTrie struct { db *odrDatabase diff --git a/miner/worker.go b/miner/worker.go index 984af7baaf..ef7a8c5630 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -1011,16 +1011,6 @@ func (w *worker) commit(uncles []*types.Header, interval func(), update bool, st return nil } -// copyReceipts makes a deep copy of the given receipts. -func copyReceipts(receipts []*types.Receipt) []*types.Receipt { - result := make([]*types.Receipt, len(receipts)) - for i, l := range receipts { - cpy := *l - result[i] = &cpy - } - return result -} - // postSideBlock fires a side chain event, only use it for testing. func (w *worker) postSideBlock(event core.ChainSideEvent) { select { @@ -1028,12 +1018,3 @@ func (w *worker) postSideBlock(event core.ChainSideEvent) { case <-w.exitCh: } } - -// totalFees computes total consumed fees in ETH. Block transactions and receipts have to have the same order. -func totalFees(block *types.Block, receipts []*types.Receipt) *big.Float { - feesWei := new(big.Int) - for i, tx := range block.Transactions() { - feesWei.Add(feesWei, new(big.Int).Mul(new(big.Int).SetUint64(receipts[i].GasUsed), tx.GasPrice())) - } - return new(big.Float).Quo(new(big.Float).SetInt(feesWei), new(big.Float).SetInt(big.NewInt(params.Ether))) -} diff --git a/node/node.go b/node/node.go index 3a3331bcaa..f1564bea23 100644 --- a/node/node.go +++ b/node/node.go @@ -639,16 +639,16 @@ func (n *Node) OpenDiffDatabase(name string, handles int, diff, namespace string var err error if n.config.DataDir == "" { panic("datadir is missing") - } else { - root := n.ResolvePath(name) - switch { - case diff == "": - diff = filepath.Join(root, "diff") - case !filepath.IsAbs(diff): - diff = n.ResolvePath(diff) - } - db, err = leveldb.New(diff, 0, handles, namespace, readonly) } + root := n.ResolvePath(name) + switch { + case diff == "": + diff = filepath.Join(root, "diff") + case !filepath.IsAbs(diff): + diff = n.ResolvePath(diff) + } + db, err = leveldb.New(diff, 0, handles, namespace, readonly) + return db, err } diff --git a/rlp/typecache.go b/rlp/typecache.go index 62553d3b55..c21025ad26 100644 --- a/rlp/typecache.go +++ b/rlp/typecache.go @@ -172,16 +172,6 @@ func structFields(typ reflect.Type) (fields []field, err error) { return fields, nil } -// anyOptionalFields returns the index of the first field with "optional" tag. -func firstOptionalField(fields []field) int { - for i, f := range fields { - if f.optional { - return i - } - } - return len(fields) -} - type structFieldError struct { typ reflect.Type field int From 85e0fd4c7d066a234d6fd5e48d891a5e53506ebc Mon Sep 17 00:00:00 2001 From: guagualvcha <296179868@qq.com> Date: Mon, 27 Sep 2021 11:21:10 +0800 Subject: [PATCH 19/22] resolve comments --- core/blockchain.go | 57 ++++++++++++++++++------------------ core/state/statedb.go | 2 +- core/state_processor.go | 18 ++++++------ eth/downloader/downloader.go | 24 ++++++++------- 4 files changed, 51 insertions(+), 50 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 78ee92dced..3cfa21654f 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -2486,21 +2486,20 @@ func (bc *BlockChain) GetUnTrustedDiffLayer(blockHash common.Hash, pid string) * for _, diff := range diffs { return diff } - } else { - // pick the one from exact same peer if we know where the block comes from - if pid != "" { - if diffHashes, exist := bc.diffPeersToDiffHashes[pid]; exist { - for diff := range diffs { - if _, overlap := diffHashes[diff]; overlap { - return bc.blockHashToDiffLayers[blockHash][diff] - } + } + // pick the one from exact same peer if we know where the block comes from + if pid != "" { + if diffHashes, exist := bc.diffPeersToDiffHashes[pid]; exist { + for diff := range diffs { + if _, overlap := diffHashes[diff]; overlap { + return bc.blockHashToDiffLayers[blockHash][diff] } } } - // Do not find overlap, do random pick - for _, diff := range diffs { - return diff - } + } + // Do not find overlap, do random pick + for _, diff := range diffs { + return diff } } return nil @@ -2562,14 +2561,17 @@ func (bc *BlockChain) RemoveDiffPeer(pid string) { } func (bc *BlockChain) untrustedDiffLayerPruneLoop() { - recheck := time.Tick(diffLayerPruneRecheckInterval) + recheck := time.NewTicker(diffLayerPruneRecheckInterval) bc.wg.Add(1) - defer bc.wg.Done() + defer func() { + bc.wg.Done() + recheck.Stop() + }() for { select { case <-bc.quit: return - case <-recheck: + case <-recheck.C: bc.pruneDiffLayer() } } @@ -2588,17 +2590,16 @@ func (bc *BlockChain) pruneDiffLayer() { }) staleBlockHashes := make(map[common.Hash]struct{}) for _, number := range sortNumbers { - if number < currentHeight-maxDiffForkDist { - affectedHashes := bc.diffNumToBlockHashes[number] - if affectedHashes != nil { - for affectedHash := range affectedHashes { - staleBlockHashes[affectedHash] = struct{}{} - } - delete(bc.diffNumToBlockHashes, number) - } - } else { + if number >= currentHeight-maxDiffForkDist { break } + affectedHashes := bc.diffNumToBlockHashes[number] + if affectedHashes != nil { + for affectedHash := range affectedHashes { + staleBlockHashes[affectedHash] = struct{}{} + } + delete(bc.diffNumToBlockHashes, number) + } } staleDiffHashes := make(map[common.Hash]struct{}) for blockHash := range staleBlockHashes { @@ -2637,11 +2638,9 @@ func (bc *BlockChain) HandleDiffLayer(diffLayer *types.DiffLayer, pid string, fu bc.diffMux.Lock() defer bc.diffMux.Unlock() - if !fulfilled { - if len(bc.diffPeersToDiffHashes[pid]) > maxDiffLimitForBroadcast { - log.Error("too many accumulated diffLayers", "pid", pid) - return nil - } + if !fulfilled && len(bc.diffPeersToDiffHashes[pid]) > maxDiffLimitForBroadcast { + log.Error("too many accumulated diffLayers", "pid", pid) + return nil } if len(bc.diffPeersToDiffHashes[pid]) > maxDiffLimit { diff --git a/core/state/statedb.go b/core/state/statedb.go index 1b55382779..0eabf59dce 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -1239,6 +1239,7 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, *types.DiffLayer for i := 0; i < threads; i++ { wg.Add(1) go func() { + defer wg.Done() codeWriter := s.db.TrieDB().DiskDB().NewBatch() for { select { @@ -1250,7 +1251,6 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, *types.DiffLayer log.Crit("Failed to commit dirty codes", "error", err) } } - wg.Done() return } } diff --git a/core/state_processor.go b/core/state_processor.go index 0677a50dce..9ef90409f7 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -269,18 +269,18 @@ func (p *LightStateProcessor) LightProcess(diffLayer *types.DiffLayer, block *ty storageChange, exist := snapStorage[diffAccount] snapMux.RUnlock() - if exist { - for k, v := range storageChange { - if len(v) != 0 { - accountTrie.TryUpdate([]byte(k), v) - } else { - accountTrie.TryDelete([]byte(k)) - } - } - } else { + if !exist { errChan <- errors.New("missing storage change in difflayer") return } + for k, v := range storageChange { + if len(v) != 0 { + accountTrie.TryUpdate([]byte(k), v) + } else { + accountTrie.TryDelete([]byte(k)) + } + } + // check storage root accountRootHash := accountTrie.Hash() if latestRoot != accountRootHash { diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index a1cc1068ae..55be200d59 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -236,18 +236,20 @@ func EnableDiffFetchOp(peers IPeerSet) DownloadOption { if len(args) < 2 { return } - if mode, ok := args[0].(SyncMode); ok { - if mode == FullSync { - if peerID, ok := args[1].(string); ok { - if ep := peers.GetDiffPeer(peerID); ep != nil { - hashes := make([]common.Hash, 0, len(headers)) - for _, header := range headers { - hashes = append(hashes, header.Hash()) - } - ep.RequestDiffLayers(hashes) - } - } + peerID, ok := args[1].(string) + if !ok { + return + } + mode, ok := args[0].(SyncMode) + if !ok { + return + } + if ep := peers.GetDiffPeer(peerID); mode == FullSync && ep != nil { + hashes := make([]common.Hash, 0, len(headers)) + for _, header := range headers { + hashes = append(hashes, header.Hash()) } + ep.RequestDiffLayers(hashes) } } dl.bodyFetchHook = hook From 0bbbcc572f0d488fd88387d611c14f3011606cb8 Mon Sep 17 00:00:00 2001 From: guagualvcha <296179868@qq.com> Date: Mon, 27 Sep 2021 11:47:53 +0800 Subject: [PATCH 20/22] resolve comments --- core/state_processor.go | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/core/state_processor.go b/core/state_processor.go index 9ef90409f7..45e2fcccd0 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -116,19 +116,18 @@ func (p *LightStateProcessor) Process(block *types.Block, statedb *state.StateDB if err == nil { log.Info("do light process success at block", "num", block.NumberU64()) return statedb, receipts, logs, gasUsed, nil - } else { - log.Error("do light process err at block", "num", block.NumberU64(), "err", err) - p.bc.removeDiffLayers(diffLayer.DiffHash) - // prepare new statedb - statedb.StopPrefetcher() - parent := p.bc.GetHeader(block.ParentHash(), block.NumberU64()-1) - statedb, err = state.New(parent.Root, p.bc.stateCache, p.bc.snaps) - if err != nil { - return statedb, nil, nil, 0, err - } - // Enable prefetching to pull in trie node paths while processing transactions - statedb.StartPrefetcher("chain") } + log.Error("do light process err at block", "num", block.NumberU64(), "err", err) + p.bc.removeDiffLayers(diffLayer.DiffHash) + // prepare new statedb + statedb.StopPrefetcher() + parent := p.bc.GetHeader(block.ParentHash(), block.NumberU64()-1) + statedb, err = state.New(parent.Root, p.bc.stateCache, p.bc.snaps) + if err != nil { + return statedb, nil, nil, 0, err + } + // Enable prefetching to pull in trie node paths while processing transactions + statedb.StartPrefetcher("chain") } } // fallback to full process From 0011461de0bdaebc1ee807b484dbca5235ef77da Mon Sep 17 00:00:00 2001 From: guagualvcha <296179868@qq.com> Date: Mon, 27 Sep 2021 16:55:46 +0800 Subject: [PATCH 21/22] resolve comment --- common/gopool/pool.go | 14 +++++++++++++- core/state/statedb.go | 21 ++++++--------------- core/state_processor.go | 9 ++------- 3 files changed, 21 insertions(+), 23 deletions(-) diff --git a/common/gopool/pool.go b/common/gopool/pool.go index b4fc1c459d..bfcc618e46 100644 --- a/common/gopool/pool.go +++ b/common/gopool/pool.go @@ -1,6 +1,7 @@ package gopool import ( + "runtime" "time" "github.com/panjf2000/ants/v2" @@ -8,7 +9,8 @@ import ( var ( // Init a instance pool when importing ants. - defaultPool, _ = ants.NewPool(ants.DefaultAntsPoolSize, ants.WithExpiryDuration(10*time.Second)) + defaultPool, _ = ants.NewPool(ants.DefaultAntsPoolSize, ants.WithExpiryDuration(10*time.Second)) + minNumberPerTask = 5 ) // Logger is used for logging formatted messages. @@ -46,3 +48,13 @@ func Release() { func Reboot() { defaultPool.Reboot() } + +func Threads(tasks int) int { + threads := tasks / minNumberPerTask + if threads > runtime.NumCPU() { + threads = runtime.NumCPU() + } else if threads == 0 { + threads = 1 + } + return threads +} diff --git a/core/state/statedb.go b/core/state/statedb.go index 0eabf59dce..c68e09490c 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -27,6 +27,7 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/gopool" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state/snapshot" "github.com/ethereum/go-ethereum/core/types" @@ -39,9 +40,8 @@ import ( ) const ( - preLoadLimit = 128 - defaultNumOfSlots = 100 - minNumberOfAccountPerTask = 5 + preLoadLimit = 128 + defaultNumOfSlots = 100 ) type revision struct { @@ -1107,12 +1107,8 @@ func (s *StateDB) LightCommit(root common.Hash) (common.Hash, *types.DiffLayer, tasksNum := 0 finishCh := make(chan struct{}) defer close(finishCh) - threads := len(s.diffTries) / minNumberOfAccountPerTask - if threads > runtime.NumCPU() { - threads = runtime.NumCPU() - } else if threads == 0 { - threads = 1 - } + threads := gopool.Threads(len(s.diffTries)) + for i := 0; i < threads; i++ { go func() { for { @@ -1229,12 +1225,7 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, *types.DiffLayer tasksNum := 0 finishCh := make(chan struct{}) - threads := len(s.stateObjectsDirty) / minNumberOfAccountPerTask - if threads > runtime.NumCPU() { - threads = runtime.NumCPU() - } else if threads == 0 { - threads = 1 - } + threads := gopool.Threads(len(s.stateObjectsDirty)) wg := sync.WaitGroup{} for i := 0; i < threads; i++ { wg.Add(1) diff --git a/core/state_processor.go b/core/state_processor.go index 45e2fcccd0..973fd27b54 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -22,11 +22,11 @@ import ( "fmt" "math/big" "math/rand" - "runtime" "sync" "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/gopool" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/misc" "github.com/ethereum/go-ethereum/core/rawdb" @@ -152,12 +152,7 @@ func (p *LightStateProcessor) LightProcess(diffLayer *types.DiffLayer, block *ty for des := range snapDestructs { statedb.Trie().TryDelete(des[:]) } - threads := len(snapAccounts) / minNumberOfAccountPerTask - if threads > runtime.NumCPU() { - threads = runtime.NumCPU() - } else if threads == 0 { - threads = 1 - } + threads := gopool.Threads(len(snapAccounts)) iteAccounts := make([]common.Address, 0, len(snapAccounts)) for diffAccount := range snapAccounts { From 844f2abd76bc5acea0709f76c9414587f68e6747 Mon Sep 17 00:00:00 2001 From: guagualvcha <296179868@qq.com> Date: Mon, 27 Sep 2021 17:29:29 +0800 Subject: [PATCH 22/22] fix mistake --- cmd/faucet/faucet.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go index fe6d44b4a0..500a1e920f 100644 --- a/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -148,7 +148,7 @@ func main() { amountStr := big.NewFloat(0).Quo(big.NewFloat(0).SetInt(n), big.NewFloat(0).SetInt64(params.Ether)).String() bep2eInfos[s] = bep2eInfo{ - Contract: common.HexToAddreparlia.goss(contracts[idx]), + Contract: common.HexToAddress(contracts[idx]), Amount: *n, AmountStr: amountStr, }