Skip to content

Commit

Permalink
generate diff layer by replaying block
Browse files Browse the repository at this point in the history
  • Loading branch information
keefel committed Jan 26, 2022
1 parent 66dd9ea commit 6306002
Show file tree
Hide file tree
Showing 3 changed files with 252 additions and 0 deletions.
60 changes: 60 additions & 0 deletions core/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
117 changes: 117 additions & 0 deletions core/blockchain_diff_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
}
}
}
75 changes: 75 additions & 0 deletions core/state/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit 6306002

Please sign in to comment.