diff --git a/cmd/state/state.go b/cmd/state/state.go index 86eaf8b7e812755c2f5b465f32ff8c4f4aa3c21b..005ed3f8065c0f9d0c2bfc20f76663d1a98ac7de 100644 --- a/cmd/state/state.go +++ b/cmd/state/state.go @@ -57,6 +57,7 @@ var triesize = flag.Int("triesize", 1024*1024, "maximum number of nodes in the s var preroot = flag.Bool("preroot", false, "Attempt to compute hash of the trie without modifying it") var snapshotInterval = flag.Uint64("snapshotInterval", 0, "how often to take snapshots (0 - never, 1 - every block, 1000 - every 1000th block, etc)") var snapshotFrom = flag.Uint64("snapshotFrom", 0, "from which block to start snapshots") +var witnessInterval = flag.Uint64("witnessInterval", 1, "after which block to extract witness (put a large number like 10000000 to disable)") func check(e error) { if e != nil { @@ -1676,7 +1677,11 @@ func main() { //nakedAccountChart() //specExecChart1() if *action == "stateless" { - stateless(*chaindata, *statefile, *triesize, *preroot, *snapshotInterval, *snapshotFrom) + createDb := func(path string) (ethdb.Database, error) { + return ethdb.NewBoltDatabase(path) + } + + stateless(*chaindata, *statefile, *triesize, *preroot, *snapshotInterval, *snapshotFrom, *witnessInterval, createDb) } if *action == "stateless_chart" { stateless_chart_key_values("/Users/alexeyakhunov/mygit/go-ethereum/st_1/stateless.csv", []int{21, 20, 19, 18}, "breakdown.png", 2800000, 1) diff --git a/cmd/state/state_snapshot.go b/cmd/state/state_snapshot.go index 226cf883d8bb94e1e78a1c742ac3bb7f9fc07008..b249b2e6875babc6d1432aa4d8a7aa85cff90660 100644 --- a/cmd/state/state_snapshot.go +++ b/cmd/state/state_snapshot.go @@ -110,88 +110,87 @@ func constructSnapshot(ethDb ethdb.Database, blockNum uint64) { check(err) } -func copyBucket(bucketName []byte, fromTx *bolt.Tx, toDB *bolt.DB) error { - toTx, err := toDB.Begin(true) - if err != nil { - return err - } +type bucketWriter struct { + db ethdb.Database + bucket []byte + pending ethdb.DbWithPendingMutations + written uint64 +} - toBucket, err := toTx.CreateBucket(bucketName, true) - if err != nil { - return err +func (bw *bucketWriter) printStats() { + if bw.written == 0 { + fmt.Printf(" -- nothing to copy for bucket: '%s'...", string(bw.bucket)) + } else { + fmt.Printf("\r -- commited %d records for bucket: '%s'...", bw.written, string(bw.bucket)) } +} - count := 0 +func (bw *bucketWriter) walker(k, v []byte) (bool, error) { + if bw.pending == nil { + bw.pending = bw.db.NewBatch() + } - printStats := func() { - fmt.Printf("\r -- commited %d records for bucket: '%s'...", count, string(bucketName)) + if err := bw.pending.Put(bw.bucket, common.CopyBytes(k), common.CopyBytes(v)); err != nil { + return false, err } + bw.written++ - if fromBucket := fromTx.Bucket(bucketName); fromBucket != nil { - defer printStats() + if bw.pending.BatchSize() >= 100000 { + if _, err := bw.pending.Commit(); err != nil { + return false, err + } - fromCursor := fromBucket.Cursor() + bw.printStats() - for k, v := fromCursor.First(); k != nil; k, v = fromCursor.Next() { - err = toBucket.Put(common.CopyBytes(k), common.CopyBytes(v)) - if err != nil { - return err - } - count++ + bw.pending = bw.db.NewBatch() + } - if count%100000 == 0 { - err = toTx.Commit() - if err != nil { - return err - } + return true, nil +} - printStats() +func (bw *bucketWriter) commit() error { + defer bw.printStats() - toTx, err = toDB.Begin(true) - if err != nil { - return err - } - toBucket = toTx.Bucket(bucketName) - } - } - } else { - fmt.Printf(" -- nothing to copy for the bucket name: '%s'...", string(bucketName)) + if bw.pending != nil { + _, err := bw.pending.Commit() + return err } - return toTx.Commit() + return nil } -func copyDatabase(fromDB *bolt.DB, toDB *bolt.DB) error { - return fromDB.View(func(tx *bolt.Tx) error { - fmt.Printf(" - copying AccountsBucket...\n") - if err := copyBucket(dbutils.AccountsBucket, tx, toDB); err != nil { - fmt.Println("FAIL") - return err - } - fmt.Println("OK") +func newBucketWriter(db ethdb.Database, bucket []byte) *bucketWriter { + return &bucketWriter{ + db: db, + bucket: bucket, + pending: nil, + written: 0, + } +} + +func copyDatabase(fromDB ethdb.Database, toDB ethdb.Database) error { + for _, bucket := range [][]byte{dbutils.AccountsBucket, dbutils.StorageBucket, dbutils.CodeBucket} { + fmt.Printf(" - copying bucket '%s'...\n", string(bucket)) + writer := newBucketWriter(toDB, bucket) - fmt.Printf(" - copying StorageBucket...\n") - if err := copyBucket(dbutils.StorageBucket, tx, toDB); err != nil { + if err := fromDB.Walk(bucket, nil, 0, writer.walker); err != nil { fmt.Println("FAIL") return err } - fmt.Println("OK") - fmt.Printf(" - copying CodeBucket...\n") - if err := copyBucket(dbutils.CodeBucket, tx, toDB); err != nil { + if err := writer.commit(); err != nil { fmt.Println("FAIL") return err } fmt.Println("OK") - - return nil - }) + } + return nil } -func saveSnapshot(db *bolt.DB, filename string) { +func saveSnapshot(db ethdb.Database, filename string, createDb CreateDbFunc) { fmt.Printf("Saving snapshot to %s\n", filename) - diskDb, err := bolt.Open(filename, 0600, &bolt.Options{}) + diskDb, err := createDb(filename) check(err) defer diskDb.Close() @@ -199,9 +198,10 @@ func saveSnapshot(db *bolt.DB, filename string) { check(err) } -func loadSnapshot(db *bolt.DB, filename string) { +func loadSnapshot(db ethdb.Database, filename string, createDb CreateDbFunc) { fmt.Printf("Loading snapshot from %s\n", filename) - diskDb, err := bolt.Open(filename, 0600, &bolt.Options{}) + + diskDb, err := createDb(filename) check(err) defer diskDb.Close() @@ -308,7 +308,7 @@ func compare_snapshot(stateDb ethdb.Database, db *bolt.DB, filename string) { check(err) } -func checkRoots(stateDb ethdb.Database, db *bolt.DB, rootHash common.Hash, blockNum uint64) { +func checkRoots(stateDb ethdb.Database, rootHash common.Hash, blockNum uint64) { startTime := time.Now() t := trie.New(rootHash) r := trie.NewResolver(0, true, blockNum) @@ -324,26 +324,24 @@ func checkRoots(stateDb ethdb.Database, db *bolt.DB, rootHash common.Hash, block startTime = time.Now() var addrHash common.Hash roots := make(map[common.Hash]common.Hash) - err = db.View(func(tx *bolt.Tx) error { - sb := tx.Bucket(dbutils.StorageBucket) - b := tx.Bucket(dbutils.AccountsBucket) - c := sb.Cursor() - for k, _ := c.First(); k != nil; k, _ = c.Next() { - copy(addrHash[:], k[:32]) - if _, ok := roots[addrHash]; !ok { - if enc, _ := b.Get(addrHash[:]); enc == nil { - roots[addrHash] = common.Hash{} - } else { - var account accounts.Account - if err = account.DecodeForStorage(enc); err != nil { - return err - } - roots[addrHash] = account.Root + + err = stateDb.Walk(dbutils.StorageBucket, nil, 0, func(k, v []byte) (bool, error) { + copy(addrHash[:], k[:32]) + if _, ok := roots[addrHash]; !ok { + if enc, _ := stateDb.Get(dbutils.AccountsBucket, addrHash[:]); enc == nil { + roots[addrHash] = common.Hash{} + } else { + var account accounts.Account + if err = account.DecodeForStorage(enc); err != nil { + return false, err } + roots[addrHash] = account.Root } } - return nil + + return true, nil }) + if err != nil { panic(err) } @@ -384,7 +382,9 @@ func stateSnapshot() error { stateDb, db := ethdb.NewMemDatabase2() defer stateDb.Close() if _, err := os.Stat("statedb0"); err == nil { - loadSnapshot(db, "statedb0") + loadSnapshot(stateDb, "statedb0", func(path string) (ethdb.Database, error) { + return ethdb.NewBoltDatabase(path) + }) if err := loadCodes(db, ethDb); err != nil { return err } @@ -398,7 +398,7 @@ func stateSnapshot() error { block := bc.GetBlockByNumber(blockNum) fmt.Printf("Block number: %d\n", blockNum) fmt.Printf("Block root hash: %x\n", block.Root()) - checkRoots(ethDb, ethDb.DB(), block.Root(), blockNum) + checkRoots(ethDb, block.Root(), blockNum) return nil } @@ -416,5 +416,5 @@ func verifySnapshot(chaindata string) { fmt.Printf("Block number: %d\n", currentBlockNr) fmt.Printf("Block root hash: %x\n", currentBlock.Root()) preRoot := currentBlock.Root() - checkRoots(ethDb, ethDb.DB(), preRoot, blockNum) + checkRoots(ethDb, preRoot, blockNum) } diff --git a/cmd/state/state_snapshot_test.go b/cmd/state/state_snapshot_test.go new file mode 100644 index 0000000000000000000000000000000000000000..72a36ac9976aa4934306a7939c45c7282c4b584e --- /dev/null +++ b/cmd/state/state_snapshot_test.go @@ -0,0 +1,120 @@ +package main + +import ( + "bytes" + "fmt" + "testing" + + "github.com/ledgerwatch/turbo-geth/common/dbutils" + "github.com/ledgerwatch/turbo-geth/ethdb" +) + +type testData map[string][]byte + +func generateData(prefix string) testData { + data := make(map[string][]byte) + for i := 0; i < 100; i++ { + data[fmt.Sprintf("key:%s-%d", prefix, i)] = []byte(fmt.Sprintf("val:%s-%d", prefix, i)) + } + + return testData(data) +} + +func writeDataToDb(t *testing.T, db ethdb.Database, bucket []byte, data testData) { + for k, v := range data { + err := db.Put(bucket, []byte(k), v) + if err != nil { + t.Errorf("error while forming the source db: %v", err) + } + } +} + +func checkDataInDb(t *testing.T, db ethdb.Database, bucket []byte, data testData) { + for k, v := range data { + val, err := db.Get(bucket, []byte(k)) + if err != nil { + t.Errorf("error while requesting the dest db: %v", err) + } + + if !bytes.Equal(v, val) { + t.Errorf("unexpected value for the key %s (got: %s expected: %x)", k, string(val), string(v)) + } + } + + err := db.Walk(bucket, nil, 0, func(k, v []byte) (bool, error) { + val, ok := data[string(k)] + if !ok { + t.Errorf("unexpected key in the database (not in the data): %s", string(k)) + } + + if !bytes.Equal(v, val) { + t.Errorf("unexpected value for the key %s (got: %s expected: %x)", k, string(val), string(v)) + } + + return true, nil + }) + + if err != nil { + t.Errorf("error while walking the dest db: %v", err) + } +} + +func TestCopyDatabase(t *testing.T) { + doTestcase(t, map[string]testData{}) + + doTestcase(t, map[string]testData{ + string(dbutils.AccountsBucket): generateData(string(dbutils.AccountsBucket)), + }) + + doTestcase(t, map[string]testData{ + string(dbutils.StorageBucket): generateData(string(dbutils.StorageBucket)), + }) + + doTestcase(t, map[string]testData{ + string(dbutils.CodeBucket): generateData(string(dbutils.CodeBucket)), + }) + + doTestcase(t, map[string]testData{ + string(dbutils.AccountsBucket): generateData(string(dbutils.AccountsBucket)), + string(dbutils.StorageBucket): generateData(string(dbutils.StorageBucket)), + }) + + doTestcase(t, map[string]testData{ + string(dbutils.StorageBucket): generateData(string(dbutils.StorageBucket)), + string(dbutils.CodeBucket): generateData(string(dbutils.CodeBucket)), + }) + + doTestcase(t, map[string]testData{ + string(dbutils.AccountsBucket): generateData(string(dbutils.AccountsBucket)), + string(dbutils.CodeBucket): generateData(string(dbutils.CodeBucket)), + }) + + doTestcase(t, map[string]testData{ + string(dbutils.AccountsBucket): generateData(string(dbutils.AccountsBucket)), + string(dbutils.StorageBucket): generateData(string(dbutils.StorageBucket)), + string(dbutils.CodeBucket): generateData(string(dbutils.CodeBucket)), + }) + +} + +func doTestcase(t *testing.T, testCase map[string]testData) { + sourceDb := ethdb.NewMemDatabase() + defer sourceDb.Close() + + destDb := ethdb.NewMemDatabase() + defer destDb.Close() + + for bucket, data := range testCase { + writeDataToDb(t, sourceDb, []byte(bucket), data) + } + + err := copyDatabase(sourceDb, destDb) + + if err != nil { + t.Errorf("error while copying the db: %v", err) + } + + for bucket, data := range testCase { + checkDataInDb(t, destDb, []byte(bucket), data) + } +} diff --git a/cmd/state/stateless.go b/cmd/state/stateless.go index b8487c550e2104d1740c2e34b86daf3d59e80bed..90d1458b148d5a249a1bccc9fbe02871569c88c6 100644 --- a/cmd/state/stateless.go +++ b/cmd/state/stateless.go @@ -106,7 +106,17 @@ func writeStats(w io.Writer, blockNum uint64, blockProof trie.BlockProof) { } */ -func stateless(chaindata string, statefile string, triesize int, tryPreRoot bool, interval uint64, ignoreOlderThan uint64) { +type CreateDbFunc func(string) (ethdb.Database, error) + +func stateless(chaindata string, + statefile string, + triesize int, + tryPreRoot bool, + interval uint64, + ignoreOlderThan uint64, + witnessThreshold uint64, + createDb CreateDbFunc) { + state.MaxTrieCacheGen = uint32(triesize) startTime := time.Now() sigs := make(chan os.Signal, 1) @@ -118,7 +128,7 @@ func stateless(chaindata string, statefile string, triesize int, tryPreRoot bool interruptCh <- true }() - ethDb, err := ethdb.NewBoltDatabase(chaindata) + ethDb, err := createDb(chaindata) check(err) defer ethDb.Close() chainConfig := params.MainnetChainConfig @@ -131,10 +141,9 @@ func stateless(chaindata string, statefile string, triesize int, tryPreRoot bool engine := ethash.NewFullFaker() bcb, err := core.NewBlockChain(ethDb, nil, chainConfig, engine, vm.Config{}, nil) check(err) - stateDb, err := ethdb.NewBoltDatabase(statefile) + stateDb, err := createDb(statefile) check(err) defer stateDb.Close() - db := stateDb.DB() blockNum := uint64(*block) var preRoot common.Hash if blockNum == 1 { @@ -150,7 +159,7 @@ func stateless(chaindata string, statefile string, triesize int, tryPreRoot bool fmt.Printf("Block number: %d\n", blockNum-1) fmt.Printf("Block root hash: %x\n", block.Root()) preRoot = block.Root() - checkRoots(stateDb, db, preRoot, blockNum-1) + checkRoots(stateDb, preRoot, blockNum-1) } batch := stateDb.NewBatch() defer func() { @@ -166,11 +175,10 @@ func stateless(chaindata string, statefile string, triesize int, tryPreRoot bool tds.SetResolveReads(false) tds.SetNoHistory(true) interrupt := false - var thresholdBlock uint64 = 1 var witness []byte for !interrupt { trace := false // blockNum == 545080 - tds.SetResolveReads(blockNum >= thresholdBlock) + tds.SetResolveReads(blockNum >= witnessThreshold) block := bcb.GetBlockByNumber(blockNum) if block == nil { break @@ -213,7 +221,7 @@ func stateless(chaindata string, statefile string, triesize int, tryPreRoot bool return } witness = nil - if blockNum >= thresholdBlock { + if blockNum >= witnessThreshold { // Witness has to be extracted before the state trie is modified witness, err = tds.ExtractWitness(trace) if err != nil { @@ -222,7 +230,7 @@ func stateless(chaindata string, statefile string, triesize int, tryPreRoot bool } } finalRootFail := false - if blockNum >= thresholdBlock && witness != nil { // witness == nil means the extraction fails + if blockNum >= witnessThreshold && witness != nil { // witness == nil means the extraction fails var s *state.Stateless s, err = state.NewStateless(preRoot, witness, blockNum-1, trace) if err != nil { @@ -280,6 +288,7 @@ func stateless(chaindata string, statefile string, triesize int, tryPreRoot bool nextRoot := roots[len(roots)-1] if nextRoot != block.Root() { fmt.Printf("Root hash does not match for block %d, expected %x, was %x\n", blockNum, block.Root(), nextRoot) + return } tds.SetBlockNr(blockNum) @@ -302,7 +311,8 @@ func stateless(chaindata string, statefile string, triesize int, tryPreRoot bool if willSnapshot { // Snapshots of the state will be written to the same directory as the state file fmt.Printf("\nSaving snapshot at block %d, hash %x\n", blockNum, block.Root()) - saveSnapshot(db, fmt.Sprintf("%s_%d", statefile, blockNum)) + + saveSnapshot(stateDb, fmt.Sprintf("%s_%d", statefile, blockNum), createDb) } preRoot = header.Root