From dba3363b52bfd6d1d3f34f1da61ca73d684e2e74 Mon Sep 17 00:00:00 2001 From: ledgerwatch <akhounov@gmail.com> Date: Wed, 27 May 2020 17:24:34 +0100 Subject: [PATCH] Fix RestAPI for tx_retrace and make it work for local boltdb (#584) * Not hash, keep the files * Calculate savings * Fix * Fix * Fix * Fix * RestAPI to support local boltdb * Not error on read-only db * Changes so far * Continue * More * Roll back a bit * Restore newline * something compiles * Fix restapi * Fix block number * Fix reads * Use plain writer * Maps for storage reads and writes * Clean up coersions * Fix accounts/abi/bind * Fix tests * More fixes * more fixes * More fixes * Fixes * Fixed core/state * Fixed eth tests * Move code, fix linter * Fix test * Fix linter * Fix linter * Fix linter, badger_db to support AbstractKV * Increase IdealBatchSize for badger * Fix linter * Fix linter --- accounts/abi/bind/backends/simulated.go | 27 +- accounts/abi/bind/backends/simulated_test.go | 3 +- cmd/evm/runner.go | 5 +- cmd/evm/staterunner.go | 9 +- cmd/geth/chaincmd.go | 5 +- cmd/geth/retesteth.go | 90 +--- cmd/hack/hack.go | 83 +--- cmd/restapi/apis/common.go | 1 + cmd/restapi/apis/remote_reader.go | 158 +++---- cmd/restapi/apis/retrace_tx_api.go | 45 +- cmd/restapi/commands/root.go | 6 +- cmd/restapi/rest/serve_rest.go | 14 +- cmd/rpcdaemon/commands/daemon.go | 4 +- cmd/state/commands/state_snapshot.go | 19 - cmd/state/stateless/check_change_sets.go | 2 +- cmd/state/stateless/deps.go | 2 +- cmd/state/stateless/naked_accouts.go | 2 +- cmd/state/stateless/naked_storage.go | 2 +- cmd/state/stateless/spec_exec.go | 2 +- cmd/state/stateless/state.go | 4 +- cmd/state/stateless/state_snapshot.go | 122 ------ cmd/state/stateless/tokens.go | 6 +- cmd/state/stateless/transaction_stats.go | 2 +- core/blockchain.go | 37 -- core/blockchain_test.go | 24 +- core/chain_makers_test.go | 9 +- core/state/database.go | 45 +- core/state/database_test.go | 44 +- core/state/dump.go | 16 +- core/state/history_test.go | 8 +- core/state/intra_block_state_test.go | 4 +- core/state/readonly.go | 30 +- core/state/state_test.go | 9 +- core/tx_pool.go | 2 - eth/api.go | 10 +- eth/api_backend.go | 10 +- eth/api_test.go | 14 +- eth/api_tracer.go | 14 +- eth/backend.go | 16 +- eth/handler.go | 334 -------------- eth/helper_test.go | 2 +- ethdb/badger_db.go | 6 +- ethdb/bolt_db.go | 408 +----------------- ethdb/history.go | 111 +++++ ethdb/interface.go | 6 - ethdb/kv_abstract.go | 4 +- ethdb/kv_bolt.go | 34 +- ethdb/mutation.go | 18 - ethdb/remote_bolt_db.go | 41 -- ethdb/walk.go | 328 +++++++++++++- miner/worker.go | 5 +- tests/block_test_util.go | 5 +- tests/statedb_chain_test.go | 5 +- .../statedb_insert_chain_transaction_test.go | 70 +-- 54 files changed, 804 insertions(+), 1478 deletions(-) delete mode 100644 cmd/state/commands/state_snapshot.go create mode 100644 ethdb/history.go diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 5270a2758f..443741fb41 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -64,6 +64,7 @@ var ( type SimulatedBackend struct { prependDb ethdb.Database database ethdb.Database // In memory database to store our testing data + kv ethdb.KV // Same as database, but different interface engine consensus.Engine blockchain *core.BlockChain // Ethereum blockchain to handle the consensus @@ -92,10 +93,15 @@ func NewSimulatedBackendWithDatabase(database ethdb.Database, alloc core.Genesis } blockchain.EnableReceipts(true) + var kv ethdb.KV + if hasKV, ok := database.(ethdb.HasAbstractKV); ok { + kv = hasKV.AbstractKV() + } backend := &SimulatedBackend{ prependBlock: genesisBlock, prependDb: database.MemCopy(), database: database, + kv: kv, engine: engine, blockchain: blockchain, config: genesis.Config, @@ -114,14 +120,14 @@ func NewSimulatedBackendWithConfig(alloc core.GenesisAlloc, config *params.Chain engine := ethash.NewFaker() blockchain, err := core.NewBlockChain(database, nil, genesis.Config, engine, vm.Config{}, nil, nil) if err != nil { - panic(fmt.Sprintf("%v", err)) + panic(err) } blockchain.EnableReceipts(true) - backend := &SimulatedBackend{ prependBlock: genesisBlock, prependDb: database.MemCopy(), database: database, + kv: database.AbstractKV(), engine: engine, blockchain: blockchain, config: genesis.Config, @@ -137,6 +143,10 @@ func NewSimulatedBackend(alloc core.GenesisAlloc, gasLimit uint64) *SimulatedBac return NewSimulatedBackendWithDatabase(ethdb.NewMemDatabase(), alloc, gasLimit) } +func (b *SimulatedBackend) KV() ethdb.KV { + return b.kv +} + // Close terminates the underlying blockchain's update loop. func (b *SimulatedBackend) Close() error { b.blockchain.Stop() @@ -185,11 +195,9 @@ func (b *SimulatedBackend) prependingState() *state.IntraBlockState { // stateByBlockNumber retrieves a state by a given blocknumber. func (b *SimulatedBackend) stateByBlockNumber(ctx context.Context, blockNumber *big.Int) (*state.IntraBlockState, error) { if blockNumber == nil || blockNumber.Cmp(b.blockchain.CurrentBlock().Number()) == 0 { - s, _, err := b.blockchain.State() - return s, err + return state.New(state.NewDbState(b.kv, b.blockchain.CurrentBlock().NumberU64())), nil } - state, _, err := b.blockchain.StateAt(uint64(blockNumber.Int64())) - return state, err + return state.New(state.NewDbState(b.kv, uint64(blockNumber.Int64()))), nil } // CodeAt returns the code associated with a certain account in the blockchain. @@ -395,11 +403,8 @@ func (b *SimulatedBackend) CallContract(ctx context.Context, call ethereum.CallM if blockNumber != nil && blockNumber.Cmp(b.blockchain.CurrentBlock().Number()) != 0 { return nil, errBlockNumberUnsupported } - state, _, err := b.blockchain.State() - if err != nil { - return nil, err - } - res, err := b.callContract(ctx, call, b.blockchain.CurrentBlock(), state) + s := state.New(state.NewDbState(b.kv, b.blockchain.CurrentBlock().NumberU64())) + res, err := b.callContract(ctx, call, b.blockchain.CurrentBlock(), s) if err != nil { return nil, err } diff --git a/accounts/abi/bind/backends/simulated_test.go b/accounts/abi/bind/backends/simulated_test.go index 2aa8f8bf69..e669a8ccce 100644 --- a/accounts/abi/bind/backends/simulated_test.go +++ b/accounts/abi/bind/backends/simulated_test.go @@ -32,6 +32,7 @@ import ( "github.com/ledgerwatch/turbo-geth/accounts/abi/bind" "github.com/ledgerwatch/turbo-geth/common" "github.com/ledgerwatch/turbo-geth/core" + "github.com/ledgerwatch/turbo-geth/core/state" "github.com/ledgerwatch/turbo-geth/core/types" "github.com/ledgerwatch/turbo-geth/crypto" "github.com/ledgerwatch/turbo-geth/params" @@ -126,7 +127,7 @@ func TestNewSimulatedBackend(t *testing.T) { t.Errorf("expected sim blockchain config to equal params.AllEthashProtocolChanges, got %v", sim.config) } - statedb, _, _ := sim.blockchain.State() + statedb := state.New(state.NewDbState(sim.KV(), sim.blockchain.CurrentBlock().NumberU64())) bal := statedb.GetBalance(testAddr) if !bal.Eq(expectedBal) { t.Errorf("expected balance for test address not received. expected: %v actual: %v", expectedBal, bal) diff --git a/cmd/evm/runner.go b/cmd/evm/runner.go index dcfd5c1273..5f1dc7c45c 100644 --- a/cmd/evm/runner.go +++ b/cmd/evm/runner.go @@ -120,7 +120,6 @@ func runCmd(ctx *cli.Context) error { tracer vm.Tracer debugLogger *vm.StructLogger statedb *state.IntraBlockState - db ethdb.Database chainConfig *params.ChainConfig sender = common.BytesToAddress([]byte("sender")) receiver = common.BytesToAddress([]byte("receiver")) @@ -134,7 +133,7 @@ func runCmd(ctx *cli.Context) error { } else { debugLogger = vm.NewStructLogger(logconfig) } - db = ethdb.NewMemDatabase() + db := ethdb.NewMemDatabase() if ctx.GlobalString(GenesisFlag.Name) != "" { gen := readGenesis(ctx.GlobalString(GenesisFlag.Name)) genesisConfig = gen @@ -280,7 +279,7 @@ func runCmd(ctx *cli.Context) error { fmt.Println("Could not commit state: ", err) os.Exit(1) } - fmt.Println(string(state.NewDumper(db, 0).DefaultDump())) + fmt.Println(string(state.NewDumper(db.AbstractKV(), 0).DefaultDump())) } if memProfilePath := ctx.GlobalString(MemProfileFlag.Name); memProfilePath != "" { diff --git a/cmd/evm/staterunner.go b/cmd/evm/staterunner.go index 28ccef4242..fb88d4318b 100644 --- a/cmd/evm/staterunner.go +++ b/cmd/evm/staterunner.go @@ -26,6 +26,7 @@ import ( "github.com/ledgerwatch/turbo-geth/core/state" "github.com/ledgerwatch/turbo-geth/core/vm" + "github.com/ledgerwatch/turbo-geth/ethdb" "github.com/ledgerwatch/turbo-geth/log" "github.com/ledgerwatch/turbo-geth/tests" @@ -106,8 +107,12 @@ func stateTestCmd(ctx *cli.Context) error { // Test failed, mark as so and dump any state to aid debugging result.Pass, *result.Error = false, err.Error() if ctx.GlobalBool(DumpFlag.Name) && statedb != nil { - dump := state.NewDumper(tds.Database(), tds.GetBlockNr()).DefaultRawDump() - result.State = &dump + if hasKV, ok := tds.Database().(ethdb.HasAbstractKV); ok { + dump := state.NewDumper(hasKV.AbstractKV(), tds.GetBlockNr()).DefaultRawDump() + result.State = &dump + } else { + fmt.Fprintf(os.Stderr, "database does not implement AbstractKV: %T\n", tds.Database()) + } } } diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index 422e2d0fab..688f875743 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -523,7 +523,10 @@ func dump(ctx *cli.Context) error { excludeCode := ctx.Bool(utils.ExcludeCodeFlag.Name) excludeStorage := ctx.Bool(utils.ExcludeStorageFlag.Name) includeMissing := ctx.Bool(utils.IncludeIncompletesFlag.Name) - fmt.Printf("%s\n", state.NewDumper(chainDb, block.NumberU64()).Dump(excludeCode, excludeStorage, !includeMissing)) + if hasKV, ok := chainDb.(ethdb.HasAbstractKV); ok { + fmt.Printf("%s\n", state.NewDumper(hasKV.AbstractKV(), block.NumberU64()).Dump(excludeCode, excludeStorage, !includeMissing)) + } + fmt.Printf("database %T does not support AbstracKV\n", chainDb) } } return nil diff --git a/cmd/geth/retesteth.go b/cmd/geth/retesteth.go index d92570e562..ab06cb8ab0 100644 --- a/cmd/geth/retesteth.go +++ b/cmd/geth/retesteth.go @@ -109,6 +109,7 @@ type RetestWeb3API interface { type RetestethAPI struct { ethDb ethdb.Database + kv ethdb.KV //db state.Database chainConfig *params.ChainConfig author common.Address @@ -415,6 +416,9 @@ func (api *RetestethAPI) SetChainParams(_ context.Context, chainParams ChainPara api.author = chainParams.Genesis.Author api.extraData = chainParams.Genesis.ExtraData api.ethDb = ethDb + if hasKV, ok := api.ethDb.(ethdb.HasAbstractKV); ok { + api.kv = hasKV.AbstractKV() + } api.engine = engine api.blockchain = blockchain //api.db = state.NewDatabase(api.ethDb) @@ -672,19 +676,9 @@ func (api *RetestethAPI) AccountRange(ctx context.Context, } parentHeader := api.blockchain.GetHeaderByHash(header.ParentHash) - var dbState *state.DbState - var err error - if parentHeader == nil || int(txIndex) >= len(block.Transactions()) { - _, dbState, err = api.blockchain.StateAt(header.Number.Uint64()) - if err != nil { - return AccountRangeResult{}, err - } - } else { - var statedb *state.IntraBlockState - statedb, dbState, err = api.blockchain.StateAt(parentHeader.Number.Uint64()) - if err != nil { - return AccountRangeResult{}, err - } + dbState := state.NewDbState(api.kv, header.Number.Uint64()) + if parentHeader != nil && int(txIndex) < len(block.Transactions()) { + statedb := state.New(dbState) // Recompute transactions up to the target index. signer := types.MakeSigner(api.blockchain.Config(), block.Number()) for idx, tx := range block.Transactions() { @@ -693,13 +687,12 @@ func (api *RetestethAPI) AccountRange(ctx context.Context, context := core.NewEVMContext(msg, block.Header(), api.blockchain, nil) // Not yet the searched for transaction, execute on top of the current state vmenv := vm.NewEVM(context, statedb, api.blockchain.Config(), vm.Config{}) - if _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { + if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { return AccountRangeResult{}, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) } // Ensure any modifications are committed to the state if idx == int(txIndex) { - err = statedb.CommitBlock(ctx, dbState) - if err != nil { + if err := statedb.CommitBlock(ctx, dbState); err != nil { return AccountRangeResult{}, err } break @@ -707,7 +700,7 @@ func (api *RetestethAPI) AccountRange(ctx context.Context, } } - iterator, err1 := dbState.Dumper().IteratorDump(false, false, false, common.BigToHash((*big.Int)(addressHash)).Bytes(), int(maxResults)) + iterator, err1 := state.NewDumper(api.kv, header.Number.Uint64()).IteratorDump(false, false, false, common.BigToHash((*big.Int)(addressHash)).Bytes(), int(maxResults)) if err1 != nil { return AccountRangeResult{}, err1 } @@ -728,19 +721,13 @@ func (api *RetestethAPI) AccountRange(ctx context.Context, func (api *RetestethAPI) GetBalance(_ context.Context, address common.Address, blockNr math.HexOrDecimal64) (*math.HexOrDecimal256, error) { //fmt.Printf("GetBalance %x, block %d\n", address, blockNr) header := api.blockchain.GetHeaderByNumber(uint64(blockNr)) - statedb, _, err := api.blockchain.StateAt(header.Number.Uint64()) - if err != nil { - return nil, err - } + statedb := state.New(state.NewDbState(api.kv, header.Number.Uint64())) return (*math.HexOrDecimal256)(statedb.GetBalance(address).ToBig()), nil } func (api *RetestethAPI) GetCode(_ context.Context, address common.Address, blockNr math.HexOrDecimal64) (hexutil.Bytes, error) { header := api.blockchain.GetHeaderByNumber(uint64(blockNr)) - statedb, _, err := api.blockchain.StateAt(header.Number.Uint64()) - if err != nil { - return nil, err - } + statedb := state.New(state.NewDbState(api.kv, header.Number.Uint64())) return getCode(statedb.GetCode(address)), nil } @@ -762,10 +749,7 @@ func getCode(c hexutil.Bytes) hexutil.Bytes { func (api *RetestethAPI) GetTransactionCount(_ context.Context, address common.Address, blockNr math.HexOrDecimal64) (uint64, error) { header := api.blockchain.GetHeaderByNumber(uint64(blockNr)) - statedb, _, err := api.blockchain.StateAt(header.Number.Uint64()) - if err != nil { - return 0, err - } + statedb := state.New(state.NewDbState(api.kv, header.Number.Uint64())) return statedb.GetNonce(address), nil } @@ -798,58 +782,16 @@ func (api *RetestethAPI) StorageRangeAt(ctx context.Context, parentHeader := api.blockchain.GetHeaderByHash(header.ParentHash) var dbstate *state.DbState - var err error if parentHeader == nil || int(txIndex) >= len(block.Transactions()) { - _, dbstate, err = api.blockchain.StateAt(header.Number.Uint64()) - if err != nil { - return StorageRangeResult{}, err - } + dbstate = state.NewDbState(api.kv, header.Number.Uint64()) } else { - _, _, _, dbstate, err = eth.ComputeTxEnv(ctx, api.blockchain, api.blockchain.Config(), api.blockchain, api.blockchain.ChainDb(), block.Hash(), txIndex) + var err error + _, _, _, dbstate, err = eth.ComputeTxEnv(ctx, api.blockchain, api.blockchain.Config(), api.blockchain, api.kv, block.Hash(), txIndex) if err != nil { return StorageRangeResult{}, err } } - /* - storageTrie := statedb.StorageTrie(address) - it := trie.NewIterator(storageTrie.NodeIterator(common.BigToHash((*big.Int)(begin)).Bytes())) - */ - result := StorageRangeResult{Storage: make(map[common.Hash]SRItem)} - - /* - for i := 0; i < int(maxResults) && it.Next(); i++ { - if preimage := storageTrie.GetKey(it.Key); preimage != nil { - key := (*math.HexOrDecimal256)(big.NewInt(0).SetBytes(preimage)) - v, _, err := rlp.SplitString(it.Value) - if err != nil { - return StorageRangeResult{}, err - } - value := (*math.HexOrDecimal256)(big.NewInt(0).SetBytes(v)) - ks, _ := key.MarshalText() - vs, _ := value.MarshalText() - if len(ks)%2 != 0 { - ks = append(append(append([]byte{}, ks[:2]...), byte('0')), ks[2:]...) - } - if len(vs)%2 != 0 { - vs = append(append(append([]byte{}, vs[:2]...), byte('0')), vs[2:]...) - } - result.Storage[common.BytesToHash(it.Key)] = SRItem{ - Key: string(ks), - Value: string(vs), - } - //fmt.Printf("Key: %s, Value: %s\n", ks, vs) - } else { - //fmt.Printf("Did not find preimage for %x\n", it.Key) - } - } - if it.Next() { - result.Complete = false - } else { - result.Complete = true - } - */ - beginHash := common.BigToHash((*big.Int)(begin)) rangeResults, err := eth.StorageRangeAt(dbstate, address, beginHash.Bytes(), int(maxResults)) diff --git a/cmd/hack/hack.go b/cmd/hack/hack.go index a617f72d4c..3b62352cf9 100644 --- a/cmd/hack/hack.go +++ b/cmd/hack/hack.go @@ -1145,67 +1145,6 @@ func addPreimage(chaindata string, image common.Hash, preimage []byte) { check(err) } -func loadAccount() { - ethDb, err := ethdb.NewBoltDatabase("/home/akhounov/.ethereum/geth/chaindata") - //ethDb, err := ethdb.NewBoltDatabase(node.DefaultDataDir() + "/geth/chaindata") - //ethDb, err := ethdb.NewBoltDatabase("/Volumes/tb4/turbo-geth/geth/chaindata") - check(err) - defer ethDb.Close() - blockNr := uint64(*block) - blockSuffix := dbutils.EncodeTimestamp(blockNr) - accountBytes := common.FromHex(*account) - secKey := crypto.Keccak256(accountBytes) - accountData, err := ethDb.GetAsOf(dbutils.CurrentStateBucket, dbutils.AccountsHistoryBucket, secKey, blockNr+1) - check(err) - fmt.Printf("Account data: %x\n", accountData) - startkey := make([]byte, len(accountBytes)+32) - copy(startkey, accountBytes) - t := trie.New(common.Hash{}) - count := 0 - if err := ethDb.WalkAsOf(dbutils.CurrentStateBucket, dbutils.StorageHistoryBucket, startkey, len(accountBytes)*8, blockNr, func(k, v []byte) (bool, error) { - key := k[len(accountBytes):] - //fmt.Printf("%x: %x\n", key, v) - t.Update(key, v) - count++ - return true, nil - }); err != nil { - panic(err) - } - fmt.Printf("After %d updates, reconstructed storage root: %x\n", count, t.Hash()) - var keys [][]byte - if err := ethDb.Walk(dbutils.StorageHistoryBucket, accountBytes, len(accountBytes)*8, func(k, v []byte) (bool, error) { - if !bytes.HasSuffix(k, blockSuffix) { - return true, nil - } - key := k[:len(k)-len(blockSuffix)] - keys = append(keys, common.CopyBytes(key)) - return true, nil - }); err != nil { - panic(err) - } - fmt.Printf("%d keys updated\n", len(keys)) - for _, k := range keys { - v, err := ethDb.GetAsOf(dbutils.CurrentStateBucket, dbutils.StorageHistoryBucket, k, blockNr+1) - if err != nil { - fmt.Printf("for key %x err %v\n", k, err) - } - vOrig, err := ethDb.GetAsOf(dbutils.CurrentStateBucket, dbutils.StorageHistoryBucket, k, blockNr) - if err != nil { - fmt.Printf("for key %x err %v\n", k, err) - } - key := ([]byte(k))[len(accountBytes):] - if len(v) > 0 { - fmt.Printf("Updated %x: %x from %x\n", key, v, vOrig) - t.Update(key, v) - check(err) - } else { - fmt.Printf("Deleted %x from %x\n", key, vOrig) - t.Delete(key) - } - } - fmt.Printf("Updated storage root: %x\n", t.Hash()) -} - func printBranches(block uint64) { ethDb, err := ethdb.NewBoltDatabase(node.DefaultDataDir() + "/testnet/geth/chaindata") //ethDb, err := ethdb.NewBoltDatabase("/home/akhounov/.ethereum/geth/chaindata") @@ -1994,23 +1933,6 @@ func getModifiedAccounts(chaindata string) { fmt.Printf("Len(addrs)=%d\n", len(addrs)) } -func walkOverStorage(chaindata string) { - db, err := ethdb.NewBoltDatabase(chaindata) - check(err) - var startkey [32 + 8 + 32]byte - h, err := common.HashData(common.FromHex("0x109c4f2ccc82c4d77bde15f306707320294aea3f")) - check(err) - copy(startkey[:], h[:]) - binary.BigEndian.PutUint64(startkey[32:], ^uint64(1)) - fmt.Printf("startkey %x\n", startkey) - err = db.WalkAsOf(dbutils.CurrentStateBucket, dbutils.StorageHistoryBucket, startkey[:], 8*(32+8), 50796, func(k []byte, v []byte) (bool, error) { - fmt.Printf("%x: %x\n", k, v) - return true, nil - }) - check(err) - fmt.Printf("Success\n") -} - func resetState(chaindata string) { db, err := ethdb.NewBoltDatabase(chaindata) check(err) @@ -2049,6 +1971,8 @@ func resetHistoryIndex(chaindata string) { check(err) err = downloader.SaveStageProgress(db, downloader.StorageHistoryIndex, 0) check(err) + err = downloader.SaveStageProgress(db, downloader.HashCheck, 0) + check(err) fmt.Printf("Reset history index done\n") } @@ -2372,9 +2296,6 @@ func main() { if *action == "modiAccounts" { getModifiedAccounts(*chaindata) } - if *action == "storage" { - walkOverStorage(*chaindata) - } if *action == "slice" { dbSlice(*chaindata, common.FromHex(*hash)) } diff --git a/cmd/restapi/apis/common.go b/cmd/restapi/apis/common.go index b7550e4264..c8a414c661 100644 --- a/cmd/restapi/apis/common.go +++ b/cmd/restapi/apis/common.go @@ -10,5 +10,6 @@ var ErrEntityNotFound = errors.New("entity not found") type Env struct { DB ethdb.KV + BoltPath string RemoteDBAddress string } diff --git a/cmd/restapi/apis/remote_reader.go b/cmd/restapi/apis/remote_reader.go index 2eb55efef6..336d45c87c 100644 --- a/cmd/restapi/apis/remote_reader.go +++ b/cmd/restapi/apis/remote_reader.go @@ -2,7 +2,6 @@ package apis import ( "bytes" - "encoding/binary" "math/big" "github.com/ledgerwatch/turbo-geth/common" @@ -21,8 +20,9 @@ import ( // ChangeSetReader is a mock StateWriter that accumulates changes in-memory into ChangeSets. type RemoteReader struct { - accountReads map[common.Address]bool - storageReads map[common.Hash]bool + accountReads map[common.Address]struct{} + storageReads map[common.Address]map[common.Hash]struct{} + codeReads map[common.Address]struct{} blockNr uint64 db ethdb.KV } @@ -80,8 +80,9 @@ func (c *powEngine) Author(header *types.Header) (common.Address, error) { func NewRemoteReader(db ethdb.KV, blockNr uint64) *RemoteReader { return &RemoteReader{ - accountReads: make(map[common.Address]bool), - storageReads: make(map[common.Hash]bool), + accountReads: make(map[common.Address]struct{}), + storageReads: make(map[common.Address]map[common.Hash]struct{}), + codeReads: make(map[common.Address]struct{}), db: db, blockNr: blockNr, } @@ -89,104 +90,80 @@ func NewRemoteReader(db ethdb.KV, blockNr uint64) *RemoteReader { func (r *RemoteReader) GetAccountReads() [][]byte { output := make([][]byte, 0) - for key := range r.accountReads { - output = append(output, key.Bytes()) + for address := range r.accountReads { + output = append(output, address.Bytes()) } return output } func (r *RemoteReader) GetStorageReads() [][]byte { output := make([][]byte, 0) - for key := range r.storageReads { + for address, m := range r.storageReads { + for key := range m { + output = append(output, append(address.Bytes(), key.Bytes()...)) + } + } + return output +} + +func (r *RemoteReader) GetCodeReads() [][]byte { + output := make([][]byte, 0) + for key := range r.codeReads { output = append(output, key.Bytes()) } return output } func (r *RemoteReader) ReadAccountData(address common.Address) (*accounts.Account, error) { - r.accountReads[address] = true - addrHash, _ := common.HashData(address[:]) - key := addrHash[:] - r.accountReads[address] = true - composite, _ := dbutils.CompositeKeySuffix(key, r.blockNr) - acc := accounts.NewAccount() - err := r.db.View(context.Background(), func(tx ethdb.Tx) error { - { - hB := tx.Bucket(dbutils.AccountsHistoryBucket) - hC := hB.Cursor() - hK, hV, err := hC.Seek(composite) - if err != nil { - return err - } - - if hK != nil && bytes.HasPrefix(hK, key) { - err = acc.DecodeForStorage(hV) - if err != nil { - return err - } - return nil - } - } - { - v, err := tx.Bucket(dbutils.CurrentStateBucket).Get(key) - if err != nil { - return err - } - if v == nil { - return nil - } - - root, err := tx.Bucket(dbutils.IntermediateTrieHashBucket).Get(key) - if err != nil { - return err - } - if root == nil { - return nil - } - - err = acc.DecodeForStorage(v) - if err != nil { - return err - } - acc.Root = common.BytesToHash(root) - return nil - } - - return ethdb.ErrKeyNotFound - }) + r.accountReads[address] = struct{}{} + addrHash, err := common.HashData(address[:]) if err != nil { + return nil, err + } + enc, err := ethdb.GetAsOf(r.db, dbutils.CurrentStateBucket, dbutils.AccountsHistoryBucket, addrHash[:], r.blockNr) + if err != nil || enc == nil || len(enc) == 0 { return nil, nil } - + var acc accounts.Account + if err := acc.DecodeForStorage(enc); err != nil { + return nil, err + } return &acc, nil } func (r *RemoteReader) ReadAccountStorage(address common.Address, incarnation uint64, key *common.Hash) ([]byte, error) { - keyHash, _ := common.HashData(key[:]) - addrHash, _ := common.HashData(address[:]) + m, ok := r.storageReads[address] + if !ok { + m = make(map[common.Hash]struct{}) + r.storageReads[address] = m + } + m[*key] = struct{}{} + keyHash, err := common.HashData(key[:]) + if err != nil { + return nil, err + } - compositeKey := dbutils.GenerateCompositeStorageKey(addrHash, incarnation, keyHash) - var val []byte - err := r.db.View(context.Background(), func(tx ethdb.Tx) error { - b := tx.Bucket(dbutils.CurrentStateBucket) - v, err := b.Get(compositeKey) - val = v - return err - }) + addrHash, err := common.HashData(address[:]) if err != nil { return nil, err } - r.storageReads[*key] = true - return val, nil + + compositeKey := dbutils.GenerateCompositeStorageKey(addrHash, incarnation, keyHash) + enc, err := ethdb.GetAsOf(r.db, dbutils.CurrentStateBucket, dbutils.StorageHistoryBucket, compositeKey, r.blockNr) + if err != nil || enc == nil { + return nil, nil + } + return enc, nil } func (r *RemoteReader) ReadAccountCode(address common.Address, codeHash common.Hash) ([]byte, error) { + r.codeReads[address] = struct{}{} if bytes.Equal(codeHash[:], crypto.Keccak256(nil)) { return nil, nil } var val []byte err := r.db.View(context.Background(), func(tx ethdb.Tx) error { - b := tx.Bucket(dbutils.CurrentStateBucket) + b := tx.Bucket(dbutils.CodeBucket) v, err := b.Get(codeHash[:]) val = v return err @@ -198,47 +175,14 @@ func (r *RemoteReader) ReadAccountCode(address common.Address, codeHash common.H } func (r *RemoteReader) ReadAccountCodeSize(address common.Address, codeHash common.Hash) (int, error) { - if bytes.Equal(codeHash[:], crypto.Keccak256(nil)) { - return 0, nil - } - var val []byte - err := r.db.View(context.Background(), func(tx ethdb.Tx) error { - b := tx.Bucket(dbutils.CurrentStateBucket) - v, err := b.Get(codeHash[:]) - val = v - return err - }) + code, err := r.ReadAccountCode(address, codeHash) if err != nil { return 0, err } - return len(val), nil + return len(code), nil } func (r *RemoteReader) ReadAccountIncarnation(address common.Address) (uint64, error) { - addrHash, _ := common.HashData(address[:]) - var incarnationBytes [common.IncarnationLength]byte - startkey := make([]byte, common.HashLength+common.IncarnationLength+common.HashLength) - copy(startkey, addrHash[:]) - found := false - err := r.db.View(context.Background(), func(tx ethdb.Tx) error { - b := tx.Bucket(dbutils.CurrentStateBucket) - c := b.Cursor() - k, _, err := c.Seek(startkey) - if err != nil { - return err - } - if k != nil && bytes.HasPrefix(k, addrHash[:]) { - copy(incarnationBytes[:], k[common.HashLength:]) - found = true - } - return nil - }) - if err != nil { - return 0, err - } - if found { - return (^binary.BigEndian.Uint64(incarnationBytes[:])), nil - } return 0, nil } diff --git a/cmd/restapi/apis/retrace_tx_api.go b/cmd/restapi/apis/retrace_tx_api.go index a4890da71d..7c94187d0c 100644 --- a/cmd/restapi/apis/retrace_tx_api.go +++ b/cmd/restapi/apis/retrace_tx_api.go @@ -35,17 +35,24 @@ func (e *Env) GetWritesReads(c *gin.Context) { c.JSON(http.StatusOK, results) } -type WritesReads struct { +type AccountWritesReads struct { Reads []string `json:"reads"` Writes []string `json:"writes"` } +type StorageWriteReads struct { + Reads map[string][]string + Writes map[string][]string +} type RetraceResponse struct { - Storage WritesReads `json:"storage"` - Account WritesReads `json:"accounts"` + Storage StorageWriteReads `json:"storage"` + Account AccountWritesReads `json:"accounts"` } func Retrace(blockNumber, chain string, remoteDB ethdb.KV) (RetraceResponse, error) { - chainConfig := ReadChainConfig(remoteDB, chain) + chainConfig, err := ReadChainConfig(remoteDB, chain) + if err != nil { + return RetraceResponse{}, err + } noOpWriter := state.NewNoopWriter() bn, err := strconv.Atoi(blockNumber) if err != nil { @@ -56,7 +63,7 @@ func Retrace(blockNumber, chain string, remoteDB ethdb.KV) (RetraceResponse, err if err != nil { return RetraceResponse{}, err } - writer := state.NewChangeSetWriter() + writer := state.NewChangeSetWriterPlain() reader := NewRemoteReader(remoteDB, uint64(bn)) intraBlockState := state.New(reader) @@ -77,11 +84,19 @@ func Retrace(blockNumber, chain string, remoteDB ethdb.KV) (RetraceResponse, err } storageChanges, _ := writer.GetStorageChanges() + output.Storage.Writes = make(map[string][]string) for _, ch := range storageChanges.Changes { - output.Storage.Writes = append(output.Storage.Writes, common.Bytes2Hex(ch.Key)) + addrKey := common.Bytes2Hex(ch.Key[:common.AddressLength]) + l := output.Storage.Writes[addrKey] + l = append(l, common.Bytes2Hex(ch.Key[common.AddressLength+common.IncarnationLength:])) + output.Storage.Writes[addrKey] = l } - for _, ch := range reader.GetStorageReads() { - output.Storage.Reads = append(output.Storage.Reads, common.Bytes2Hex(ch)) + output.Storage.Reads = make(map[string][]string) + for _, key := range reader.GetStorageReads() { + addrKey := common.Bytes2Hex(key[:common.AddressLength]) + l := output.Storage.Reads[addrKey] + l = append(l, common.Bytes2Hex(key[common.AddressLength+common.IncarnationLength:])) + output.Storage.Reads[addrKey] = l } return output, nil } @@ -131,7 +146,7 @@ func GetBlockByNumber(db ethdb.KV, number uint64) (*types.Block, error) { } // ReadChainConfig retrieves the consensus settings based on the given genesis hash. -func ReadChainConfig(db ethdb.KV, chain string) *params.ChainConfig { +func ReadChainConfig(db ethdb.KV, chain string) (*params.ChainConfig, error) { var k []byte var data []byte switch chain { @@ -144,13 +159,17 @@ func ReadChainConfig(db ethdb.KV, chain string) *params.ChainConfig { case "goerli": k = params.GoerliGenesisHash[:] } - _ = db.View(context.Background(), func(tx ethdb.Tx) error { + if err := db.View(context.Background(), func(tx ethdb.Tx) error { b := tx.Bucket(dbutils.ConfigPrefix) d, _ := b.Get(k) data = d return nil - }) + }); err != nil { + return nil, err + } var config params.ChainConfig - _ = json.Unmarshal(data, &config) - return &config + if err := json.Unmarshal(data, &config); err != nil { + return nil, err + } + return &config, nil } diff --git a/cmd/restapi/commands/root.go b/cmd/restapi/commands/root.go index 7003c9f38e..67af2d745c 100644 --- a/cmd/restapi/commands/root.go +++ b/cmd/restapi/commands/root.go @@ -14,11 +14,13 @@ import ( var ( remoteDbAddress string + boltPath string listenAddress string ) func init() { - rootCmd.Flags().StringVar(&remoteDbAddress, "remote-db-addr", "localhost:9999", "address of remote DB listener of a turbo-geth node") + rootCmd.Flags().StringVar(&remoteDbAddress, "remote-db-addr", "", "address of remote DB listener of a turbo-geth node") + rootCmd.Flags().StringVar(&boltPath, "bolt-path", "", "path to the boltdb database") rootCmd.Flags().StringVar(&listenAddress, "rpcaddr", "localhost:8080", "REST server listening interface") } @@ -26,7 +28,7 @@ var rootCmd = &cobra.Command{ Use: "restapi", Short: "restapi exposes read-only blockchain APIs through REST (requires running turbo-geth node)", RunE: func(cmd *cobra.Command, args []string) error { - return rest.ServeREST(cmd.Context(), listenAddress, remoteDbAddress) + return rest.ServeREST(cmd.Context(), listenAddress, remoteDbAddress, boltPath) }, } diff --git a/cmd/restapi/rest/serve_rest.go b/cmd/restapi/rest/serve_rest.go index 05a65fc7c1..71bf1397d8 100644 --- a/cmd/restapi/rest/serve_rest.go +++ b/cmd/restapi/rest/serve_rest.go @@ -2,6 +2,7 @@ package rest import ( "context" + "fmt" "log" "net/http" "time" @@ -19,7 +20,7 @@ func printError(name string, err error) { } } -func ServeREST(ctx context.Context, localAddress, remoteDBAddress string) error { +func ServeREST(ctx context.Context, localAddress, remoteDBAddress string, boltPath string) error { r := gin.Default() root := r.Group("api/v1") allowCORS(root) @@ -30,7 +31,15 @@ func ServeREST(ctx context.Context, localAddress, remoteDBAddress string) error } }) - db, err := ethdb.NewRemote().Path(remoteDBAddress).Open(ctx) + var db ethdb.KV + var err error + if remoteDBAddress != "" { + db, err = ethdb.NewRemote().Path(remoteDBAddress).Open(ctx) + } else if boltPath != "" { + db, err = ethdb.NewBolt().Path(boltPath).ReadOnly().Open(ctx) + } else { + err = fmt.Errorf("either remote db or bolt db must be specified") + } if err != nil { return err } @@ -38,6 +47,7 @@ func ServeREST(ctx context.Context, localAddress, remoteDBAddress string) error e := &apis.Env{ DB: db, RemoteDBAddress: remoteDBAddress, + BoltPath: boltPath, } if err = apis.RegisterRemoteDBAPI(root.Group("remote-db"), e); err != nil { diff --git a/cmd/rpcdaemon/commands/daemon.go b/cmd/rpcdaemon/commands/daemon.go index 818ed43c85..e773a8d135 100644 --- a/cmd/rpcdaemon/commands/daemon.go +++ b/cmd/rpcdaemon/commands/daemon.go @@ -226,7 +226,7 @@ func (api *APIImpl) GetBlockByNumber(ctx context.Context, number rpc.BlockNumber // StorageRangeAt re-implementation of eth/api.go:StorageRangeAt func (api *PrivateDebugAPIImpl) StorageRangeAt(ctx context.Context, blockHash common.Hash, txIndex uint64, contractAddress common.Address, keyStart hexutil.Bytes, maxResult int) (eth.StorageRangeResult, error) { - _, _, _, dbstate, err := eth.ComputeTxEnv(ctx, &blockGetter{api.dbReader}, params.MainnetChainConfig, &chainContext{db: api.dbReader}, api.dbReader, blockHash, txIndex) + _, _, _, dbstate, err := eth.ComputeTxEnv(ctx, &blockGetter{api.dbReader}, params.MainnetChainConfig, &chainContext{db: api.dbReader}, api.db, blockHash, txIndex) if err != nil { return eth.StorageRangeResult{}, err } @@ -241,7 +241,7 @@ func (api *PrivateDebugAPIImpl) StorageRangeAt(ctx context.Context, blockHash co // attempted to be reexecuted to generate the desired state. func (api *PrivateDebugAPIImpl) computeIntraBlockState(block *types.Block) (*state.IntraBlockState, *state.DbState) { // If we have the state fully available, use that - dbstate := state.NewDbState(api.dbReader, block.NumberU64()) + dbstate := state.NewDbState(api.db, block.NumberU64()) statedb := state.New(dbstate) return statedb, dbstate } diff --git a/cmd/state/commands/state_snapshot.go b/cmd/state/commands/state_snapshot.go deleted file mode 100644 index 96a4701d3d..0000000000 --- a/cmd/state/commands/state_snapshot.go +++ /dev/null @@ -1,19 +0,0 @@ -package commands - -import ( - "github.com/ledgerwatch/turbo-geth/cmd/state/stateless" - "github.com/spf13/cobra" -) - -func init() { - withBlock(stateSnapshotCmd) - rootCmd.AddCommand(stateSnapshotCmd) -} - -var stateSnapshotCmd = &cobra.Command{ - Use: "stateSnapshot", - Short: "stateSnapshot", - RunE: func(cmd *cobra.Command, args []string) error { - return stateless.StateSnapshot(block) - }, -} diff --git a/cmd/state/stateless/check_change_sets.go b/cmd/state/stateless/check_change_sets.go index c1866b4cc1..153dde6271 100644 --- a/cmd/state/stateless/check_change_sets.go +++ b/cmd/state/stateless/check_change_sets.go @@ -66,7 +66,7 @@ func CheckChangeSets(genesis *core.Genesis, blockNum uint64, chaindata string, h break } - dbstate := state.NewDbState(historyDb, block.NumberU64()-1) + dbstate := state.NewDbState(historyDb.AbstractKV(), block.NumberU64()-1) intraBlockState := state.New(dbstate) csw := state.NewChangeSetWriter() var blockWriter state.StateWriter diff --git a/cmd/state/stateless/deps.go b/cmd/state/stateless/deps.go index bf21bc0adc..21becb5c73 100644 --- a/cmd/state/stateless/deps.go +++ b/cmd/state/stateless/deps.go @@ -140,7 +140,7 @@ func dataDependencies(blockNum uint64) { if block == nil { break } - dbstate := state.NewDbState(ethDb, block.NumberU64()-1) + dbstate := state.NewDbState(ethDb.AbstractKV(), block.NumberU64()-1) statedb := state.New(dbstate) statedb.SetTracer(dt) signer := types.MakeSigner(chainConfig, block.Number()) diff --git a/cmd/state/stateless/naked_accouts.go b/cmd/state/stateless/naked_accouts.go index 486a3955c4..cf3598672e 100644 --- a/cmd/state/stateless/naked_accouts.go +++ b/cmd/state/stateless/naked_accouts.go @@ -105,7 +105,7 @@ func accountsReadWrites(blockNum uint64) { if block == nil { break } - dbstate := state.NewDbState(ethDb, block.NumberU64()-1) + dbstate := state.NewDbState(ethDb.AbstractKV(), block.NumberU64()-1) statedb := state.New(dbstate) statedb.SetTracer(at) signer := types.MakeSigner(chainConfig, block.Number()) diff --git a/cmd/state/stateless/naked_storage.go b/cmd/state/stateless/naked_storage.go index 8efc5b8068..a910f703b1 100644 --- a/cmd/state/stateless/naked_storage.go +++ b/cmd/state/stateless/naked_storage.go @@ -133,7 +133,7 @@ func storageReadWrites(blockNum uint64) { if block == nil { break } - dbstate := state.NewDbState(ethDb, block.NumberU64()-1) + dbstate := state.NewDbState(ethDb.AbstractKV(), block.NumberU64()-1) statedb := state.New(dbstate) signer := types.MakeSigner(chainConfig, block.Number()) st.loaded = make(map[common.Address]map[common.Hash]struct{}) diff --git a/cmd/state/stateless/spec_exec.go b/cmd/state/stateless/spec_exec.go index fb31de8063..97b923e7fd 100644 --- a/cmd/state/stateless/spec_exec.go +++ b/cmd/state/stateless/spec_exec.go @@ -180,7 +180,7 @@ func speculativeExecution(blockNum uint64) { if block == nil { break } - dbstate := state.NewDbState(ethDb, block.NumberU64()-1) + dbstate := state.NewDbState(ethDb.AbstractKV(), block.NumberU64()-1) // First pass - execute transactions in sequence statedb1 := state.New(dbstate) diff --git a/cmd/state/stateless/state.go b/cmd/state/stateless/state.go index 6d74c5d4d3..4357dd60e4 100644 --- a/cmd/state/stateless/state.go +++ b/cmd/state/stateless/state.go @@ -1391,7 +1391,7 @@ func makeCreators(blockNum uint64) { if block == nil { break } - dbstate := state.NewDbState(ethDb, block.NumberU64()-1) + dbstate := state.NewDbState(ethDb.AbstractKV(), block.NumberU64()-1) statedb := state.New(dbstate) signer := types.MakeSigner(chainConfig, block.Number()) for _, tx := range block.Transactions() { @@ -1977,7 +1977,7 @@ func makeSha3Preimages(blockNum uint64) { if block == nil { break } - dbstate := state.NewDbState(ethDb, block.NumberU64()-1) + dbstate := state.NewDbState(ethDb.AbstractKV(), block.NumberU64()-1) statedb := state.New(dbstate) signer := types.MakeSigner(chainConfig, block.Number()) for _, tx := range block.Transactions() { diff --git a/cmd/state/stateless/state_snapshot.go b/cmd/state/stateless/state_snapshot.go index ea4ed6afa5..b5408e5d28 100644 --- a/cmd/state/stateless/state_snapshot.go +++ b/cmd/state/stateless/state_snapshot.go @@ -4,113 +4,21 @@ import ( "bytes" "encoding/binary" "fmt" - "os" "time" "github.com/ledgerwatch/bolt" "github.com/ledgerwatch/turbo-geth/common" "github.com/ledgerwatch/turbo-geth/common/dbutils" - "github.com/ledgerwatch/turbo-geth/consensus/ethash" - "github.com/ledgerwatch/turbo-geth/core" "github.com/ledgerwatch/turbo-geth/core/rawdb" "github.com/ledgerwatch/turbo-geth/core/types/accounts" - "github.com/ledgerwatch/turbo-geth/core/vm" - "github.com/ledgerwatch/turbo-geth/crypto" "github.com/ledgerwatch/turbo-geth/ethdb" "github.com/ledgerwatch/turbo-geth/migrations" - "github.com/ledgerwatch/turbo-geth/params" "github.com/ledgerwatch/turbo-geth/trie" ) // NOTE: This file is not the part of the Turbo-Geth binary. It i s part of the experimental utility, state // to perform data analysis related to the state growth, state rent, and statelesss clients -func constructSnapshot(ethDb ethdb.Database, blockNum uint64) { - diskDb, err := bolt.Open(fmt.Sprintf("/Volumes/tb4/turbo-geth-copy/state_%d", blockNum), 0600, &bolt.Options{}) - check(err) - defer diskDb.Close() - var startKey [32]byte - txDisk, err := diskDb.Begin(true) - check(err) - bDisk, err := txDisk.CreateBucket(dbutils.CurrentStateBucket, true) - check(err) - count := 0 - err = ethDb.WalkAsOf(dbutils.CurrentStateBucket, dbutils.AccountsHistoryBucket, startKey[:], 0, blockNum+1, - func(key []byte, value []byte) (bool, error) { - if len(key) != 32 { - return false, nil - } - if len(value) == 0 { - return true, nil - } - if err = bDisk.Put(common.CopyBytes(key), common.CopyBytes(value)); err != nil { - return false, err - } - count++ - if count%1000 == 0 { - if err := txDisk.Commit(); err != nil { - return false, err - } - fmt.Printf("Committed %d records\n", count) - var err error - txDisk, err = diskDb.Begin(true) - if err != nil { - return false, err - } - bDisk = txDisk.Bucket(dbutils.CurrentStateBucket) - } - return true, nil - }, - ) - check(err) - err = txDisk.Commit() - check(err) - txDisk, err = diskDb.Begin(true) - check(err) - b := txDisk.Bucket(dbutils.CurrentStateBucket) - count = 0 - var address common.Address - //var hash common.Hash - exist := make(map[common.Address]bool) - var sk [52]byte - err = ethDb.WalkAsOf(dbutils.CurrentStateBucket, dbutils.StorageHistoryBucket, sk[:], 0, blockNum, - func(key []byte, value []byte) (bool, error) { - if len(value) == 0 { - return true, nil - } - copy(address[:], key[:20]) - if e, ok := exist[address]; ok { - if !e { - return true, nil - } - } else { - v, _ := b.Get(crypto.Keccak256(address[:])) - exist[address] = v != nil - } - if err = b.Put(common.CopyBytes(key), common.CopyBytes(value)); err != nil { - return false, err - } - count++ - if count%1000 == 0 { - if err := txDisk.Commit(); err != nil { - return false, err - } - fmt.Printf("Committed %d records\n", count) - var err error - txDisk, err = diskDb.Begin(true) - if err != nil { - return false, err - } - b = txDisk.Bucket(dbutils.CurrentStateBucket) - } - return true, nil - }, - ) - check(err) - err = txDisk.Commit() - check(err) -} - type bucketWriter struct { db ethdb.Database bucket []byte @@ -384,36 +292,6 @@ func checkRoots(stateDb ethdb.Database, rootHash common.Hash, blockNum uint64) { fmt.Printf("Storage trie computation took %v\n", time.Since(startTime)) } -func StateSnapshot(blockNum uint64) error { - startTime := time.Now() - //ethDb, err := ethdb.NewBoltDatabase("/Users/alexeyakhunov/Library/Ethereum/geth/chaindata") - ethDb, err := ethdb.NewBoltDatabase("/Volumes/tb4/turbo-geth-copy/geth/chaindata") - //ethDb, err := ethdb.NewBoltDatabase("/home/akhounov/.ethereum/geth/chaindata1") - check(err) - defer ethDb.Close() - stateDb, db := ethdb.NewMemDatabase2() - defer stateDb.Close() - if _, err := os.Stat("statedb0"); err == nil { - loadSnapshot(stateDb, "statedb0", func(path string) (ethdb.Database, error) { - return ethdb.NewBoltDatabase(path) - }) - if err := loadCodes(db, ethDb); err != nil { - return err - } - } else { - constructSnapshot(ethDb, blockNum) - } - fmt.Printf("Snapshot took %v\n", time.Since(startTime)) - startTime = time.Now() - bc, err := core.NewBlockChain(ethDb, nil, params.MainnetChainConfig, ethash.NewFaker(), vm.Config{}, nil, nil) - check(err) - block := bc.GetBlockByNumber(blockNum) - fmt.Printf("Block number: %d\n", blockNum) - fmt.Printf("Block root hash: %x\n", block.Root()) - checkRoots(ethDb, block.Root(), blockNum) - return nil -} - func VerifySnapshot(path string) { ethDb, err := ethdb.NewBoltDatabase(path) check(err) diff --git a/cmd/state/stateless/tokens.go b/cmd/state/stateless/tokens.go index 3b6b33f4b4..c6684c44f3 100644 --- a/cmd/state/stateless/tokens.go +++ b/cmd/state/stateless/tokens.go @@ -151,7 +151,7 @@ func makeTokens(blockNum uint64) { if block == nil { break } - dbstate := state.NewDbState(ethDb, block.NumberU64()-1) + dbstate := state.NewDbState(ethDb.AbstractKV(), block.NumberU64()-1) statedb := state.New(dbstate) signer := types.MakeSigner(chainConfig, block.Number()) for _, tx := range block.Transactions() { @@ -234,7 +234,7 @@ func makeTokenBalances() { fmt.Printf("Analysing token %x...", token) count := 0 addrCount := 0 - dbstate := state.NewDbState(ethDb, currentBlockNr) + dbstate := state.NewDbState(ethDb.AbstractKV(), currentBlockNr) statedb := state.New(dbstate) msg := types.NewMessage( caller, @@ -454,7 +454,7 @@ func makeTokenAllowances() { fmt.Printf("Analysing token %x...", token) count := 0 addrCount := 0 - dbstate := state.NewDbState(ethDb, currentBlockNr) + dbstate := state.NewDbState(ethDb.AbstractKV(), currentBlockNr) statedb := state.New(dbstate) msg := types.NewMessage( caller, diff --git a/cmd/state/stateless/transaction_stats.go b/cmd/state/stateless/transaction_stats.go index 1cd8908923..29825205c7 100644 --- a/cmd/state/stateless/transaction_stats.go +++ b/cmd/state/stateless/transaction_stats.go @@ -202,7 +202,7 @@ func transactionStats(blockNum uint64) { if block == nil { break } - dbstate := state.NewDbState(ethDb, block.NumberU64()-1) + dbstate := state.NewDbState(ethDb.AbstractKV(), block.NumberU64()-1) statedb := state.New(dbstate) signer := types.MakeSigner(chainConfig, block.Number()) for txIdx, tx := range block.Transactions() { diff --git a/core/blockchain.go b/core/blockchain.go index 7a25fdf42a..872e255ba7 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -591,35 +591,6 @@ func (bc *BlockChain) Processor() Processor { return bc.processor } -// State returns a new mutable state based on the current HEAD block. -func (bc *BlockChain) State() (*state.IntraBlockState, *state.DbState, error) { - return bc.StateAt(bc.CurrentBlock().NumberU64()) -} - -// StateAt returns a new mutable state based on a particular point in time. -func (bc *BlockChain) StateAt(blockNr uint64) (*state.IntraBlockState, *state.DbState, error) { - dbstate := state.NewDbState(bc.db, blockNr) - return state.New(dbstate), dbstate, nil -} - -// GetAddressFromItsHash returns the preimage of a given address hash. -func (bc *BlockChain) GetAddressFromItsHash(hash common.Hash) (common.Address, error) { - var addr common.Address - - _, dbstate, err := bc.State() - if err != nil { - return addr, err - } - - key := dbstate.GetKey(hash.Bytes()) - if len(key) != common.AddressLength { - return addr, ErrNotFound - } - - addr.SetBytes(key) - return addr, nil -} - // Reset purges the entire blockchain, restoring it to its genesis state. func (bc *BlockChain) Reset() error { return bc.ResetWithGenesisBlock(bc.genesisBlock) @@ -900,14 +871,6 @@ func (bc *BlockChain) GetUnclesInChain(block *types.Block, length int) []*types. return uncles } -// ByteCode retrieves the runtime byte code associated with an account. -func (bc *BlockChain) ByteCode(addr common.Address) ([]byte, error) { - stateDB, _, err := bc.State() - if err != nil { - return nil, err - } - return stateDB.GetCode(addr), nil -} // Stop stops the blockchain service. If any imports are currently in progress // it will abort them using the procInterrupt. diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 72a5015933..06b022c84c 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -1669,7 +1669,7 @@ func TestEIP161AccountRemoval(t *testing.T) { if _, err := blockchain.InsertChain(context.Background(), types.Blocks{blocks[0]}); err != nil { t.Fatal(err) } - if st, _, _ := blockchain.State(); !st.Exist(theAddr) { + if st := state.New(state.NewDbState(db.AbstractKV(), blockchain.CurrentBlock().NumberU64())); !st.Exist(theAddr) { t.Error("expected account to exist") } @@ -1677,7 +1677,7 @@ func TestEIP161AccountRemoval(t *testing.T) { if _, err := blockchain.InsertChain(context.Background(), types.Blocks{blocks[1]}); err != nil { t.Fatal(err) } - if st, _, _ := blockchain.State(); st.Exist(theAddr) { + if st := state.New(state.NewDbState(db.AbstractKV(), blockchain.CurrentBlock().NumberU64())); st.Exist(theAddr) { t.Error("account should not exist") } @@ -1685,7 +1685,7 @@ func TestEIP161AccountRemoval(t *testing.T) { if _, err := blockchain.InsertChain(context.Background(), types.Blocks{blocks[2]}); err != nil { t.Fatal(err) } - if st, _, _ := blockchain.State(); st.Exist(theAddr) { + if st := state.New(state.NewDbState(db.AbstractKV(), blockchain.CurrentBlock().NumberU64())); st.Exist(theAddr) { t.Error("account should not exist") } } @@ -1744,19 +1744,19 @@ func TestDoubleAccountRemoval(t *testing.T) { _, err = blockchain.db.Commit() assert.NoError(t, err) - st, _, err := blockchain.State() + st := state.New(state.NewDbState(db.AbstractKV(), blockchain.CurrentBlock().NumberU64())) assert.NoError(t, err) assert.False(t, st.Exist(theAddr), "Contract should've been removed") - st, _, err = blockchain.StateAt(0) + st = state.New(state.NewDbState(db.AbstractKV(), 0)) assert.NoError(t, err) assert.False(t, st.Exist(theAddr), "Contract should not exist at block #0") - st, _, err = blockchain.StateAt(1) + st = state.New(state.NewDbState(db.AbstractKV(), 1)) assert.NoError(t, err) assert.True(t, st.Exist(theAddr), "Contract should exist at block #1") - st, _, err = blockchain.StateAt(2) + st = state.New(state.NewDbState(db.AbstractKV(), 2)) assert.NoError(t, err) assert.True(t, st.Exist(theAddr), "Contract should exist at block #2") } @@ -2760,7 +2760,7 @@ func TestDeleteRecreateSlots(t *testing.T) { if n, err := chain.InsertChain(context.Background(), blocks); err != nil { t.Fatalf("block %d: failed to insert into chain: %v", n, err) } - statedb, _, _ := chain.State() + statedb := state.New(state.NewDbState(diskdb.AbstractKV(), chain.CurrentBlock().NumberU64())) // If all is correct, then slot 1 and 2 are zero key1 := common.HexToHash("01") @@ -2857,7 +2857,7 @@ func TestDeleteRecreateAccount(t *testing.T) { if n, err := chain.InsertChain(context.Background(), blocks); err != nil { t.Fatalf("block %d: failed to insert into chain: %v", n, err) } - statedb, _, _ := chain.State() + statedb := state.New(state.NewDbState(diskdb.AbstractKV(), chain.CurrentBlock().NumberU64())) // If all is correct, then both slots are zero key1 := common.HexToHash("01") @@ -3051,7 +3051,7 @@ func TestDeleteRecreateSlotsAcrossManyBlocks(t *testing.T) { if n, err := chain.InsertChain(context.Background(), []*types.Block{block}); err != nil { t.Fatalf("block %d: failed to insert into chain: %v", n, err) } - statedb, _, _ := chain.State() + statedb := state.New(state.NewDbState(diskdb.AbstractKV(), chain.CurrentBlock().NumberU64())) // If all is correct, then slot 1 and 2 are zero key1 := common.HexToHash("01") var got uint256.Int @@ -3196,7 +3196,7 @@ func TestInitThenFailCreateContract(t *testing.T) { if err != nil { t.Fatalf("failed to create tester chain: %v", err) } - statedb, _, _ := chain.State() + statedb := state.New(state.NewDbState(diskdb.AbstractKV(), chain.CurrentBlock().NumberU64())) if got, exp := statedb.GetBalance(aa), uint64(100000); got.Uint64() != exp { t.Fatalf("Genesis err, got %v exp %v", got, exp) } @@ -3206,7 +3206,7 @@ func TestInitThenFailCreateContract(t *testing.T) { if _, err := chain.InsertChain(context.Background(), []*types.Block{blocks[0]}); err != nil { t.Fatalf("block %d: failed to insert into chain: %v", block.NumberU64(), err) } - statedb, _, _ = chain.State() + statedb = state.New(state.NewDbState(diskdb.AbstractKV(), chain.CurrentBlock().NumberU64())) if got, exp := statedb.GetBalance(aa), uint64(100000); got.Uint64() != exp { t.Fatalf("block %d: got %v exp %v", block.NumberU64(), got, exp) } diff --git a/core/chain_makers_test.go b/core/chain_makers_test.go index 43a8ef723c..1d935ffeb3 100644 --- a/core/chain_makers_test.go +++ b/core/chain_makers_test.go @@ -22,6 +22,7 @@ import ( "math/big" "github.com/ledgerwatch/turbo-geth/consensus/ethash" + "github.com/ledgerwatch/turbo-geth/core/state" "github.com/ledgerwatch/turbo-geth/core/types" "github.com/ledgerwatch/turbo-geth/core/vm" "github.com/ledgerwatch/turbo-geth/crypto" @@ -96,11 +97,11 @@ func ExampleGenerateChain() { return } - state, _, _ := blockchain.State() + st := state.New(state.NewDbState(db.AbstractKV(), blockchain.CurrentBlock().NumberU64())) fmt.Printf("last block: #%d\n", blockchain.CurrentBlock().Number()) - fmt.Println("balance of addr1:", state.GetBalance(addr1)) - fmt.Println("balance of addr2:", state.GetBalance(addr2)) - fmt.Println("balance of addr3:", state.GetBalance(addr3)) + fmt.Println("balance of addr1:", st.GetBalance(addr1)) + fmt.Println("balance of addr2:", st.GetBalance(addr2)) + fmt.Println("balance of addr3:", st.GetBalance(addr3)) // Output: // last block: #5 // balance of addr1: 989000 diff --git a/core/state/database.go b/core/state/database.go index ac3dbd2b31..0fd5a18994 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -1014,35 +1014,11 @@ func (tds *TrieDbState) readAccountDataByHash(addrHash common.Hash) (*accounts.A } // Not present in the trie, try the database - var err error - var enc []byte var a accounts.Account - if tds.historical { - enc, err = tds.db.GetAsOf(dbutils.CurrentStateBucket, dbutils.AccountsHistoryBucket, addrHash[:], tds.blockNr+1) - if err != nil { - enc = nil - } - if len(enc) == 0 { - return nil, nil - } - if err := a.DecodeForStorage(enc); err != nil { - return nil, err - } - } else { - if ok, err := rawdb.ReadAccount(tds.db, addrHash, &a); err != nil { - return nil, err - } else if !ok { - return nil, nil - } - } - - if tds.historical && a.Incarnation > 0 { - codeHash, err := tds.db.Get(dbutils.ContractCodeBucket, dbutils.GenerateStoragePrefix(addrHash[:], a.Incarnation)) - if err == nil { - a.CodeHash = common.BytesToHash(codeHash) - } else { - log.Error("Get code hash is incorrect", "err", err) - } + if ok, err := rawdb.ReadAccount(tds.db, addrHash, &a); err != nil { + return nil, err + } else if !ok { + return nil, nil } return &a, nil } @@ -1105,16 +1081,9 @@ func (tds *TrieDbState) ReadAccountStorage(address common.Address, incarnation u enc, ok := tds.t.Get(dbutils.GenerateCompositeTrieKey(addrHash, seckey)) if !ok { // Not present in the trie, try database - if tds.historical { - enc, err = tds.db.GetAsOf(dbutils.CurrentStateBucket, dbutils.StorageHistoryBucket, dbutils.GenerateCompositeStorageKey(addrHash, incarnation, seckey), tds.blockNr) - if err != nil { - enc = nil - } - } else { - enc, err = tds.db.Get(dbutils.CurrentStateBucket, dbutils.GenerateCompositeStorageKey(addrHash, incarnation, seckey)) - if err != nil { - enc = nil - } + enc, err = tds.db.Get(dbutils.CurrentStateBucket, dbutils.GenerateCompositeStorageKey(addrHash, incarnation, seckey)) + if err != nil { + enc = nil } } return enc, nil diff --git a/core/state/database_test.go b/core/state/database_test.go index 238d019487..8562f72c0d 100644 --- a/core/state/database_test.go +++ b/core/state/database_test.go @@ -132,7 +132,7 @@ func TestCreate2Revive(t *testing.T) { contractBackend.Commit() }) - st, _, _ := blockchain.State() + st := state.New(state.NewDbState(db.AbstractKV(), blockchain.CurrentBlock().NumberU64())) if !st.Exist(address) { t.Error("expected account to exist") } @@ -145,7 +145,7 @@ func TestCreate2Revive(t *testing.T) { t.Fatal(err) } - st, _, _ = blockchain.State() + st = state.New(state.NewDbState(db.AbstractKV(), blockchain.CurrentBlock().NumberU64())) if !st.Exist(contractAddress) { t.Error("expected contractAddress to exist at the block 1", contractAddress.String()) } @@ -165,7 +165,7 @@ func TestCreate2Revive(t *testing.T) { if it.Event.D != create2address { t.Errorf("Wrong create2address: %x, expected %x", it.Event.D, create2address) } - st, _, _ = blockchain.State() + st = state.New(state.NewDbState(db.AbstractKV(), blockchain.CurrentBlock().NumberU64())) if !st.Exist(create2address) { t.Error("expected create2address to exist at the block 2", create2address.String()) } @@ -181,7 +181,7 @@ func TestCreate2Revive(t *testing.T) { if _, err = blockchain.InsertChain(context.Background(), types.Blocks{blocks[2]}); err != nil { t.Fatal(err) } - st, _, _ = blockchain.State() + st = state.New(state.NewDbState(db.AbstractKV(), blockchain.CurrentBlock().NumberU64())) if st.Exist(create2address) { t.Error("expected create2address to be self-destructed at the block 3", create2address.String()) } @@ -200,7 +200,7 @@ func TestCreate2Revive(t *testing.T) { if it.Event.D != create2address { t.Errorf("Wrong create2address: %x, expected %x", it.Event.D, create2address) } - st, _, _ = blockchain.State() + st = state.New(state.NewDbState(db.AbstractKV(), blockchain.CurrentBlock().NumberU64())) if !st.Exist(create2address) { t.Error("expected create2address to exist at the block 2", create2address.String()) } @@ -304,7 +304,7 @@ func TestReorgOverSelfDestruct(t *testing.T) { contractBackendLonger.Commit() }) - st, _, _ := blockchain.State() + st := state.New(state.NewDbState(db.AbstractKV(), blockchain.CurrentBlock().NumberU64())) if !st.Exist(address) { t.Error("expected account to exist") } @@ -317,7 +317,7 @@ func TestReorgOverSelfDestruct(t *testing.T) { t.Fatal(err) } - st, _, _ = blockchain.State() + st = state.New(state.NewDbState(db.AbstractKV(), blockchain.CurrentBlock().NumberU64())) if !st.Exist(contractAddress) { t.Error("expected contractAddress to exist at the block 1", contractAddress.String()) } @@ -332,7 +332,7 @@ func TestReorgOverSelfDestruct(t *testing.T) { t.Fatal(err) } - st, _, _ = blockchain.State() + st = state.New(state.NewDbState(db.AbstractKV(), blockchain.CurrentBlock().NumberU64())) if st.Exist(contractAddress) { t.Error("expected contractAddress to not exist at the block 3", contractAddress.String()) } @@ -342,7 +342,7 @@ func TestReorgOverSelfDestruct(t *testing.T) { if _, err = blockchain.InsertChain(context.Background(), types.Blocks{longerBlocks[1], longerBlocks[2], longerBlocks[3]}); err != nil { t.Fatal(err) } - st, _, _ = blockchain.State() + st = state.New(state.NewDbState(db.AbstractKV(), blockchain.CurrentBlock().NumberU64())) if !st.Exist(contractAddress) { t.Error("expected contractAddress to exist at the block 4", contractAddress.String()) } @@ -352,7 +352,7 @@ func TestReorgOverSelfDestruct(t *testing.T) { if err != nil { t.Fatal(err) } - st, _, _ = blockchain.State() + st = state.New(state.NewDbState(db.AbstractKV(), blockchain.CurrentBlock().NumberU64())) var valueX uint256.Int st.GetState(contractAddress, &key0, &valueX) if valueX != correctValueX { @@ -440,7 +440,7 @@ func TestReorgOverStateChange(t *testing.T) { contractBackendLonger.Commit() }) - st, _, _ := blockchain.State() + st := state.New(state.NewDbState(db.AbstractKV(), blockchain.CurrentBlock().NumberU64())) if !st.Exist(address) { t.Error("expected account to exist") } @@ -453,7 +453,7 @@ func TestReorgOverStateChange(t *testing.T) { t.Fatal(err) } - st, _, _ = blockchain.State() + st = state.New(state.NewDbState(db.AbstractKV(), blockchain.CurrentBlock().NumberU64())) if !st.Exist(contractAddress) { t.Error("expected contractAddress to exist at the block 1", contractAddress.String()) } @@ -474,7 +474,7 @@ func TestReorgOverStateChange(t *testing.T) { if _, err = blockchain.InsertChain(context.Background(), types.Blocks{longerBlocks[1], longerBlocks[2]}); err != nil { t.Fatal(err) } - st, _, _ = blockchain.State() + st = state.New(state.NewDbState(db.AbstractKV(), blockchain.CurrentBlock().NumberU64())) if !st.Exist(contractAddress) { t.Error("expected contractAddress to exist at the block 4", contractAddress.String()) } @@ -484,7 +484,7 @@ func TestReorgOverStateChange(t *testing.T) { if err != nil { t.Fatal(err) } - st, _, _ = blockchain.State() + st = state.New(state.NewDbState(db.AbstractKV(), blockchain.CurrentBlock().NumberU64())) var valueX uint256.Int st.GetState(contractAddress, &key0, &valueX) if valueX != correctValueX { @@ -731,7 +731,7 @@ func TestCreateOnExistingStorage(t *testing.T) { contractBackend.Commit() }) - st, _, _ := blockchain.State() + st := state.New(state.NewDbState(db.AbstractKV(), blockchain.CurrentBlock().NumberU64())) if !st.Exist(address) { t.Error("expected account to exist") } @@ -744,7 +744,7 @@ func TestCreateOnExistingStorage(t *testing.T) { t.Fatal(err) } - st, _, _ = blockchain.State() + st = state.New(state.NewDbState(db.AbstractKV(), blockchain.CurrentBlock().NumberU64())) if !st.Exist(contractAddress) { t.Error("expected contractAddress to exist at the block 1", contractAddress.String()) } @@ -877,7 +877,7 @@ func TestEip2200Gas(t *testing.T) { contractBackend.Commit() }) - st, _, _ := blockchain.State() + st := state.New(state.NewDbState(db.AbstractKV(), blockchain.CurrentBlock().NumberU64())) if !st.Exist(address) { t.Error("expected account to exist") } @@ -891,7 +891,7 @@ func TestEip2200Gas(t *testing.T) { t.Fatal(err) } - st, _, _ = blockchain.State() + st = state.New(state.NewDbState(db.AbstractKV(), blockchain.CurrentBlock().NumberU64())) if !st.Exist(contractAddress) { t.Error("expected contractAddress to exist at the block 1", contractAddress.String()) } @@ -963,7 +963,7 @@ func TestWrongIncarnation(t *testing.T) { contractBackend.Commit() }) - st, _, _ := blockchain.State() + st := state.New(state.NewDbState(db.AbstractKV(), blockchain.CurrentBlock().NumberU64())) if !st.Exist(address) { t.Error("expected account to exist") } @@ -990,7 +990,7 @@ func TestWrongIncarnation(t *testing.T) { t.Fatal("Incorrect incarnation", acc.Incarnation) } - st, _, _ = blockchain.State() + st = state.New(state.NewDbState(db.AbstractKV(), blockchain.CurrentBlock().NumberU64())) if !st.Exist(contractAddress) { t.Error("expected contractAddress to exist at the block 1", contractAddress.String()) } @@ -1121,7 +1121,7 @@ func TestWrongIncarnation2(t *testing.T) { contractBackendLonger.Commit() }) - st, _, _ := blockchain.State() + st := state.New(state.NewDbState(db.AbstractKV(), blockchain.CurrentBlock().NumberU64())) if !st.Exist(address) { t.Error("expected account to exist") } @@ -1136,7 +1136,7 @@ func TestWrongIncarnation2(t *testing.T) { t.Fatal(err) } - st, _, _ = blockchain.State() + st = state.New(state.NewDbState(db.AbstractKV(), blockchain.CurrentBlock().NumberU64())) if !st.Exist(contractAddress) { t.Error("expected contractAddress to exist at the block 1", contractAddress.String()) } diff --git a/core/state/dump.go b/core/state/dump.go index 915b4562a2..fc6db4805e 100644 --- a/core/state/dump.go +++ b/core/state/dump.go @@ -35,7 +35,7 @@ type trieHasher interface { type Dumper struct { blockNumber uint64 - db ethdb.Getter + db ethdb.KV } // DumpAccount represents an account in the state. @@ -114,7 +114,7 @@ func (d iterativeDump) onRoot(root common.Hash) { }{root}) } -func NewDumper(db ethdb.Getter, blockNumber uint64) *Dumper { +func NewDumper(db ethdb.KV, blockNumber uint64) *Dumper { return &Dumper{db: db, blockNumber: blockNumber} } @@ -128,7 +128,7 @@ func (d *Dumper) dump(c collector, excludeCode, excludeStorage, _ bool, start [] var acc accounts.Account numberOfResults := 0 - err = d.db.WalkAsOf(dbutils.CurrentStateBucket, dbutils.AccountsHistoryBucket, start, 0, d.blockNumber+1, func(k, v []byte) (bool, error) { + err = ethdb.WalkAsOf(d.db, dbutils.CurrentStateBucket, dbutils.AccountsHistoryBucket, start, 0, d.blockNumber+1, func(k, v []byte) (bool, error) { if maxResults > 0 && numberOfResults >= maxResults { if nextKey == nil { nextKey = make([]byte, len(k)) @@ -167,14 +167,14 @@ func (d *Dumper) dump(c collector, excludeCode, excludeStorage, _ bool, start [] account := accountList[i] incarnation := incarnationList[i] var addr []byte - addr, err = d.db.Get(dbutils.PreimagePrefix, addrHash[:]) + addr, err = ethdb.Get(d.db, dbutils.PreimagePrefix, addrHash[:]) if err != nil { return nil, fmt.Errorf("getting preimage of %x: %v", addrHash, err) } storagePrefix := dbutils.GenerateStoragePrefix(addrHash[:], incarnation) if incarnation > 0 { var codeHash []byte - codeHash, err = d.db.Get(dbutils.ContractCodeBucket, storagePrefix) + codeHash, err = ethdb.Get(d.db, dbutils.ContractCodeBucket, storagePrefix) if err != nil && err != ethdb.ErrKeyNotFound { return nil, fmt.Errorf("getting code hash for %x: %v", addrHash, err) } @@ -185,7 +185,7 @@ func (d *Dumper) dump(c collector, excludeCode, excludeStorage, _ bool, start [] } if !excludeCode && codeHash != nil && !bytes.Equal(emptyCodeHash[:], codeHash) { var code []byte - if code, err = d.db.Get(dbutils.CodeBucket, codeHash); err != nil { + if code, err = ethdb.Get(d.db, dbutils.CodeBucket, codeHash); err != nil { return nil, err } account.Code = common.Bytes2Hex(code) @@ -193,7 +193,7 @@ func (d *Dumper) dump(c collector, excludeCode, excludeStorage, _ bool, start [] } if !excludeStorage { storageMap := make(map[common.Hash][]byte) - err = d.db.WalkAsOf( + err = ethdb.WalkAsOf(d.db, dbutils.CurrentStateBucket, dbutils.StorageHistoryBucket, storagePrefix, @@ -208,7 +208,7 @@ func (d *Dumper) dump(c collector, excludeCode, excludeStorage, _ bool, start [] } for keyHash, vs := range storageMap { var key []byte - key, err = d.db.Get(dbutils.PreimagePrefix, keyHash[:]) //remove account address and version from composite key + key, err = ethdb.Get(d.db, dbutils.PreimagePrefix, keyHash[:]) //remove account address and version from composite key if err != nil { return nil, fmt.Errorf("getting preimage for storage %x %x: %v", addrHash, keyHash, err) } diff --git a/core/state/history_test.go b/core/state/history_test.go index 928d47582a..51e025ec75 100644 --- a/core/state/history_test.go +++ b/core/state/history_test.go @@ -155,7 +155,7 @@ func TestMutationCommitThinHistory(t *testing.T) { } for k, v := range accHistoryStateStorage[i] { - res, err := db.GetAsOf(dbutils.CurrentStateBucket, dbutils.StorageHistoryBucket, dbutils.GenerateCompositeStorageKey(addrHash, acc.Incarnation, k), 1) + res, err := ethdb.GetAsOf(db.AbstractKV(), dbutils.CurrentStateBucket, dbutils.StorageHistoryBucket, dbutils.GenerateCompositeStorageKey(addrHash, acc.Incarnation, k), 1) if err != nil { t.Fatal(err) } @@ -426,7 +426,7 @@ func TestBoltDB_WalkAsOf1(t *testing.T) { //walk and collect walkAsOf result var err error var startKey [72]byte - err = db.WalkAsOf(dbutils.CurrentStateBucket, dbutils.StorageHistoryBucket, startKey[:], 0, 2, func(k []byte, v []byte) (b bool, e error) { + err = ethdb.WalkAsOf(db.AbstractKV(), dbutils.CurrentStateBucket, dbutils.StorageHistoryBucket, startKey[:], 0, 2, func(k []byte, v []byte) (b bool, e error) { err = block2.Add(common.CopyBytes(k), common.CopyBytes(v)) if err != nil { t.Fatal(err) @@ -438,7 +438,7 @@ func TestBoltDB_WalkAsOf1(t *testing.T) { t.Fatal(err) } - err = db.WalkAsOf(dbutils.CurrentStateBucket, dbutils.StorageHistoryBucket, startKey[:], 0, 4, func(k []byte, v []byte) (b bool, e error) { + err = ethdb.WalkAsOf(db.AbstractKV(), dbutils.CurrentStateBucket, dbutils.StorageHistoryBucket, startKey[:], 0, 4, func(k []byte, v []byte) (b bool, e error) { err = block4.Add(common.CopyBytes(k), common.CopyBytes(v)) if err != nil { t.Fatal(err) @@ -450,7 +450,7 @@ func TestBoltDB_WalkAsOf1(t *testing.T) { t.Fatal(err) } - err = db.WalkAsOf(dbutils.CurrentStateBucket, dbutils.StorageHistoryBucket, startKey[:], 0, 6, func(k []byte, v []byte) (b bool, e error) { + err = ethdb.WalkAsOf(db.AbstractKV(), dbutils.CurrentStateBucket, dbutils.StorageHistoryBucket, startKey[:], 0, 6, func(k []byte, v []byte) (b bool, e error) { err = block6.Add(common.CopyBytes(k), common.CopyBytes(v)) if err != nil { t.Fatal(err) diff --git a/core/state/intra_block_state_test.go b/core/state/intra_block_state_test.go index 81ee007a99..ad5b707e28 100644 --- a/core/state/intra_block_state_test.go +++ b/core/state/intra_block_state_test.go @@ -364,7 +364,7 @@ func (test *snapshotTest) run() bool { // Run all actions and create snapshots. var ( db = ethdb.NewMemDatabase() - ds = NewDbState(db, 0) + ds = NewDbState(db.AbstractKV(), 0) state = New(ds) snapshotRevs = make([]int, len(test.snapshots)) sindex = 0 @@ -379,7 +379,7 @@ func (test *snapshotTest) run() bool { // Revert all snapshots in reverse order. Each revert must yield a state // that is equivalent to fresh state with all actions up the snapshot applied. for sindex--; sindex >= 0; sindex-- { - checkds := NewDbState(db, 0) + checkds := NewDbState(db.AbstractKV(), 0) checkstate := New(checkds) for _, action := range test.actions[:test.snapshots[sindex]] { action.fn(action, checkstate) diff --git a/core/state/readonly.go b/core/state/readonly.go index abbd3dc3b5..2d5ce8ed1e 100644 --- a/core/state/readonly.go +++ b/core/state/readonly.go @@ -49,12 +49,12 @@ func (a *storageItem) Less(b llrb.Item) bool { // Implements StateReader by wrapping database only, without trie type DbState struct { - db ethdb.Getter + db ethdb.KV blockNr uint64 storage map[common.Address]*llrb.LLRB } -func NewDbState(db ethdb.Getter, blockNr uint64) *DbState { +func NewDbState(db ethdb.KV, blockNr uint64) *DbState { return &DbState{ db: db, blockNr: blockNr, @@ -80,7 +80,7 @@ func (dbs *DbState) ForEachStorage(addr common.Address, start []byte, cb func(ke st := llrb.New() var s [common.HashLength + common.IncarnationLength + common.HashLength]byte copy(s[:], addrHash[:]) - accData, _ := dbs.db.GetAsOf(dbutils.CurrentStateBucket, dbutils.AccountsHistoryBucket, addrHash[:], dbs.blockNr+1) + accData, _ := ethdb.GetAsOf(dbs.db, dbutils.CurrentStateBucket, dbutils.AccountsHistoryBucket, addrHash[:], dbs.blockNr+1) var acc accounts.Account if err = acc.DecodeForStorage(accData); err != nil { log.Error("Error decoding account", "error", err) @@ -105,7 +105,7 @@ func (dbs *DbState) ForEachStorage(addr common.Address, start []byte, cb func(ke }) } numDeletes := st.Len() - overrideCounter - err = dbs.db.WalkAsOf(dbutils.CurrentStateBucket, dbutils.StorageHistoryBucket, s[:], 8*(common.HashLength+common.IncarnationLength), dbs.blockNr+1, func(ks, vs []byte) (bool, error) { + err = ethdb.WalkAsOf(dbs.db, dbutils.CurrentStateBucket, dbutils.StorageHistoryBucket, s[:], 8*(common.HashLength+common.IncarnationLength), dbs.blockNr+1, func(ks, vs []byte) (bool, error) { if !bytes.HasPrefix(ks, addrHash[:]) { return false, nil } @@ -139,7 +139,7 @@ func (dbs *DbState) ForEachStorage(addr common.Address, start []byte, cb func(ke if !item.value.IsZero() { // Skip if value == 0 if item.key == emptyHash { - key, err := dbs.db.Get(dbutils.PreimagePrefix, item.seckey[:]) + key, err := ethdb.Get(dbs.db, dbutils.PreimagePrefix, item.seckey[:]) if err == nil { copy(item.key[:], key) } else { @@ -158,7 +158,7 @@ func (dbs *DbState) ForEachStorage(addr common.Address, start []byte, cb func(ke func (dbs *DbState) ForEachAccount(start []byte, cb func(address *common.Address, addrHash common.Hash), maxResults int) { results := 0 - err := dbs.db.WalkAsOf(dbutils.CurrentStateBucket, dbutils.AccountsHistoryBucket, start[:], 0, dbs.blockNr+1, func(ks, vs []byte) (bool, error) { + err := ethdb.WalkAsOf(dbs.db, dbutils.CurrentStateBucket, dbutils.AccountsHistoryBucket, start[:], 0, dbs.blockNr+1, func(ks, vs []byte) (bool, error) { if vs == nil || len(vs) == 0 { // Skip deleted entries return true, nil @@ -168,7 +168,7 @@ func (dbs *DbState) ForEachAccount(start []byte, cb func(address *common.Address return true, nil } addrHash := common.BytesToHash(ks[:common.HashLength]) - preimage, err := dbs.db.Get(dbutils.PreimagePrefix, addrHash[:]) + preimage, err := ethdb.Get(dbs.db, dbutils.PreimagePrefix, addrHash[:]) if err == nil { addr := &common.Address{} addr.SetBytes(preimage) @@ -189,7 +189,7 @@ func (dbs *DbState) ReadAccountData(address common.Address) (*accounts.Account, if err != nil { return nil, err } - enc, err := dbs.db.GetAsOf(dbutils.CurrentStateBucket, dbutils.AccountsHistoryBucket, addrHash[:], dbs.blockNr+1) + enc, err := ethdb.GetAsOf(dbs.db, dbutils.CurrentStateBucket, dbutils.AccountsHistoryBucket, addrHash[:], dbs.blockNr+1) if err != nil || enc == nil || len(enc) == 0 { return nil, nil } @@ -212,7 +212,7 @@ func (dbs *DbState) ReadAccountStorage(address common.Address, incarnation uint6 } compositeKey := dbutils.GenerateCompositeStorageKey(addrHash, incarnation, keyHash) - enc, err := dbs.db.GetAsOf(dbutils.CurrentStateBucket, dbutils.StorageHistoryBucket, compositeKey, dbs.blockNr+1) + enc, err := ethdb.GetAsOf(dbs.db, dbutils.CurrentStateBucket, dbutils.StorageHistoryBucket, compositeKey, dbs.blockNr+1) if err != nil || enc == nil { return nil, nil } @@ -223,7 +223,7 @@ func (dbs *DbState) ReadAccountCode(address common.Address, codeHash common.Hash if bytes.Equal(codeHash[:], emptyCodeHash) { return nil, nil } - return dbs.db.Get(dbutils.CodeBucket, codeHash[:]) + return ethdb.Get(dbs.db, dbutils.CodeBucket, codeHash[:]) } func (dbs *DbState) ReadAccountCodeSize(address common.Address, codeHash common.Hash) (int, error) { @@ -281,14 +281,10 @@ func (dbs *DbState) CreateContract(address common.Address) error { } func (dbs *DbState) GetKey(shaKey []byte) []byte { - key, _ := dbs.db.Get(dbutils.PreimagePrefix, shaKey) + key, _ := ethdb.Get(dbs.db, dbutils.PreimagePrefix, shaKey) return key } -func (dbs *DbState) Dumper() *Dumper { - return &Dumper{db: dbs.db, blockNumber: dbs.blockNr} -} - // WalkStorageRange calls the walker for each storage item whose key starts with a given prefix, // for no more than maxItems. // Returns whether all matching storage items were traversed (provided there was no error). @@ -306,7 +302,7 @@ func (dbs *DbState) WalkStorageRange(addrHash common.Hash, prefix trie.Keybytes, i := 0 - err := dbs.db.WalkAsOf(dbutils.CurrentStateBucket, dbutils.StorageHistoryBucket, startkey, fixedbits, dbs.blockNr+1, + err := ethdb.WalkAsOf(dbs.db, dbutils.CurrentStateBucket, dbutils.StorageHistoryBucket, startkey, fixedbits, dbs.blockNr+1, func(key []byte, value []byte) (bool, error) { val := new(big.Int).SetBytes(value) @@ -336,7 +332,7 @@ func (dbs *DbState) WalkRangeOfAccounts(prefix trie.Keybytes, maxItems int, walk i := 0 var acc accounts.Account - err := dbs.db.WalkAsOf(dbutils.CurrentStateBucket, dbutils.AccountsHistoryBucket, startkey, fixedbits, dbs.blockNr+1, + err := ethdb.WalkAsOf(dbs.db, dbutils.CurrentStateBucket, dbutils.AccountsHistoryBucket, startkey, fixedbits, dbs.blockNr+1, func(key []byte, value []byte) (bool, error) { if len(key) > 32 { return true, nil diff --git a/core/state/state_test.go b/core/state/state_test.go index 2b729f645a..405672c3e8 100644 --- a/core/state/state_test.go +++ b/core/state/state_test.go @@ -32,6 +32,7 @@ import ( type StateSuite struct { db ethdb.Database + kv ethdb.KV // Same as db, but with a different interface state *IntraBlockState tds *TrieDbState } @@ -68,7 +69,7 @@ func (s *StateSuite) TestDump(c *checker.C) { c.Check(err, checker.IsNil) // check that dump contains the state objects that are in trie - got := string(NewDumper(s.db, 1).DefaultDump()) + got := string(NewDumper(s.kv, 1).DefaultDump()) want := `{ "root": "71edff0130dd2385947095001c73d9e28d862fc286fca2b922ca6f6f3cddfdd2", "accounts": { @@ -99,7 +100,9 @@ func (s *StateSuite) TestDump(c *checker.C) { } func (s *StateSuite) SetUpTest(c *checker.C) { - s.db = ethdb.NewMemDatabase() + db := ethdb.NewMemDatabase() + s.db = db + s.kv = db.AbstractKV() s.tds = NewTrieDbState(common.Hash{}, s.db, 0) s.state = New(s.tds) s.tds.StartNewBuffer() @@ -343,7 +346,7 @@ func TestDump(t *testing.T) { } // check that dump contains the state objects that are in trie - got := string(NewDumper(db, 2).DefaultDump()) + got := string(NewDumper(db.AbstractKV(), 2).DefaultDump()) want := `{ "root": "0000000000000000000000000000000000000000000000000000000000000000", "accounts": { diff --git a/core/tx_pool.go b/core/tx_pool.go index a2fbfcf7cd..ebd2cc9f2a 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -126,8 +126,6 @@ const ( type blockChain interface { CurrentBlock() *types.Block GetBlock(hash common.Hash, number uint64) *types.Block - StateAt(blockNr uint64) (*state.IntraBlockState, *state.DbState, error) - SubscribeChainHeadEvent(ch chan<- ChainHeadEvent) event.Subscription GetTrieDbState() (*state.TrieDbState, error) } diff --git a/eth/api.go b/eth/api.go index bf37c39f1c..419dff8704 100644 --- a/eth/api.go +++ b/eth/api.go @@ -298,11 +298,7 @@ func (api *PublicDebugAPI) DumpBlock(blockNr rpc.BlockNumber) (state.Dump, error if block == nil { return state.Dump{}, fmt.Errorf("block #%d not found", blockNr) } - _, tds, err := api.eth.BlockChain().StateAt(uint64(blockNr)) - if err != nil { - return state.Dump{}, err - } - return tds.Dumper().DefaultRawDump(), nil + return state.NewDumper(api.eth.ChainKV(), block.NumberU64()).DefaultRawDump(), nil } // PrivateDebugAPI is the collection of Ethereum full node APIs exposed over @@ -382,7 +378,7 @@ func (api *PublicDebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, sta if maxResults > AccountRangeMaxResults || maxResults <= 0 { maxResults = AccountRangeMaxResults } - dumper := state.NewDumper(api.eth.chainDb, blockNumber) + dumper := state.NewDumper(api.eth.ChainKV(), blockNumber) return dumper.IteratorDump(nocode, nostorage, incompletes, start, maxResults) } @@ -401,7 +397,7 @@ type StorageEntry struct { // StorageRangeAt returns the storage at the given block height and transaction index. func (api *PrivateDebugAPI) StorageRangeAt(ctx context.Context, blockHash common.Hash, txIndex uint64, contractAddress common.Address, keyStart hexutil.Bytes, maxResult int) (StorageRangeResult, error) { - _, _, _, dbstate, err := ComputeTxEnv(ctx, api.eth.blockchain, api.eth.blockchain.Config(), api.eth.blockchain, api.eth.ChainDb(), blockHash, txIndex) + _, _, _, dbstate, err := ComputeTxEnv(ctx, api.eth.blockchain, api.eth.blockchain.Config(), api.eth.blockchain, api.eth.ChainKV(), blockHash, txIndex) if err != nil { return StorageRangeResult{}, err } diff --git a/eth/api_backend.go b/eth/api_backend.go index c73b40cadd..c537c4fdbf 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -155,9 +155,10 @@ func (b *EthAPIBackend) StateAndHeaderByNumber(ctx context.Context, blockNr rpc. if header == nil { return nil, nil, errors.New("header not found") } - ds := state.NewDbState(b.eth.chainDb, bn) + ds := state.NewDbState(b.eth.ChainKV(), bn) stateDb := state.New(ds) return stateDb, header, nil + } func (b *EthAPIBackend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.IntraBlockState, *types.Header, error) { @@ -175,8 +176,9 @@ func (b *EthAPIBackend) StateAndHeaderByNumberOrHash(ctx context.Context, blockN if blockNrOrHash.RequireCanonical && b.eth.blockchain.GetCanonicalHash(header.Number.Uint64()) != hash { return nil, nil, errors.New("hash is not currently canonical") } - stateDb, _, err := b.eth.BlockChain().StateAt(header.Number.Uint64()) - return stateDb, header, err + ds := state.NewDbState(b.eth.ChainKV(), header.Number.Uint64()) + stateDb := state.New(ds) + return stateDb, header, nil } return nil, nil, errors.New("invalid arguments; neither block nor hash specified") } @@ -197,7 +199,7 @@ func (b *EthAPIBackend) GetReceipts(ctx context.Context, hash common.Hash) (type } func (b *EthAPIBackend) getReceiptsByReApplyingTransactions(block *types.Block, number uint64) (types.Receipts, error) { - dbstate := state.NewDbState(b.eth.chainDb, number-1) + dbstate := state.NewDbState(b.eth.ChainKV(), number-1) statedb := state.New(dbstate) header := block.Header() var receipts types.Receipts diff --git a/eth/api_test.go b/eth/api_test.go index d24b87310a..6518f13f84 100644 --- a/eth/api_test.go +++ b/eth/api_test.go @@ -37,7 +37,7 @@ import ( var dumper = spew.ConfigState{Indent: " "} -func accountRangeTest(t *testing.T, trie *trie.Trie, db ethdb.Getter, blockNumber uint64, sdb *state.IntraBlockState, start common.Hash, requestedNum int, expectedNum int) state.IteratorDump { //nolint: unparam +func accountRangeTest(t *testing.T, trie *trie.Trie, db ethdb.KV, blockNumber uint64, sdb *state.IntraBlockState, start common.Hash, requestedNum int, expectedNum int) state.IteratorDump { //nolint: unparam result, err := state.NewDumper(db, blockNumber).IteratorDump(true, true, false, start.Bytes(), requestedNum) if err != nil { t.Fatal(err) @@ -95,10 +95,10 @@ func TestAccountRange(t *testing.T) { trie := tds.Trie() - accountRangeTest(t, trie, db, 0, sdb, common.Hash{}, AccountRangeMaxResults/2, AccountRangeMaxResults/2) + accountRangeTest(t, trie, db.AbstractKV(), 0, sdb, common.Hash{}, AccountRangeMaxResults/2, AccountRangeMaxResults/2) // test pagination - firstResult := accountRangeTest(t, trie, db, 0, sdb, common.Hash{}, AccountRangeMaxResults, AccountRangeMaxResults) - secondResult := accountRangeTest(t, trie, db, 0, sdb, common.BytesToHash(firstResult.Next), AccountRangeMaxResults, AccountRangeMaxResults) + firstResult := accountRangeTest(t, trie, db.AbstractKV(), 0, sdb, common.Hash{}, AccountRangeMaxResults, AccountRangeMaxResults) + secondResult := accountRangeTest(t, trie, db.AbstractKV(), 0, sdb, common.BytesToHash(firstResult.Next), AccountRangeMaxResults, AccountRangeMaxResults) hList := make(resultHash, 0) for addr1 := range firstResult.Accounts { @@ -116,7 +116,7 @@ func TestAccountRange(t *testing.T) { // set and get an even split between the first and second sets. sort.Sort(hList) middleH := hList[AccountRangeMaxResults/2] - middleResult := accountRangeTest(t, trie, db, 0, sdb, middleH, AccountRangeMaxResults, AccountRangeMaxResults) + middleResult := accountRangeTest(t, trie, db.AbstractKV(), 0, sdb, middleH, AccountRangeMaxResults, AccountRangeMaxResults) missing, infirst, insecond := 0, 0, 0 for h := range middleResult.Accounts { if _, ok := firstResult.Accounts[h]; ok { @@ -148,7 +148,7 @@ func TestEmptyAccountRange(t *testing.T) { if err != nil { t.Error(err) } - results, err1 := state.NewDumper(db, 0).IteratorDump(true, true, true, (common.Hash{}).Bytes(), AccountRangeMaxResults) + results, err1 := state.NewDumper(db.AbstractKV(), 0).IteratorDump(true, true, true, (common.Hash{}).Bytes(), AccountRangeMaxResults) if err1 != nil { t.Fatal(err1) } @@ -233,7 +233,7 @@ func TestStorageRangeAt(t *testing.T) { want: StorageRangeResult{StorageMap{keys[1]: storage[keys[1]], keys[2]: storage[keys[2]]}, &keys[3]}, }, } - dbs := state.NewDbState(db, 1) + dbs := state.NewDbState(db.AbstractKV(), 1) for i, test := range tests { test := test t.Run("test_"+strconv.Itoa(i), func(t *testing.T) { diff --git a/eth/api_tracer.go b/eth/api_tracer.go index 21635b3cba..1a619a58f7 100644 --- a/eth/api_tracer.go +++ b/eth/api_tracer.go @@ -433,7 +433,7 @@ func (api *PrivateDebugAPI) traceBlock(ctx context.Context, block *types.Block, if parent == nil { return nil, fmt.Errorf("parent %#x not found", block.ParentHash()) } - statedb, dbstate := ComputeIntraBlockState(api.eth.ChainDb(), parent) + statedb, dbstate := ComputeIntraBlockState(api.eth.ChainKV(), parent) // Execute all the transaction contained within the block concurrently var ( signer = types.MakeSigner(api.eth.blockchain.Config(), block.Number()) @@ -520,7 +520,7 @@ func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block reexec = *config.Reexec } */ - statedb, dbstate := ComputeIntraBlockState(api.eth.ChainDb(), parent) + statedb, dbstate := ComputeIntraBlockState(api.eth.ChainKV(), parent) // Retrieve the tracing configurations, or use default values var ( logConfig vm.LogConfig @@ -608,9 +608,9 @@ func containsTx(block *types.Block, hash common.Hash) bool { // computeIntraBlockState retrieves the state database associated with a certain block. // If no state is locally available for the given block, a number of blocks are // attempted to be reexecuted to generate the desired state. -func ComputeIntraBlockState(chainDb ethdb.Getter, block *types.Block) (*state.IntraBlockState, *state.DbState) { +func ComputeIntraBlockState(chainKV ethdb.KV, block *types.Block) (*state.IntraBlockState, *state.DbState) { // If we have the state fully available, use that - dbstate := state.NewDbState(chainDb, block.NumberU64()) + dbstate := state.NewDbState(chainKV, block.NumberU64()) statedb := state.New(dbstate) return statedb, dbstate } @@ -623,7 +623,7 @@ func (api *PrivateDebugAPI) TraceTransaction(ctx context.Context, hash common.Ha if tx == nil { return nil, fmt.Errorf("transaction %#x not found", hash) } - msg, vmctx, statedb, _, err := ComputeTxEnv(ctx, api.eth.blockchain, api.eth.blockchain.Config(), api.eth.blockchain, api.eth.ChainDb(), blockHash, index) + msg, vmctx, statedb, _, err := ComputeTxEnv(ctx, api.eth.blockchain, api.eth.blockchain.Config(), api.eth.blockchain, api.eth.ChainKV(), blockHash, index) if err != nil { return nil, err } @@ -702,7 +702,7 @@ type BlockGetter interface { } // computeTxEnv returns the execution environment of a certain transaction. -func ComputeTxEnv(ctx context.Context, blockGetter BlockGetter, cfg *params.ChainConfig, chain core.ChainContext, chainDb ethdb.Getter, blockHash common.Hash, txIndex uint64) (core.Message, vm.Context, *state.IntraBlockState, *state.DbState, error) { +func ComputeTxEnv(ctx context.Context, blockGetter BlockGetter, cfg *params.ChainConfig, chain core.ChainContext, chainKV ethdb.KV, blockHash common.Hash, txIndex uint64) (core.Message, vm.Context, *state.IntraBlockState, *state.DbState, error) { // Create the parent state database block := blockGetter.GetBlockByHash(blockHash) if block == nil { @@ -713,7 +713,7 @@ func ComputeTxEnv(ctx context.Context, blockGetter BlockGetter, cfg *params.Chai return nil, vm.Context{}, nil, nil, fmt.Errorf("parent %x not found", block.ParentHash()) } - statedb, dbstate := ComputeIntraBlockState(chainDb, parent) + statedb, dbstate := ComputeIntraBlockState(chainKV, parent) if txIndex == 0 && len(block.Transactions()) == 0 { return nil, vm.Context{}, statedb, dbstate, nil diff --git a/eth/backend.go b/eth/backend.go index 058358ff46..5db07c4083 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -80,6 +80,7 @@ type Ethereum struct { // DB interfaces chainDb ethdb.Database // Block chain database + chainKV ethdb.KV // Same as chainDb, but different interface eventMux *event.TypeMux engine consensus.Engine @@ -144,10 +145,14 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { if err != nil { return nil, err } + var chainKV ethdb.KV + if hasKV, ok := chainDb.(ethdb.HasAbstractKV); ok { + chainKV = hasKV.AbstractKV() + } else { + return nil, fmt.Errorf("database %T does not implement AbstractKV", chainDb) + } if ctx.Config.RemoteDbListenAddress != "" { - if casted, ok := chainDb.(ethdb.HasAbstractKV); ok { - remotedbserver.StartDeprecated(casted.AbstractKV(), ctx.Config.RemoteDbListenAddress) - } + remotedbserver.StartDeprecated(chainKV, ctx.Config.RemoteDbListenAddress) } chainConfig, genesisHash, _, genesisErr := core.SetupGenesisBlock(chainDb, config.Genesis, config.StorageMode.History) @@ -160,6 +165,7 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { eth := &Ethereum{ config: config, chainDb: chainDb, + chainKV: chainKV, eventMux: ctx.EventMux, accountManager: ctx.AccountManager, engine: CreateConsensusEngine(ctx, chainConfig, &config.Ethash, config.Miner.Notify, config.Miner.Noverify, chainDb), @@ -575,6 +581,7 @@ func (s *Ethereum) TxPool() *core.TxPool { return s.txPool } func (s *Ethereum) EventMux() *event.TypeMux { return s.eventMux } func (s *Ethereum) Engine() consensus.Engine { return s.engine } func (s *Ethereum) ChainDb() ethdb.Database { return s.chainDb } +func (s *Ethereum) ChainKV() ethdb.KV { return s.chainKV } func (s *Ethereum) IsListening() bool { return true } // Always listening func (s *Ethereum) EthVersion() int { return int(ProtocolVersions[0]) } func (s *Ethereum) NetVersion() uint64 { return s.networkID } @@ -592,9 +599,6 @@ func (s *Ethereum) Protocols() []p2p.Protocol { protos[i].DialCandidates = s.dialCandiates } - // Firehose - protos = append(protos, s.protocolManager.makeFirehoseProtocol()) - // Debug protos = append(protos, s.protocolManager.makeDebugProtocol()) diff --git a/eth/handler.go b/eth/handler.go index f6b87e9649..e7f8bd838e 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -19,7 +19,6 @@ package eth import ( "bytes" "context" - "encoding/binary" "encoding/json" "errors" "fmt" @@ -35,7 +34,6 @@ import ( "github.com/ledgerwatch/turbo-geth/core" "github.com/ledgerwatch/turbo-geth/core/forkid" "github.com/ledgerwatch/turbo-geth/core/types" - "github.com/ledgerwatch/turbo-geth/core/types/accounts" "github.com/ledgerwatch/turbo-geth/core/vm" "github.com/ledgerwatch/turbo-geth/crypto" "github.com/ledgerwatch/turbo-geth/eth/downloader" @@ -227,36 +225,6 @@ func initPm(manager *ProtocolManager, txpool txPool, engine consensus.Engine, bl manager.chainSync = newChainSyncer(manager) } -func (pm *ProtocolManager) makeFirehoseProtocol() p2p.Protocol { - // Initiate Firehose - log.Info("Initialising Firehose protocol", "versions", FirehoseVersions) - return p2p.Protocol{ - Name: FirehoseName, - Version: FirehoseVersions[0], - Length: FirehoseLengths[0], - Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error { - peer := &firehosePeer{Peer: p, rw: rw} - select { - case <-pm.quitSync: - return p2p.DiscQuitting - default: - pm.wg.Add(1) - defer pm.wg.Done() - return pm.handleFirehose(peer) - } - }, - NodeInfo: func() interface{} { - return pm.NodeInfo() - }, - PeerInfo: func(id enode.ID) interface{} { - if p := pm.peers.Peer(fmt.Sprintf("%x", id[:8])); p != nil { - return p.Info() - } - return nil - }, - } -} - func (pm *ProtocolManager) makeDebugProtocol() p2p.Protocol { // Initiate Debug protocol log.Info("Initialising Debug protocol", "versions", FirehoseVersions) @@ -500,15 +468,6 @@ func (pm *ProtocolManager) handle(p *peer) error { } } -func (pm *ProtocolManager) handleFirehose(p *firehosePeer) error { - for { - if err := pm.handleFirehoseMsg(p); err != nil { - p.Log().Debug("Firehose message handling failed", "err", err) - return err - } - } -} - func (pm *ProtocolManager) handleDebug(p *debugPeer) error { for { if err := pm.handleDebugMsg(p); err != nil { @@ -1000,299 +959,6 @@ func (pm *ProtocolManager) extractAddressHash(addressOrHash []byte) (common.Hash } } -func (pm *ProtocolManager) handleFirehoseMsg(p *firehosePeer) error { - msg, readErr := p.rw.ReadMsg() - if readErr != nil { - return readErr - } - if msg.Size > FirehoseMaxMsgSize { - return errResp(ErrMsgTooLarge, "%v > %v", msg.Size, FirehoseMaxMsgSize) - } - defer msg.Discard() - - switch msg.Code { - case GetStateRangesCode: - msgStream := rlp.NewStream(msg.Payload, uint64(msg.Size)) - var request getStateRangesOrNodes - if err := msgStream.Decode(&request); err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - - n := len(request.Prefixes) - - var response stateRangesMsg - response.ID = request.ID - response.Entries = make([]firehoseAccountRange, n) - - for i := 0; i < n; i++ { - response.Entries[i].Status = NoData - } - - block := pm.blockchain.GetBlockByHash(request.Block) - if block != nil { - _, dbstate, err := pm.blockchain.StateAt(block.NumberU64()) - if err != nil { - return err - } - for i, responseSize := 0, 0; i < n && responseSize < softResponseLimit; i++ { - var leaves []accountLeaf - allTraversed, err := dbstate.WalkRangeOfAccounts(request.Prefixes[i], MaxLeavesPerPrefix, - func(key common.Hash, value *accounts.Account) { - leaves = append(leaves, accountLeaf{key, value}) - }, - ) - if err != nil { - return err - } - if allTraversed { - response.Entries[i].Status = OK - response.Entries[i].Leaves = leaves - responseSize += len(leaves) - } else { - response.Entries[i].Status = TooManyLeaves - } - } - } else { - response.AvailableBlocks = pm.blockchain.AvailableBlocks() - } - - return p2p.Send(p.rw, StateRangesCode, response) - - case StateRangesCode: - return errResp(ErrNotImplemented, "Not implemented yet") - - case GetStorageRangesCode: - msgStream := rlp.NewStream(msg.Payload, uint64(msg.Size)) - var request getStorageRangesOrNodes - if err := msgStream.Decode(&request); err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - - numReq := len(request.Requests) - - var response storageRangesMsg - response.ID = request.ID - response.Entries = make([][]storageRange, numReq) - - block := pm.blockchain.GetBlockByHash(request.Block) - if block != nil { - _, dbstate, err := pm.blockchain.StateAt(block.NumberU64()) - if err != nil { - return err - } - - for j, responseSize := 0, 0; j < numReq; j++ { - req := request.Requests[j] - - n := len(req.Prefixes) - response.Entries[j] = make([]storageRange, n) - for i := 0; i < n; i++ { - response.Entries[j][i].Status = NoData - } - - addrHash, err := pm.extractAddressHash(req.Account) - if err != nil { - return err - } - - for i := 0; i < n && responseSize < softResponseLimit; i++ { - var leaves []storageLeaf - allTraversed, err := dbstate.WalkStorageRange(addrHash, req.Prefixes[i], MaxLeavesPerPrefix, - func(key common.Hash, value big.Int) { - leaves = append(leaves, storageLeaf{key, value}) - }, - ) - if err != nil { - return err - } - if allTraversed { - response.Entries[j][i].Status = OK - response.Entries[j][i].Leaves = leaves - responseSize += len(leaves) - } else { - response.Entries[j][i].Status = TooManyLeaves - } - } - } - } else { - response.AvailableBlocks = pm.blockchain.AvailableBlocks() - } - - return p2p.Send(p.rw, StorageRangesCode, response) - - case StorageRangesCode: - return errResp(ErrNotImplemented, "Not implemented yet") - - case GetStateNodesCode: - msgStream := rlp.NewStream(msg.Payload, uint64(msg.Size)) - var request getStateRangesOrNodes - if err := msgStream.Decode(&request); err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - - n := len(request.Prefixes) - - var response stateNodesMsg - response.ID = request.ID - response.Nodes = make([][]byte, n) - - block := pm.blockchain.GetBlockByHash(request.Block) - if block != nil { - //tr := trie.New(common.Hash{}) - - for i, responseSize := 0, 0; i < n && responseSize < softResponseLimit; i++ { - //prefix := request.Prefixes[i] - //rr := tr.NewResolveRequest(nil, prefix.ToHex(), prefix.Nibbles()) - //rr.RequiresRLP = true - - loader := trie.NewSubTrieLoader(block.NumberU64()) - - rl := trie.NewRetainList(0) - if _, err2 := loader.LoadSubTries(pm.blockchain.ChainDb(), block.NumberU64(), rl, [][]byte{nil}, []int{0}, false); err2 != nil { - return err2 - } - /* - node := rr.NodeRLP - response.Nodes[i] = make([]byte, len(node)) - copy(response.Nodes[i], node) - responseSize += len(node) - */ - } - } else { - response.AvailableBlocks = pm.blockchain.AvailableBlocks() - } - - return p2p.Send(p.rw, StateNodesCode, response) - - case StateNodesCode: - return errResp(ErrNotImplemented, "Not implemented yet") - - case GetStorageNodesCode: - msgStream := rlp.NewStream(msg.Payload, uint64(msg.Size)) - var request getStorageRangesOrNodes - if err := msgStream.Decode(&request); err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - - numReq := len(request.Requests) - - var response storageNodesMsg - response.ID = request.ID - response.Nodes = make([][][]byte, numReq) - - block := pm.blockchain.GetBlockByHash(request.Block) - if block != nil { - - for j, responseSize := 0, 0; j < numReq; j++ { - req := request.Requests[j] - - n := len(req.Prefixes) - response.Nodes[j] = make([][]byte, n) - - addrHash, err := pm.extractAddressHash(req.Account) - if err != nil { - return err - } - - //tr := trie.New(common.Hash{}) - - for i := 0; i < n && responseSize < softResponseLimit; i++ { - contractPrefix := make([]byte, common.HashLength+common.IncarnationLength) - copy(contractPrefix, addrHash.Bytes()) - binary.BigEndian.PutUint64(contractPrefix[common.HashLength:], ^uint64(1)) - // TODO [Issue 99] support incarnations - //storagePrefix := req.Prefixes[i] - //rr := tr.NewResolveRequest(contractPrefix, storagePrefix.ToHex(), storagePrefix.Nibbles()) - //rr.RequiresRLP = true - - loader := trie.NewSubTrieLoader(block.NumberU64()) - - rl := trie.NewRetainList(0) - if _, err2 := loader.LoadSubTries(pm.blockchain.ChainDb(), block.NumberU64(), rl, [][]byte{nil}, []int{0}, false); err2 != nil { - return err2 - } - /* - node := rr.NodeRLP - response.Nodes[j][i] = make([]byte, len(node)) - copy(response.Nodes[j][i], node) - responseSize += len(node) - */ - } - } - } else { - response.AvailableBlocks = pm.blockchain.AvailableBlocks() - } - - return p2p.Send(p.rw, StorageNodesCode, response) - - case StorageNodesCode: - return errResp(ErrNotImplemented, "Not implemented yet") - - case GetBytecodeCode: - // Decode the retrieval message - msgStream := rlp.NewStream(msg.Payload, uint64(msg.Size)) - if _, err := msgStream.List(); err != nil { - return err - } - var reqID uint64 - if err := msgStream.Decode(&reqID); err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - if _, err := msgStream.List(); err != nil { - return err - } - - // Gather bytecodes until the fetch or network limit is reached - var ( - responseSize int - code [][]byte - ) - for responseSize < softResponseLimit && len(code) < downloader.MaxStateFetch { - var req bytecodeRef - if err := msgStream.Decode(&req); err == rlp.EOL { - break - } else if err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - - var addr common.Address - if len(req.Account) == common.AddressLength { - addr.SetBytes(req.Account) - } else if len(req.Account) == common.HashLength { - var preimageErr error - addr, preimageErr = pm.blockchain.GetAddressFromItsHash(common.BytesToHash(req.Account)) - if preimageErr == core.ErrNotFound { - code = append(code, []byte{}) - break - } else if preimageErr != nil { - return preimageErr - } - } else { - return errResp(ErrDecode, "not an account address or its hash") - } - - // Retrieve requested byte code, stopping if enough was found - if entry, err := pm.blockchain.ByteCode(addr); err == nil { - code = append(code, entry) - responseSize += len(entry) - } - } - return p.SendByteCode(reqID, code) - - case BytecodeCode: - return errResp(ErrNotImplemented, "Not implemented yet") - - case GetStorageSizesCode: - return errResp(ErrNotImplemented, "Not implemented yet") - - case StorageSizesCode: - return errResp(ErrNotImplemented, "Not implemented yet") - - default: - return errResp(ErrInvalidMsgCode, "%v", msg.Code) - } -} - func (pm *ProtocolManager) handleDebugMsg(p *debugPeer) error { msg, readErr := p.rw.ReadMsg() if readErr != nil { diff --git a/eth/helper_test.go b/eth/helper_test.go index 3cfefc8889..9475b9cd7f 100644 --- a/eth/helper_test.go +++ b/eth/helper_test.go @@ -230,7 +230,7 @@ func newFirehoseTestPeer(name string, pm *ProtocolManager) (*testFirehosePeer, < case <-pm.quitSync: errc <- p2p.DiscQuitting default: - errc <- pm.handleFirehose(peer) + //errc <- pm.handleFirehose(peer) } }() diff --git a/ethdb/badger_db.go b/ethdb/badger_db.go index 769c803366..0fc269532a 100644 --- a/ethdb/badger_db.go +++ b/ethdb/badger_db.go @@ -101,6 +101,10 @@ func NewEphemeralBadger() (*BadgerDatabase, error) { }, nil } +func (db *BadgerDatabase) AbstractKV() KV { + return &badgerDB{badger: db.db} +} + // Close closes the database. func (db *BadgerDatabase) Close() { if db.gcTicker != nil { @@ -372,7 +376,7 @@ func (db *BadgerDatabase) NewBatch() DbWithPendingMutations { // IdealBatchSize defines the size of the data batches should ideally add in one write. func (db *BadgerDatabase) IdealBatchSize() int { - return 100 * 1024 + return 50 * 1024 * 1024 } // DiskSize returns the total disk size of the database in bytes. diff --git a/ethdb/bolt_db.go b/ethdb/bolt_db.go index cc6f5eb7c7..0c20ffb7f4 100644 --- a/ethdb/bolt_db.go +++ b/ethdb/bolt_db.go @@ -20,23 +20,16 @@ package ethdb import ( "bytes" "context" - "encoding/binary" - "fmt" "os" "path" "github.com/ledgerwatch/bolt" "github.com/ledgerwatch/turbo-geth/common" - "github.com/ledgerwatch/turbo-geth/common/changeset" "github.com/ledgerwatch/turbo-geth/common/dbutils" "github.com/ledgerwatch/turbo-geth/core/types/accounts" "github.com/ledgerwatch/turbo-geth/log" ) -var OpenFileLimit = 64 - -const HeapSize = 512 * 1024 * 1024 - // BoltDatabase is a wrapper over BoltDb, // compatible with the Database interface. type BoltDatabase struct { @@ -197,6 +190,26 @@ func (db *BoltDatabase) Get(bucket, key []byte) ([]byte, error) { return dat, err } +func Get(db KV, bucket, key []byte) ([]byte, error) { + // Retrieve the key and increment the miss counter if not found + var dat []byte + err := db.View(context.Background(), func(tx Tx) error { + b := tx.Bucket(bucket) + if b != nil { + v, _ := b.Get(key) + if v != nil { + dat = make([]byte, len(v)) + copy(dat, v) + } + } + return nil + }) + if dat == nil { + return nil, ErrKeyNotFound + } + return dat, err +} + // GetIndexChunk returns proper index chunk or return error if index is not created. // key must contain inverted block number in the end func (db *BoltDatabase) GetIndexChunk(bucket, key []byte, timestamp uint64) ([]byte, error) { @@ -244,32 +257,6 @@ func (db *BoltDatabase) GetChangeSetByBlock(hBucket []byte, timestamp uint64) ([ return dat, nil } -// GetAsOf returns the value valid as of a given timestamp. -func (db *BoltDatabase) GetAsOf(bucket, hBucket, key []byte, timestamp uint64) ([]byte, error) { - var dat []byte - err := db.db.View(func(tx *bolt.Tx) error { - v, err := BoltDBFindByHistory(tx, hBucket, key, timestamp) - if err != nil { - log.Debug("BoltDB BoltDBFindByHistory err", "err", err) - } else { - dat = make([]byte, len(v)) - copy(dat, v) - return nil - } - { - v, _ := tx.Bucket(bucket).Get(key) - if v == nil { - return ErrKeyNotFound - } - - dat = make([]byte, len(v)) - copy(dat, v) - return nil - } - }) - return dat, err -} - func HackAddRootToAccountBytes(accNoRoot []byte, root []byte) (accWithRoot []byte, err error) { var acc accounts.Account if err := acc.DecodeForStorage(accNoRoot); err != nil { @@ -369,287 +356,6 @@ func (db *BoltDatabase) MultiWalk(bucket []byte, startkeys [][]byte, fixedbits [ return err } -func (db *BoltDatabase) walkAsOfThinAccounts(startkey []byte, fixedbits int, timestamp uint64, walker func(k []byte, v []byte) (bool, error)) error { - fixedbytes, mask := Bytesmask(fixedbits) - err := db.db.View(func(tx *bolt.Tx) error { - b := tx.Bucket(dbutils.CurrentStateBucket) - if b == nil { - return fmt.Errorf("currentStateBucket not found") - } - hB := tx.Bucket(dbutils.AccountsHistoryBucket) - if hB == nil { - return fmt.Errorf("accountsHistoryBucket not found") - } - csB := tx.Bucket(dbutils.AccountChangeSetBucket) - if csB == nil { - return fmt.Errorf("accountChangeBucket not found") - } - //for state - mainCursor := b.Cursor() - //for historic data - historyCursor := newSplitCursor( - hB, - startkey, - fixedbits, - common.HashLength, /* part1end */ - common.HashLength, /* part2start */ - common.HashLength+8, /* part3start */ - ) - k, v := mainCursor.Seek(startkey) - for k != nil && len(k) > common.HashLength { - k, v = mainCursor.Next() - } - hK, tsEnc, _, hV := historyCursor.Seek() - for hK != nil && binary.BigEndian.Uint64(tsEnc) < timestamp { - hK, tsEnc, _, hV = historyCursor.Next() - } - goOn := true - var err error - for goOn { - //exit or next conditions - if k != nil && fixedbits > 0 && !bytes.Equal(k[:fixedbytes-1], startkey[:fixedbytes-1]) { - k = nil - } - if k != nil && fixedbits > 0 && (k[fixedbytes-1]&mask) != (startkey[fixedbytes-1]&mask) { - k = nil - } - var cmp int - if k == nil { - if hK == nil { - break - } else { - cmp = 1 - } - } else if hK == nil { - cmp = -1 - } else { - cmp = bytes.Compare(k, hK) - } - if cmp < 0 { - goOn, err = walker(k, v) - } else { - index := dbutils.WrapHistoryIndex(hV) - if changeSetBlock, set, ok := index.Search(timestamp); ok { - // set == true if this change was from empty record (non-existent account) to non-empty - // In such case, we do not need to examine changeSet and simply skip the record - if !set { - // Extract value from the changeSet - csKey := dbutils.EncodeTimestamp(changeSetBlock) - changeSetData, _ := csB.Get(csKey) - if changeSetData == nil { - return fmt.Errorf("could not find ChangeSet record for index entry %d (query timestamp %d)", changeSetBlock, timestamp) - } - data, err1 := changeset.AccountChangeSetBytes(changeSetData).FindLast(hK) - if err1 != nil { - return fmt.Errorf("could not find key %x in the ChangeSet record for index entry %d (query timestamp %d)", - hK, - changeSetBlock, - timestamp, - ) - } - if len(data) > 0 { // Skip accounts did not exist - goOn, err = walker(hK, data) - } - } - } else if cmp == 0 { - goOn, err = walker(k, v) - } - } - if goOn { - if cmp <= 0 { - k, v = mainCursor.Next() - for k != nil && len(k) > common.HashLength { - k, v = mainCursor.Next() - } - } - if cmp >= 0 { - hK0 := hK - for hK != nil && (bytes.Equal(hK0, hK) || binary.BigEndian.Uint64(tsEnc) < timestamp) { - hK, tsEnc, _, hV = historyCursor.Next() - } - } - } - } - return err - }) - return err -} - -// splitCursor implements cursor with two keys -// it is used to ignore incarnations in the middle -// of composite storage key, but without -// reconstructing the key -// Instead, the key is split into two parts and -// functions `Seek` and `Next` deliver both -// parts as well as the corresponding value -type splitCursor struct { - c *bolt.Cursor // Unlerlying bolt cursor - startkey []byte // Starting key (also contains bits that need to be preserved) - matchBytes int - mask uint8 - part1end int // Position in the key where the first part ends - part2start int // Position in the key where the second part starts - part3start int // Position in the key where the third part starts -} - -func newSplitCursor(b *bolt.Bucket, startkey []byte, matchBits int, part1end, part2start, part3start int) *splitCursor { - var sc splitCursor - sc.c = b.Cursor() - sc.startkey = startkey - sc.part1end = part1end - sc.part2start = part2start - sc.part3start = part3start - sc.matchBytes, sc.mask = Bytesmask(matchBits) - return &sc -} - -func (sc *splitCursor) matchKey(k []byte) bool { - if k == nil { - return false - } - if sc.matchBytes == 0 { - return true - } - if len(k) < sc.matchBytes { - return false - } - if !bytes.Equal(k[:sc.matchBytes-1], sc.startkey[:sc.matchBytes-1]) { - return false - } - return (k[sc.matchBytes-1] & sc.mask) == (sc.startkey[sc.matchBytes-1] & sc.mask) -} - -func (sc *splitCursor) Seek() (key1, key2, key3, val []byte) { - k, v := sc.c.Seek(sc.startkey) - if !sc.matchKey(k) { - return nil, nil, nil, nil - } - return k[:sc.part1end], k[sc.part2start:sc.part3start], k[sc.part3start:], v -} - -func (sc *splitCursor) Next() (key1, key2, key3, val []byte) { - k, v := sc.c.Next() - if !sc.matchKey(k) { - return nil, nil, nil, nil - } - return k[:sc.part1end], k[sc.part2start:sc.part3start], k[sc.part3start:], v -} - -func (db *BoltDatabase) walkAsOfThinStorage(startkey []byte, fixedbits int, timestamp uint64, walker func(k1, k2, v []byte) (bool, error)) error { - err := db.db.View(func(tx *bolt.Tx) error { - b := tx.Bucket(dbutils.CurrentStateBucket) - if b == nil { - return fmt.Errorf("storageBucket not found") - } - hB := tx.Bucket(dbutils.StorageHistoryBucket) - if hB == nil { - return fmt.Errorf("storageHistoryBucket not found") - } - csB := tx.Bucket(dbutils.StorageChangeSetBucket) - if csB == nil { - return fmt.Errorf("storageChangeBucket not found") - } - startkeyNoInc := make([]byte, len(startkey)-common.IncarnationLength) - copy(startkeyNoInc, startkey[:common.HashLength]) - copy(startkeyNoInc[common.HashLength:], startkey[common.HashLength+common.IncarnationLength:]) - //for storage - mainCursor := newSplitCursor( - b, - startkey, - fixedbits, - common.HashLength, /* part1end */ - common.HashLength+common.IncarnationLength, /* part2start */ - common.HashLength+common.IncarnationLength+common.HashLength, /* part3start */ - ) - //for historic data - historyCursor := newSplitCursor( - hB, - startkeyNoInc, - fixedbits-8*common.IncarnationLength, - common.HashLength, /* part1end */ - common.HashLength, /* part2start */ - common.HashLength*2, /* part3start */ - ) - addrHash, keyHash, _, v := mainCursor.Seek() - hAddrHash, hKeyHash, tsEnc, hV := historyCursor.Seek() - for hKeyHash != nil && binary.BigEndian.Uint64(tsEnc) < timestamp { - hAddrHash, hKeyHash, tsEnc, hV = historyCursor.Next() - } - goOn := true - var err error - for goOn { - var cmp int - if keyHash == nil { - if hKeyHash == nil { - break - } else { - cmp = 1 - } - } else if hKeyHash == nil { - cmp = -1 - } else { - cmp = bytes.Compare(keyHash, hKeyHash) - } - if cmp < 0 { - goOn, err = walker(addrHash, keyHash, v) - } else { - index := dbutils.WrapHistoryIndex(hV) - if changeSetBlock, set, ok := index.Search(timestamp); ok { - // set == true if this change was from empty record (non-existent storage item) to non-empty - // In such case, we do not need to examine changeSet and simply skip the record - if !set { - // Extract value from the changeSet - csKey := dbutils.EncodeTimestamp(changeSetBlock) - changeSetData, _ := csB.Get(csKey) - if changeSetData == nil { - return fmt.Errorf("could not find ChangeSet record for index entry %d (query timestamp %d)", changeSetBlock, timestamp) - } - data, err1 := changeset.StorageChangeSetBytes(changeSetData).FindWithoutIncarnation(hAddrHash, hKeyHash) - if err1 != nil { - return fmt.Errorf("could not find key %x%x in the ChangeSet record for index entry %d (query timestamp %d): %v", - hAddrHash, hKeyHash, - changeSetBlock, - timestamp, - err1, - ) - } - if len(data) > 0 { // Skip deleted entries - goOn, err = walker(hAddrHash, hKeyHash, data) - } - } - } else if cmp == 0 { - goOn, err = walker(addrHash, keyHash, v) - } - } - if goOn { - if cmp <= 0 { - addrHash, keyHash, _, v = mainCursor.Next() - } - if cmp >= 0 { - hKeyHash0 := hKeyHash - for hKeyHash != nil && (bytes.Equal(hKeyHash0, hKeyHash) || binary.BigEndian.Uint64(tsEnc) < timestamp) { - hAddrHash, hKeyHash, tsEnc, hV = historyCursor.Next() - } - } - } - } - return err - }) - return err -} - -func (db *BoltDatabase) WalkAsOf(bucket, hBucket, startkey []byte, fixedbits int, timestamp uint64, walker func(k []byte, v []byte) (bool, error)) error { - //fmt.Printf("WalkAsOf %x %x %x %d %d\n", bucket, hBucket, startkey, fixedbits, timestamp) - if bytes.Equal(bucket, dbutils.CurrentStateBucket) && bytes.Equal(hBucket, dbutils.AccountsHistoryBucket) { - return db.walkAsOfThinAccounts(startkey, fixedbits, timestamp, walker) - } else if bytes.Equal(bucket, dbutils.CurrentStateBucket) && bytes.Equal(hBucket, dbutils.StorageHistoryBucket) { - return db.walkAsOfThinStorage(startkey, fixedbits, timestamp, func(k1, k2, v []byte) (bool, error) { - return walker(append(common.CopyBytes(k1), k2...), v) - }) - } - panic("Not implemented for arbitrary buckets") -} - // Delete deletes the key from the queue and database func (db *BoltDatabase) Delete(bucket, key []byte) error { // Execute the actual operation @@ -748,77 +454,3 @@ func NewDatabaseWithFreezer(db Database, dir, suffix string) (Database, error) { // FIXME: implement freezer in Turbo-Geth return db, nil } - -func BoltDBFindByHistory(tx *bolt.Tx, hBucket []byte, key []byte, timestamp uint64) ([]byte, error) { - //check - hB := tx.Bucket(hBucket) - if hB == nil { - return nil, ErrKeyNotFound - } - var keyF []byte - if bytes.Equal(dbutils.StorageHistoryBucket, hBucket) { - keyF = make([]byte, len(key)-common.IncarnationLength) - copy(keyF, key[:common.HashLength]) - copy(keyF[common.HashLength:], key[common.HashLength+common.IncarnationLength:]) - } else { - keyF = common.CopyBytes(key) - } - - c := hB.Cursor() - k, v := c.Seek(dbutils.IndexChunkKey(key, timestamp)) - if !bytes.HasPrefix(k, keyF) { - return nil, ErrKeyNotFound - } - index := dbutils.WrapHistoryIndex(v) - - changeSetBlock, set, ok := index.Search(timestamp) - if !ok { - return nil, ErrKeyNotFound - } - // set == true if this change was from empty record (non-existent account) to non-empty - // In such case, we do not need to examine changeSet and return empty data - if set { - return []byte{}, nil - } - csB := tx.Bucket(dbutils.ChangeSetByIndexBucket(hBucket)) - if csB == nil { - return nil, ErrKeyNotFound - } - - csKey := dbutils.EncodeTimestamp(changeSetBlock) - changeSetData, _ := csB.Get(csKey) - - var ( - data []byte - err error - ) - switch { - case bytes.Equal(dbutils.AccountsHistoryBucket, hBucket): - data, err = changeset.AccountChangeSetBytes(changeSetData).FindLast(key) - case bytes.Equal(dbutils.StorageHistoryBucket, hBucket): - data, err = changeset.StorageChangeSetBytes(changeSetData).FindWithoutIncarnation(key[:common.HashLength], key[common.HashLength+common.IncarnationLength:]) - } - if err != nil { - return nil, ErrKeyNotFound - } - - //restore codehash - if bytes.Equal(dbutils.AccountsHistoryBucket, hBucket) { - var acc accounts.Account - if err := acc.DecodeForStorage(data); err != nil { - return nil, err - } - if acc.Incarnation > 0 && acc.IsEmptyCodeHash() { - codeBucket := tx.Bucket(dbutils.ContractCodeBucket) - codeHash, _ := codeBucket.Get(dbutils.GenerateStoragePrefix(key, acc.Incarnation)) - if len(codeHash) > 0 { - acc.CodeHash = common.BytesToHash(codeHash) - } - data = make([]byte, acc.EncodingLengthForStorage()) - acc.EncodeForStorage(data) - } - return data, nil - } - - return data, nil -} diff --git a/ethdb/history.go b/ethdb/history.go new file mode 100644 index 0000000000..58fc2c0beb --- /dev/null +++ b/ethdb/history.go @@ -0,0 +1,111 @@ +package ethdb + +import ( + "bytes" + "context" + + "github.com/ledgerwatch/turbo-geth/common" + "github.com/ledgerwatch/turbo-geth/common/changeset" + "github.com/ledgerwatch/turbo-geth/common/dbutils" + "github.com/ledgerwatch/turbo-geth/core/types/accounts" + "github.com/ledgerwatch/turbo-geth/log" +) + +func GetAsOf(db KV, bucket, hBucket, key []byte, timestamp uint64) ([]byte, error) { + var dat []byte + err := db.View(context.Background(), func(tx Tx) error { + v, err := FindByHistory(tx, hBucket, key, timestamp) + if err != nil { + log.Debug("FindByHistory err", "err", err) + } else { + dat = make([]byte, len(v)) + copy(dat, v) + return nil + } + { + v, _ := tx.Bucket(bucket).Get(key) + if v == nil { + return ErrKeyNotFound + } + + dat = make([]byte, len(v)) + copy(dat, v) + return nil + } + }) + return dat, err +} + +func FindByHistory(tx Tx, hBucket []byte, key []byte, timestamp uint64) ([]byte, error) { + //check + hB := tx.Bucket(hBucket) + if hB == nil { + return nil, ErrKeyNotFound + } + var keyF []byte + if bytes.Equal(dbutils.StorageHistoryBucket, hBucket) { + keyF = make([]byte, len(key)-common.IncarnationLength) + copy(keyF, key[:common.HashLength]) + copy(keyF[common.HashLength:], key[common.HashLength+common.IncarnationLength:]) + } else { + keyF = common.CopyBytes(key) + } + + c := hB.Cursor() + k, v, err := c.Seek(dbutils.IndexChunkKey(key, timestamp)) + if err != nil { + return nil, err + } + if !bytes.HasPrefix(k, keyF) { + return nil, ErrKeyNotFound + } + index := dbutils.WrapHistoryIndex(v) + + changeSetBlock, set, ok := index.Search(timestamp) + if !ok { + return nil, ErrKeyNotFound + } + // set == true if this change was from empty record (non-existent account) to non-empty + // In such case, we do not need to examine changeSet and return empty data + if set { + return []byte{}, nil + } + csB := tx.Bucket(dbutils.ChangeSetByIndexBucket(hBucket)) + if csB == nil { + return nil, ErrKeyNotFound + } + + csKey := dbutils.EncodeTimestamp(changeSetBlock) + changeSetData, _ := csB.Get(csKey) + + var data []byte + switch { + case bytes.Equal(dbutils.AccountsHistoryBucket, hBucket): + data, err = changeset.AccountChangeSetBytes(changeSetData).FindLast(key) + case bytes.Equal(dbutils.StorageHistoryBucket, hBucket): + data, err = changeset.StorageChangeSetBytes(changeSetData).FindWithoutIncarnation(key[:common.HashLength], key[common.HashLength+common.IncarnationLength:]) + } + if err != nil { + return nil, ErrKeyNotFound + } + + //restore codehash + if bytes.Equal(dbutils.AccountsHistoryBucket, hBucket) { + var acc accounts.Account + if err := acc.DecodeForStorage(data); err != nil { + return nil, err + } + if acc.Incarnation > 0 && acc.IsEmptyCodeHash() { + codeBucket := tx.Bucket(dbutils.ContractCodeBucket) + codeHash, _ := codeBucket.Get(dbutils.GenerateStoragePrefix(key, acc.Incarnation)) + if len(codeHash) > 0 { + acc.CodeHash = common.BytesToHash(codeHash) + } + data = make([]byte, acc.EncodingLengthForStorage()) + acc.EncodeForStorage(data) + } + return data, nil + } + + return data, nil +} diff --git a/ethdb/interface.go b/ethdb/interface.go index 3f554749f1..066b5ba5f4 100644 --- a/ethdb/interface.go +++ b/ethdb/interface.go @@ -42,10 +42,6 @@ type Getter interface { // Key must contain 8byte inverted block number in the end. GetIndexChunk(bucket, key []byte, timestamp uint64) ([]byte, error) - // GetAsOf returns the value valid as of a given timestamp. - // timestamp == block number - GetAsOf(bucket, hBucket, key []byte, timestamp uint64) ([]byte, error) - // Has indicates whether a key exists in the database. Has(bucket, key []byte) (bool, error) @@ -57,8 +53,6 @@ type Getter interface { // MultiWalk is similar to multiple Walk calls folded into one. MultiWalk(bucket []byte, startkeys [][]byte, fixedbits []int, walker func(int, []byte, []byte) error) error - - WalkAsOf(bucket, hBucket, startkey []byte, fixedbits int, timestamp uint64, walker func([]byte, []byte) (bool, error)) error } type GetterPutter interface { diff --git a/ethdb/kv_abstract.go b/ethdb/kv_abstract.go index c1f15a5280..58073413d4 100644 --- a/ethdb/kv_abstract.go +++ b/ethdb/kv_abstract.go @@ -5,8 +5,8 @@ import ( ) type KV interface { - View(ctx context.Context, f func(tx Tx) error) (err error) - Update(ctx context.Context, f func(tx Tx) error) (err error) + View(ctx context.Context, f func(tx Tx) error) error + Update(ctx context.Context, f func(tx Tx) error) error Close() Begin(ctx context.Context, writable bool) (Tx, error) diff --git a/ethdb/kv_bolt.go b/ethdb/kv_bolt.go index 33a58ce74f..4efd135e5e 100644 --- a/ethdb/kv_bolt.go +++ b/ethdb/kv_bolt.go @@ -64,21 +64,21 @@ func (opts boltOpts) Path(path string) boltOpts { return opts } -func (opts boltOpts) Open(ctx context.Context) (db KV, err error) { - boltDB, err := bolt.Open(opts.path, 0600, opts.Bolt) - if err != nil { - return nil, err - } - if err := boltDB.Update(func(tx *bolt.Tx) error { - for _, name := range dbutils.Buckets { - _, createErr := tx.CreateBucketIfNotExists(name, false) - if createErr != nil { - return createErr +// WrapBoltDb provides a way for the code to gradually migrate +// to the abstract interface +func (opts boltOpts) WrapBoltDb(boltDB *bolt.DB) (db KV, err error) { + if !opts.Bolt.ReadOnly { + if err := boltDB.Update(func(tx *bolt.Tx) error { + for _, name := range dbutils.Buckets { + _, createErr := tx.CreateBucketIfNotExists(name, false) + if createErr != nil { + return createErr + } } + return nil + }); err != nil { + return nil, err } - return nil - }); err != nil { - return nil, err } return &BoltKV{ opts: opts, @@ -87,6 +87,14 @@ func (opts boltOpts) Open(ctx context.Context) (db KV, err error) { }, nil } +func (opts boltOpts) Open(ctx context.Context) (db KV, err error) { + boltDB, err := bolt.Open(opts.path, 0600, opts.Bolt) + if err != nil { + return nil, err + } + return opts.WrapBoltDb(boltDB) +} + func (opts boltOpts) MustOpen(ctx context.Context) KV { db, err := opts.Open(ctx) if err != nil { diff --git a/ethdb/mutation.go b/ethdb/mutation.go index 4b96ca04a6..234d41d385 100644 --- a/ethdb/mutation.go +++ b/ethdb/mutation.go @@ -121,11 +121,6 @@ func (m *mutation) IdealBatchSize() int { return m.db.IdealBatchSize() } -func (m *mutation) GetAsOf(bucket, hBucket, key []byte, timestamp uint64) ([]byte, error) { - m.panicOnEmptyDB() - return m.db.GetAsOf(bucket, hBucket, key, timestamp) -} - // WARNING: Merged mem/DB walk is not implemented func (m *mutation) Walk(bucket, startkey []byte, fixedbits int, walker func([]byte, []byte) (bool, error)) error { m.panicOnEmptyDB() @@ -138,11 +133,6 @@ func (m *mutation) MultiWalk(bucket []byte, startkeys [][]byte, fixedbits []int, return m.db.MultiWalk(bucket, startkeys, fixedbits, walker) } -func (m *mutation) WalkAsOf(bucket, hBucket, startkey []byte, fixedbits int, timestamp uint64, walker func([]byte, []byte) (bool, error)) error { - m.panicOnEmptyDB() - return m.db.WalkAsOf(bucket, hBucket, startkey, fixedbits, timestamp, walker) -} - func (m *mutation) Delete(bucket, key []byte) error { m.mu.Lock() defer m.mu.Unlock() @@ -271,10 +261,6 @@ func (d *RWCounterDecorator) Get(bucket, key []byte) ([]byte, error) { return d.Database.Get(bucket, key) } -func (d *RWCounterDecorator) GetAsOf(bucket, hBucket, key []byte, timestamp uint64) ([]byte, error) { - atomic.AddUint64(&d.DBCounterStats.GetAsOf, 1) - return d.Database.GetAsOf(bucket, hBucket, key, timestamp) -} func (d *RWCounterDecorator) Has(bucket, key []byte) (bool, error) { atomic.AddUint64(&d.DBCounterStats.Has, 1) return d.Database.Has(bucket, key) @@ -287,10 +273,6 @@ func (d *RWCounterDecorator) MultiWalk(bucket []byte, startkeys [][]byte, fixedb atomic.AddUint64(&d.DBCounterStats.MultiWalk, 1) return d.Database.MultiWalk(bucket, startkeys, fixedbits, walker) } -func (d *RWCounterDecorator) WalkAsOf(bucket, hBucket, startkey []byte, fixedbits int, timestamp uint64, walker func([]byte, []byte) (bool, error)) error { - atomic.AddUint64(&d.DBCounterStats.WalkAsOf, 1) - return d.Database.WalkAsOf(bucket, hBucket, startkey, fixedbits, timestamp, walker) -} func (d *RWCounterDecorator) Delete(bucket, key []byte) error { atomic.AddUint64(&d.DBCounterStats.Delete, 1) return d.Database.Delete(bucket, key) diff --git a/ethdb/remote_bolt_db.go b/ethdb/remote_bolt_db.go index 4c2e55771f..4704ec7937 100644 --- a/ethdb/remote_bolt_db.go +++ b/ethdb/remote_bolt_db.go @@ -129,47 +129,6 @@ func (db *RemoteBoltDatabase) GetIndexChunk(bucket, key []byte, timestamp uint64 return dat, err } -// GetAsOf returns the value valid as of a given timestamp. -//func (db *RemoteBoltDatabase) GetAsOf(bucket, hBucket, key []byte, timestamp uint64) ([]byte, error) { -// return db.db.CmdGetAsOf(context.Background(), bucket, hBucket, key, timestamp) -//} - -// GetAsOf returns the value valid as of a given timestamp. -func (db *RemoteBoltDatabase) GetAsOf(bucket, hBucket, key []byte, timestamp uint64) ([]byte, error) { - composite, _ := dbutils.CompositeKeySuffix(key, timestamp) - var dat []byte - err := db.db.View(context.Background(), func(tx Tx) error { - { - hK, hV, err := tx.Bucket(hBucket).Cursor().Seek(composite) - if err != nil { - return err - } - - if hK != nil && bytes.HasPrefix(hK, key) { - dat = make([]byte, len(hV)) - copy(dat, hV) - return nil - } - } - { - v, err := tx.Bucket(bucket).Get(key) - if err != nil { - return err - } - if v == nil { - return ErrKeyNotFound - } - - dat = make([]byte, len(v)) - copy(dat, v) - return nil - } - - return ErrKeyNotFound - }) - return dat, err -} - func (db *RemoteBoltDatabase) Walk(bucket, startkey []byte, fixedbits int, walker func(k, v []byte) (bool, error)) error { fixedbytes, mask := Bytesmask(fixedbits) err := db.db.View(context.Background(), func(tx Tx) error { diff --git a/ethdb/walk.go b/ethdb/walk.go index cb633e1dce..af506b6a21 100644 --- a/ethdb/walk.go +++ b/ethdb/walk.go @@ -17,6 +17,9 @@ package ethdb import ( + "bytes" + "context" + "encoding/binary" "fmt" "github.com/ledgerwatch/turbo-geth/common/changeset" @@ -25,8 +28,331 @@ import ( "github.com/ledgerwatch/turbo-geth/common/dbutils" ) -var EndSuffix = []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff} +func walkAsOfThinAccounts(db KV, startkey []byte, fixedbits int, timestamp uint64, walker func(k []byte, v []byte) (bool, error)) error { + fixedbytes, mask := Bytesmask(fixedbits) + err := db.View(context.Background(), func(tx Tx) error { + b := tx.Bucket(dbutils.CurrentStateBucket) + if b == nil { + return fmt.Errorf("currentStateBucket not found") + } + hB := tx.Bucket(dbutils.AccountsHistoryBucket) + if hB == nil { + return fmt.Errorf("accountsHistoryBucket not found") + } + csB := tx.Bucket(dbutils.AccountChangeSetBucket) + if csB == nil { + return fmt.Errorf("accountChangeBucket not found") + } + //for state + mainCursor := b.Cursor() + //for historic data + historyCursor := newSplitCursor( + hB, + startkey, + fixedbits, + common.HashLength, /* part1end */ + common.HashLength, /* part2start */ + common.HashLength+8, /* part3start */ + ) + k, v, err1 := mainCursor.Seek(startkey) + if err1 != nil { + return err1 + } + for k != nil && len(k) > common.HashLength { + k, v, err1 = mainCursor.Next() + if err1 != nil { + return err1 + } + } + hK, tsEnc, _, hV, err1 := historyCursor.Seek() + if err1 != nil { + return err1 + } + for hK != nil && binary.BigEndian.Uint64(tsEnc) < timestamp { + hK, tsEnc, _, hV, err1 = historyCursor.Next() + if err1 != nil { + return err1 + } + } + goOn := true + var err error + for goOn { + //exit or next conditions + if k != nil && fixedbits > 0 && !bytes.Equal(k[:fixedbytes-1], startkey[:fixedbytes-1]) { + k = nil + } + if k != nil && fixedbits > 0 && (k[fixedbytes-1]&mask) != (startkey[fixedbytes-1]&mask) { + k = nil + } + var cmp int + if k == nil { + if hK == nil { + break + } else { + cmp = 1 + } + } else if hK == nil { + cmp = -1 + } else { + cmp = bytes.Compare(k, hK) + } + if cmp < 0 { + goOn, err = walker(k, v) + } else { + index := dbutils.WrapHistoryIndex(hV) + if changeSetBlock, set, ok := index.Search(timestamp); ok { + // set == true if this change was from empty record (non-existent account) to non-empty + // In such case, we do not need to examine changeSet and simply skip the record + if !set { + // Extract value from the changeSet + csKey := dbutils.EncodeTimestamp(changeSetBlock) + changeSetData, _ := csB.Get(csKey) + if changeSetData == nil { + return fmt.Errorf("could not find ChangeSet record for index entry %d (query timestamp %d)", changeSetBlock, timestamp) + } + data, err2 := changeset.AccountChangeSetBytes(changeSetData).FindLast(hK) + if err2 != nil { + return fmt.Errorf("could not find key %x in the ChangeSet record for index entry %d (query timestamp %d): %v", + hK, + changeSetBlock, + timestamp, + err2, + ) + } + if len(data) > 0 { // Skip accounts did not exist + goOn, err = walker(hK, data) + } + } + } else if cmp == 0 { + goOn, err = walker(k, v) + } + } + if goOn { + if cmp <= 0 { + k, v, err1 = mainCursor.Next() + if err1 != nil { + return err1 + } + for k != nil && len(k) > common.HashLength { + k, v, err1 = mainCursor.Next() + if err1 != nil { + return err1 + } + } + } + if cmp >= 0 { + hK0 := hK + for hK != nil && (bytes.Equal(hK0, hK) || binary.BigEndian.Uint64(tsEnc) < timestamp) { + hK, tsEnc, _, hV, err1 = historyCursor.Next() + if err1 != nil { + return err1 + } + } + } + } + } + return err + }) + return err +} +// splitCursor implements cursor with two keys +// it is used to ignore incarnations in the middle +// of composite storage key, but without +// reconstructing the key +// Instead, the key is split into two parts and +// functions `Seek` and `Next` deliver both +// parts as well as the corresponding value +type splitCursor struct { + c Cursor // Unlerlying bolt cursor + startkey []byte // Starting key (also contains bits that need to be preserved) + matchBytes int + mask uint8 + part1end int // Position in the key where the first part ends + part2start int // Position in the key where the second part starts + part3start int // Position in the key where the third part starts +} + +func newSplitCursor(b Bucket, startkey []byte, matchBits int, part1end, part2start, part3start int) *splitCursor { + var sc splitCursor + sc.c = b.Cursor() + sc.startkey = startkey + sc.part1end = part1end + sc.part2start = part2start + sc.part3start = part3start + sc.matchBytes, sc.mask = Bytesmask(matchBits) + return &sc +} + +func (sc *splitCursor) matchKey(k []byte) bool { + if k == nil { + return false + } + if sc.matchBytes == 0 { + return true + } + if len(k) < sc.matchBytes { + return false + } + if !bytes.Equal(k[:sc.matchBytes-1], sc.startkey[:sc.matchBytes-1]) { + return false + } + return (k[sc.matchBytes-1] & sc.mask) == (sc.startkey[sc.matchBytes-1] & sc.mask) +} + +func (sc *splitCursor) Seek() (key1, key2, key3, val []byte, err error) { + k, v, err1 := sc.c.Seek(sc.startkey) + if err1 != nil { + return nil, nil, nil, nil, err1 + } + if !sc.matchKey(k) { + return nil, nil, nil, nil, nil + } + return k[:sc.part1end], k[sc.part2start:sc.part3start], k[sc.part3start:], v, nil +} + +func (sc *splitCursor) Next() (key1, key2, key3, val []byte, err error) { + k, v, err1 := sc.c.Next() + if err1 != nil { + return nil, nil, nil, nil, err1 + } + if !sc.matchKey(k) { + return nil, nil, nil, nil, nil + } + return k[:sc.part1end], k[sc.part2start:sc.part3start], k[sc.part3start:], v, nil +} + +func walkAsOfThinStorage(db KV, startkey []byte, fixedbits int, timestamp uint64, walker func(k1, k2, v []byte) (bool, error)) error { + err := db.View(context.Background(), func(tx Tx) error { + b := tx.Bucket(dbutils.CurrentStateBucket) + if b == nil { + return fmt.Errorf("storageBucket not found") + } + hB := tx.Bucket(dbutils.StorageHistoryBucket) + if hB == nil { + return fmt.Errorf("storageHistoryBucket not found") + } + csB := tx.Bucket(dbutils.StorageChangeSetBucket) + if csB == nil { + return fmt.Errorf("storageChangeBucket not found") + } + startkeyNoInc := make([]byte, len(startkey)-common.IncarnationLength) + copy(startkeyNoInc, startkey[:common.HashLength]) + copy(startkeyNoInc[common.HashLength:], startkey[common.HashLength+common.IncarnationLength:]) + //for storage + mainCursor := newSplitCursor( + b, + startkey, + fixedbits, + common.HashLength, /* part1end */ + common.HashLength+common.IncarnationLength, /* part2start */ + common.HashLength+common.IncarnationLength+common.HashLength, /* part3start */ + ) + //for historic data + historyCursor := newSplitCursor( + hB, + startkeyNoInc, + fixedbits-8*common.IncarnationLength, + common.HashLength, /* part1end */ + common.HashLength, /* part2start */ + common.HashLength*2, /* part3start */ + ) + addrHash, keyHash, _, v, err1 := mainCursor.Seek() + if err1 != nil { + return err1 + } + hAddrHash, hKeyHash, tsEnc, hV, err2 := historyCursor.Seek() + if err2 != nil { + return err2 + } + for hKeyHash != nil && binary.BigEndian.Uint64(tsEnc) < timestamp { + hAddrHash, hKeyHash, tsEnc, hV, err2 = historyCursor.Next() + if err2 != nil { + return err2 + } + } + goOn := true + var err error + for goOn { + var cmp int + if keyHash == nil { + if hKeyHash == nil { + break + } else { + cmp = 1 + } + } else if hKeyHash == nil { + cmp = -1 + } else { + cmp = bytes.Compare(keyHash, hKeyHash) + } + if cmp < 0 { + goOn, err = walker(addrHash, keyHash, v) + } else { + index := dbutils.WrapHistoryIndex(hV) + if changeSetBlock, set, ok := index.Search(timestamp); ok { + // set == true if this change was from empty record (non-existent storage item) to non-empty + // In such case, we do not need to examine changeSet and simply skip the record + if !set { + // Extract value from the changeSet + csKey := dbutils.EncodeTimestamp(changeSetBlock) + changeSetData, _ := csB.Get(csKey) + if changeSetData == nil { + return fmt.Errorf("could not find ChangeSet record for index entry %d (query timestamp %d)", changeSetBlock, timestamp) + } + data, err3 := changeset.StorageChangeSetBytes(changeSetData).FindWithoutIncarnation(hAddrHash, hKeyHash) + if err3 != nil { + return fmt.Errorf("could not find key %x%x in the ChangeSet record for index entry %d (query timestamp %d): %v", + hAddrHash, hKeyHash, + changeSetBlock, + timestamp, + err3, + ) + } + if len(data) > 0 { // Skip deleted entries + goOn, err = walker(hAddrHash, hKeyHash, data) + } + } + } else if cmp == 0 { + goOn, err = walker(addrHash, keyHash, v) + } + } + if goOn { + if cmp <= 0 { + addrHash, keyHash, _, v, err1 = mainCursor.Next() + if err1 != nil { + return err1 + } + } + if cmp >= 0 { + hKeyHash0 := hKeyHash + for hKeyHash != nil && (bytes.Equal(hKeyHash0, hKeyHash) || binary.BigEndian.Uint64(tsEnc) < timestamp) { + hAddrHash, hKeyHash, tsEnc, hV, err2 = historyCursor.Next() + if err2 != nil { + return err2 + } + } + } + } + } + return err + }) + return err +} + +func WalkAsOf(db KV, bucket, hBucket, startkey []byte, fixedbits int, timestamp uint64, walker func(k []byte, v []byte) (bool, error)) error { + //fmt.Printf("WalkAsOf %x %x %x %d %d\n", bucket, hBucket, startkey, fixedbits, timestamp) + if bytes.Equal(bucket, dbutils.CurrentStateBucket) && bytes.Equal(hBucket, dbutils.AccountsHistoryBucket) { + return walkAsOfThinAccounts(db, startkey, fixedbits, timestamp, walker) + } else if bytes.Equal(bucket, dbutils.CurrentStateBucket) && bytes.Equal(hBucket, dbutils.StorageHistoryBucket) { + return walkAsOfThinStorage(db, startkey, fixedbits, timestamp, func(k1, k2, v []byte) (bool, error) { + return walker(append(common.CopyBytes(k1), k2...), v) + }) + } + panic("Not implemented for arbitrary buckets") +} + +var EndSuffix = []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff} func GetModifiedAccounts(db Getter, startTimestamp, endTimestamp uint64) ([]common.Address, error) { keys := make(map[common.Hash]struct{}) diff --git a/miner/worker.go b/miner/worker.go index b471f17297..4f91e52fc3 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -1179,10 +1179,7 @@ func GetState(blockchain *core.BlockChain, parent *types.Block) (*state.IntraBlo tds.SetResolveReads(false) tds.SetNoHistory(true) - statedb, _, err := blockchain.StateAt(parent.NumberU64()) - if err != nil { - return nil, nil, err - } + statedb := state.New(tds) return statedb, tds, nil } diff --git a/tests/block_test_util.go b/tests/block_test_util.go index 4d91e7881a..3ae9a64f5f 100644 --- a/tests/block_test_util.go +++ b/tests/block_test_util.go @@ -133,10 +133,7 @@ func (t *BlockTest) Run(_ bool) error { if common.Hash(t.json.BestBlock) != cmlast { return fmt.Errorf("last block hash validation mismatch: want: %x, have: %x", t.json.BestBlock, cmlast) } - newDB, _, err := chain.State() - if err != nil { - return err - } + newDB := state.New(state.NewDbState(db.AbstractKV(), chain.CurrentBlock().NumberU64())) if err = t.validatePostState(newDB); err != nil { return fmt.Errorf("post state validation failed: %v", err) } diff --git a/tests/statedb_chain_test.go b/tests/statedb_chain_test.go index 02451b3173..776cfe6563 100644 --- a/tests/statedb_chain_test.go +++ b/tests/statedb_chain_test.go @@ -26,6 +26,7 @@ import ( "github.com/ledgerwatch/turbo-geth/common" "github.com/ledgerwatch/turbo-geth/consensus/ethash" "github.com/ledgerwatch/turbo-geth/core" + "github.com/ledgerwatch/turbo-geth/core/state" "github.com/ledgerwatch/turbo-geth/core/types" "github.com/ledgerwatch/turbo-geth/core/vm" "github.com/ledgerwatch/turbo-geth/crypto" @@ -101,7 +102,7 @@ func TestSelfDestructReceive(t *testing.T) { contractBackend.Commit() }) - st, _, _ := blockchain.State() + st := state.New(state.NewDbState(db.AbstractKV(), blockchain.CurrentBlock().NumberU64())) if !st.Exist(address) { t.Error("expected account to exist") } @@ -130,7 +131,7 @@ func TestSelfDestructReceive(t *testing.T) { // and that means that the state of the accounts written in the first block was correct. // This test checks that the storage root of the account is properly set to the root of the empty tree - st, _, _ = blockchain.State() + st = state.New(state.NewDbState(db.AbstractKV(), blockchain.CurrentBlock().NumberU64())) if !st.Exist(address) { t.Error("expected account to exist") } diff --git a/tests/statedb_insert_chain_transaction_test.go b/tests/statedb_insert_chain_transaction_test.go index eddc63198c..d65107540e 100644 --- a/tests/statedb_insert_chain_transaction_test.go +++ b/tests/statedb_insert_chain_transaction_test.go @@ -12,6 +12,7 @@ import ( "github.com/ledgerwatch/turbo-geth/common" "github.com/ledgerwatch/turbo-geth/consensus/ethash" "github.com/ledgerwatch/turbo-geth/core" + "github.com/ledgerwatch/turbo-geth/core/state" "github.com/ledgerwatch/turbo-geth/core/types" "github.com/ledgerwatch/turbo-geth/core/vm" "github.com/ledgerwatch/turbo-geth/crypto" @@ -26,7 +27,7 @@ func TestInsertIncorrectStateRootDifferentAccounts(t *testing.T) { fromKey := data.keys[0] to := common.Address{1} - blockchain, blocks, receipts, err := genBlocks(data.genesisSpec, map[int]tx{ + blockchain, _, blocks, receipts, err := genBlocks(data.genesisSpec, map[int]tx{ 0: { getBlockTx(from, to, big.NewInt(1000)), fromKey, @@ -54,7 +55,8 @@ func TestInsertIncorrectStateRootDifferentAccounts(t *testing.T) { } // insert a correct block - blockchain, blocks, _, err = genBlocks(data.genesisSpec, map[int]tx{ + var kv ethdb.KV + blockchain, kv, blocks, _, err = genBlocks(data.genesisSpec, map[int]tx{ 0: { getBlockTx(data.addresses[1], to, big.NewInt(5000)), data.keys[1], @@ -68,7 +70,7 @@ func TestInsertIncorrectStateRootDifferentAccounts(t *testing.T) { t.Fatal(err) } - st, _, _ := blockchain.State() + st := state.New(state.NewDbState(kv, blockchain.CurrentBlock().NumberU64())) if !st.Exist(to) { t.Error("expected account to exist") } @@ -90,7 +92,7 @@ func TestInsertIncorrectStateRootSameAccount(t *testing.T) { fromKey := data.keys[0] to := common.Address{1} - blockchain, blocks, receipts, err := genBlocks(data.genesisSpec, map[int]tx{ + blockchain, _, blocks, receipts, err := genBlocks(data.genesisSpec, map[int]tx{ 0: { getBlockTx(from, to, big.NewInt(1000)), fromKey, @@ -118,7 +120,8 @@ func TestInsertIncorrectStateRootSameAccount(t *testing.T) { } // insert a correct block - blockchain, blocks, _, err = genBlocks(data.genesisSpec, map[int]tx{ + var kv ethdb.KV + blockchain, kv, blocks, _, err = genBlocks(data.genesisSpec, map[int]tx{ 0: { getBlockTx(from, to, big.NewInt(5000)), fromKey, @@ -132,7 +135,7 @@ func TestInsertIncorrectStateRootSameAccount(t *testing.T) { t.Fatal(err) } - st, _, _ := blockchain.State() + st := state.New(state.NewDbState(kv, blockchain.CurrentBlock().NumberU64())) if !st.Exist(to) { t.Error("expected account to exist") } @@ -151,7 +154,7 @@ func TestInsertIncorrectStateRootSameAccountSameAmount(t *testing.T) { fromKey := data.keys[0] to := common.Address{1} - blockchain, blocks, receipts, err := genBlocks(data.genesisSpec, map[int]tx{ + blockchain, _, blocks, receipts, err := genBlocks(data.genesisSpec, map[int]tx{ 0: { getBlockTx(from, to, big.NewInt(1000)), fromKey, @@ -176,7 +179,8 @@ func TestInsertIncorrectStateRootSameAccountSameAmount(t *testing.T) { } // insert a correct block - blockchain, blocks, _, err = genBlocks(data.genesisSpec, map[int]tx{ + var kv ethdb.KV + blockchain, kv, blocks, _, err = genBlocks(data.genesisSpec, map[int]tx{ 0: { getBlockTx(from, to, big.NewInt(1000)), fromKey, @@ -190,7 +194,7 @@ func TestInsertIncorrectStateRootSameAccountSameAmount(t *testing.T) { t.Fatal(err) } - st, _, _ := blockchain.State() + st := state.New(state.NewDbState(kv, blockchain.CurrentBlock().NumberU64())) if !st.Exist(to) { t.Error("expected account to exist") } @@ -209,7 +213,7 @@ func TestInsertIncorrectStateRootAllFundsRoot(t *testing.T) { fromKey := data.keys[0] to := common.Address{1} - blockchain, blocks, receipts, err := genBlocks(data.genesisSpec, map[int]tx{ + blockchain, _, blocks, receipts, err := genBlocks(data.genesisSpec, map[int]tx{ 0: { getBlockTx(from, to, big.NewInt(1000)), fromKey, @@ -234,7 +238,8 @@ func TestInsertIncorrectStateRootAllFundsRoot(t *testing.T) { } // insert a correct block - blockchain, blocks, _, err = genBlocks(data.genesisSpec, map[int]tx{ + var kv ethdb.KV + blockchain, kv, blocks, _, err = genBlocks(data.genesisSpec, map[int]tx{ 0: { getBlockTx(from, to, big.NewInt(1000)), fromKey, @@ -248,7 +253,7 @@ func TestInsertIncorrectStateRootAllFundsRoot(t *testing.T) { t.Fatal(err) } - st, _, _ := blockchain.State() + st := state.New(state.NewDbState(kv, blockchain.CurrentBlock().NumberU64())) if !st.Exist(to) { t.Error("expected account to exist") } @@ -267,7 +272,7 @@ func TestInsertIncorrectStateRootAllFunds(t *testing.T) { fromKey := data.keys[0] to := common.Address{1} - blockchain, blocks, receipts, err := genBlocks(data.genesisSpec, map[int]tx{ + blockchain, _, blocks, receipts, err := genBlocks(data.genesisSpec, map[int]tx{ 0: { getBlockTx(from, to, big.NewInt(3000)), fromKey, @@ -291,7 +296,8 @@ func TestInsertIncorrectStateRootAllFunds(t *testing.T) { } // insert a correct block - blockchain, blocks, _, err = genBlocks(data.genesisSpec, map[int]tx{ + var kv ethdb.KV + blockchain, kv, blocks, _, err = genBlocks(data.genesisSpec, map[int]tx{ 0: { getBlockTx(from, to, big.NewInt(1000)), fromKey, @@ -305,7 +311,7 @@ func TestInsertIncorrectStateRootAllFunds(t *testing.T) { t.Fatal(err) } - st, _, _ := blockchain.State() + st := state.New(state.NewDbState(kv, blockchain.CurrentBlock().NumberU64())) if !st.Exist(to) { t.Error("expected account to exist") } @@ -327,7 +333,7 @@ func TestAccountDeployIncorrectRoot(t *testing.T) { var contractAddress common.Address eipContract := new(contracts.Testcontract) - blockchain, blocks, receipts, err := genBlocks(data.genesisSpec, map[int]tx{ + blockchain, kv, blocks, receipts, err := genBlocks(data.genesisSpec, map[int]tx{ 0: { getBlockTx(from, to, big.NewInt(10)), fromKey, @@ -346,7 +352,7 @@ func TestAccountDeployIncorrectRoot(t *testing.T) { t.Fatal(err) } - st, _, _ := blockchain.State() + st := state.New(state.NewDbState(kv, blockchain.CurrentBlock().NumberU64())) if !st.Exist(from) { t.Error("expected account to exist") } @@ -364,7 +370,7 @@ func TestAccountDeployIncorrectRoot(t *testing.T) { t.Fatal("should fail") } - st, _, _ = blockchain.State() + st = state.New(state.NewDbState(kv, blockchain.CurrentBlock().NumberU64())) if !st.Exist(from) { t.Error("expected account to exist") } @@ -378,7 +384,7 @@ func TestAccountDeployIncorrectRoot(t *testing.T) { t.Fatal(err) } - st, _, _ = blockchain.State() + st = state.New(state.NewDbState(kv, blockchain.CurrentBlock().NumberU64())) if !st.Exist(from) { t.Error("expected account to exist") } @@ -397,7 +403,7 @@ func TestAccountCreateIncorrectRoot(t *testing.T) { var contractAddress common.Address eipContract := new(contracts.Testcontract) - blockchain, blocks, receipts, err := genBlocks(data.genesisSpec, map[int]tx{ + blockchain, kv, blocks, receipts, err := genBlocks(data.genesisSpec, map[int]tx{ 0: { getBlockTx(from, to, big.NewInt(10)), fromKey, @@ -420,7 +426,7 @@ func TestAccountCreateIncorrectRoot(t *testing.T) { t.Fatal(err) } - st, _, _ := blockchain.State() + st := state.New(state.NewDbState(kv, blockchain.CurrentBlock().NumberU64())) if !st.Exist(from) { t.Error("expected account to exist") } @@ -434,7 +440,7 @@ func TestAccountCreateIncorrectRoot(t *testing.T) { t.Fatal(err) } - st, _, _ = blockchain.State() + st = state.New(state.NewDbState(kv, blockchain.CurrentBlock().NumberU64())) if !st.Exist(from) { t.Error("expected account to exist") } @@ -467,7 +473,7 @@ func TestAccountUpdateIncorrectRoot(t *testing.T) { var contractAddress common.Address eipContract := new(contracts.Testcontract) - blockchain, blocks, receipts, err := genBlocks(data.genesisSpec, map[int]tx{ + blockchain, kv, blocks, receipts, err := genBlocks(data.genesisSpec, map[int]tx{ 0: { getBlockTx(from, to, big.NewInt(10)), fromKey, @@ -494,7 +500,7 @@ func TestAccountUpdateIncorrectRoot(t *testing.T) { t.Fatal(err) } - st, _, _ := blockchain.State() + st := state.New(state.NewDbState(kv, blockchain.CurrentBlock().NumberU64())) if !st.Exist(from) { t.Error("expected account to exist") } @@ -508,7 +514,7 @@ func TestAccountUpdateIncorrectRoot(t *testing.T) { t.Fatal(err) } - st, _, _ = blockchain.State() + st = state.New(state.NewDbState(kv, blockchain.CurrentBlock().NumberU64())) if !st.Exist(from) { t.Error("expected account to exist") } @@ -546,7 +552,7 @@ func TestAccountDeleteIncorrectRoot(t *testing.T) { var contractAddress common.Address eipContract := new(contracts.Testcontract) - blockchain, blocks, receipts, err := genBlocks(data.genesisSpec, map[int]tx{ + blockchain, kv, blocks, receipts, err := genBlocks(data.genesisSpec, map[int]tx{ 0: { getBlockTx(from, to, big.NewInt(10)), fromKey, @@ -573,7 +579,7 @@ func TestAccountDeleteIncorrectRoot(t *testing.T) { t.Fatal(err) } - st, _, _ := blockchain.State() + st := state.New(state.NewDbState(kv, blockchain.CurrentBlock().NumberU64())) if !st.Exist(from) { t.Error("expected account to exist") } @@ -587,7 +593,7 @@ func TestAccountDeleteIncorrectRoot(t *testing.T) { t.Fatal(err) } - st, _, _ = blockchain.State() + st = state.New(state.NewDbState(kv, blockchain.CurrentBlock().NumberU64())) if !st.Exist(from) { t.Error("expected account to exist") } @@ -669,7 +675,7 @@ type tx struct { key *ecdsa.PrivateKey } -func genBlocks(gspec *core.Genesis, txs map[int]tx) (*core.BlockChain, []*types.Block, []types.Receipts, error) { +func genBlocks(gspec *core.Genesis, txs map[int]tx) (*core.BlockChain, ethdb.KV, []*types.Block, []types.Receipts, error) { engine := ethash.NewFaker() db := ethdb.NewMemDatabase() genesis := gspec.MustCommit(db) @@ -677,7 +683,7 @@ func genBlocks(gspec *core.Genesis, txs map[int]tx) (*core.BlockChain, []*types. blockchain, err := core.NewBlockChain(db, nil, gspec.Config, engine, vm.Config{}, nil, nil) if err != nil { - return nil, nil, nil, err + return nil, nil, nil, nil, err } blockchain.EnableReceipts(true) @@ -715,10 +721,10 @@ func genBlocks(gspec *core.Genesis, txs map[int]tx) (*core.BlockChain, []*types. }) if err != nil { - err = fmt.Errorf("block %d, error %v", blockNumber, err) + return nil, nil, nil, nil, fmt.Errorf("block %d, error %v", blockNumber, err) } - return blockchain, blocks, receipts, err + return blockchain, db.AbstractKV(), blocks, receipts, err } type blockTx func(_ *core.BlockGen, backend bind.ContractBackend) (*types.Transaction, bool) -- GitLab