diff --git a/core/blockchain.go b/core/blockchain.go index e959d081cd..01c65fdedf 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1852,8 +1852,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) } else { status, err = bc.writeBlockAndSetHead(block, receipts, logs, statedb, false) } - statedb.Witness.Block = block - state.DumpWitnessToFile(statedb.Witness) + state.DumpBlockWithWitnessToFile(statedb.GetWitness(), block) followupInterrupt.Store(true) if err != nil { diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 435accbe25..03885472e7 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -17,7 +17,6 @@ package core import ( - "crypto/sha256" "errors" "fmt" "math/big" @@ -4718,89 +4717,6 @@ func TestEIP3651(t *testing.T) { } } -func hashAccount(addr common.Address) (output common.Hash) { - hasher := sha256.New() - hasher.Write(addr[:]) - res := hasher.Sum(nil) - copy(output[:], res[:]) - return output -} - -/* -func debugPrintTrie(db ethdb.Database, account common.Address) { - var bc BlockChain - latestBlock := bc.GetBlockByNumber(1) - storageRoot := bc.StateAt(latestBlock.Root()).GetStorageRoot(account) - - tr, err := trie.New(trie.StorageTrieID(latestBlock.Root(), hashAccount(account), storageRoot), s.Database()) - if err != nil { - panic(err) - } - trIterator -} -*/ - -func TestWitnessStorageClear(t *testing.T) { - var ( - engine = ethash.NewFaker() - - // A sender who makes transactions, has some funds - key, _ = crypto.HexToECDSA("b8b7a0e412606eb858a27a913766f16fd03611fd3223218428c350f2bf083f87") - address = crypto.PubkeyToAddress(key.PublicKey) - funds = big.NewInt(1000000000000000) - bb = common.HexToAddress("0x000000000000000000000000000000000000bbbb") - aaStorage = make(map[common.Hash]common.Hash) // Initial storage in AA - ) - // Populate one slots - aaStorage[common.HexToHash("01")] = common.HexToHash("01") - aaStorage[common.HexToHash("02")] = common.HexToHash("01") - - code := []byte{ - byte(vm.PUSH1), 0x0, // value - byte(vm.PUSH1), 0x2, // key - byte(vm.SSTORE), - } - gspec := &Genesis{ - Config: params.TestChainConfig, - Alloc: GenesisAlloc{ - address: {Balance: funds}, - // The contract increments a slot (sets to blocknumber) - bb: { - Code: code, - Balance: big.NewInt(1), - Storage: aaStorage, - }, - }, - } - var nonce uint64 - _, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) { - b.SetCoinbase(common.Address{1}) - - tx, _ := types.SignTx(types.NewTransaction(nonce, bb, - big.NewInt(0), 500000, b.header.BaseFee, nil), types.HomesteadSigner{}, key) - nonce++ - b.AddTx(tx) - }) - // Import the canonical chain - cache := DefaultCacheConfigWithScheme(rawdb.PathScheme) - cache.SnapshotLimit = 0 // disable snapshot - //cache.SnapshotLimit = 500 // enable snapshot - db := rawdb.NewMemoryDatabase() - chain, err := NewBlockChain(db, cache, gspec, nil, engine, vm.Config{ - Tracer: logger.NewJSONLogger(nil, os.Stdout), - }, nil, nil) - if err != nil { - t.Fatalf("failed to create tester chain: %v", err) - } - defer chain.Stop() - for _, block := range blocks { - fmt.Println("insert block") - if n, err := chain.InsertChain([]*types.Block{block}); err != nil { - t.Fatalf("block %d: failed to insert into chain: %v", n, err) - } - } -} - func TestIncrementSlotAcrossManyBlocks(t *testing.T) { //testDeleteRecreateSlotsAcrossManyBlocks(t, rawdb.HashScheme) testIncrementSlotAcrossManyBlocks(t, rawdb.PathScheme) diff --git a/core/state/state_witness.go b/core/state/state_witness.go index 656ea709ba..80d0f8fc4c 100644 --- a/core/state/state_witness.go +++ b/core/state/state_witness.go @@ -6,120 +6,117 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rlp" - "io" "os" ) type Witness struct { - Block *types.Block - UsedBlockHashes map[uint64]common.Hash - Codes map[common.Hash]Code - Root common.Hash - Lists map[common.Hash]map[string][]byte -} - -type EncodeWitness struct { - block *types.Block + blockHashes map[uint64]common.Hash + codes map[common.Hash]Code root common.Hash - owners []common.Hash - paths [][]string - nodes [][][]byte - blockNums []uint64 - blockHashes []common.Hash - codeHashes []common.Hash - codes []Code + lists map[common.Hash]map[string][]byte } -func (w *Witness) EncodeRLP() []byte { - var e EncodeWitness - e.block = w.Block - for owner, nodeMap := range w.Lists { - e.owners = append(e.owners, owner) - var paths []string - var nodes [][]byte +func (w *Witness) EncodeRLP(b *types.Block) []byte { + buf := new(bytes.Buffer) + eb := rlp.NewEncoderBuffer(buf) + + var owners []common.Hash + var allPaths [][]string + var allNodes [][][]byte + var blockNums []uint64 + var blockHashes []common.Hash + var codes []Code + var codeHashes []common.Hash + + for owner, nodeMap := range w.lists { + owners = append(owners, owner) + var ownerPaths []string + var ownerNodes [][]byte for path, node := range nodeMap { - paths = append(paths, path) - nodes = append(nodes, node) + ownerPaths = append(ownerPaths, path) + ownerNodes = append(ownerNodes, node) } - e.paths = append(e.paths, paths) - e.nodes = append(e.nodes, nodes) + allPaths = append(allPaths, ownerPaths) + allNodes = append(allNodes, ownerNodes) } - for codeHash, code := range w.Codes { - e.codeHashes = append(e.codeHashes, codeHash) - e.codes = append(e.codes, code) + for codeHash, code := range w.codes { + codeHashes = append(codeHashes, codeHash) + codes = append(codes, code) } - for blockNum, blockHash := range w.UsedBlockHashes { - e.blockNums = append(e.blockNums, blockNum) - e.blockHashes = append(e.blockHashes, blockHash) + for blockNum, blockHash := range w.blockHashes { + blockNums = append(blockNums, blockNum) + blockHashes = append(blockHashes, blockHash) } - res := new(bytes.Buffer) - if err := e.encode(res); err != nil { + l := eb.List() + b.EncodeRLP(eb) + if err := rlp.Encode(eb, owners); err != nil { panic(err) } - return res.Bytes() -} - -// generated by rlpgen -func (obj *EncodeWitness) encode(_w io.Writer) error { - w := rlp.NewEncoderBuffer(_w) - _tmp0 := w.List() - obj.block.EncodeRLP(w) - if err := rlp.Encode(w, obj.root); err != nil { + if err := rlp.Encode(eb, allPaths); err != nil { panic(err) } - if err := rlp.Encode(w, obj.owners); err != nil { + if err := rlp.Encode(eb, allNodes); err != nil { panic(err) } - if err := rlp.Encode(w, obj.paths); err != nil { + if err := rlp.Encode(eb, blockNums); err != nil { panic(err) } - if err := rlp.Encode(w, obj.nodes); err != nil { + if err := rlp.Encode(eb, blockHashes); err != nil { panic(err) } - if err := rlp.Encode(w, obj.blockNums); err != nil { + if err := rlp.Encode(eb, codeHashes); err != nil { panic(err) } - if err := rlp.Encode(w, obj.blockHashes); err != nil { + if err := rlp.Encode(eb, codes); err != nil { panic(err) } - if err := rlp.Encode(w, obj.codeHashes); err != nil { - panic(err) - } - if err := rlp.Encode(w, obj.codes); err != nil { - panic(err) - } - w.ListEnd(_tmp0) - return w.Flush() -} - -func newWitness(originalRoot common.Hash) *Witness { - return &Witness{Root: originalRoot, Lists: make(map[common.Hash]map[string][]byte)} + eb.ListEnd(l) + eb.Flush() + return buf.Bytes() } func (w *Witness) addAccessList(owner common.Hash, list map[string][]byte) { if len(list) > 0 { - w.Lists[owner] = list + w.lists[owner] = list } } -func (w *Witness) Dump() { - /* - fmt.Printf("[witness] Root %x\n", w.Root) - for i, list := range w.Lists { - owner := w.Owners[i] - fmt.Printf("[witness] Owner %#x, %d entries: \n", owner, len(list)) - for path, v := range list { - fmt.Printf("[witness] - '%#x': %#x\n", path, v) - } - } - */ +func (w *Witness) AddBlockHash(hash common.Hash, num uint64) { + w.blockHashes[num] = hash } -func DumpWitnessToFile(w *Witness) { - enc := w.EncodeRLP() +// TODO: don't include the code hash in the witness if not necessary +func (w *Witness) AddCode(hash common.Hash, code Code) { + if code, ok := w.codes[hash]; ok && len(code) > 0 { + return + } + w.codes[hash] = code +} + +func (w *Witness) AddCodeHash(hash common.Hash) { + if _, ok := w.codes[hash]; ok { + return + } + w.codes[hash] = []byte{} +} + +func (w Witness) Copy() Witness { + panic("not implemented") +} + +func NewWitness() *Witness { + return &Witness{ + make(map[uint64]common.Hash), + make(map[common.Hash]Code), + common.Hash{}, + make(map[common.Hash]map[string][]byte), + } +} +func DumpBlockWithWitnessToFile(w *Witness, b *types.Block) { + enc := w.EncodeRLP(b) path, err := os.Getwd() if err != nil { panic("shite") @@ -128,7 +125,7 @@ func DumpWitnessToFile(w *Witness) { if err != nil { panic("shite2") } - outputFName := fmt.Sprintf("%d-%x.rlp", w.Block.NumberU64(), w.Block.Hash()) + outputFName := fmt.Sprintf("%d-%x.rlp", b.NumberU64(), b.Hash()) err = os.WriteFile(path+"/block-dump/"+outputFName, enc, 0644) if err != nil { panic("shite 3") diff --git a/core/state/statedb.go b/core/state/statedb.go index 884bf8209c..3d6bcd1369 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -84,9 +84,6 @@ type StateDB struct { stateObjectsDirty map[common.Address]struct{} // State objects modified in the current execution stateObjectsDestruct map[common.Address]*types.StateAccount // State objects destructed in the block along with its previous value - usedBlockHashes map[uint64]common.Hash - codes map[common.Hash]Code - // DB error. // State objects are used by the consensus core and VM which are // unable to deal with database-level errors. Any error that occurs @@ -142,7 +139,7 @@ type StateDB struct { // Testing hooks onCommit func(states *triestate.Set) // Hook invoked when commit is performed - Witness *Witness + witness *Witness } // New creates a new state from a given trie. @@ -171,8 +168,7 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) transientStorage: newTransientStorage(), hasher: crypto.NewKeccakState(), - usedBlockHashes: make(map[uint64]common.Hash), - codes: make(map[common.Hash]Code), + witness: NewWitness(), } if sdb.snaps != nil { sdb.snap = sdb.snaps.Snapshot(root) @@ -180,14 +176,6 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) return sdb, nil } -func (s *StateDB) MarkUsedBlockHash(hash common.Hash, num uint64) { - s.usedBlockHashes[num] = hash -} - -func (s *StateDB) MarkWitnessCode(hash common.Hash, code Code) { - s.codes[hash] = code -} - // StartPrefetcher initializes a new trie prefetcher to pull in nodes from the // state trie concurrently while the state is mutated so that when we reach the // commit phase, most of the needed data is already hot. @@ -332,15 +320,8 @@ func (s *StateDB) TxIndex() int { func (s *StateDB) GetCode(addr common.Address) []byte { stateObject := s.getStateObject(addr) if stateObject != nil { - code := stateObject.Code() - if code != nil { - var codeHash common.Hash - copy(codeHash[:], stateObject.CodeHash()) - s.MarkWitnessCode(codeHash, stateObject.Code()) - } - return code + return stateObject.Code() } - return nil } @@ -738,14 +719,7 @@ func (s *StateDB) Copy() *StateDB { snaps: s.snaps, snap: s.snap, - codes: make(map[common.Hash]Code), - usedBlockHashes: make(map[uint64]common.Hash), - } - for codeHash, code := range s.codes { - state.codes[codeHash] = code - } - for num, bh := range s.usedBlockHashes { - state.usedBlockHashes[num] = bh + witness: NewWitness(), // TODO: deep copy witness } // Copy the dirty states, logs, and preimages for addr := range s.journal.dirties { @@ -1195,11 +1169,9 @@ type BlockProof struct { witness Witness } -/* -func (s *StateDB) GetBlockProof() { - +func (s *StateDB) GetWitness() *Witness { + return s.witness } -*/ // Commit writes the state to the underlying in-memory trie database. // Once the state is committed, tries cached in stateDB (including account @@ -1214,9 +1186,6 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er if s.dbErr != nil { return common.Hash{}, fmt.Errorf("commit aborted due to earlier error: %v", s.dbErr) } - w := newWitness(s.originalRoot) - w.Codes = s.codes - w.UsedBlockHashes = s.usedBlockHashes // Finalize any pending changes and merge everything into the tries s.IntermediateRoot(deleteEmptyObjects) @@ -1247,7 +1216,7 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er } // Write any storage changes in the state object to its storage trie set, accessList, err := obj.commit() - w.addAccessList(obj.addrHash, accessList) + s.witness.addAccessList(obj.addrHash, accessList) if err != nil { return common.Hash{}, err } @@ -1275,7 +1244,7 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er } //root, set, err := s.trie.Commit(true) root, set, accessList, err := s.trie.CommitAndObtainAccessList(true) - w.addAccessList(common.Hash{}, accessList) + s.witness.addAccessList(common.Hash{}, accessList) if err != nil { return common.Hash{}, err } @@ -1353,9 +1322,6 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er s.stateObjectsDirty = make(map[common.Address]struct{}) s.stateObjectsDestruct = make(map[common.Address]*types.StateAccount) - //w.Dump() - s.Witness = w - return root, nil } diff --git a/core/vm/evm.go b/core/vm/evm.go index 088b18aaa4..65084d1ece 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -293,6 +293,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, // Initialise a new contract and set the code that is to be used by the EVM. // The contract is a scoped environment for this execution context only. contract := NewContract(caller, AccountRef(caller.Address()), value, gas) + evm.StateDB.GetWitness().AddCode(evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) ret, err = evm.interpreter.Run(contract, input, false) gas = contract.Gas @@ -337,6 +338,7 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by addrCopy := addr // Initialise a new contract and make initialise the delegate values contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate() + evm.StateDB.GetWitness().AddCode(evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) ret, err = evm.interpreter.Run(contract, input, false) gas = contract.Gas @@ -390,6 +392,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte // Initialise a new contract and set the code that is to be used by the EVM. // The contract is a scoped environment for this execution context only. contract := NewContract(caller, AccountRef(addrCopy), new(big.Int), gas) + evm.StateDB.GetWitness().AddCode(evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) // When an error was returned by the EVM or when setting the creation code // above we revert to the snapshot and consume any gas remaining. Additionally diff --git a/core/vm/instructions.go b/core/vm/instructions.go index c5eb31c759..27d53c073b 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -383,6 +383,7 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) uint64CodeOffset = 0xffffffffffffffff } addr := common.Address(a.Bytes20()) + interpreter.evm.StateDB.GetWitness().AddCode(interpreter.evm.StateDB.GetCodeHash(addr), interpreter.evm.StateDB.GetCode(addr)) codeCopy := getData(interpreter.evm.StateDB.GetCode(addr), uint64CodeOffset, length.Uint64()) scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) @@ -421,6 +422,7 @@ func opExtCodeHash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) if interpreter.evm.StateDB.Empty(address) { slot.Clear() } else { + interpreter.evm.StateDB.GetWitness().AddCodeHash(interpreter.evm.StateDB.GetCodeHash(address)) slot.SetBytes(interpreter.evm.StateDB.GetCodeHash(address).Bytes()) } return nil, nil @@ -450,7 +452,7 @@ func opBlockhash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ( res := interpreter.evm.Context.GetHash(num64).Bytes() var bh common.Hash copy(bh[:], res[:]) - interpreter.evm.StateDB.MarkUsedBlockHash(bh, num64) + interpreter.evm.StateDB.GetWitness().AddBlockHash(bh, num64) num.SetBytes(res[:]) } else { num.Clear() diff --git a/core/vm/interface.go b/core/vm/interface.go index ee735cbc99..8e5ae6e110 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -17,6 +17,7 @@ package vm import ( + "github.com/ethereum/go-ethereum/core/state" "math/big" "github.com/ethereum/go-ethereum/common" @@ -79,7 +80,7 @@ type StateDB interface { AddLog(*types.Log) AddPreimage(common.Hash, []byte) - MarkUsedBlockHash(common.Hash, uint64) + GetWitness() *state.Witness } // CallContext provides a basic interface for the EVM calling conventions. The EVM