From 6306002822f69e806207d3e3165bfd60df71720d Mon Sep 17 00:00:00 2001 From: Keefe-Liu Date: Fri, 21 Jan 2022 18:17:06 +0800 Subject: [PATCH] generate diff layer by replaying block --- core/blockchain.go | 60 ++++++++++++++++++ core/blockchain_diff_test.go | 117 +++++++++++++++++++++++++++++++++++ core/state/statedb.go | 75 ++++++++++++++++++++++ 3 files changed, 252 insertions(+) diff --git a/core/blockchain.go b/core/blockchain.go index ad9b7db8a7..20c47294bb 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -3105,6 +3105,66 @@ func (bc *BlockChain) GetTrustedDiffLayer(blockHash common.Hash) *types.DiffLaye return diff } +// GenerateDiffLayer generates DiffLayer of a specified block by replaying the block's transactions. +// If the block is an empty block, no DiffLayer will be generated. +// The generated DiffLayer whose Receipts are empty, whose DiffAccounts' storage root is empty. +func (bc *BlockChain) GenerateDiffLayer(blockHash common.Hash) (*types.DiffLayer, error) { + if bc.snaps == nil { + return nil, fmt.Errorf("snapshot disabled, can't generate difflayer") + } + + block := bc.GetBlockByHash(blockHash) + if block == nil { + return nil, fmt.Errorf("block not found, block number: %d, blockhash: %v", block.NumberU64(), blockHash) + } + + parent := bc.GetBlockByHash(block.ParentHash()) + if parent == nil { + return nil, fmt.Errorf("block not found, block number: %d, blockhash: %v", block.NumberU64()-1, block.ParentHash()) + } + statedb, err := bc.StateAt(parent.Root()) + if err != nil { + return nil, fmt.Errorf("state not found for block number (%d): %v", parent.NumberU64(), err) + } + + // Empty block, no DiffLayer would be generated. + if block.Header().TxHash == types.EmptyRootHash { + return nil, nil + } + + // Replay transactions. + signer := types.MakeSigner(bc.Config(), block.Number()) + for _, tx := range block.Transactions() { + msg, _ := tx.AsMessage(signer) + txContext := NewEVMTxContext(msg) + context := NewEVMBlockContext(block.Header(), bc, nil) + vmenv := vm.NewEVM(context, txContext, statedb, bc.Config(), vm.Config{}) + + if posa, ok := bc.Engine().(consensus.PoSA); ok { + if isSystem, _ := posa.IsSystemTransaction(tx, block.Header()); isSystem { + balance := statedb.GetBalance(consensus.SystemAddress) + if balance.Cmp(common.Big0) > 0 { + statedb.SetBalance(consensus.SystemAddress, big.NewInt(0)) + statedb.AddBalance(block.Header().Coinbase, balance) + } + } + } + + if _, err := ApplyMessage(vmenv, msg, new(GasPool).AddGas(tx.Gas())); err != nil { + return nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) + } + statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number())) + } + + diffLayer := statedb.GenerateDiffLayer() + if diffLayer != nil { + diffLayer.BlockHash = blockHash + diffLayer.Number = block.NumberU64() + } + + return diffLayer, nil +} + func GetTrustedDiffHash(d *types.DiffLayer) (common.Hash, error) { diff := &types.ExtDiffLayer{ BlockHash: d.BlockHash, diff --git a/core/blockchain_diff_test.go b/core/blockchain_diff_test.go index c213eb157f..df85141a8c 100644 --- a/core/blockchain_diff_test.go +++ b/core/blockchain_diff_test.go @@ -27,6 +27,8 @@ import ( "testing" "time" + "github.com/ethereum/go-ethereum/consensus/clique" + "golang.org/x/crypto/sha3" "github.com/ethereum/go-ethereum/common" @@ -644,3 +646,118 @@ func TestGetRootByDiffHash(t *testing.T) { testGetRootByDiffHash(t, chain1, chain2, 24, types.StatusBlockNewer) testGetRootByDiffHash(t, chain1, chain2, 35, types.StatusBlockTooNew) } + +func newBlockChainWithCliqueEngine(blocks int) *BlockChain { + signer := types.HomesteadSigner{} + db := rawdb.NewMemoryDatabase() + engine := clique.New(params.AllCliqueProtocolChanges.Clique, db) + genspec := &Genesis{ + //Config: params.TestChainConfig, + ExtraData: make([]byte, 32+common.AddressLength+65), + Alloc: GenesisAlloc{testAddr: {Balance: big.NewInt(100000000000000000)}}, + } + copy(genspec.ExtraData[32:], testAddr[:]) + genesis := genspec.MustCommit(db) + + chain, _ := NewBlockChain(db, nil, params.AllCliqueProtocolChanges, engine, 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) + block.SetDifficulty(big.NewInt(2)) + + for idx, testBlock := range testBlocks { + // Specific block setting, the index in this generator has 1 diff from specified blockNr. + if i+1 == testBlock.blockNr { + for _, testTransaction := range testBlock.txs { + var transaction *types.Transaction + if testTransaction.to == nil { + transaction = types.NewContractCreation(block.TxNonce(testAddr), + testTransaction.value, uint64(commonGas), nil, testTransaction.data) + } else { + transaction = types.NewTransaction(block.TxNonce(testAddr), *testTransaction.to, + testTransaction.value, uint64(commonGas), nil, testTransaction.data) + } + tx, err := types.SignTx(transaction, signer, testKey) + if err != nil { + panic(err) + } + block.AddTxWithChain(chain, tx) + } + break + } + + // Default block setting. + if idx == len(testBlocks)-1 { + // 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. + for _, testTransaction := range testBlocks[0].txs { + tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddr), *testTransaction.to, + testTransaction.value, uint64(commonGas), nil, testTransaction.data), signer, testKey) + if err != nil { + panic(err) + } + block.AddTxWithChain(chain, tx) + } + } + } + + } + bs, _ := GenerateChain(params.AllCliqueProtocolChanges, genesis, engine, db, blocks, generator) + for i, block := range bs { + header := block.Header() + if i > 0 { + header.ParentHash = bs[i-1].Hash() + } + header.Extra = make([]byte, 32+65) + header.Difficulty = big.NewInt(2) + + sig, _ := crypto.Sign(clique.SealHash(header).Bytes(), testKey) + copy(header.Extra[len(header.Extra)-65:], sig) + bs[i] = block.WithSeal(header) + } + + if _, err := chain.InsertChain(bs); err != nil { + panic(err) + } + + return chain +} + +func TestGenerateDiffLayer(t *testing.T) { + blockNum := 32 + chain := newBlockChainWithCliqueEngine(blockNum) + defer chain.Stop() + + for blockNr := 1; blockNr <= blockNum; blockNr++ { + block := chain.GetBlockByNumber(uint64(blockNr)) + if block == nil { + t.Fatal("block should not be nil") + } + + expDiffLayer := chain.GetTrustedDiffLayer(block.Hash()) + if expDiffLayer == nil { + // Skip empty block. + if blockNr == 15 { + continue + } + t.Fatalf("unexpected nil diff layer, block number: %v, block hash: %v", blockNr, block.Hash()) + } + expDiffHash, err := GetTrustedDiffHash(expDiffLayer) + if err != nil { + t.Fatalf("compute diff hash failed: %v", err) + } + + diffLayer, err := chain.GenerateDiffLayer(block.Hash()) + if err != nil || diffLayer == nil { + t.Fatalf("generate diff layer failed: %v", err) + } + diffHash, err := GetTrustedDiffHash(diffLayer) + if err != nil { + t.Fatalf("compute diff hash failed: %v", err) + } + if expDiffHash != diffHash { + t.Fatalf("generated wrong diff layer for block: %d, expected hash: %v, real hash: %v", blockNr, expDiffHash, diffHash) + } + } +} diff --git a/core/state/statedb.go b/core/state/statedb.go index 64d06a721a..f05de2ceb1 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -1401,6 +1401,81 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, *types.DiffLayer return root, diffLayer, nil } +// GenerateDiffLayer generates block's DiffLayer after executing the block's txs. +// Attention, the DiffLayer returned include no Receipts, whose accounts' storage root +// is empty, whose BlockHash and Number field is empty, should further process by caller. +func (s *StateDB) GenerateDiffLayer() *types.DiffLayer { + if s.snap == nil { + return nil + } + + for addr := range s.stateObjectsPending { + if obj := s.stateObjects[addr]; !obj.deleted { + // The snapshot storage map for the object + var storage map[string][]byte + obj.finalise(false) + for key, value := range obj.pendingStorage { + // Skip noop changes, persist actual changes + if value == obj.originStorage[key] { + continue + } + obj.originStorage[key] = value + + var v []byte + if (value != common.Hash{}) { + // Encoding []byte cannot fail, ok to ignore the error. + v, _ = rlp.EncodeToBytes(common.TrimLeftZeroes(value[:])) + } + + obj.db.snapMux.Lock() + if storage == nil { + // Retrieve the old storage map, if available, create a new one otherwise + if storage = obj.db.snapStorage[obj.address]; storage == nil { + storage = make(map[string][]byte) + obj.db.snapStorage[obj.address] = storage + } + } + storage[string(key[:])] = v // v will be nil if value is 0x00 + obj.db.snapMux.Unlock() + } + + if !obj.deleted { + s.snapMux.Lock() + // The storage root hasn't been intermediate, pass empty storage root here. + s.snapAccounts[obj.address] = snapshot.SlimAccountRLP(obj.data.Nonce, obj.data.Balance, common.Hash{}, obj.data.CodeHash) + s.snapMux.Unlock() + } + } + } + + var diffLayer = &types.DiffLayer{} + 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, + }) + } + } + } + + diffLayer.Destructs, diffLayer.Accounts, diffLayer.Storages = s.SnapToDiffLayer() + sort.SliceStable(diffLayer.Codes, func(i, j int) bool { + return diffLayer.Codes[i].Hash.Hex() < diffLayer.Codes[j].Hash.Hex() + }) + sort.SliceStable(diffLayer.Destructs, func(i, j int) bool { + return diffLayer.Destructs[i].Hex() < (diffLayer.Destructs[j].Hex()) + }) + sort.SliceStable(diffLayer.Accounts, func(i, j int) bool { + return diffLayer.Accounts[i].Account.Hex() < diffLayer.Accounts[j].Account.Hex() + }) + sort.SliceStable(diffLayer.Storages, func(i, j int) bool { + return diffLayer.Storages[i].Account.Hex() < diffLayer.Storages[j].Account.Hex() + }) + return diffLayer +} + 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)