diff --git a/accounts/abi/abi.go b/accounts/abi/abi.go
index b9f1863beb..66dadac2ca 100644
--- a/accounts/abi/abi.go
+++ b/accounts/abi/abi.go
@@ -105,7 +105,7 @@ func (abi ABI) getArguments(name string, data []byte) (Arguments, error) {
args = event.Inputs
}
if args == nil {
- return nil, errors.New("abi: could not locate named method or event")
+ return nil, fmt.Errorf("abi: could not locate named method or event: %s", name)
}
return args, nil
}
diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go
index 3922756e54..38aa5ad26a 100644
--- a/accounts/abi/bind/backends/simulated.go
+++ b/accounts/abi/bind/backends/simulated.go
@@ -97,7 +97,8 @@ type SimulatedBackend struct {
acceptedBlock *types.Block // Currently accepted block that will be imported on request
acceptedState *state.StateDB // Currently accepted state that will be the active on request
- events *filters.EventSystem // Event system for filtering log events live
+ events *filters.EventSystem // for filtering log events live
+ filterSystem *filters.FilterSystem // for filtering database logs
config *params.ChainConfig
}
@@ -118,7 +119,11 @@ func NewSimulatedBackendWithDatabase(database ethdb.Database, alloc core.Genesis
blockchain: blockchain,
config: genesis.Config,
}
- backend.events = filters.NewEventSystem(&filterBackend{database, blockchain, backend}, false)
+
+ filterBackend := &filterBackend{database, blockchain, backend}
+ backend.filterSystem = filters.NewFilterSystem(filterBackend, filters.Config{})
+ backend.events = filters.NewEventSystem(backend.filterSystem, false)
+
backend.rollback(blockchain.CurrentBlock())
return backend
}
@@ -647,7 +652,7 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call interfaces.Cal
// User specified the legacy gas field, convert to 1559 gas typing
call.GasFeeCap, call.GasTipCap = call.GasPrice, call.GasPrice
} else {
- // User specified 1559 gas feilds (or none), use those
+ // User specified 1559 gas fields (or none), use those
if call.GasFeeCap == nil {
call.GasFeeCap = new(big.Int)
}
@@ -729,7 +734,7 @@ func (b *SimulatedBackend) FilterLogs(ctx context.Context, query interfaces.Filt
var filter *filters.Filter
if query.BlockHash != nil {
// Block filter requested, construct a single-shot filter
- filter = filters.NewBlockFilter(&filterBackend{b.database, b.blockchain, b}, *query.BlockHash, query.Addresses, query.Topics)
+ filter = b.filterSystem.NewBlockFilter(*query.BlockHash, query.Addresses, query.Topics)
} else {
// Initialize unset filter boundaries to run from genesis to chain head
from := int64(0)
@@ -741,7 +746,7 @@ func (b *SimulatedBackend) FilterLogs(ctx context.Context, query interfaces.Filt
to = query.ToBlock.Int64()
}
// Construct the range filter
- filter, _ = filters.NewRangeFilter(&filterBackend{b.database, b.blockchain, b}, from, to, query.Addresses, query.Topics)
+ filter, _ = b.filterSystem.NewRangeFilter(from, to, query.Addresses, query.Topics)
}
// Run the filter and return all the logs
logs, err := filter.Logs(ctx)
@@ -891,7 +896,8 @@ func (fb *filterBackend) GetMaxBlocksPerRequest() int64 {
return eth.DefaultSettings.MaxBlocksPerRequest
}
-func (fb *filterBackend) ChainDb() ethdb.Database { return fb.db }
+func (fb *filterBackend) ChainDb() ethdb.Database { return fb.db }
+
func (fb *filterBackend) EventMux() *event.TypeMux { panic("not supported") }
func (fb *filterBackend) HeaderByNumber(ctx context.Context, block rpc.BlockNumber) (*types.Header, error) {
@@ -913,19 +919,8 @@ func (fb *filterBackend) GetReceipts(ctx context.Context, hash common.Hash) (typ
return rawdb.ReadReceipts(fb.db, hash, *number, fb.bc.Config()), nil
}
-func (fb *filterBackend) GetLogs(ctx context.Context, hash common.Hash) ([][]*types.Log, error) {
- number := rawdb.ReadHeaderNumber(fb.db, hash)
- if number == nil {
- return nil, nil
- }
- receipts := rawdb.ReadReceipts(fb.db, hash, *number, fb.bc.Config())
- if receipts == nil {
- return nil, nil
- }
- logs := make([][]*types.Log, len(receipts))
- for i, receipt := range receipts {
- logs[i] = receipt.Logs
- }
+func (fb *filterBackend) GetLogs(ctx context.Context, hash common.Hash, number uint64) ([][]*types.Log, error) {
+ logs := rawdb.ReadLogs(fb.db, hash, number)
return logs, nil
}
diff --git a/accounts/abi/reflect.go b/accounts/abi/reflect.go
index 3a49cd07dd..07bada3905 100644
--- a/accounts/abi/reflect.go
+++ b/accounts/abi/reflect.go
@@ -109,7 +109,7 @@ func mustArrayToByteSlice(value reflect.Value) reflect.Value {
func set(dst, src reflect.Value) error {
dstType, srcType := dst.Type(), src.Type()
switch {
- case dstType.Kind() == reflect.Interface && dst.Elem().IsValid():
+ case dstType.Kind() == reflect.Interface && dst.Elem().IsValid() && (dst.Elem().Type().Kind() == reflect.Ptr || dst.Elem().CanSet()):
return set(dst.Elem(), src)
case dstType.Kind() == reflect.Ptr && dstType.Elem() != reflect.TypeOf(big.Int{}):
return set(dst.Elem(), src)
diff --git a/accounts/abi/reflect_test.go b/accounts/abi/reflect_test.go
index 2754fea638..f11cc11328 100644
--- a/accounts/abi/reflect_test.go
+++ b/accounts/abi/reflect_test.go
@@ -42,7 +42,7 @@ type reflectTest struct {
var reflectTests = []reflectTest{
{
- name: "OneToOneCorrespondance",
+ name: "OneToOneCorrespondence",
args: []string{"fieldA"},
struc: struct {
FieldA int `abi:"fieldA"`
diff --git a/accounts/abi/unpack_test.go b/accounts/abi/unpack_test.go
index a71f38ce35..e5e400ec56 100644
--- a/accounts/abi/unpack_test.go
+++ b/accounts/abi/unpack_test.go
@@ -362,6 +362,11 @@ func TestMethodMultiReturn(t *testing.T) {
&[]interface{}{&expected.Int, &expected.String},
"",
"Can unpack into a slice",
+ }, {
+ &[]interface{}{&bigint, ""},
+ &[]interface{}{&expected.Int, expected.String},
+ "",
+ "Can unpack into a slice without indirection",
}, {
&[2]interface{}{&bigint, new(string)},
&[2]interface{}{&expected.Int, &expected.String},
diff --git a/accounts/keystore/account_cache_test.go b/accounts/keystore/account_cache_test.go
index eb0d012e67..c96c1dbdc1 100644
--- a/accounts/keystore/account_cache_test.go
+++ b/accounts/keystore/account_cache_test.go
@@ -329,7 +329,7 @@ func TestUpdatedKeyfileContents(t *testing.T) {
t.Skip("FLAKY")
t.Parallel()
- // Create a temporary kesytore to test with
+ // Create a temporary keystore to test with
rand.Seed(time.Now().UnixNano())
dir := filepath.Join(os.TempDir(), fmt.Sprintf("eth-keystore-watch-test-%d-%d", os.Getpid(), rand.Int()))
ks := NewKeyStore(dir, LightScryptN, LightScryptP)
diff --git a/accounts/keystore/file_cache.go b/accounts/keystore/file_cache.go
index 25355de538..24dba59982 100644
--- a/accounts/keystore/file_cache.go
+++ b/accounts/keystore/file_cache.go
@@ -49,7 +49,7 @@ type fileCache struct {
func (fc *fileCache) scan(keyDir string) (mapset.Set, mapset.Set, mapset.Set, error) {
t0 := time.Now()
- // List all the failes from the keystore folder
+ // List all the files from the keystore folder
files, err := os.ReadDir(keyDir)
if err != nil {
return nil, nil, nil, err
@@ -71,7 +71,7 @@ func (fc *fileCache) scan(keyDir string) (mapset.Set, mapset.Set, mapset.Set, er
log.Trace("Ignoring file on account scan", "path", path)
continue
}
- // Gather the set of all and fresly modified files
+ // Gather the set of all and freshly modified files
all.Add(path)
info, err := fi.Info()
diff --git a/accounts/keystore/keystore_test.go b/accounts/keystore/keystore_test.go
index d021264bdc..0a7786476c 100644
--- a/accounts/keystore/keystore_test.go
+++ b/accounts/keystore/keystore_test.go
@@ -224,7 +224,7 @@ func TestSignRace(t *testing.T) {
// Tests that the wallet notifier loop starts and stops correctly based on the
// addition and removal of wallet event subscriptions.
func TestWalletNotifierLifecycle(t *testing.T) {
- // Create a temporary kesytore to test with
+ // Create a temporary keystore to test with
_, ks := tmpKeyStore(t, false)
// Ensure that the notification updater is not running yet
diff --git a/core/blockchain.go b/core/blockchain.go
index f5864b5816..d716ee1e84 100644
--- a/core/blockchain.go
+++ b/core/blockchain.go
@@ -710,6 +710,10 @@ func (bc *BlockChain) Stop() {
log.Error("Failed to Shutdown state manager", "err", err)
}
log.Info("State manager shut down", "t", time.Since(start))
+ // Flush the collected preimages to disk
+ if err := bc.stateCache.TrieDB().CommitPreimages(); err != nil {
+ log.Error("Failed to commit trie preimages", "err", err)
+ }
// Stop senderCacher's goroutines
log.Info("Shutting down sender cacher")
@@ -884,7 +888,7 @@ func (bc *BlockChain) newTip(block *types.Block) bool {
// writeBlockAndSetHead expects to be the last verification step during InsertBlock
// since it creates a reference that will only be cleaned up by Accept/Reject.
func (bc *BlockChain) writeBlockAndSetHead(block *types.Block, receipts []*types.Receipt, logs []*types.Log, state *state.StateDB) error {
- if err := bc.writeBlockWithState(block, receipts, logs, state); err != nil {
+ if err := bc.writeBlockWithState(block, receipts, state); err != nil {
return err
}
@@ -901,7 +905,7 @@ func (bc *BlockChain) writeBlockAndSetHead(block *types.Block, receipts []*types
// writeBlockWithState writes the block and all associated state to the database,
// but it expects the chain mutex to be held.
-func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.Receipt, logs []*types.Log, state *state.StateDB) error {
+func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.Receipt, state *state.StateDB) error {
// Irrelevant of the canonical status, write the block itself to the database.
//
// Note all the components of block(hash->number map, header, body, receipts)
@@ -1309,7 +1313,7 @@ func (bc *BlockChain) reportBlock(block *types.Block, receipts types.Receipts, e
i, receipt.CumulativeGasUsed, receipt.GasUsed, receipt.ContractAddress.Hex(),
receipt.Status, receipt.TxHash.Hex(), receipt.Logs, receipt.Bloom, receipt.PostState)
}
- log.Error(fmt.Sprintf(`
+ log.Debug(fmt.Sprintf(`
########## BAD BLOCK #########
Chain config: %v
@@ -1531,7 +1535,7 @@ func (bc *BlockChain) reprocessState(current *types.Block, reexec uint64) error
// Flatten snapshot if initialized, holding a reference to the state root until the next block
// is processed.
if err := bc.flattenSnapshot(func() error {
- triedb.Reference(root, common.Hash{}, true)
+ triedb.Reference(root, common.Hash{})
if previousRoot != (common.Hash{}) {
triedb.Dereference(previousRoot)
}
diff --git a/core/blockchain_reader.go b/core/blockchain_reader.go
index b5cdcc6c93..e812fdd01e 100644
--- a/core/blockchain_reader.go
+++ b/core/blockchain_reader.go
@@ -100,6 +100,9 @@ func (bc *BlockChain) HasBlock(hash common.Hash, number uint64) bool {
if bc.blockCache.Contains(hash) {
return true
}
+ if !bc.HasHeader(hash, number) {
+ return false
+ }
return rawdb.HasBody(bc.db, hash, number)
}
diff --git a/core/bloom_indexer.go b/core/bloom_indexer.go
index 4d8f58fdfc..60ac039e06 100644
--- a/core/bloom_indexer.go
+++ b/core/bloom_indexer.go
@@ -75,7 +75,7 @@ func (b *BloomIndexer) Process(ctx context.Context, header *types.Header) error
// Commit implements core.ChainIndexerBackend, finalizing the bloom section and
// writing it out into the database.
func (b *BloomIndexer) Commit() error {
- batch := b.db.NewBatch()
+ batch := b.db.NewBatchWithSize((int(b.size) / 8) * types.BloomBitLength)
for i := 0; i < types.BloomBitLength; i++ {
bits, err := b.gen.Bitset(uint(i))
if err != nil {
diff --git a/core/rawdb/accessors_chain_test.go b/core/rawdb/accessors_chain_test.go
index cf7b64653a..9feb4c0de0 100644
--- a/core/rawdb/accessors_chain_test.go
+++ b/core/rawdb/accessors_chain_test.go
@@ -191,7 +191,7 @@ func TestPartialBlockStorage(t *testing.T) {
func TestCanonicalMappingStorage(t *testing.T) {
db := NewMemoryDatabase()
- // Create a test canonical number and assinged hash to move around
+ // Create a test canonical number and assigned hash to move around
hash, number := common.Hash{0: 0xff}, uint64(314)
if entry := ReadCanonicalHash(db, number); entry != (common.Hash{}) {
t.Fatalf("Non existent canonical mapping returned: %v", entry)
diff --git a/core/state/database.go b/core/state/database.go
index bbe8770b01..e861234d28 100644
--- a/core/state/database.go
+++ b/core/state/database.go
@@ -73,7 +73,7 @@ type Trie interface {
// GetKey returns the sha3 preimage of a hashed key that was previously used
// to store a value.
//
- // TODO(fjl): remove this when SecureTrie is removed
+ // TODO(fjl): remove this when StateTrie is removed
GetKey([]byte) []byte
// TryGet returns the value for key stored in the trie. The value bytes must
@@ -81,8 +81,8 @@ type Trie interface {
// trie.MissingNodeError is returned.
TryGet(key []byte) ([]byte, error)
- // TryUpdateAccount abstract an account write in the trie.
- TryUpdateAccount(key []byte, account *types.StateAccount) error
+ // TryGetAccount abstract an account read from the trie.
+ TryGetAccount(key []byte) (*types.StateAccount, error)
// TryUpdate associates key with value in the trie. If value has length zero, any
// existing value is deleted from the trie. The value bytes must not be modified
@@ -90,17 +90,27 @@ type Trie interface {
// database, a trie.MissingNodeError is returned.
TryUpdate(key, value []byte) error
+ // TryUpdateAccount abstract an account write to the trie.
+ TryUpdateAccount(key []byte, account *types.StateAccount) error
+
// TryDelete removes any existing value for key from the trie. If a node was not
// found in the database, a trie.MissingNodeError is returned.
TryDelete(key []byte) error
+ // TryDeleteAccount abstracts an account deletion from the trie.
+ TryDeleteAccount(key []byte) error
+
// Hash returns the root hash of the trie. It does not write to the database and
// can be used even if the trie doesn't have one.
Hash() common.Hash
- // Commit writes all nodes to the trie's memory database, tracking the internal
- // and external (for account tries) references.
- Commit(onleaf trie.LeafCallback, referenceRoot bool) (common.Hash, int, error)
+ // Commit collects all dirty nodes in the trie and replace them with the
+ // corresponding node hash. All collected nodes(including dirty leaves if
+ // collectLeaf is true) will be encapsulated into a nodeset for return.
+ // The returned nodeset can be nil if the trie is clean(nothing to commit).
+ // Once the trie is committed, it's not usable anymore. A new trie must
+ // be created with new root and updated trie database for following usage
+ Commit(collectLeaf bool) (common.Hash, *trie.NodeSet, error)
// NodeIterator returns an iterator that returns nodes of the trie. Iteration
// starts at the key after the given start key.
@@ -143,7 +153,7 @@ type cachingDB struct {
// OpenTrie opens the main account trie at a specific root hash.
func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) {
- tr, err := trie.NewSecure(common.Hash{}, root, db.db)
+ tr, err := trie.NewStateTrie(common.Hash{}, root, db.db)
if err != nil {
return nil, err
}
@@ -152,7 +162,7 @@ func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) {
// OpenStorageTrie opens the storage trie of an account.
func (db *cachingDB) OpenStorageTrie(addrHash, root common.Hash) (Trie, error) {
- tr, err := trie.NewSecure(addrHash, root, db.db)
+ tr, err := trie.NewStateTrie(addrHash, root, db.db)
if err != nil {
return nil, err
}
@@ -162,7 +172,7 @@ func (db *cachingDB) OpenStorageTrie(addrHash, root common.Hash) (Trie, error) {
// CopyTrie returns an independent copy of the given trie.
func (db *cachingDB) CopyTrie(t Trie) Trie {
switch t := t.(type) {
- case *trie.SecureTrie:
+ case *trie.StateTrie:
return t.Copy()
default:
panic(fmt.Errorf("unknown trie type %T", t))
diff --git a/core/state/metrics.go b/core/state/metrics.go
index cc7838a7ef..6d702312f7 100644
--- a/core/state/metrics.go
+++ b/core/state/metrics.go
@@ -29,10 +29,10 @@ package state
import "github.com/ava-labs/coreth/metrics"
var (
- accountUpdatedMeter = metrics.NewRegisteredMeter("state/update/account", nil)
- storageUpdatedMeter = metrics.NewRegisteredMeter("state/update/storage", nil)
- accountDeletedMeter = metrics.NewRegisteredMeter("state/delete/account", nil)
- storageDeletedMeter = metrics.NewRegisteredMeter("state/delete/storage", nil)
- accountCommittedMeter = metrics.NewRegisteredMeter("state/commit/account", nil)
- storageCommittedMeter = metrics.NewRegisteredMeter("state/commit/storage", nil)
+ accountUpdatedMeter = metrics.NewRegisteredMeter("state/update/account", nil)
+ storageUpdatedMeter = metrics.NewRegisteredMeter("state/update/storage", nil)
+ accountDeletedMeter = metrics.NewRegisteredMeter("state/delete/account", nil)
+ storageDeletedMeter = metrics.NewRegisteredMeter("state/delete/storage", nil)
+ accountTrieCommittedMeter = metrics.NewRegisteredMeter("state/commit/accountnodes", nil)
+ storageTriesCommittedMeter = metrics.NewRegisteredMeter("state/commit/storagenodes", nil)
)
diff --git a/core/state/pruner/pruner.go b/core/state/pruner/pruner.go
index 80118d638f..d730156284 100644
--- a/core/state/pruner/pruner.go
+++ b/core/state/pruner/pruner.go
@@ -341,7 +341,7 @@ func extractGenesis(db ethdb.Database, stateBloom *stateBloom) error {
if genesis == nil {
return errors.New("missing genesis block")
}
- t, err := trie.NewSecure(common.Hash{}, genesis.Root(), trie.NewDatabase(db))
+ t, err := trie.NewStateTrie(common.Hash{}, genesis.Root(), trie.NewDatabase(db))
if err != nil {
return err
}
@@ -361,7 +361,7 @@ func extractGenesis(db ethdb.Database, stateBloom *stateBloom) error {
return err
}
if acc.Root != emptyRoot {
- storageTrie, err := trie.NewSecure(common.BytesToHash(accIter.LeafKey()), acc.Root, trie.NewDatabase(db))
+ storageTrie, err := trie.NewStateTrie(common.BytesToHash(accIter.LeafKey()), acc.Root, trie.NewDatabase(db))
if err != nil {
return err
}
diff --git a/core/state/snapshot/generate.go b/core/state/snapshot/generate.go
index f052fe3cc2..7362e530c5 100644
--- a/core/state/snapshot/generate.go
+++ b/core/state/snapshot/generate.go
@@ -274,7 +274,7 @@ func (dl *diskLayer) generate(stats *generatorStats) {
}
}
// Create an account and state iterator pointing to the current generator marker
- accTrie, err := trie.NewSecure(common.Hash{}, dl.root, dl.triedb)
+ accTrie, err := trie.NewStateTrie(common.Hash{}, dl.root, dl.triedb)
if err != nil {
// The account trie is missing (GC), surf the chain until one becomes available
stats.Info("Trie missing, state snapshotting paused", dl.root, dl.genMarker)
@@ -329,7 +329,7 @@ func (dl *diskLayer) generate(stats *generatorStats) {
// If the iterated account is a contract, iterate through corresponding contract
// storage to generate snapshot entries.
if acc.Root != emptyRoot {
- storeTrie, err := trie.NewSecure(accountHash, acc.Root, dl.triedb)
+ storeTrie, err := trie.NewStateTrie(accountHash, acc.Root, dl.triedb)
if err != nil {
log.Error("Generator failed to access storage trie", "root", dl.root, "account", accountHash, "stroot", acc.Root, "err", err)
abort := <-dl.genAbort
diff --git a/core/state/snapshot/generate_test.go b/core/state/snapshot/generate_test.go
index ed3eb5cdcc..b1ab8a49b8 100644
--- a/core/state/snapshot/generate_test.go
+++ b/core/state/snapshot/generate_test.go
@@ -152,17 +152,19 @@ func checkSnapRoot(t *testing.T, snap *diskLayer, trieRoot common.Hash) {
type testHelper struct {
diskdb ethdb.Database
triedb *trie.Database
- accTrie *trie.SecureTrie
+ accTrie *trie.StateTrie
+ nodes *trie.MergedNodeSet
}
func newHelper() *testHelper {
diskdb := rawdb.NewMemoryDatabase()
triedb := trie.NewDatabase(diskdb)
- accTrie, _ := trie.NewSecure(common.Hash{}, common.Hash{}, triedb)
+ accTrie, _ := trie.NewStateTrie(common.Hash{}, common.Hash{}, triedb)
return &testHelper{
diskdb: diskdb,
triedb: triedb,
accTrie: accTrie,
+ nodes: trie.NewMergedNodeSet(),
}
}
@@ -190,21 +192,26 @@ func (t *testHelper) addSnapStorage(accKey string, keys []string, vals []string)
}
func (t *testHelper) makeStorageTrie(owner common.Hash, keys []string, vals []string, commit bool) []byte {
- stTrie, _ := trie.NewSecure(owner, common.Hash{}, t.triedb)
+ stTrie, _ := trie.NewStateTrie(owner, common.Hash{}, t.triedb)
for i, k := range keys {
stTrie.Update([]byte(k), []byte(vals[i]))
}
- var root common.Hash
if !commit {
- root = stTrie.Hash()
- } else {
- root, _, _ = stTrie.Commit(nil, false)
+ return stTrie.Hash().Bytes()
+ }
+ root, nodes, _ := stTrie.Commit(false)
+ if nodes != nil {
+ t.nodes.Merge(nodes)
}
return root.Bytes()
}
func (t *testHelper) Commit() common.Hash {
- root, _, _ := t.accTrie.Commit(nil, false)
+ root, nodes, _ := t.accTrie.Commit(true)
+ if nodes != nil {
+ t.nodes.Merge(nodes)
+ }
+ t.triedb.Update(t.nodes)
t.triedb.Commit(root, false, nil)
return root
}
@@ -223,12 +230,10 @@ func (t *testHelper) CommitAndGenerate() (common.Hash, *diskLayer) {
// - miss in the beginning
// - miss in the middle
// - miss in the end
-//
// - the contract(non-empty storage) has wrong storage slots
// - wrong slots in the beginning
// - wrong slots in the middle
// - wrong slots in the end
-//
// - the contract(non-empty storage) has extra storage slots
// - extra slots in the beginning
// - extra slots in the middle
@@ -390,7 +395,7 @@ func TestGenerateCorruptAccountTrie(t *testing.T) {
helper.addTrieAccount("acc-2", &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}) // 0xf73118e0254ce091588d66038744a0afae5f65a194de67cff310c683ae43329e
helper.addTrieAccount("acc-3", &Account{Balance: big.NewInt(3), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}) // 0x515d3de35e143cd976ad476398d910aa7bf8a02e8fd7eb9e3baacddbbcbfcb41
- root, _, _ := helper.accTrie.Commit(nil, false) // Root: 0xfa04f652e8bd3938971bf7d71c3c688574af334ca8bc20e64b01ba610ae93cad
+ root := helper.Commit() // Root: 0xfa04f652e8bd3938971bf7d71c3c688574af334ca8bc20e64b01ba610ae93cad
// Delete an account trie leaf and ensure the generator chokes
helper.triedb.Commit(root, false, nil)
@@ -425,20 +430,7 @@ func TestGenerateMissingStorageTrie(t *testing.T) {
helper.addTrieAccount("acc-2", &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}) // 0xf73118e0254ce091588d66038744a0afae5f65a194de67cff310c683ae43329e
stRoot = helper.makeStorageTrie(hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.addTrieAccount("acc-3", &Account{Balance: big.NewInt(3), Root: stRoot, CodeHash: emptyCode.Bytes()}) // 0x70da4ebd7602dd313c936b39000ed9ab7f849986a90ea934f0c3ec4cc9840441
- root, _, _ := helper.accTrie.Commit(nil, false)
-
- // We can only corrupt the disk database, so flush the tries out
- helper.triedb.Reference(
- common.BytesToHash(stRoot),
- common.HexToHash("0x547b07c3a71669c00eda14077d85c7fd14575b92d459572540b25b9a11914dcb"),
- true,
- )
- helper.triedb.Reference(
- common.BytesToHash(stRoot),
- common.HexToHash("0x70da4ebd7602dd313c936b39000ed9ab7f849986a90ea934f0c3ec4cc9840441"),
- true,
- )
- helper.triedb.Commit(root, false, nil)
+ root := helper.Commit()
// Delete a storage trie root and ensure the generator chokes
helper.diskdb.Delete(stRoot) // We can only corrupt the disk database, so flush the tries out
@@ -471,21 +463,7 @@ func TestGenerateCorruptStorageTrie(t *testing.T) {
helper.addTrieAccount("acc-2", &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}) // 0xf73118e0254ce091588d66038744a0afae5f65a194de67cff310c683ae43329e
stRoot = helper.makeStorageTrie(hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.addTrieAccount("acc-3", &Account{Balance: big.NewInt(3), Root: stRoot, CodeHash: emptyCode.Bytes()}) // 0x70da4ebd7602dd313c936b39000ed9ab7f849986a90ea934f0c3ec4cc9840441
-
- root, _, _ := helper.accTrie.Commit(nil, false)
-
- // We can only corrupt the disk database, so flush the tries out
- helper.triedb.Reference(
- common.BytesToHash(stRoot),
- common.HexToHash("0x547b07c3a71669c00eda14077d85c7fd14575b92d459572540b25b9a11914dcb"),
- true,
- )
- helper.triedb.Reference(
- common.BytesToHash(stRoot),
- common.HexToHash("0x70da4ebd7602dd313c936b39000ed9ab7f849986a90ea934f0c3ec4cc9840441"),
- true,
- )
- helper.triedb.Commit(root, false, nil)
+ root := helper.Commit()
// Delete a storage trie leaf and ensure the generator chokes
helper.diskdb.Delete(common.HexToHash("0x18a0f4d79cff4459642dd7604f303886ad9d77c30cf3d7d7cedb3a693ab6d371").Bytes())
@@ -836,10 +814,12 @@ func populateDangling(disk ethdb.KeyValueStore) {
// This test will populate some dangling storages to see if they can be cleaned up.
func TestGenerateCompleteSnapshotWithDanglingStorage(t *testing.T) {
var helper = newHelper()
- stRoot := helper.makeStorageTrie(common.Hash{}, []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
+ stRoot := helper.makeStorageTrie(hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.addAccount("acc-1", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()})
helper.addAccount("acc-2", &Account{Balance: big.NewInt(1), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()})
+
+ helper.makeStorageTrie(hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.addAccount("acc-3", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()})
helper.addSnapStorage("acc-1", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
@@ -869,10 +849,12 @@ func TestGenerateCompleteSnapshotWithDanglingStorage(t *testing.T) {
// This test will populate some dangling storages to see if they can be cleaned up.
func TestGenerateBrokenSnapshotWithDanglingStorage(t *testing.T) {
var helper = newHelper()
- stRoot := helper.makeStorageTrie(common.Hash{}, []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
+ stRoot := helper.makeStorageTrie(hashData([]byte("acc-1")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.addTrieAccount("acc-1", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()})
helper.addTrieAccount("acc-2", &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()})
+
+ helper.makeStorageTrie(hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true)
helper.addTrieAccount("acc-3", &Account{Balance: big.NewInt(3), Root: stRoot, CodeHash: emptyCode.Bytes()})
populateDangling(helper.diskdb)
diff --git a/core/state/snapshot/iterator_fast.go b/core/state/snapshot/iterator_fast.go
index 1011da355c..bf0e3acdc9 100644
--- a/core/state/snapshot/iterator_fast.go
+++ b/core/state/snapshot/iterator_fast.go
@@ -328,7 +328,7 @@ func (fi *fastIterator) Slot() []byte {
}
// Release iterates over all the remaining live layer iterators and releases each
-// of thme individually.
+// of them individually.
func (fi *fastIterator) Release() {
for _, it := range fi.iterators {
it.it.Release()
@@ -336,7 +336,7 @@ func (fi *fastIterator) Release() {
fi.iterators = nil
}
-// Debug is a convencience helper during testing
+// Debug is a convenience helper during testing
func (fi *fastIterator) Debug() {
for _, it := range fi.iterators {
fmt.Printf("[p=%v v=%v] ", it.priority, it.it.Hash()[0])
diff --git a/core/state/snapshot/snapshot_test.go b/core/state/snapshot/snapshot_test.go
index 2daca9c85d..03879c4875 100644
--- a/core/state/snapshot/snapshot_test.go
+++ b/core/state/snapshot/snapshot_test.go
@@ -246,7 +246,7 @@ func TestPostFlattenBasicDataAccess(t *testing.T) {
snaps.Update(common.HexToHash("0xa3"), common.HexToHash("0xffa3"), common.HexToHash("0xa2"), nil, setAccount("0xa3"), nil)
snaps.Update(common.HexToHash("0xb3"), common.HexToHash("0xffb3"), common.HexToHash("0xb2"), nil, setAccount("0xb3"), nil)
- // checkExist verifies if an account exiss in a snapshot
+ // checkExist verifies if an account exists in a snapshot
checkExist := func(layer Snapshot, key string) error {
if data, _ := layer.Account(common.HexToHash(key)); data == nil {
return fmt.Errorf("expected %x to exist, got nil", common.HexToHash(key))
diff --git a/core/state/state_object.go b/core/state/state_object.go
index 6a20a47004..7d1ac913e6 100644
--- a/core/state/state_object.go
+++ b/core/state/state_object.go
@@ -36,6 +36,7 @@ import (
"github.com/ava-labs/coreth/core/types"
"github.com/ava-labs/coreth/metrics"
+ "github.com/ava-labs/coreth/trie"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp"
@@ -390,23 +391,23 @@ func (s *stateObject) updateRoot(db Database) {
// CommitTrie the storage trie of the object to db.
// This updates the trie root.
-func (s *stateObject) CommitTrie(db Database) (int, error) {
+func (s *stateObject) CommitTrie(db Database) (*trie.NodeSet, error) {
// If nothing changed, don't bother with hashing anything
if s.updateTrie(db) == nil {
- return 0, nil
+ return nil, nil
}
if s.dbErr != nil {
- return 0, s.dbErr
+ return nil, s.dbErr
}
// Track the amount of time wasted on committing the storage trie
if metrics.EnabledExpensive {
defer func(start time.Time) { s.db.StorageCommits += time.Since(start) }(time.Now())
}
- root, committed, err := s.trie.Commit(nil, false)
+ root, nodes, err := s.trie.Commit(false)
if err == nil {
s.data.Root = root
}
- return committed, err
+ return nodes, err
}
// AddBalance adds amount to s's balance.
diff --git a/core/state/statedb.go b/core/state/statedb.go
index 4a5476bfce..ca69d1bd0b 100644
--- a/core/state/statedb.go
+++ b/core/state/statedb.go
@@ -552,7 +552,7 @@ func (s *StateDB) deleteStateObject(obj *stateObject) {
}
// Delete the account from the trie
addr := obj.Address()
- if err := s.trie.TryDelete(addr[:]); err != nil {
+ if err := s.trie.TryDeleteAccount(addr[:]); err != nil {
s.setError(fmt.Errorf("deleteStateObject (%x) error: %v", addr[:], err))
}
}
@@ -606,20 +606,16 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject {
// If snapshot unavailable or reading from it failed, load from the database
if data == nil {
start := time.Now()
- enc, err := s.trie.TryGet(addr.Bytes())
+ var err error
+ data, err = s.trie.TryGetAccount(addr.Bytes())
if metrics.EnabledExpensive {
s.AccountReads += time.Since(start)
}
if err != nil {
- s.setError(fmt.Errorf("getDeleteStateObject (%x) error: %v", addr.Bytes(), err))
+ s.setError(fmt.Errorf("getDeleteStateObject (%x) error: %w", addr.Bytes(), err))
return nil
}
- if len(enc) == 0 {
- return nil
- }
- data = new(types.StateAccount)
- if err := rlp.DecodeBytes(enc, data); err != nil {
- log.Error("Failed to decode state object", "addr", addr, "err", err)
+ if data == nil {
return nil
}
}
@@ -842,7 +838,7 @@ func (s *StateDB) GetRefund() uint64 {
return s.refund
}
-// Finalise finalises the state by removing the s destructed objects and clears
+// Finalise finalises the state by removing the destructed objects and clears
// the journal as well as the refunds. Finalise, however, will not push any updates
// into the tries just yet. Only IntermediateRoot or Commit will do that.
func (s *StateDB) Finalise(deleteEmptyObjects bool) {
@@ -864,7 +860,7 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
// If state snapshotting is active, also mark the destruction there.
// Note, we can't do this only at the end of a block because multiple
// transactions within the same block might self destruct and then
- // ressurrect an account; but the snapshotter needs both events.
+ // resurrect an account; but the snapshotter needs both events.
if s.snap != nil {
s.snapDestructs[obj.addrHash] = struct{}{} // We need to maintain account deletions explicitly (will remain set indefinitely)
delete(s.snapAccounts, obj.addrHash) // Clear out any previously updated account data (may be recreated via a ressurrect)
@@ -912,7 +908,7 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
// Although naively it makes sense to retrieve the account trie and then do
// the contract storage and account updates sequentially, that short circuits
// the account prefetcher. Instead, let's process all the storage updates
- // first, giving the account prefeches just a few more milliseconds of time
+ // first, giving the account prefetches just a few more milliseconds of time
// to pull useful data from disk.
for addr := range s.stateObjectsPending {
if obj := s.stateObjects[addr]; !obj.deleted {
@@ -963,7 +959,7 @@ func (s *StateDB) clearJournalAndRefund() {
s.journal = newJournal()
s.refund = 0
}
- s.validRevisions = s.validRevisions[:0] // Snapshots can be created without journal entires
+ s.validRevisions = s.validRevisions[:0] // Snapshots can be created without journal entries
}
// Commit writes the state to the underlying in-memory trie database.
@@ -986,7 +982,11 @@ func (s *StateDB) commit(deleteEmptyObjects bool, snaps *snapshot.Tree, blockHas
s.IntermediateRoot(deleteEmptyObjects)
// Commit objects to the trie, measuring the elapsed time
- var storageCommitted int
+ var (
+ accountTrieNodes int
+ storageTrieNodes int
+ nodes = trie.NewMergedNodeSet()
+ )
codeWriter := s.db.TrieDB().DiskDB().NewBatch()
for addr := range s.stateObjectsDirty {
if obj := s.stateObjects[addr]; !obj.deleted {
@@ -996,11 +996,17 @@ func (s *StateDB) commit(deleteEmptyObjects bool, snaps *snapshot.Tree, blockHas
obj.dirtyCode = false
}
// Write any storage changes in the state object to its storage trie
- committed, err := obj.CommitTrie(s.db)
+ set, err := obj.CommitTrie(s.db)
if err != nil {
return common.Hash{}, err
}
- storageCommitted += committed
+ // Merge the dirty nodes of storage trie into global set
+ if set != nil {
+ if err := nodes.Merge(set); err != nil {
+ return common.Hash{}, err
+ }
+ storageTrieNodes += set.Len()
+ }
}
}
if len(s.stateObjectsDirty) > 0 {
@@ -1016,21 +1022,17 @@ func (s *StateDB) commit(deleteEmptyObjects bool, snaps *snapshot.Tree, blockHas
if metrics.EnabledExpensive {
start = time.Now()
}
- // The onleaf func is called _serially_, so we can reuse the same account
- // for unmarshalling every time.
- var account types.StateAccount
- root, accountCommitted, err := s.trie.Commit(func(_ [][]byte, _ []byte, leaf []byte, parent common.Hash, _ []byte) error {
- if err := rlp.DecodeBytes(leaf, &account); err != nil {
- return nil
- }
- if account.Root != emptyRoot {
- s.db.TrieDB().Reference(account.Root, parent, false)
- }
- return nil
- }, referenceRoot)
+ root, set, err := s.trie.Commit(true)
if err != nil {
return common.Hash{}, err
}
+ // Merge the dirty nodes of account trie into global set
+ if set != nil {
+ if err := nodes.Merge(set); err != nil {
+ return common.Hash{}, err
+ }
+ accountTrieNodes = set.Len()
+ }
if metrics.EnabledExpensive {
s.AccountCommits += time.Since(start)
@@ -1038,8 +1040,8 @@ func (s *StateDB) commit(deleteEmptyObjects bool, snaps *snapshot.Tree, blockHas
storageUpdatedMeter.Mark(int64(s.StorageUpdated))
accountDeletedMeter.Mark(int64(s.AccountDeleted))
storageDeletedMeter.Mark(int64(s.StorageDeleted))
- accountCommittedMeter.Mark(int64(accountCommitted))
- storageCommittedMeter.Mark(int64(storageCommitted))
+ accountTrieCommittedMeter.Mark(int64(accountTrieNodes))
+ storageTriesCommittedMeter.Mark(int64(storageTrieNodes))
s.AccountUpdated, s.AccountDeleted = 0, 0
s.StorageUpdated, s.StorageDeleted = 0, 0
}
@@ -1056,6 +1058,15 @@ func (s *StateDB) commit(deleteEmptyObjects bool, snaps *snapshot.Tree, blockHas
}
s.snap, s.snapDestructs, s.snapAccounts, s.snapStorage = nil, nil, nil, nil
}
+ if referenceRoot {
+ if err := s.db.TrieDB().UpdateAndReferenceRoot(nodes, root); err != nil {
+ return common.Hash{}, err
+ }
+ } else {
+ if err := s.db.TrieDB().Update(nodes); err != nil {
+ return common.Hash{}, err
+ }
+ }
s.originalRoot = root
return root, err
}
diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go
index eed36f739f..c2156b2e08 100644
--- a/core/state/statedb_test.go
+++ b/core/state/statedb_test.go
@@ -783,7 +783,7 @@ func TestStateDBAccessList(t *testing.T) {
t.Fatalf("expected %x to be in access list", address)
}
}
- // Check that only the expected addresses are present in the acesslist
+ // Check that only the expected addresses are present in the access list
for address := range state.accessList.addresses {
if _, exist := addressMap[address]; !exist {
t.Fatalf("extra address %x in access list", address)
@@ -1072,3 +1072,43 @@ func TestGenerateMultiCoinAccounts(t *testing.T) {
t.Fatalf("Expected asset balance: %v, found %v", assetBalance, actualAssetBalance)
}
}
+
+// Tests that account and storage tries are flushed in the correct order and that
+// no data loss occurs.
+func TestFlushOrderDataLoss(t *testing.T) {
+ // Create a state trie with many accounts and slots
+ var (
+ memdb = rawdb.NewMemoryDatabase()
+ statedb = NewDatabase(memdb)
+ state, _ = New(common.Hash{}, statedb, nil)
+ )
+ for a := byte(0); a < 10; a++ {
+ state.CreateAccount(common.Address{a})
+ for s := byte(0); s < 10; s++ {
+ state.SetState(common.Address{a}, common.Hash{a, s}, common.Hash{a, s})
+ }
+ }
+ root, err := state.Commit(false, false)
+ if err != nil {
+ t.Fatalf("failed to commit state trie: %v", err)
+ }
+ statedb.TrieDB().Reference(root, common.Hash{})
+ if err := statedb.TrieDB().Cap(1024); err != nil {
+ t.Fatalf("failed to cap trie dirty cache: %v", err)
+ }
+ if err := statedb.TrieDB().Commit(root, false, nil); err != nil {
+ t.Fatalf("failed to commit state trie: %v", err)
+ }
+ // Reopen the state trie from flushed disk and verify it
+ state, err = New(root, NewDatabase(memdb), nil)
+ if err != nil {
+ t.Fatalf("failed to reopen state trie: %v", err)
+ }
+ for a := byte(0); a < 10; a++ {
+ for s := byte(0); s < 10; s++ {
+ if have := state.GetState(common.Address{a}, common.Hash{a, s}); have != (common.Hash{a, s}) {
+ t.Errorf("account %d: slot %d: state mismatch: have %x, want %x", a, s, have, common.Hash{a, s})
+ }
+ }
+ }
+}
diff --git a/core/state/trie_prefetcher.go b/core/state/trie_prefetcher.go
index 349bc39ef6..5da0e9cc37 100644
--- a/core/state/trie_prefetcher.go
+++ b/core/state/trie_prefetcher.go
@@ -222,7 +222,7 @@ type subfetcher struct {
wake chan struct{} // Wake channel if a new task is scheduled
stop chan struct{} // Channel to interrupt processing
- term chan struct{} // Channel to signal iterruption
+ term chan struct{} // Channel to signal interruption
copy chan chan Trie // Channel to request a copy of the current trie
seen map[string]struct{} // Tracks the entries already loaded
@@ -342,7 +342,12 @@ func (sf *subfetcher) loop() {
if _, ok := sf.seen[string(task)]; ok {
sf.dups++
} else {
- _, err := sf.trie.TryGet(task)
+ var err error
+ if len(task) == len(common.Address{}) {
+ _, err = sf.trie.TryGetAccount(task)
+ } else {
+ _, err = sf.trie.TryGet(task)
+ }
if err != nil {
log.Error("Trie prefetcher failed fetching", "root", sf.root, "err", err)
}
diff --git a/core/state_processor.go b/core/state_processor.go
index 535a39b642..c6874e31aa 100644
--- a/core/state_processor.go
+++ b/core/state_processor.go
@@ -94,7 +94,7 @@ func (p *StateProcessor) Process(block *types.Block, parent *types.Header, state
return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
}
statedb.Prepare(tx.Hash(), i)
- receipt, err := applyTransaction(msg, p.config, p.bc, nil, gp, statedb, blockNumber, blockHash, tx, usedGas, vmenv)
+ receipt, err := applyTransaction(msg, p.config, nil, gp, statedb, blockNumber, blockHash, tx, usedGas, vmenv)
if err != nil {
return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
}
@@ -109,7 +109,7 @@ func (p *StateProcessor) Process(block *types.Block, parent *types.Header, state
return receipts, allLogs, *usedGas, nil
}
-func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (*types.Receipt, error) {
+func applyTransaction(msg types.Message, config *params.ChainConfig, author *common.Address, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (*types.Receipt, error) {
// Create a new context to be used in the EVM environment.
txContext := NewEVMTxContext(msg)
evm.Reset(txContext, statedb)
@@ -166,5 +166,5 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo
// Create a new context to be used in the EVM environment
blockContext := NewEVMBlockContext(header, bc, author)
vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, config, cfg)
- return applyTransaction(msg, config, bc, author, gp, statedb, header.Number, header.Hash(), tx, usedGas, vmenv)
+ return applyTransaction(msg, config, author, gp, statedb, header.Number, header.Hash(), tx, usedGas, vmenv)
}
diff --git a/core/state_transition.go b/core/state_transition.go
index fb1b960660..b5f9b14278 100644
--- a/core/state_transition.go
+++ b/core/state_transition.go
@@ -27,6 +27,7 @@
package core
import (
+ "errors"
"fmt"
"math"
"math/big"
@@ -347,6 +348,13 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1)
ret, st.gas, vmerr = st.evm.Call(sender, st.to(), st.data, st.gas, st.value)
}
+ if errors.Is(vmerr, vmerrs.ErrToAddrProhibitedSoft) { // Only invalidate soft error here
+ return &ExecutionResult{
+ UsedGas: st.gasUsed(),
+ Err: vmerr,
+ ReturnData: ret,
+ }, vmerr
+ }
st.refundGas(rules.IsApricotPhase1)
st.state.AddBalance(st.evm.Context.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice))
diff --git a/core/tx_journal.go b/core/tx_journal.go
index 82d3c62f56..05f59a30d3 100644
--- a/core/tx_journal.go
+++ b/core/tx_journal.go
@@ -29,6 +29,7 @@ package core
import (
"errors"
"io"
+ "io/fs"
"os"
"github.com/ava-labs/coreth/core/types"
@@ -67,12 +68,12 @@ func newTxJournal(path string) *txJournal {
// load parses a transaction journal dump from disk, loading its contents into
// the specified pool.
func (journal *txJournal) load(add func([]*types.Transaction) []error) error {
- // Skip the parsing if the journal file doesn't exist at all
- if !common.FileExist(journal.path) {
- return nil
- }
// Open the journal for loading any past transactions
input, err := os.Open(journal.path)
+ if errors.Is(err, fs.ErrNotExist) {
+ // Skip the parsing if the journal file doesn't exist at all
+ return nil
+ }
if err != nil {
return err
}
diff --git a/core/tx_pool.go b/core/tx_pool.go
index 879cca735c..ce39186a48 100644
--- a/core/tx_pool.go
+++ b/core/tx_pool.go
@@ -1074,6 +1074,13 @@ func (pool *TxPool) HasLocal(hash common.Hash) bool {
return pool.all.GetLocal(hash) != nil
}
+func (pool *TxPool) RemoveTx(hash common.Hash) {
+ pool.mu.Lock()
+ defer pool.mu.Unlock()
+
+ pool.removeTx(hash, true)
+}
+
// removeTx removes a single transaction from the queue, moving all subsequent
// transactions back to the future queue.
func (pool *TxPool) removeTx(hash common.Hash, outofbound bool) {
diff --git a/core/types/block.go b/core/types/block.go
index 0fe5b554b1..a8f6ea177a 100644
--- a/core/types/block.go
+++ b/core/types/block.go
@@ -378,7 +378,7 @@ func (b *Block) Header() *Header { return CopyHeader(b.header) }
func (b *Block) Body() *Body { return &Body{b.transactions, b.uncles, b.version, b.extdata} }
// Size returns the true RLP encoded storage size of the block, either by encoding
-// and returning it, or returning a previsouly cached value.
+// and returning it, or returning a previously cached value.
func (b *Block) Size() common.StorageSize {
if size := b.size.Load(); size != nil {
return size.(common.StorageSize)
diff --git a/core/types/bloom9.go b/core/types/bloom9.go
index 872fa85fa2..aa172a0b1b 100644
--- a/core/types/bloom9.go
+++ b/core/types/bloom9.go
@@ -164,7 +164,7 @@ func bloomValues(data []byte, hashbuf []byte) (uint, byte, uint, byte, uint, byt
return i1, v1, i2, v2, i3, v3
}
-// BloomLookup is a convenience-method to check presence int he bloom filter
+// BloomLookup is a convenience-method to check presence in the bloom filter
func BloomLookup(bin Bloom, topic bytesBacked) bool {
return bin.Test(topic.Bytes())
}
diff --git a/core/vm/contracts.go b/core/vm/contracts.go
index 9d53df76cd..7574925a3a 100644
--- a/core/vm/contracts.go
+++ b/core/vm/contracts.go
@@ -107,7 +107,25 @@ var PrecompiledContractsApricotPhase2 = map[common.Address]precompile.StatefulPr
NativeAssetCallAddr: &nativeAssetCall{gasCost: params.AssetCallApricot},
}
+// PrecompiledContractsApricotPhase6 contains the default set of pre-compiled Ethereum
+// contracts used in the Apricot Phase 6 release.
+var PrecompiledContractsApricotPhase6 = map[common.Address]precompile.StatefulPrecompiledContract{
+ common.BytesToAddress([]byte{1}): newWrappedPrecompiledContract(&ecrecover{}),
+ common.BytesToAddress([]byte{2}): newWrappedPrecompiledContract(&sha256hash{}),
+ common.BytesToAddress([]byte{3}): newWrappedPrecompiledContract(&ripemd160hash{}),
+ common.BytesToAddress([]byte{4}): newWrappedPrecompiledContract(&dataCopy{}),
+ common.BytesToAddress([]byte{5}): newWrappedPrecompiledContract(&bigModExp{eip2565: true}),
+ common.BytesToAddress([]byte{6}): newWrappedPrecompiledContract(&bn256AddIstanbul{}),
+ common.BytesToAddress([]byte{7}): newWrappedPrecompiledContract(&bn256ScalarMulIstanbul{}),
+ common.BytesToAddress([]byte{8}): newWrappedPrecompiledContract(&bn256PairingIstanbul{}),
+ common.BytesToAddress([]byte{9}): newWrappedPrecompiledContract(&blake2F{}),
+ genesisContractAddr: &deprecatedContract{},
+ NativeAssetBalanceAddr: &deprecatedContract{},
+ NativeAssetCallAddr: &deprecatedContract{},
+}
+
var (
+ PrecompiledAddressesApricotPhase6 []common.Address
PrecompiledAddressesApricotPhase2 []common.Address
PrecompiledAddressesIstanbul []common.Address
PrecompiledAddressesByzantium []common.Address
@@ -128,12 +146,17 @@ func init() {
for k := range PrecompiledContractsApricotPhase2 {
PrecompiledAddressesApricotPhase2 = append(PrecompiledAddressesApricotPhase2, k)
}
+ for k := range PrecompiledContractsApricotPhase6 {
+ PrecompiledAddressesApricotPhase6 = append(PrecompiledAddressesApricotPhase6, k)
+ }
+
// Set of all native precompile addresses that are in use
// Note: this will repeat some addresses, but this is cheap and makes the code clearer.
PrecompileAllNativeAddresses = make(map[common.Address]struct{})
addrsList := append(PrecompiledAddressesHomestead, PrecompiledAddressesByzantium...)
addrsList = append(addrsList, PrecompiledAddressesIstanbul...)
addrsList = append(addrsList, PrecompiledAddressesApricotPhase2...)
+ addrsList = append(addrsList, PrecompiledAddressesApricotPhase6...)
for _, k := range addrsList {
PrecompileAllNativeAddresses[k] = struct{}{}
}
@@ -165,6 +188,8 @@ func init() {
// ActivePrecompiles returns the precompiles enabled with the current configuration.
func ActivePrecompiles(rules params.Rules) []common.Address {
switch {
+ case rules.IsApricotPhase6:
+ return PrecompiledAddressesApricotPhase6
case rules.IsApricotPhase2:
return PrecompiledAddressesApricotPhase2
case rules.IsIstanbul:
diff --git a/core/vm/contracts_stateful_test.go b/core/vm/contracts_stateful_test.go
index f89a752824..c951480e2d 100644
--- a/core/vm/contracts_stateful_test.go
+++ b/core/vm/contracts_stateful_test.go
@@ -468,7 +468,7 @@ func TestStatefulPrecompile(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
stateDB := test.setupStateDB()
// Create EVM with BlockNumber and Time initialized to 0 to enable Apricot Rules.
- evm := NewEVM(vmCtx, TxContext{}, stateDB, params.TestChainConfig, Config{})
+ evm := NewEVM(vmCtx, TxContext{}, stateDB, params.TestApricotPhase5Config, Config{})
ret, gasRemaining, err := evm.Call(AccountRef(test.from), test.precompileAddr, test.input, test.gasInput, test.value)
// Place gas remaining check before error check, so that it is not skipped when there is an error
assert.Equal(t, test.expectedGasRemaining, gasRemaining, "unexpected gas remaining")
diff --git a/core/vm/evm.go b/core/vm/evm.go
index 95d896a559..f64107daf3 100644
--- a/core/vm/evm.go
+++ b/core/vm/evm.go
@@ -59,6 +59,25 @@ func IsProhibited(addr common.Address) bool {
return false
}
+// This is the only part that activated, we need to keep the behavior here the same.
+func (evm *EVM) isProhibitedWithTimestamp(addr common.Address) error {
+ if addr != NativeAssetCallAddr {
+ return nil
+ }
+
+ // Return error depending on the phase
+ switch {
+ case evm.chainRules.IsApricotPhasePost6: // If we are in the soft fork, return the soft error
+ return vmerrs.ErrToAddrProhibitedSoft
+ case evm.chainRules.IsApricotPhase6: // If we are in Phase6, return nil
+ return nil
+ case evm.chainRules.IsApricotPhasePre6: // If we are in PrePhase6, return Prohibited6
+ return vmerrs.ErrToAddrProhibited6
+ default: // Prior to Pre6, don't alter behavior at all
+ return nil
+ }
+}
+
// emptyCodeHash is used by create to ensure deployment is disallowed to already
// deployed contract addresses (relevant after the account abstraction).
var emptyCodeHash = crypto.Keccak256Hash(nil)
@@ -78,6 +97,8 @@ type (
func (evm *EVM) precompile(addr common.Address) (precompile.StatefulPrecompiledContract, bool) {
var precompiles map[common.Address]precompile.StatefulPrecompiledContract
switch {
+ case evm.chainRules.IsApricotPhase6: // This stayed the same
+ precompiles = PrecompiledContractsApricotPhase2
case evm.chainRules.IsApricotPhase2:
precompiles = PrecompiledContractsApricotPhase2
case evm.chainRules.IsIstanbul:
@@ -230,6 +251,9 @@ func (evm *EVM) Interpreter() *EVMInterpreter {
// the necessary steps to create accounts and reverses the state in case of an
// execution error or failed value transfer.
func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) {
+ if prohibitErr := evm.isProhibitedWithTimestamp(addr); prohibitErr != nil {
+ return nil, gas, prohibitErr
+ }
// Fail if we're trying to execute above the call depth limit
if evm.depth > int(params.CallCreateDepth) {
return nil, gas, vmerrs.ErrDepth
@@ -313,6 +337,9 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
// This allows the user transfer balance of a specified coinId in addition to a normal Call().
func (evm *EVM) CallExpert(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int, coinID common.Hash, value2 *big.Int) (ret []byte, leftOverGas uint64, err error) {
+ if prohibitErr := evm.isProhibitedWithTimestamp(addr); prohibitErr != nil {
+ return nil, gas, prohibitErr
+ }
// Fail if we're trying to execute above the call depth limit
if evm.depth > int(params.CallCreateDepth) {
return nil, gas, vmerrs.ErrDepth
@@ -396,6 +423,9 @@ func (evm *EVM) CallExpert(caller ContractRef, addr common.Address, input []byte
// CallCode differs from Call in the sense that it executes the given address'
// code with the caller as context.
func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) {
+ if prohibitErr := evm.isProhibitedWithTimestamp(addr); prohibitErr != nil {
+ return nil, gas, prohibitErr
+ }
// Fail if we're trying to execute above the call depth limit
if evm.depth > int(params.CallCreateDepth) {
return nil, gas, vmerrs.ErrDepth
@@ -447,6 +477,9 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
// DelegateCall differs from CallCode in the sense that it executes the given address'
// code with the caller as context and the caller is set to the caller of the caller.
func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) {
+ if prohibitErr := evm.isProhibitedWithTimestamp(addr); prohibitErr != nil {
+ return nil, gas, prohibitErr
+ }
// Fail if we're trying to execute above the call depth limit
if evm.depth > int(params.CallCreateDepth) {
return nil, gas, vmerrs.ErrDepth
@@ -486,6 +519,9 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
// Opcodes that attempt to perform such modifications will result in exceptions
// instead of performing the modifications.
func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) {
+ if prohibitErr := evm.isProhibitedWithTimestamp(addr); prohibitErr != nil {
+ return nil, gas, prohibitErr
+ }
// Fail if we're trying to execute above the call depth limit
if evm.depth > int(params.CallCreateDepth) {
return nil, gas, vmerrs.ErrDepth
diff --git a/core/vm/instructions.go b/core/vm/instructions.go
index 5344460cf9..c2d3d6db26 100644
--- a/core/vm/instructions.go
+++ b/core/vm/instructions.go
@@ -618,6 +618,10 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b
}
res, addr, returnGas, suberr := interpreter.evm.Create(scope.Contract, input, gas, bigVal)
+ // Special case the error in the op code
+ if errors.Is(suberr, vmerrs.ErrToAddrProhibitedSoft) {
+ return nil, suberr
+ }
// Push item on the stack based on the returned error. If the ruleset is
// homestead we must check for CodeStoreOutOfGasError (homestead only
// rule) and treat as an error, if the ruleset is frontier we must
@@ -664,6 +668,10 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]
}
res, addr, returnGas, suberr := interpreter.evm.Create2(scope.Contract, input, gas,
bigEndowment, &salt)
+ // Special case the error in the op code
+ if errors.Is(suberr, vmerrs.ErrToAddrProhibitedSoft) {
+ return nil, suberr
+ }
// Push item on the stack based on the returned error.
if suberr != nil {
stackvalue.Clear()
@@ -706,7 +714,10 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt
}
ret, returnGas, err := interpreter.evm.Call(scope.Contract, toAddr, args, gas, bigVal)
-
+ // Special case the error in the op code
+ if errors.Is(err, vmerrs.ErrToAddrProhibitedSoft) {
+ return nil, err
+ }
if err != nil {
temp.Clear()
} else {
@@ -757,7 +768,10 @@ func opCallExpert(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext)
}
ret, returnGas, err := interpreter.evm.CallExpert(scope.Contract, toAddr, args, gas, bigVal, coinID, bigVal2)
-
+ // Special case the error in the op code
+ if errors.Is(err, vmerrs.ErrToAddrProhibitedSoft) {
+ return nil, err
+ }
if err != nil {
temp.Clear()
} else {
@@ -793,6 +807,10 @@ func opCallCode(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([
}
ret, returnGas, err := interpreter.evm.CallCode(scope.Contract, toAddr, args, gas, bigVal)
+ // Special case the error in the op code
+ if errors.Is(err, vmerrs.ErrToAddrProhibitedSoft) {
+ return nil, err
+ }
if err != nil {
temp.Clear()
} else {
@@ -822,6 +840,10 @@ func opDelegateCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext
args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64()))
ret, returnGas, err := interpreter.evm.DelegateCall(scope.Contract, toAddr, args, gas)
+ // Special case the error in the op code
+ if errors.Is(err, vmerrs.ErrToAddrProhibitedSoft) {
+ return nil, err
+ }
if err != nil {
temp.Clear()
} else {
@@ -851,6 +873,10 @@ func opStaticCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext)
args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64()))
ret, returnGas, err := interpreter.evm.StaticCall(scope.Contract, toAddr, args, gas)
+ // Special case the error in the op code
+ if errors.Is(err, vmerrs.ErrToAddrProhibitedSoft) {
+ return nil, err
+ }
if err != nil {
temp.Clear()
} else {
diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go
index 91985d4f32..1b2106a255 100644
--- a/core/vm/runtime/runtime_test.go
+++ b/core/vm/runtime/runtime_test.go
@@ -343,7 +343,7 @@ func benchmarkNonModifyingCode(gas uint64, code []byte, name string, tracerCode
cfg.State, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
cfg.GasLimit = gas
if len(tracerCode) > 0 {
- tracer, err := tracers.New(tracerCode, new(tracers.Context))
+ tracer, err := tracers.New(tracerCode, new(tracers.Context), nil)
if err != nil {
b.Fatal(err)
}
@@ -467,7 +467,7 @@ func BenchmarkSimpleLoop(b *testing.B) {
byte(vm.JUMP),
}
- calllRevertingContractWithInput := []byte{
+ callRevertingContractWithInput := []byte{
byte(vm.JUMPDEST), //
// push args for the call
byte(vm.PUSH1), 0, // out size
@@ -495,7 +495,7 @@ func BenchmarkSimpleLoop(b *testing.B) {
benchmarkNonModifyingCode(100000000, loopingCode, "loop-100M", "", b)
benchmarkNonModifyingCode(100000000, callInexistant, "call-nonexist-100M", "", b)
benchmarkNonModifyingCode(100000000, callEOA, "call-EOA-100M", "", b)
- benchmarkNonModifyingCode(100000000, calllRevertingContractWithInput, "call-reverting-100M", "", b)
+ benchmarkNonModifyingCode(100000000, callRevertingContractWithInput, "call-reverting-100M", "", b)
//benchmarkNonModifyingCode(10000000, staticCallIdentity, "staticcall-identity-10M", b)
//benchmarkNonModifyingCode(10000000, loopingCode, "loop-10M", b)
diff --git a/eth/api.go b/eth/api.go
index 352c2562f3..12368afc92 100644
--- a/eth/api.go
+++ b/eth/api.go
@@ -92,7 +92,7 @@ func (api *AdminAPI) ExportChain(file string, first *uint64, last *uint64) (bool
}
if _, err := os.Stat(file); err == nil {
// File already exists. Allowing overwrite could be a DoS vector,
- // since the 'file' may point to arbitrary paths on the drive
+ // since the 'file' may point to arbitrary paths on the drive.
return false, errors.New("location would overwrite an existing file")
}
// Make sure we can create the file to export into
@@ -419,11 +419,11 @@ func (api *DebugAPI) getModifiedAccounts(startBlock, endBlock *types.Block) ([]c
}
triedb := api.eth.BlockChain().StateCache().TrieDB()
- oldTrie, err := trie.NewSecure(common.Hash{}, startBlock.Root(), triedb)
+ oldTrie, err := trie.NewStateTrie(common.Hash{}, startBlock.Root(), triedb)
if err != nil {
return nil, err
}
- newTrie, err := trie.NewSecure(common.Hash{}, endBlock.Root(), triedb)
+ newTrie, err := trie.NewStateTrie(common.Hash{}, endBlock.Root(), triedb)
if err != nil {
return nil, err
}
diff --git a/eth/api_backend.go b/eth/api_backend.go
index c53213f59c..ff828bca2b 100644
--- a/eth/api_backend.go
+++ b/eth/api_backend.go
@@ -29,7 +29,6 @@ package eth
import (
"context"
"errors"
- "fmt"
"math/big"
"time"
@@ -246,23 +245,8 @@ func (b *EthAPIBackend) GetReceipts(ctx context.Context, hash common.Hash) (type
return b.eth.blockchain.GetReceiptsByHash(hash), nil
}
-func (b *EthAPIBackend) GetLogs(ctx context.Context, hash common.Hash) ([][]*types.Log, error) {
- if err := ctx.Err(); err != nil {
- return nil, err
- }
-
- header, err := b.HeaderByHash(ctx, hash)
- if header == nil || err != nil {
- return nil, fmt.Errorf("failed to get block number for hash %#x", hash)
- }
-
- db := b.eth.ChainDb()
- number := header.Number.Uint64()
- logs := rawdb.ReadLogs(db, hash, number)
- if logs == nil {
- return nil, fmt.Errorf("failed to get logs for block #%d (0x%s)", number, hash.TerminalString())
- }
- return logs, nil
+func (b *EthAPIBackend) GetLogs(ctx context.Context, hash common.Hash, number uint64) ([][]*types.Log, error) {
+ return rawdb.ReadLogs(b.eth.chainDb, hash, number), nil
}
func (b *EthAPIBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config) (*vm.EVM, func() error, error) {
diff --git a/eth/backend.go b/eth/backend.go
index adaca919f4..5aad4913b3 100644
--- a/eth/backend.go
+++ b/eth/backend.go
@@ -283,6 +283,13 @@ func (s *Ethereum) APIs() []rpc.API {
// Add the APIs from the node
apis = append(apis, s.stackRPCs...)
+ // Create [filterSystem] with the log cache size set in the config.
+ ethcfg := s.APIBackend.eth.config
+ filterSystem := filters.NewFilterSystem(s.APIBackend, filters.Config{
+ LogCacheSize: ethcfg.FilterLogCacheSize,
+ Timeout: 5 * time.Minute,
+ })
+
// Append all the local APIs and return
return append(apis, []rpc.API{
{
@@ -291,7 +298,7 @@ func (s *Ethereum) APIs() []rpc.API {
Name: "eth",
}, {
Namespace: "eth",
- Service: filters.NewFilterAPI(s.APIBackend, false, 5*time.Minute),
+ Service: filters.NewFilterAPI(filterSystem, false /* isLightClient */),
Name: "eth-filter",
}, {
Namespace: "admin",
diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go
index 11bfc1972d..2365d2d1b2 100644
--- a/eth/ethconfig/config.go
+++ b/eth/ethconfig/config.go
@@ -59,6 +59,7 @@ func NewDefaultConfig() Config {
TrieDirtyCache: 256,
TrieDirtyCommitTarget: 20,
SnapshotCache: 128,
+ FilterLogCacheSize: 32,
Miner: miner.Config{},
TxPool: core.DefaultTxPoolConfig,
RPCGasCap: 25000000,
@@ -118,6 +119,9 @@ type Config struct {
SnapshotCache int
Preimages bool
+ // This is the number of blocks for which logs will be cached in the filter system.
+ FilterLogCacheSize int
+
// Mining options
Miner miner.Config
@@ -140,7 +144,7 @@ type Config struct {
RPCEVMTimeout time.Duration
// RPCTxFeeCap is the global transaction fee(price * gaslimit) cap for
- // send-transction variants. The unit is ether.
+ // send-transaction variants. The unit is ether.
RPCTxFeeCap float64 `toml:",omitempty"`
// AllowUnfinalizedQueries allow unfinalized queries
diff --git a/eth/filters/api.go b/eth/filters/api.go
index eba32cdb19..db97ca4656 100644
--- a/eth/filters/api.go
+++ b/eth/filters/api.go
@@ -47,7 +47,7 @@ import (
// and associated subscription in the event system.
type filter struct {
typ Type
- deadline *time.Timer // filter is inactiv when deadline triggers
+ deadline *time.Timer // filter is inactive when deadline triggers
hashes []common.Hash
crit FilterCriteria
logs []*types.Log
@@ -57,7 +57,7 @@ type filter struct {
// FilterAPI offers support to create and manage filters. This will allow external clients to retrieve various
// information related to the Ethereum protocol such als blocks, transactions and logs.
type FilterAPI struct {
- backend Backend
+ sys *FilterSystem
events *EventSystem
filtersMu sync.Mutex
filters map[rpc.ID]*filter
@@ -65,14 +65,14 @@ type FilterAPI struct {
}
// NewFilterAPI returns a new PublicFilterAPI instance.
-func NewFilterAPI(backend Backend, lightMode bool, timeout time.Duration) *FilterAPI {
+func NewFilterAPI(system *FilterSystem, lightMode bool) *FilterAPI {
api := &FilterAPI{
- backend: backend,
- events: NewEventSystem(backend, lightMode),
+ sys: system,
+ events: NewEventSystem(system, lightMode),
filters: make(map[rpc.ID]*filter),
- timeout: timeout,
+ timeout: system.cfg.Timeout,
}
- go api.timeoutLoop(timeout)
+ go api.timeoutLoop(system.cfg.Timeout)
return api
}
@@ -218,7 +218,7 @@ func (api *FilterAPI) NewBlockFilter() rpc.ID {
headerSub *Subscription
)
- if api.backend.GetVMConfig().AllowUnfinalizedQueries {
+ if api.sys.backend.GetVMConfig().AllowUnfinalizedQueries {
headerSub = api.events.SubscribeNewHeads(headers)
} else {
headerSub = api.events.SubscribeAcceptedHeads(headers)
@@ -264,7 +264,7 @@ func (api *FilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, error) {
headersSub event.Subscription
)
- if api.backend.GetVMConfig().AllowUnfinalizedQueries {
+ if api.sys.backend.GetVMConfig().AllowUnfinalizedQueries {
headersSub = api.events.SubscribeNewHeads(headers)
} else {
headersSub = api.events.SubscribeAcceptedHeads(headers)
@@ -301,7 +301,7 @@ func (api *FilterAPI) Logs(ctx context.Context, crit FilterCriteria) (*rpc.Subsc
err error
)
- if api.backend.GetVMConfig().AllowUnfinalizedQueries {
+ if api.sys.backend.GetVMConfig().AllowUnfinalizedQueries {
logsSub, err = api.events.SubscribeLogs(interfaces.FilterQuery(crit), matchedLogs)
if err != nil {
return nil, err
@@ -356,7 +356,7 @@ func (api *FilterAPI) NewFilter(crit FilterCriteria) (rpc.ID, error) {
err error
)
- if api.backend.GetVMConfig().AllowUnfinalizedQueries {
+ if api.sys.backend.GetVMConfig().AllowUnfinalizedQueries {
logsSub, err = api.events.SubscribeLogs(interfaces.FilterQuery(crit), logs)
if err != nil {
return rpc.ID(""), err
@@ -398,7 +398,7 @@ func (api *FilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([]*type
var filter *Filter
if crit.BlockHash != nil {
// Block filter requested, construct a single-shot filter
- filter = NewBlockFilter(api.backend, *crit.BlockHash, crit.Addresses, crit.Topics)
+ filter = api.sys.NewBlockFilter(*crit.BlockHash, crit.Addresses, crit.Topics)
} else {
// Convert the RPC block numbers into internal representations
// LatestBlockNumber is left in place here to be handled
@@ -413,7 +413,7 @@ func (api *FilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([]*type
}
// Construct the range filter
var err error
- filter, err = NewRangeFilter(api.backend, begin, end, crit.Addresses, crit.Topics)
+ filter, err = api.sys.NewRangeFilter(begin, end, crit.Addresses, crit.Topics)
if err != nil {
return nil, err
}
@@ -455,7 +455,7 @@ func (api *FilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]*types.Lo
var filter *Filter
if f.crit.BlockHash != nil {
// Block filter requested, construct a single-shot filter
- filter = NewBlockFilter(api.backend, *f.crit.BlockHash, f.crit.Addresses, f.crit.Topics)
+ filter = api.sys.NewBlockFilter(*f.crit.BlockHash, f.crit.Addresses, f.crit.Topics)
} else {
// Convert the RPC block numbers into internal representations
// Leave LatestBlockNumber in place here as the defaults
@@ -471,7 +471,7 @@ func (api *FilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]*types.Lo
}
// Construct the range filter
var err error
- filter, err = NewRangeFilter(api.backend, begin, end, f.crit.Addresses, f.crit.Topics)
+ filter, err = api.sys.NewRangeFilter(begin, end, f.crit.Addresses, f.crit.Topics)
if err != nil {
return nil, err
}
diff --git a/eth/filters/filter.go b/eth/filters/filter.go
index 88614048ac..02b0d57efb 100644
--- a/eth/filters/filter.go
+++ b/eth/filters/filter.go
@@ -32,49 +32,16 @@ import (
"fmt"
"math/big"
- "github.com/ava-labs/coreth/core/vm"
-
- "github.com/ava-labs/coreth/core"
"github.com/ava-labs/coreth/core/bloombits"
"github.com/ava-labs/coreth/core/types"
- "github.com/ava-labs/coreth/ethdb"
"github.com/ava-labs/coreth/rpc"
"github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/event"
)
-type Backend interface {
- ChainDb() ethdb.Database
- HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error)
- HeaderByHash(ctx context.Context, blockHash common.Hash) (*types.Header, error)
- GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error)
- GetLogs(ctx context.Context, blockHash common.Hash) ([][]*types.Log, error)
-
- SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription
- SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription
- SubscribeChainAcceptedEvent(ch chan<- core.ChainEvent) event.Subscription
- SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription
- SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription
- SubscribeAcceptedLogsEvent(ch chan<- []*types.Log) event.Subscription
-
- SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription
-
- SubscribeAcceptedTransactionEvent(ch chan<- core.NewTxsEvent) event.Subscription
-
- BloomStatus() (uint64, uint64)
- ServiceFilter(ctx context.Context, session *bloombits.MatcherSession)
-
- // Added to the backend interface to support limiting of logs requests
- GetVMConfig() *vm.Config
- LastAcceptedBlock() *types.Block
- GetMaxBlocksPerRequest() int64
-}
-
// Filter can be used to retrieve and filter logs.
type Filter struct {
- backend Backend
+ sys *FilterSystem
- db ethdb.Database
addresses []common.Address
topics [][]common.Hash
@@ -86,9 +53,9 @@ type Filter struct {
// NewRangeFilter creates a new filter which uses a bloom filter on blocks to
// figure out whether a particular block is interesting or not.
-func NewRangeFilter(backend Backend, begin, end int64, addresses []common.Address, topics [][]common.Hash) (*Filter, error) {
- allowUnfinalizedQueries := backend.GetVMConfig().AllowUnfinalizedQueries
- acceptedBlock := backend.LastAcceptedBlock()
+func (sys *FilterSystem) NewRangeFilter(begin, end int64, addresses []common.Address, topics [][]common.Hash) (*Filter, error) {
+ allowUnfinalizedQueries := sys.backend.GetVMConfig().AllowUnfinalizedQueries
+ acceptedBlock := sys.backend.LastAcceptedBlock()
// Flatten the address and topic filter clauses into a single bloombits filter
// system. Since the bloombits are not positional, nil topics are permitted,
@@ -108,7 +75,7 @@ func NewRangeFilter(backend Backend, begin, end int64, addresses []common.Addres
}
filters = append(filters, filter)
}
- size, _ := backend.BloomStatus()
+ size, _ := sys.backend.BloomStatus()
if !allowUnfinalizedQueries && acceptedBlock != nil {
lastAccepted := acceptedBlock.Number().Int64()
@@ -121,7 +88,7 @@ func NewRangeFilter(backend Backend, begin, end int64, addresses []common.Addres
}
// Create a generic filter and convert it into a range filter
- filter := newFilter(backend, addresses, topics)
+ filter := newFilter(sys, addresses, topics)
filter.matcher = bloombits.NewMatcher(size, filters)
filter.begin = begin
@@ -132,21 +99,20 @@ func NewRangeFilter(backend Backend, begin, end int64, addresses []common.Addres
// NewBlockFilter creates a new filter which directly inspects the contents of
// a block to figure out whether it is interesting or not.
-func NewBlockFilter(backend Backend, block common.Hash, addresses []common.Address, topics [][]common.Hash) *Filter {
+func (sys *FilterSystem) NewBlockFilter(block common.Hash, addresses []common.Address, topics [][]common.Hash) *Filter {
// Create a generic filter and convert it into a block filter
- filter := newFilter(backend, addresses, topics)
+ filter := newFilter(sys, addresses, topics)
filter.block = block
return filter
}
// newFilter creates a generic filter that can either filter based on a block hash,
// or based on range queries. The search criteria needs to be explicitly set.
-func newFilter(backend Backend, addresses []common.Address, topics [][]common.Hash) *Filter {
+func newFilter(sys *FilterSystem, addresses []common.Address, topics [][]common.Hash) *Filter {
return &Filter{
- backend: backend,
+ sys: sys,
addresses: addresses,
topics: topics,
- db: backend.ChainDb(),
}
}
@@ -155,14 +121,14 @@ func newFilter(backend Backend, addresses []common.Address, topics [][]common.Ha
func (f *Filter) Logs(ctx context.Context) ([]*types.Log, error) {
// If we're doing singleton block filtering, execute and return
if f.block != (common.Hash{}) {
- header, err := f.backend.HeaderByHash(ctx, f.block)
+ header, err := f.sys.backend.HeaderByHash(ctx, f.block)
if err != nil {
return nil, err
}
if header == nil {
return nil, errors.New("unknown block")
}
- return f.blockLogs(ctx, header)
+ return f.blockLogs(ctx, header, false)
}
// Short-cut if all we care about is pending logs
if f.begin == rpc.PendingBlockNumber.Int64() {
@@ -175,7 +141,7 @@ func (f *Filter) Logs(ctx context.Context) ([]*types.Log, error) {
// Figure out the limits of the filter range
// LatestBlockNumber is transformed into the last accepted block in HeaderByNumber
// so it is left in place here.
- header, err := f.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber)
+ header, err := f.sys.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber)
if err != nil {
return nil, err
}
@@ -205,13 +171,13 @@ func (f *Filter) Logs(ctx context.Context) ([]*types.Log, error) {
// If the requested range of blocks exceeds the maximum number of blocks allowed by the backend
// return an error instead of searching for the logs.
- if maxBlocks := f.backend.GetMaxBlocksPerRequest(); int64(end)-f.begin > maxBlocks && maxBlocks > 0 {
+ if maxBlocks := f.sys.backend.GetMaxBlocksPerRequest(); int64(end)-f.begin > maxBlocks && maxBlocks > 0 {
return nil, fmt.Errorf("requested too many blocks from %d to %d, maximum is set to %d", f.begin, int64(end), maxBlocks)
}
// Gather all indexed logs, and finish with non indexed ones
var (
logs []*types.Log
- size, sections = f.backend.BloomStatus()
+ size, sections = f.sys.backend.BloomStatus()
)
if indexed := sections * size; indexed > uint64(f.begin) {
if indexed > end {
@@ -240,7 +206,7 @@ func (f *Filter) indexedLogs(ctx context.Context, end uint64) ([]*types.Log, err
}
defer session.Close()
- f.backend.ServiceFilter(ctx, session)
+ f.sys.backend.ServiceFilter(ctx, session)
// Iterate over the matches until exhausted or context closed
var logs []*types.Log
@@ -259,11 +225,11 @@ func (f *Filter) indexedLogs(ctx context.Context, end uint64) ([]*types.Log, err
f.begin = int64(number) + 1
// Retrieve the suggested block and pull any truly matching logs
- header, err := f.backend.HeaderByNumber(ctx, rpc.BlockNumber(number))
+ header, err := f.sys.backend.HeaderByNumber(ctx, rpc.BlockNumber(number))
if header == nil || err != nil {
return logs, err
}
- found, err := f.checkMatches(ctx, header)
+ found, err := f.blockLogs(ctx, header, true)
if err != nil {
return logs, err
}
@@ -281,11 +247,11 @@ func (f *Filter) unindexedLogs(ctx context.Context, end uint64) ([]*types.Log, e
var logs []*types.Log
for ; f.begin <= int64(end); f.begin++ {
- header, err := f.backend.HeaderByNumber(ctx, rpc.BlockNumber(f.begin))
+ header, err := f.sys.backend.HeaderByNumber(ctx, rpc.BlockNumber(f.begin))
if header == nil || err != nil {
return logs, err
}
- found, err := f.blockLogs(ctx, header)
+ found, err := f.blockLogs(ctx, header, false)
if err != nil {
return logs, err
}
@@ -295,34 +261,34 @@ func (f *Filter) unindexedLogs(ctx context.Context, end uint64) ([]*types.Log, e
}
// blockLogs returns the logs matching the filter criteria within a single block.
-func (f *Filter) blockLogs(ctx context.Context, header *types.Header) (logs []*types.Log, err error) {
- if bloomFilter(header.Bloom, f.addresses, f.topics) {
- found, err := f.checkMatches(ctx, header)
+func (f *Filter) blockLogs(ctx context.Context, header *types.Header, skipBloom bool) ([]*types.Log, error) {
+ // Fast track: no filtering criteria
+ if len(f.addresses) == 0 && len(f.topics) == 0 {
+ list, err := f.sys.cachedGetLogs(ctx, header.Hash(), header.Number.Uint64())
if err != nil {
- return logs, err
+ return nil, err
}
- logs = append(logs, found...)
+ return flatten(list), nil
+ } else if skipBloom || bloomFilter(header.Bloom, f.addresses, f.topics) {
+ return f.checkMatches(ctx, header)
}
- return logs, nil
+ return nil, nil
}
// checkMatches checks if the receipts belonging to the given header contain any log events that
// match the filter criteria. This function is called when the bloom filter signals a potential match.
-func (f *Filter) checkMatches(ctx context.Context, header *types.Header) (logs []*types.Log, err error) {
- // Get the logs of the block
- logsList, err := f.backend.GetLogs(ctx, header.Hash())
+func (f *Filter) checkMatches(ctx context.Context, header *types.Header) ([]*types.Log, error) {
+ logsList, err := f.sys.cachedGetLogs(ctx, header.Hash(), header.Number.Uint64())
if err != nil {
return nil, err
}
- var unfiltered []*types.Log
- for _, logs := range logsList {
- unfiltered = append(unfiltered, logs...)
- }
- logs = filterLogs(unfiltered, nil, nil, f.addresses, f.topics)
+
+ unfiltered := flatten(logsList)
+ logs := filterLogs(unfiltered, nil, nil, f.addresses, f.topics)
if len(logs) > 0 {
// We have matching logs, check if we need to resolve full logs via the light client
if logs[0].TxHash == (common.Hash{}) {
- receipts, err := f.backend.GetReceipts(ctx, header.Hash())
+ receipts, err := f.sys.backend.GetReceipts(ctx, header.Hash())
if err != nil {
return nil, err
}
@@ -411,3 +377,11 @@ func bloomFilter(bloom types.Bloom, addresses []common.Address, topics [][]commo
}
return true
}
+
+func flatten(list [][]*types.Log) []*types.Log {
+ var flat []*types.Log
+ for _, logs := range list {
+ flat = append(flat, logs...)
+ }
+ return flat
+}
diff --git a/eth/filters/filter_system.go b/eth/filters/filter_system.go
index 81e353e98d..5019119803 100644
--- a/eth/filters/filter_system.go
+++ b/eth/filters/filter_system.go
@@ -35,15 +35,102 @@ import (
"time"
"github.com/ava-labs/coreth/core"
+ "github.com/ava-labs/coreth/core/bloombits"
"github.com/ava-labs/coreth/core/rawdb"
"github.com/ava-labs/coreth/core/types"
+ "github.com/ava-labs/coreth/core/vm"
+ "github.com/ava-labs/coreth/ethdb"
"github.com/ava-labs/coreth/interfaces"
"github.com/ava-labs/coreth/rpc"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
+ lru "github.com/hashicorp/golang-lru"
)
+// Config represents the configuration of the filter system.
+type Config struct {
+ LogCacheSize int // maximum number of cached blocks (default: 32)
+ Timeout time.Duration // how long filters stay active (default: 5min)
+}
+
+func (cfg Config) withDefaults() Config {
+ if cfg.Timeout == 0 {
+ cfg.Timeout = 5 * time.Minute
+ }
+ if cfg.LogCacheSize == 0 {
+ cfg.LogCacheSize = 32
+ }
+ return cfg
+}
+
+type Backend interface {
+ ChainDb() ethdb.Database
+ HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error)
+ HeaderByHash(ctx context.Context, blockHash common.Hash) (*types.Header, error)
+ GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error)
+ GetLogs(ctx context.Context, blockHash common.Hash, number uint64) ([][]*types.Log, error)
+
+ SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription
+ SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription
+ SubscribeChainAcceptedEvent(ch chan<- core.ChainEvent) event.Subscription
+ SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription
+ SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription
+ SubscribeAcceptedLogsEvent(ch chan<- []*types.Log) event.Subscription
+
+ SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription
+
+ SubscribeAcceptedTransactionEvent(ch chan<- core.NewTxsEvent) event.Subscription
+
+ BloomStatus() (uint64, uint64)
+ ServiceFilter(ctx context.Context, session *bloombits.MatcherSession)
+
+ // Added to the backend interface to support limiting of logs requests
+ GetVMConfig() *vm.Config
+ LastAcceptedBlock() *types.Block
+ GetMaxBlocksPerRequest() int64
+}
+
+// FilterSystem holds resources shared by all filters.
+type FilterSystem struct {
+ backend Backend
+ logsCache *lru.Cache
+ cfg *Config
+}
+
+// NewFilterSystem creates a filter system.
+func NewFilterSystem(backend Backend, config Config) *FilterSystem {
+ config = config.withDefaults()
+
+ cache, err := lru.New(config.LogCacheSize)
+ if err != nil {
+ panic(err)
+ }
+ return &FilterSystem{
+ backend: backend,
+ logsCache: cache,
+ cfg: &config,
+ }
+}
+
+// cachedGetLogs loads block logs from the backend and caches the result.
+func (sys *FilterSystem) cachedGetLogs(ctx context.Context, blockHash common.Hash, number uint64) ([][]*types.Log, error) {
+ cached, ok := sys.logsCache.Get(blockHash)
+ if ok {
+ return cached.([][]*types.Log), nil
+ }
+
+ logs, err := sys.backend.GetLogs(ctx, blockHash, number)
+ if err != nil {
+ return nil, err
+ }
+ if logs == nil {
+ return nil, fmt.Errorf("failed to get logs for block #%d (0x%s)", number, blockHash.TerminalString())
+ }
+ sys.logsCache.Add(blockHash, logs)
+ return logs, nil
+}
+
// Type determines the kind of filter and is used to put the filter in to
// the correct bucket when added.
type Type byte
@@ -100,6 +187,7 @@ type subscription struct {
// subscription which match the subscription criteria.
type EventSystem struct {
backend Backend
+ sys *FilterSystem
lightMode bool
lastHead *types.Header
@@ -132,9 +220,10 @@ type EventSystem struct {
//
// The returned manager has a loop that needs to be stopped with the Stop function
// or by stopping the given mux.
-func NewEventSystem(backend Backend, lightMode bool) *EventSystem {
+func NewEventSystem(sys *FilterSystem, lightMode bool) *EventSystem {
m := &EventSystem{
- backend: backend,
+ sys: sys,
+ backend: sys.backend,
lightMode: lightMode,
install: make(chan *subscription),
uninstall: make(chan *subscription),
@@ -537,7 +626,7 @@ func (es *EventSystem) lightFilterLogs(header *types.Header, addresses []common.
// Get the logs of the block
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
- logsList, err := es.backend.GetLogs(ctx, header.Hash())
+ logsList, err := es.sys.cachedGetLogs(ctx, header.Hash(), header.Number.Uint64())
if err != nil {
return nil
}
diff --git a/eth/state_accessor.go b/eth/state_accessor.go
index bc45f61b41..3e5f4c99ec 100644
--- a/eth/state_accessor.go
+++ b/eth/state_accessor.go
@@ -55,7 +55,7 @@ import (
// perform Commit or other 'save-to-disk' changes, this should be set to false to avoid
// storing trash persistently
// - preferDisk: this arg can be used by the caller to signal that even though the 'base' is provided,
-// it would be preferrable to start from a fresh state, if we have it on disk.
+// it would be preferable to start from a fresh state, if we have it on disk.
func (eth *Ethereum) StateAtBlock(block *types.Block, reexec uint64, base *state.StateDB, checkLive bool, preferDisk bool) (statedb *state.StateDB, err error) {
var (
current *types.Block
diff --git a/eth/tracers/api.go b/eth/tracers/api.go
index 1625675de9..deedfe421c 100644
--- a/eth/tracers/api.go
+++ b/eth/tracers/api.go
@@ -29,6 +29,7 @@ package tracers
import (
"bytes"
"context"
+ "encoding/json"
"errors"
"fmt"
"math/big"
@@ -121,7 +122,7 @@ func (context *chainContext) GetHeader(hash common.Hash, number uint64) *types.H
return header
}
-// chainContext construts the context reader which is used by the evm for reading
+// chainContext constructs the context reader which is used by the evm for reading
// the necessary chain context.
func (api *API) chainContext(ctx context.Context) core.ChainContext {
return &chainContext{api: api, ctx: ctx}
@@ -175,15 +176,15 @@ type TraceConfig struct {
Tracer *string
Timeout *string
Reexec *uint64
+ // Config specific to given tracer. Note struct logger
+ // config are historically embedded in main object.
+ TracerConfig json.RawMessage
}
// TraceCallConfig is the config for traceCall API. It holds one more
// field to override the state for tracing.
type TraceCallConfig struct {
- *logger.Config
- Tracer *string
- Timeout *string
- Reexec *uint64
+ TraceConfig
StateOverrides *ethapi.StateOverride
BlockOverrides *ethapi.BlockOverrides
}
@@ -378,7 +379,7 @@ func (api *API) traceChain(ctx context.Context, start, end *types.Block, config
}
if trieDb := statedb.Database().TrieDB(); trieDb != nil {
// Hold the reference for tracer, will be released at the final stage
- trieDb.Reference(block.Root(), common.Hash{}, true)
+ trieDb.Reference(block.Root(), common.Hash{})
// Release the parent state because it's already held by the tracer
if parent != (common.Hash{}) {
@@ -546,7 +547,7 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config
// traceBlock configures a new tracer according to the provided configuration, and
// executes all the transactions contained within. The return value will be one item
-// per transaction, dependent on the requestd tracer.
+// per transaction, dependent on the requested tracer.
func (api *API) traceBlock(ctx context.Context, block *types.Block, config *TraceConfig) ([]*txTraceResult, error) {
if block.NumberU64() == 0 {
return nil, errors.New("genesis is not traceable")
@@ -738,7 +739,7 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex
// Default tracer is the struct logger
tracer = logger.NewStructLogger(config.Config)
if config.Tracer != nil {
- tracer, err = New(*config.Tracer, txctx)
+ tracer, err = New(*config.Tracer, txctx, config.TracerConfig)
if err != nil {
return nil, err
}
diff --git a/eth/tracers/internal/tracetest/calltrace_test.go b/eth/tracers/internal/tracetest/calltrace_test.go
index 79f1459b51..340c35d0b3 100644
--- a/eth/tracers/internal/tracetest/calltrace_test.go
+++ b/eth/tracers/internal/tracetest/calltrace_test.go
@@ -127,10 +127,11 @@ type callTrace struct {
// callTracerTest defines a single test to check the call tracer against.
type callTracerTest struct {
- Genesis *core.Genesis `json:"genesis"`
- Context *callContext `json:"context"`
- Input string `json:"input"`
- Result *callTrace `json:"result"`
+ Genesis *core.Genesis `json:"genesis"`
+ Context *callContext `json:"context"`
+ Input string `json:"input"`
+ TracerConfig json.RawMessage `json:"tracerConfig"`
+ Result *callTrace `json:"result"`
}
func TestCallTracerNative(t *testing.T) {
@@ -182,7 +183,7 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) {
}
_, statedb = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false)
)
- tracer, err := tracers.New(tracerName, new(tracers.Context))
+ tracer, err := tracers.New(tracerName, new(tracers.Context), test.TracerConfig)
if err != nil {
t.Fatalf("failed to create call tracer: %v", err)
}
@@ -297,7 +298,7 @@ func benchTracer(tracerName string, test *callTracerTest, b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
- tracer, err := tracers.New(tracerName, new(tracers.Context))
+ tracer, err := tracers.New(tracerName, new(tracers.Context), nil)
if err != nil {
b.Fatalf("failed to create call tracer: %v", err)
}
@@ -363,7 +364,7 @@ func TestZeroValueToNotExitCall(t *testing.T) {
}
_, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc, false)
// Create the tracer, the EVM environment and run it
- tracer, err := tracers.New("callTracer", nil)
+ tracer, err := tracers.New("callTracer", nil, nil)
if err != nil {
t.Fatalf("failed to create call tracer: %v", err)
}
diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer/simple_onlytop.json b/eth/tracers/internal/tracetest/testdata/call_tracer/simple_onlytop.json
new file mode 100644
index 0000000000..ac1fef4409
--- /dev/null
+++ b/eth/tracers/internal/tracetest/testdata/call_tracer/simple_onlytop.json
@@ -0,0 +1,72 @@
+{
+ "context": {
+ "difficulty": "3502894804",
+ "gasLimit": "4722976",
+ "miner": "0x1585936b53834b021f68cc13eeefdec2efc8e724",
+ "number": "2289806",
+ "timestamp": "1513601314"
+ },
+ "genesis": {
+ "alloc": {
+ "0x0024f658a46fbb89d8ac105e98d7ac7cbbaf27c5": {
+ "balance": "0x0",
+ "code": "0x",
+ "nonce": "22",
+ "storage": {}
+ },
+ "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe": {
+ "balance": "0x4d87094125a369d9bd5",
+ "code": "0x606060405236156100935763ffffffff60e060020a60003504166311ee8382811461009c57806313af4035146100be5780631f5e8f4c146100ee57806324daddc5146101125780634921a91a1461013b57806363e4bff414610157578063764978f91461017f578063893d20e8146101a1578063ba40aaa1146101cd578063cebc9a82146101f4578063e177246e14610216575b61009a5b5b565b005b34156100a457fe5b6100ac61023d565b60408051918252519081900360200190f35b34156100c657fe5b6100da600160a060020a0360043516610244565b604080519115158252519081900360200190f35b34156100f657fe5b6100da610307565b604080519115158252519081900360200190f35b341561011a57fe5b6100da6004351515610318565b604080519115158252519081900360200190f35b6100da6103d6565b604080519115158252519081900360200190f35b6100da600160a060020a0360043516610420565b604080519115158252519081900360200190f35b341561018757fe5b6100ac61046c565b60408051918252519081900360200190f35b34156101a957fe5b6101b1610473565b60408051600160a060020a039092168252519081900360200190f35b34156101d557fe5b6100da600435610483565b604080519115158252519081900360200190f35b34156101fc57fe5b6100ac61050d565b60408051918252519081900360200190f35b341561021e57fe5b6100da600435610514565b604080519115158252519081900360200190f35b6003545b90565b60006000610250610473565b600160a060020a031633600160a060020a03161415156102705760006000fd5b600160a060020a03831615156102865760006000fd5b50600054600160a060020a0390811690831681146102fb57604051600160a060020a0380851691908316907ffcf23a92150d56e85e3a3d33b357493246e55783095eb6a733eb8439ffc752c890600090a360008054600160a060020a031916600160a060020a03851617905560019150610300565b600091505b5b50919050565b60005460a060020a900460ff165b90565b60006000610324610473565b600160a060020a031633600160a060020a03161415156103445760006000fd5b5060005460a060020a900460ff16801515831515146102fb576000546040805160a060020a90920460ff1615158252841515602083015280517fe6cd46a119083b86efc6884b970bfa30c1708f53ba57b86716f15b2f4551a9539281900390910190a16000805460a060020a60ff02191660a060020a8515150217905560019150610300565b600091505b5b50919050565b60006103e0610307565b801561040557506103ef610473565b600160a060020a031633600160a060020a031614155b156104105760006000fd5b610419336105a0565b90505b5b90565b600061042a610307565b801561044f5750610439610473565b600160a060020a031633600160a060020a031614155b1561045a5760006000fd5b610463826105a0565b90505b5b919050565b6001545b90565b600054600160a060020a03165b90565b6000600061048f610473565b600160a060020a031633600160a060020a03161415156104af5760006000fd5b506001548281146102fb57604080518281526020810185905281517f79a3746dde45672c9e8ab3644b8bb9c399a103da2dc94b56ba09777330a83509929181900390910190a160018381559150610300565b600091505b5b50919050565b6002545b90565b60006000610520610473565b600160a060020a031633600160a060020a03161415156105405760006000fd5b506002548281146102fb57604080518281526020810185905281517ff6991a728965fedd6e927fdf16bdad42d8995970b4b31b8a2bf88767516e2494929181900390910190a1600283905560019150610300565b600091505b5b50919050565b60006000426105ad61023d565b116102fb576105c46105bd61050d565b4201610652565b6105cc61046c565b604051909150600160a060020a038416908290600081818185876187965a03f1925050501561063d57604080518281529051600160a060020a038516917f9bca65ce52fdef8a470977b51f247a2295123a4807dfa9e502edf0d30722da3b919081900360200190a260019150610300565b6102fb42610652565b5b600091505b50919050565b60038190555b505600a165627a7a72305820f3c973c8b7ed1f62000b6701bd5b708469e19d0f1d73fde378a56c07fd0b19090029",
+ "nonce": "1",
+ "storage": {
+ "0x0000000000000000000000000000000000000000000000000000000000000000": "0x000000000000000000000001b436ba50d378d4bbc8660d312a13df6af6e89dfb",
+ "0x0000000000000000000000000000000000000000000000000000000000000001": "0x00000000000000000000000000000000000000000000000006f05b59d3b20000",
+ "0x0000000000000000000000000000000000000000000000000000000000000002": "0x000000000000000000000000000000000000000000000000000000000000003c",
+ "0x0000000000000000000000000000000000000000000000000000000000000003": "0x000000000000000000000000000000000000000000000000000000005a37b834"
+ }
+ },
+ "0xb436ba50d378d4bbc8660d312a13df6af6e89dfb": {
+ "balance": "0x1780d77678137ac1b775",
+ "code": "0x",
+ "nonce": "29072",
+ "storage": {}
+ }
+ },
+ "config": {
+ "byzantiumBlock": 1700000,
+ "chainId": 3,
+ "daoForkSupport": true,
+ "eip150Block": 0,
+ "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d",
+ "eip155Block": 10,
+ "eip158Block": 10,
+ "ethash": {},
+ "homesteadBlock": 0
+ },
+ "difficulty": "3509749784",
+ "extraData": "0x4554482e45544846414e532e4f52472d4641313738394444",
+ "gasLimit": "4727564",
+ "hash": "0x609948ac3bd3c00b7736b933248891d6c901ee28f066241bddb28f4e00a9f440",
+ "miner": "0xbbf5029fd710d227630c8b7d338051b8e76d50b3",
+ "mixHash": "0xb131e4507c93c7377de00e7c271bf409ec7492767142ff0f45c882f8068c2ada",
+ "nonce": "0x4eb12e19c16d43da",
+ "number": "2289805",
+ "stateRoot": "0xc7f10f352bff82fac3c2999d3085093d12652e19c7fd32591de49dc5d91b4f1f",
+ "timestamp": "1513601261",
+ "totalDifficulty": "7143276353481064"
+ },
+ "input": "0xf88b8271908506fc23ac0083015f90943b873a919aa0512d5a0f09e6dcceaa4a6727fafe80a463e4bff40000000000000000000000000024f658a46fbb89d8ac105e98d7ac7cbbaf27c52aa0bdce0b59e8761854e857fe64015f06dd08a4fbb7624f6094893a79a72e6ad6bea01d9dde033cff7bb235a3163f348a6d7ab8d6b52bc0963a95b91612e40ca766a4",
+ "tracerConfig": {
+ "onlyTopCall": true
+ },
+ "result": {
+ "from": "0xb436ba50d378d4bbc8660d312a13df6af6e89dfb",
+ "gas": "0x10738",
+ "gasUsed": "0x3ef9",
+ "input": "0x63e4bff40000000000000000000000000024f658a46fbb89d8ac105e98d7ac7cbbaf27c5",
+ "output": "0x0000000000000000000000000000000000000000000000000000000000000001",
+ "to": "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe",
+ "type": "CALL",
+ "value": "0x0"
+ }
+}
diff --git a/eth/tracers/native/4byte.go b/eth/tracers/native/4byte.go
index 4b4bd12a20..1b7eced708 100644
--- a/eth/tracers/native/4byte.go
+++ b/eth/tracers/native/4byte.go
@@ -65,11 +65,11 @@ type fourByteTracer struct {
// newFourByteTracer returns a native go tracer which collects
// 4 byte-identifiers of a tx, and implements vm.EVMLogger.
-func newFourByteTracer(ctx *tracers.Context) tracers.Tracer {
+func newFourByteTracer(ctx *tracers.Context, _ json.RawMessage) (tracers.Tracer, error) {
t := &fourByteTracer{
ids: make(map[string]int),
}
- return t
+ return t, nil
}
// isPrecompiled returns whether the addr is a precompile. Logic borrowed from newJsTracer in eth/tracers/js/tracer.go
diff --git a/eth/tracers/native/call.go b/eth/tracers/native/call.go
index dc18a956b0..bc01277bab 100644
--- a/eth/tracers/native/call.go
+++ b/eth/tracers/native/call.go
@@ -60,16 +60,27 @@ type callFrame struct {
type callTracer struct {
env *vm.EVM
callstack []callFrame
+ config callTracerConfig
interrupt uint32 // Atomic flag to signal execution interruption
reason error // Textual reason for the interruption
}
+type callTracerConfig struct {
+ OnlyTopCall bool `json:"onlyTopCall"` // If true, call tracer won't collect any subcalls
+}
+
// newCallTracer returns a native go tracer which tracks
// call frames of a tx, and implements vm.EVMLogger.
-func newCallTracer(ctx *tracers.Context) tracers.Tracer {
+func newCallTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) {
+ var config callTracerConfig
+ if cfg != nil {
+ if err := json.Unmarshal(cfg, &config); err != nil {
+ return nil, err
+ }
+ }
// First callframe contains tx context info
// and is populated on start and end.
- return &callTracer{callstack: make([]callFrame, 1)}
+ return &callTracer{callstack: make([]callFrame, 1), config: config}, nil
}
// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
@@ -111,6 +122,9 @@ func (t *callTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, _ *
// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct).
func (t *callTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
+ if t.config.OnlyTopCall {
+ return
+ }
// Skip if tracing was interrupted
if atomic.LoadUint32(&t.interrupt) > 0 {
t.env.Cancel()
@@ -131,6 +145,9 @@ func (t *callTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.
// CaptureExit is called when EVM exits a scope, even if the scope didn't
// execute any code.
func (t *callTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
+ if t.config.OnlyTopCall {
+ return
+ }
size := len(t.callstack)
if size <= 1 {
return
diff --git a/eth/tracers/native/noop.go b/eth/tracers/native/noop.go
index 7fe4ab89ba..23884900ba 100644
--- a/eth/tracers/native/noop.go
+++ b/eth/tracers/native/noop.go
@@ -45,8 +45,8 @@ func init() {
type noopTracer struct{}
// newNoopTracer returns a new noop tracer.
-func newNoopTracer(ctx *tracers.Context) tracers.Tracer {
- return &noopTracer{}
+func newNoopTracer(ctx *tracers.Context, _ json.RawMessage) (tracers.Tracer, error) {
+ return &noopTracer{}, nil
}
// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
diff --git a/eth/tracers/native/prestate.go b/eth/tracers/native/prestate.go
index afc51c9f66..30a2bc743a 100644
--- a/eth/tracers/native/prestate.go
+++ b/eth/tracers/native/prestate.go
@@ -61,10 +61,10 @@ type prestateTracer struct {
reason error // Textual reason for the interruption
}
-func newPrestateTracer(ctx *tracers.Context) tracers.Tracer {
+func newPrestateTracer(ctx *tracers.Context, _ json.RawMessage) (tracers.Tracer, error) {
// First callframe contains tx context info
// and is populated on start and end.
- return &prestateTracer{prestate: prestate{}}
+ return &prestateTracer{prestate: prestate{}}, nil
}
// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
diff --git a/eth/tracers/native/revertreason.go b/eth/tracers/native/revertreason.go
index e1a0d78a91..5056ae7caa 100644
--- a/eth/tracers/native/revertreason.go
+++ b/eth/tracers/native/revertreason.go
@@ -57,8 +57,8 @@ type revertReasonTracer struct {
}
// newRevertReasonTracer returns a new revert reason tracer.
-func newRevertReasonTracer(_ *tracers.Context) tracers.Tracer {
- return &revertReasonTracer{}
+func newRevertReasonTracer(_ *tracers.Context, _ json.RawMessage) (tracers.Tracer, error) {
+ return &revertReasonTracer{}, nil
}
// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
diff --git a/eth/tracers/native/tracer.go b/eth/tracers/native/tracer.go
index 41ba6f35f9..102e68859a 100644
--- a/eth/tracers/native/tracer.go
+++ b/eth/tracers/native/tracer.go
@@ -45,6 +45,7 @@ func init() {
package native
import (
+ "encoding/json"
"errors"
"github.com/ava-labs/coreth/eth/tracers"
@@ -56,7 +57,7 @@ func init() {
}
// ctorFn is the constructor signature of a native tracer.
-type ctorFn = func(*tracers.Context) tracers.Tracer
+type ctorFn = func(*tracers.Context, json.RawMessage) (tracers.Tracer, error)
/*
ctors is a map of package-local tracer constructors.
@@ -81,12 +82,12 @@ func register(name string, ctor ctorFn) {
}
// lookup returns a tracer, if one can be matched to the given name.
-func lookup(name string, ctx *tracers.Context) (tracers.Tracer, error) {
+func lookup(name string, ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) {
if ctors == nil {
ctors = make(map[string]ctorFn)
}
if ctor, ok := ctors[name]; ok {
- return ctor(ctx), nil
+ return ctor(ctx, cfg)
}
return nil, errors.New("no tracer found")
}
diff --git a/eth/tracers/tracers.go b/eth/tracers/tracers.go
index cb1576f651..c8e1b5a2f5 100644
--- a/eth/tracers/tracers.go
+++ b/eth/tracers/tracers.go
@@ -42,7 +42,7 @@ type Tracer interface {
Stop(err error)
}
-type lookupFunc func(string, *Context) (Tracer, error)
+type lookupFunc func(string, *Context, json.RawMessage) (Tracer, error)
var (
lookups []lookupFunc
@@ -62,9 +62,9 @@ func RegisterLookup(wildcard bool, lookup lookupFunc) {
// New returns a new instance of a tracer, by iterating through the
// registered lookups.
-func New(code string, ctx *Context) (Tracer, error) {
+func New(code string, ctx *Context, cfg json.RawMessage) (Tracer, error) {
for _, lookup := range lookups {
- if tracer, err := lookup(code, ctx); err == nil {
+ if tracer, err := lookup(code, ctx, cfg); err == nil {
return tracer, nil
}
}
diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go
index 76ea4898ca..f74948ba08 100644
--- a/ethclient/ethclient.go
+++ b/ethclient/ethclient.go
@@ -98,6 +98,7 @@ type Client interface {
CallContractAtHash(ctx context.Context, msg interfaces.CallMsg, blockHash common.Hash) ([]byte, error)
SuggestGasPrice(context.Context) (*big.Int, error)
SuggestGasTipCap(context.Context) (*big.Int, error)
+ FeeHistory(ctx context.Context, blockCount uint64, lastBlock *big.Int, rewardPercentiles []float64) (*interfaces.FeeHistory, error)
EstimateGas(context.Context, interfaces.CallMsg) (uint64, error)
EstimateBaseFee(context.Context) (*big.Int, error)
SendTransaction(context.Context, *types.Transaction) error
@@ -557,6 +558,38 @@ func (ec *client) SuggestGasTipCap(ctx context.Context) (*big.Int, error) {
return (*big.Int)(&hex), nil
}
+type feeHistoryResultMarshaling struct {
+ OldestBlock *hexutil.Big `json:"oldestBlock"`
+ Reward [][]*hexutil.Big `json:"reward,omitempty"`
+ BaseFee []*hexutil.Big `json:"baseFeePerGas,omitempty"`
+ GasUsedRatio []float64 `json:"gasUsedRatio"`
+}
+
+// FeeHistory retrieves the fee market history.
+func (ec *client) FeeHistory(ctx context.Context, blockCount uint64, lastBlock *big.Int, rewardPercentiles []float64) (*interfaces.FeeHistory, error) {
+ var res feeHistoryResultMarshaling
+ if err := ec.c.CallContext(ctx, &res, "eth_feeHistory", hexutil.Uint(blockCount), ToBlockNumArg(lastBlock), rewardPercentiles); err != nil {
+ return nil, err
+ }
+ reward := make([][]*big.Int, len(res.Reward))
+ for i, r := range res.Reward {
+ reward[i] = make([]*big.Int, len(r))
+ for j, r := range r {
+ reward[i][j] = (*big.Int)(r)
+ }
+ }
+ baseFee := make([]*big.Int, len(res.BaseFee))
+ for i, b := range res.BaseFee {
+ baseFee[i] = (*big.Int)(b)
+ }
+ return &interfaces.FeeHistory{
+ OldestBlock: (*big.Int)(res.OldestBlock),
+ Reward: reward,
+ BaseFee: baseFee,
+ GasUsedRatio: res.GasUsedRatio,
+ }, nil
+}
+
// EstimateGas tries to estimate the gas needed to execute a specific transaction based on
// the current pending state of the backend blockchain. There is no guarantee that this is
// the true gas limit requirement as other transactions may be added or removed by miners,
diff --git a/ethdb/memorydb/memorydb.go b/ethdb/memorydb/memorydb.go
index 6e2f455288..dbc9adac16 100644
--- a/ethdb/memorydb/memorydb.go
+++ b/ethdb/memorydb/memorydb.go
@@ -72,7 +72,7 @@ func NewWithCap(size int) *Database {
}
// Close deallocates the internal map and ensures any consecutive data access op
-// failes with an error.
+// fails with an error.
func (db *Database) Close() error {
db.lock.Lock()
defer db.lock.Unlock()
diff --git a/go.mod b/go.mod
index 0f27ecce51..369d33b989 100644
--- a/go.mod
+++ b/go.mod
@@ -4,11 +4,11 @@ go 1.18
require (
github.com/VictoriaMetrics/fastcache v1.10.0
- github.com/ava-labs/avalanchego v1.7.18
+ github.com/ava-labs/avalanchego v1.8.1
github.com/cespare/cp v0.1.0
github.com/davecgh/go-spew v1.1.1
github.com/deckarep/golang-set v1.8.0
- github.com/ethereum/go-ethereum v1.10.21
+ github.com/ethereum/go-ethereum v1.10.23
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5
github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08
github.com/google/uuid v1.2.0
@@ -21,7 +21,7 @@ require (
github.com/mattn/go-colorable v0.1.12
github.com/mattn/go-isatty v0.0.14
github.com/olekukonko/tablewriter v0.0.5
- github.com/prometheus/client_golang v1.12.2
+ github.com/prometheus/client_golang v1.13.0
github.com/prometheus/client_model v0.2.0
github.com/rjeczalik/notify v0.9.2
github.com/shirou/gopsutil v3.21.11+incompatible
@@ -34,7 +34,7 @@ require (
github.com/tyler-smith/go-bip39 v1.0.2
github.com/urfave/cli/v2 v2.10.2
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d
- golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
+ golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f
golang.org/x/text v0.3.7
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac
@@ -97,8 +97,8 @@ require (
github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
- github.com/prometheus/common v0.32.1 // indirect
- github.com/prometheus/procfs v0.7.3 // indirect
+ github.com/prometheus/common v0.37.0 // indirect
+ github.com/prometheus/procfs v0.8.0 // indirect
github.com/prometheus/tsdb v0.10.0 // indirect
github.com/rs/cors v1.7.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
@@ -116,8 +116,8 @@ require (
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
gonum.org/v1/gonum v0.11.0 // indirect
google.golang.org/genproto v0.0.0-20220712132514-bdd2acd4974d // indirect
- google.golang.org/grpc v1.50.0-dev // indirect
- google.golang.org/protobuf v1.28.0 // indirect
+ google.golang.org/grpc v1.49.0 // indirect
+ google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/ini.v1 v1.66.4 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
diff --git a/go.sum b/go.sum
index a4f1230e90..ee6ae39690 100644
--- a/go.sum
+++ b/go.sum
@@ -60,8 +60,8 @@ github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/apache/arrow/go/arrow v0.0.0-20191024131854-af6fa24be0db/go.mod h1:VTxUBvSJ3s3eHAg65PNgrsn5BtqCRPdmyXh6rAfdxN0=
-github.com/ava-labs/avalanchego v1.7.18 h1:x4PrTKIyUZkxhPjjf/a4hQLZDUB8netA6oFMw6Z1M+Q=
-github.com/ava-labs/avalanchego v1.7.18/go.mod h1:Jo21X9sMxvkZNoo8B7GJbbelGrIJbwFPcMhwanink68=
+github.com/ava-labs/avalanchego v1.8.1 h1:V9arHfOvUEV/T8M4KrSpyOX/SSG3avNDSNukeHXAawk=
+github.com/ava-labs/avalanchego v1.8.1/go.mod h1:s1Ne9j2n7x5bUr8ocKtYQcnaIVzk6GBxQZCnoK0B8mU=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
@@ -153,8 +153,8 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
-github.com/ethereum/go-ethereum v1.10.21 h1:5lqsEx92ZaZzRyOqBEXux4/UR06m296RGzN3ol3teJY=
-github.com/ethereum/go-ethereum v1.10.21/go.mod h1:EYFyF19u3ezGLD4RqOkLq+ZCXzYbLoNDdZlMt7kyKFg=
+github.com/ethereum/go-ethereum v1.10.23 h1:Xk8XAT4/UuqcjMLIMF+7imjkg32kfVFKoeyQDaO2yWM=
+github.com/ethereum/go-ethereum v1.10.23/go.mod h1:EYFyF19u3ezGLD4RqOkLq+ZCXzYbLoNDdZlMt7kyKFg=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c=
@@ -180,10 +180,12 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
+github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
-github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
+github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA=
+github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
@@ -460,8 +462,9 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
-github.com/prometheus/client_golang v1.12.2 h1:51L9cDoUHVrXx4zWYlcLQIZ+d+VXHgqnYKkIuq4g/34=
-github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
+github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
+github.com/prometheus/client_golang v1.13.0 h1:b71QUfeo5M8gq2+evJdTPfZhYMAU0uKPkyPJ7TPsloU=
+github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
@@ -471,14 +474,16 @@ github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
-github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
+github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE=
+github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
-github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
+github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
+github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
github.com/prometheus/tsdb v0.10.0 h1:If5rVCMTp6W2SiRAQFlbpJNgVlgMEd+U2GZckwK38ic=
github.com/prometheus/tsdb v0.10.0/go.mod h1:oi49uRhEe9dPUTlS3JRZOwJuVi6tmh10QSgwXEyGCt4=
github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc=
@@ -668,6 +673,7 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220708220712-1185a9018129 h1:vucSRfWwTsoXro7P+3Cjlr6flUMtzCwzlvkxEQtHHB0=
@@ -682,6 +688,7 @@ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -692,8 +699,9 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8=
+golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -941,8 +949,8 @@ google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA5
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
-google.golang.org/grpc v1.50.0-dev h1:cL3RBCQpJ9B+dJmkAVg0OeAogLIuGkH/kWiXKX+RVSI=
-google.golang.org/grpc v1.50.0-dev/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
+google.golang.org/grpc v1.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw=
+google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -956,8 +964,9 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
+google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/interfaces/interfaces.go b/interfaces/interfaces.go
index 100e658c3c..f0b09c67a8 100644
--- a/interfaces/interfaces.go
+++ b/interfaces/interfaces.go
@@ -176,6 +176,15 @@ type GasPricer interface {
SuggestGasPrice(ctx context.Context) (*big.Int, error)
}
+// FeeHistory provides recent fee market data that consumers can use to determine
+// a reasonable maxPriorityFeePerGas value.
+type FeeHistory struct {
+ OldestBlock *big.Int // block corresponding to first response value
+ Reward [][]*big.Int // list every txs priority fee per block
+ BaseFee []*big.Int // list of each block's base fee
+ GasUsedRatio []float64 // ratio of gas used out of the total available limit
+}
+
// An AcceptedStateReceiver provides access to the accepted state ie. the state of the
// most recently accepted block.
type AcceptedStateReader interface {
diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go
index 5e08c80109..192e2af3e7 100644
--- a/internal/ethapi/api.go
+++ b/internal/ethapi/api.go
@@ -101,6 +101,7 @@ type feeHistoryResult struct {
GasUsedRatio []float64 `json:"gasUsedRatio"`
}
+// FeeHistory returns the fee market history.
func (s *EthereumAPI) FeeHistory(ctx context.Context, blockCount rpc.DecimalOrHex, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*feeHistoryResult, error) {
oldest, reward, baseFee, gasUsed, err := s.b.FeeHistory(ctx, int(blockCount), lastBlock, rewardPercentiles)
if err != nil {
@@ -1011,7 +1012,7 @@ func newRevertError(result *core.ExecutionResult) *revertError {
}
}
-// revertError is an API error that encompassas an EVM revertal with JSON error
+// revertError is an API error that encompasses an EVM revertal with JSON error
// code and a binary data blob.
type revertError struct {
error
@@ -1463,9 +1464,11 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
if db == nil || err != nil {
return nil, 0, nil, err
}
- // If the gas amount is not set, extract this as it will depend on access
- // lists and we'll need to reestimate every time
- nogas := args.Gas == nil
+ // If the gas amount is not set, default to RPC gas cap.
+ if args.Gas == nil {
+ tmp := hexutil.Uint64(b.RPCGasCap())
+ args.Gas = &tmp
+ }
// Ensure any missing fields are filled, extract the recipient and input data
if err := args.setDefaults(ctx, b); err != nil {
@@ -1490,15 +1493,6 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
accessList := prevTracer.AccessList()
log.Trace("Creating access list", "input", accessList)
- // If no gas amount was specified, each unique access list needs it's own
- // gas calculation. This is quite expensive, but we need to be accurate
- // and it's convered by the sender only anyway.
- if nogas {
- args.Gas = nil
- if err := args.setDefaults(ctx, b); err != nil {
- return nil, 0, nil, err // shouldn't happen, just in case
- }
- }
// Copy the original db so we don't modify it
statedb := db.Copy()
// Set the access list tracer to the last al
diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go
index 914aa1fb8e..fc2fbbf512 100644
--- a/internal/ethapi/backend.go
+++ b/internal/ethapi/backend.go
@@ -35,10 +35,10 @@ import (
"github.com/ava-labs/coreth/accounts"
"github.com/ava-labs/coreth/consensus"
"github.com/ava-labs/coreth/core"
- "github.com/ava-labs/coreth/core/bloombits"
"github.com/ava-labs/coreth/core/state"
"github.com/ava-labs/coreth/core/types"
"github.com/ava-labs/coreth/core/vm"
+ "github.com/ava-labs/coreth/eth/filters"
"github.com/ava-labs/coreth/ethdb"
"github.com/ava-labs/coreth/params"
"github.com/ava-labs/coreth/rpc"
@@ -90,18 +90,13 @@ type Backend interface {
TxPoolContentFrom(addr common.Address) (types.Transactions, types.Transactions)
SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription
- // Filter API
- BloomStatus() (uint64, uint64)
- GetLogs(ctx context.Context, blockHash common.Hash) ([][]*types.Log, error)
- ServiceFilter(ctx context.Context, session *bloombits.MatcherSession)
- SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription
- SubscribeAcceptedLogsEvent(ch chan<- []*types.Log) event.Subscription
- SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription
- SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription
-
ChainConfig() *params.ChainConfig
Engine() consensus.Engine
LastAcceptedBlock() *types.Block
+
+ // eth/filters needs to be initialized from this backend type, so methods needed by
+ // it must also be included here.
+ filters.Backend
}
func GetAPIs(apiBackend Backend) []rpc.API {
diff --git a/internal/ethapi/transaction_args.go b/internal/ethapi/transaction_args.go
index eeed60a50b..cf0787e3bf 100644
--- a/internal/ethapi/transaction_args.go
+++ b/internal/ethapi/transaction_args.go
@@ -34,6 +34,7 @@ import (
"math/big"
"github.com/ava-labs/coreth/core/types"
+ "github.com/ava-labs/coreth/params"
"github.com/ava-labs/coreth/rpc"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
@@ -85,57 +86,10 @@ func (args *TransactionArgs) data() []byte {
// setDefaults fills in default values for unspecified tx fields.
func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend) error {
- if args.GasPrice != nil && (args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil) {
- return errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")
- }
- // After london, default to 1559 unless gasPrice is set
- head := b.CurrentHeader()
- // If user specifies both maxPriorityfee and maxFee, then we do not
- // need to consult the chain for defaults. It's definitely a London tx.
- if args.MaxPriorityFeePerGas == nil || args.MaxFeePerGas == nil {
- // In this clause, user left some fields unspecified.
- if b.ChainConfig().IsApricotPhase3(new(big.Int).SetUint64(head.Time)) && args.GasPrice == nil {
- if args.MaxPriorityFeePerGas == nil {
- tip, err := b.SuggestGasTipCap(ctx)
- if err != nil {
- return err
- }
- args.MaxPriorityFeePerGas = (*hexutil.Big)(tip)
- }
- if args.MaxFeePerGas == nil {
- gasFeeCap := new(big.Int).Add(
- (*big.Int)(args.MaxPriorityFeePerGas),
- new(big.Int).Mul(head.BaseFee, big.NewInt(2)),
- )
- args.MaxFeePerGas = (*hexutil.Big)(gasFeeCap)
- }
- if args.MaxFeePerGas.ToInt().Cmp(args.MaxPriorityFeePerGas.ToInt()) < 0 {
- return fmt.Errorf("maxFeePerGas (%v) < maxPriorityFeePerGas (%v)", args.MaxFeePerGas, args.MaxPriorityFeePerGas)
- }
- } else {
- if args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil {
- return errors.New("maxFeePerGas or maxPriorityFeePerGas specified but london is not active yet")
- }
- if args.GasPrice == nil {
- price, err := b.SuggestGasTipCap(ctx)
- if err != nil {
- return err
- }
- if b.ChainConfig().IsApricotPhase3(new(big.Int).SetUint64(head.Time)) {
- // The legacy tx gas price suggestion should not add 2x base fee
- // because all fees are consumed, so it would result in a spiral
- // upwards.
- price.Add(price, head.BaseFee)
- }
- args.GasPrice = (*hexutil.Big)(price)
- }
- }
- } else {
- // Both maxPriorityfee and maxFee set by caller. Sanity-check their internal relation
- if args.MaxFeePerGas.ToInt().Cmp(args.MaxPriorityFeePerGas.ToInt()) < 0 {
- return fmt.Errorf("maxFeePerGas (%v) < maxPriorityFeePerGas (%v)", args.MaxFeePerGas, args.MaxPriorityFeePerGas)
- }
+ if err := args.setFeeDefaults(ctx, b); err != nil {
+ return err
}
+
if args.Value == nil {
args.Value = new(hexutil.Big)
}
@@ -188,6 +142,85 @@ func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend) error {
return nil
}
+type feeBackend interface {
+ SuggestGasTipCap(ctx context.Context) (*big.Int, error)
+ CurrentHeader() *types.Header
+ ChainConfig() *params.ChainConfig
+}
+
+// setFeeDefaults fills in default fee values for unspecified tx fields.
+func (args *TransactionArgs) setFeeDefaults(ctx context.Context, b feeBackend) error {
+ // If both gasPrice and at least one of the EIP-1559 fee parameters are specified, error.
+ if args.GasPrice != nil && (args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil) {
+ return errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")
+ }
+ // If the tx has completely specified a fee mechanism, no default is needed. This allows users
+ // who are not yet synced past London to get defaults for other tx values. See
+ // https://github.com/ethereum/go-ethereum/pull/23274 for more information.
+ eip1559ParamsSet := args.MaxFeePerGas != nil && args.MaxPriorityFeePerGas != nil
+ if (args.GasPrice != nil && !eip1559ParamsSet) || (args.GasPrice == nil && eip1559ParamsSet) {
+ // Sanity check the EIP-1559 fee parameters if present.
+ if args.GasPrice == nil && args.MaxFeePerGas.ToInt().Cmp(args.MaxPriorityFeePerGas.ToInt()) < 0 {
+ return fmt.Errorf("maxFeePerGas (%v) < maxPriorityFeePerGas (%v)", args.MaxFeePerGas, args.MaxPriorityFeePerGas)
+ }
+ return nil
+ }
+ // Now attempt to fill in default value depending on whether London is active or not.
+ head := b.CurrentHeader()
+ if b.ChainConfig().IsApricotPhase3(new(big.Int).SetUint64(head.Time)) {
+ // London is active, set maxPriorityFeePerGas and maxFeePerGas.
+ if err := args.setApricotPhase3FeeDefault(ctx, head, b); err != nil {
+ return err
+ }
+ } else {
+ if args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil {
+ return errors.New("maxFeePerGas and maxPriorityFeePerGas are not valid before London is active")
+ }
+ if args.GasPrice == nil {
+ price, err := b.SuggestGasTipCap(ctx)
+ if err != nil {
+ return err
+ }
+ if b.ChainConfig().IsApricotPhase3(new(big.Int).SetUint64(head.Time)) {
+ // The legacy tx gas price suggestion should not add 2x base fee
+ // because all fees are consumed, so it would result in a spiral
+ // upwards.
+ price.Add(price, head.BaseFee)
+ }
+ args.GasPrice = (*hexutil.Big)(price)
+ }
+ }
+ return nil
+}
+
+// setApricotPhase3FeeDefault fills in reasonable default fee values for unspecified fields.
+func (args *TransactionArgs) setApricotPhase3FeeDefault(ctx context.Context, head *types.Header, b feeBackend) error {
+ // Set maxPriorityFeePerGas if it is missing.
+ if args.MaxPriorityFeePerGas == nil {
+ tip, err := b.SuggestGasTipCap(ctx)
+ if err != nil {
+ return err
+ }
+ args.MaxPriorityFeePerGas = (*hexutil.Big)(tip)
+ }
+ // Set maxFeePerGas if it is missing.
+ if args.MaxFeePerGas == nil {
+ // Set the max fee to be 2 times larger than the previous block's base fee.
+ // The additional slack allows the tx to not become invalidated if the base
+ // fee is rising.
+ gasFeeCap := new(big.Int).Add(
+ (*big.Int)(args.MaxPriorityFeePerGas),
+ new(big.Int).Mul(head.BaseFee, big.NewInt(2)),
+ )
+ args.MaxFeePerGas = (*hexutil.Big)(gasFeeCap)
+ }
+ // Both EIP-1559 fee parameters are now set; sanity check them.
+ if args.MaxFeePerGas.ToInt().Cmp(args.MaxPriorityFeePerGas.ToInt()) < 0 {
+ return fmt.Errorf("maxFeePerGas (%v) < maxPriorityFeePerGas (%v)", args.MaxFeePerGas, args.MaxPriorityFeePerGas)
+ }
+ return nil
+}
+
// ToMessage converts the transaction arguments to the Message type used by the
// core evm. This method is used in calls and traces that do not require a real
// live transaction.
diff --git a/internal/ethapi/transaction_args_test.go b/internal/ethapi/transaction_args_test.go
new file mode 100644
index 0000000000..e29098ac15
--- /dev/null
+++ b/internal/ethapi/transaction_args_test.go
@@ -0,0 +1,257 @@
+// (c) 2022, Ava Labs, Inc.
+//
+// This file is a derived work, based on the go-ethereum library whose original
+// notices appear below.
+//
+// It is distributed under a license compatible with the licensing terms of the
+// original code from which it is derived.
+//
+// Much love to the original authors for their work.
+// **********
+// Copyright 2022 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package ethapi
+
+import (
+ "context"
+ "fmt"
+ "math/big"
+ "reflect"
+ "testing"
+
+ "github.com/ava-labs/coreth/core/types"
+ "github.com/ava-labs/coreth/params"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/hexutil"
+)
+
+var _ feeBackend = &backendMock{}
+
+// TestSetFeeDefaults tests the logic for filling in default fee values works as expected.
+func TestSetFeeDefaults(t *testing.T) {
+ type test struct {
+ name string
+ isLondon bool
+ in *TransactionArgs
+ want *TransactionArgs
+ err error
+ }
+
+ var (
+ b = newBackendMock()
+ fortytwo = (*hexutil.Big)(big.NewInt(42))
+ maxFee = (*hexutil.Big)(new(big.Int).Add(new(big.Int).Mul(b.current.BaseFee, big.NewInt(2)), fortytwo.ToInt()))
+ al = &types.AccessList{types.AccessTuple{Address: common.Address{0xaa}, StorageKeys: []common.Hash{{0x01}}}}
+ )
+
+ tests := []test{
+ // Legacy txs
+ {
+ "legacy tx pre-London",
+ false,
+ &TransactionArgs{},
+ &TransactionArgs{GasPrice: fortytwo},
+ nil,
+ },
+ {
+ "legacy tx post-London, explicit gas price",
+ true,
+ &TransactionArgs{GasPrice: fortytwo},
+ &TransactionArgs{GasPrice: fortytwo},
+ nil,
+ },
+
+ // Access list txs
+ {
+ "access list tx pre-London",
+ false,
+ &TransactionArgs{AccessList: al},
+ &TransactionArgs{AccessList: al, GasPrice: fortytwo},
+ nil,
+ },
+ {
+ "access list tx post-London, explicit gas price",
+ false,
+ &TransactionArgs{AccessList: al, GasPrice: fortytwo},
+ &TransactionArgs{AccessList: al, GasPrice: fortytwo},
+ nil,
+ },
+ {
+ "access list tx post-London",
+ true,
+ &TransactionArgs{AccessList: al},
+ &TransactionArgs{AccessList: al, MaxFeePerGas: maxFee, MaxPriorityFeePerGas: fortytwo},
+ nil,
+ },
+ {
+ "access list tx post-London, only max fee",
+ true,
+ &TransactionArgs{AccessList: al, MaxFeePerGas: maxFee},
+ &TransactionArgs{AccessList: al, MaxFeePerGas: maxFee, MaxPriorityFeePerGas: fortytwo},
+ nil,
+ },
+ {
+ "access list tx post-London, only priority fee",
+ true,
+ &TransactionArgs{AccessList: al, MaxFeePerGas: maxFee},
+ &TransactionArgs{AccessList: al, MaxFeePerGas: maxFee, MaxPriorityFeePerGas: fortytwo},
+ nil,
+ },
+
+ // Dynamic fee txs
+ {
+ "dynamic tx post-London",
+ true,
+ &TransactionArgs{},
+ &TransactionArgs{MaxFeePerGas: maxFee, MaxPriorityFeePerGas: fortytwo},
+ nil,
+ },
+ {
+ "dynamic tx post-London, only max fee",
+ true,
+ &TransactionArgs{MaxFeePerGas: maxFee},
+ &TransactionArgs{MaxFeePerGas: maxFee, MaxPriorityFeePerGas: fortytwo},
+ nil,
+ },
+ {
+ "dynamic tx post-London, only priority fee",
+ true,
+ &TransactionArgs{MaxFeePerGas: maxFee},
+ &TransactionArgs{MaxFeePerGas: maxFee, MaxPriorityFeePerGas: fortytwo},
+ nil,
+ },
+ {
+ "dynamic fee tx pre-London, maxFee set",
+ false,
+ &TransactionArgs{MaxFeePerGas: maxFee},
+ nil,
+ fmt.Errorf("maxFeePerGas and maxPriorityFeePerGas are not valid before London is active"),
+ },
+ {
+ "dynamic fee tx pre-London, priorityFee set",
+ false,
+ &TransactionArgs{MaxPriorityFeePerGas: fortytwo},
+ nil,
+ fmt.Errorf("maxFeePerGas and maxPriorityFeePerGas are not valid before London is active"),
+ },
+ {
+ "dynamic fee tx, maxFee < priorityFee",
+ true,
+ &TransactionArgs{MaxFeePerGas: maxFee, MaxPriorityFeePerGas: (*hexutil.Big)(big.NewInt(1000))},
+ nil,
+ fmt.Errorf("maxFeePerGas (0x3e) < maxPriorityFeePerGas (0x3e8)"),
+ },
+ {
+ "dynamic fee tx, maxFee < priorityFee while setting default",
+ true,
+ &TransactionArgs{MaxFeePerGas: (*hexutil.Big)(big.NewInt(7))},
+ nil,
+ fmt.Errorf("maxFeePerGas (0x7) < maxPriorityFeePerGas (0x2a)"),
+ },
+
+ // Misc
+ {
+ "set all fee parameters",
+ false,
+ &TransactionArgs{GasPrice: fortytwo, MaxFeePerGas: maxFee, MaxPriorityFeePerGas: fortytwo},
+ nil,
+ fmt.Errorf("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified"),
+ },
+ {
+ "set gas price and maxPriorityFee",
+ false,
+ &TransactionArgs{GasPrice: fortytwo, MaxPriorityFeePerGas: fortytwo},
+ nil,
+ fmt.Errorf("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified"),
+ },
+ {
+ "set gas price and maxFee",
+ true,
+ &TransactionArgs{GasPrice: fortytwo, MaxFeePerGas: maxFee},
+ nil,
+ fmt.Errorf("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified"),
+ },
+ }
+
+ ctx := context.Background()
+ for i, test := range tests {
+ if test.isLondon {
+ b.activateLondon()
+ } else {
+ b.deactivateLondon()
+ }
+ got := test.in
+ err := got.setFeeDefaults(ctx, b)
+ if err != nil && err.Error() == test.err.Error() {
+ // Test threw expected error.
+ continue
+ } else if err != nil {
+ t.Fatalf("test %d (%s): unexpected error: %s", i, test.name, err)
+ }
+ if !reflect.DeepEqual(got, test.want) {
+ t.Fatalf("test %d (%s): did not fill defaults as expected: (got: %v, want: %v)", i, test.name, got, test.want)
+ }
+ }
+}
+
+type backendMock struct {
+ current *types.Header
+ config *params.ChainConfig
+}
+
+func newBackendMock() *backendMock {
+ config := ¶ms.ChainConfig{
+ ChainID: big.NewInt(42),
+ HomesteadBlock: big.NewInt(0),
+ DAOForkBlock: nil,
+ DAOForkSupport: true,
+ EIP150Block: big.NewInt(0),
+ EIP155Block: big.NewInt(0),
+ EIP158Block: big.NewInt(0),
+ ByzantiumBlock: big.NewInt(0),
+ ConstantinopleBlock: big.NewInt(0),
+ PetersburgBlock: big.NewInt(0),
+ IstanbulBlock: big.NewInt(0),
+ MuirGlacierBlock: big.NewInt(0),
+ ApricotPhase3BlockTimestamp: big.NewInt(1000),
+ }
+ return &backendMock{
+ current: &types.Header{
+ Difficulty: big.NewInt(10000000000),
+ Number: big.NewInt(1100),
+ GasLimit: 8_000_000,
+ GasUsed: 8_000_000,
+ Time: 555,
+ Extra: make([]byte, 32),
+ BaseFee: big.NewInt(10),
+ },
+ config: config,
+ }
+}
+
+func (b *backendMock) activateLondon() {
+ b.current.Time = uint64(1100)
+}
+
+func (b *backendMock) deactivateLondon() {
+ b.current.Time = uint64(900)
+}
+func (b *backendMock) SuggestGasTipCap(ctx context.Context) (*big.Int, error) {
+ return big.NewInt(42), nil
+}
+func (b *backendMock) CurrentHeader() *types.Header { return b.current }
+func (b *backendMock) ChainConfig() *params.ChainConfig { return b.config }
diff --git a/metrics/gauge_float64_test.go b/metrics/gauge_float64_test.go
index 02b75580c4..7b854d232b 100644
--- a/metrics/gauge_float64_test.go
+++ b/metrics/gauge_float64_test.go
@@ -2,7 +2,7 @@ package metrics
import "testing"
-func BenchmarkGuageFloat64(b *testing.B) {
+func BenchmarkGaugeFloat64(b *testing.B) {
g := NewGaugeFloat64()
b.ResetTimer()
for i := 0; i < b.N; i++ {
diff --git a/metrics/gauge_test.go b/metrics/gauge_test.go
index 3aee143455..a98fe985d8 100644
--- a/metrics/gauge_test.go
+++ b/metrics/gauge_test.go
@@ -5,7 +5,7 @@ import (
"testing"
)
-func BenchmarkGuage(b *testing.B) {
+func BenchmarkGauge(b *testing.B) {
g := NewGauge()
b.ResetTimer()
for i := 0; i < b.N; i++ {
diff --git a/miner/worker.go b/miner/worker.go
index 4ce074cbf1..44c3b35a28 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -45,6 +45,7 @@ import (
"github.com/ava-labs/coreth/core/state"
"github.com/ava-labs/coreth/core/types"
"github.com/ava-labs/coreth/params"
+ "github.com/ava-labs/coreth/vmerrs"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
@@ -287,6 +288,10 @@ func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByP
log.Trace("Skipping unsupported transaction type", "sender", from, "type", tx.Type())
txs.Pop()
+ case errors.Is(err, vmerrs.ErrToAddrProhibitedSoft):
+ log.Warn("Tx dropped: failed verification", "tx", tx.Hash(), "sender", from, "data", tx.Data(), "err", err)
+ w.eth.TxPool().RemoveTx(tx.Hash())
+ txs.Pop()
default:
// Strange error, discard the transaction and get the next in line (note, the
// nonce-too-high clause will prevent us from executing in vain).
diff --git a/params/config.go b/params/config.go
index ef6826df06..defb2d2334 100644
--- a/params/config.go
+++ b/params/config.go
@@ -52,82 +52,94 @@ var (
var (
// AvalancheMainnetChainConfig is the configuration for Avalanche Main Network
AvalancheMainnetChainConfig = &ChainConfig{
- ChainID: AvalancheMainnetChainID,
- HomesteadBlock: big.NewInt(0),
- DAOForkBlock: big.NewInt(0),
- DAOForkSupport: true,
- EIP150Block: big.NewInt(0),
- EIP150Hash: common.HexToHash("0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0"),
- EIP155Block: big.NewInt(0),
- EIP158Block: big.NewInt(0),
- ByzantiumBlock: big.NewInt(0),
- ConstantinopleBlock: big.NewInt(0),
- PetersburgBlock: big.NewInt(0),
- IstanbulBlock: big.NewInt(0),
- MuirGlacierBlock: big.NewInt(0),
- ApricotPhase1BlockTimestamp: big.NewInt(time.Date(2021, time.March, 31, 14, 0, 0, 0, time.UTC).Unix()),
- ApricotPhase2BlockTimestamp: big.NewInt(time.Date(2021, time.May, 10, 11, 0, 0, 0, time.UTC).Unix()),
- ApricotPhase3BlockTimestamp: big.NewInt(time.Date(2021, time.August, 24, 14, 0, 0, 0, time.UTC).Unix()),
- ApricotPhase4BlockTimestamp: big.NewInt(time.Date(2021, time.September, 22, 21, 0, 0, 0, time.UTC).Unix()),
- ApricotPhase5BlockTimestamp: big.NewInt(time.Date(2021, time.December, 2, 18, 0, 0, 0, time.UTC).Unix()),
+ ChainID: AvalancheMainnetChainID,
+ HomesteadBlock: big.NewInt(0),
+ DAOForkBlock: big.NewInt(0),
+ DAOForkSupport: true,
+ EIP150Block: big.NewInt(0),
+ EIP150Hash: common.HexToHash("0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0"),
+ EIP155Block: big.NewInt(0),
+ EIP158Block: big.NewInt(0),
+ ByzantiumBlock: big.NewInt(0),
+ ConstantinopleBlock: big.NewInt(0),
+ PetersburgBlock: big.NewInt(0),
+ IstanbulBlock: big.NewInt(0),
+ MuirGlacierBlock: big.NewInt(0),
+ ApricotPhase1BlockTimestamp: big.NewInt(time.Date(2021, time.March, 31, 14, 0, 0, 0, time.UTC).Unix()),
+ ApricotPhase2BlockTimestamp: big.NewInt(time.Date(2021, time.May, 10, 11, 0, 0, 0, time.UTC).Unix()),
+ ApricotPhase3BlockTimestamp: big.NewInt(time.Date(2021, time.August, 24, 14, 0, 0, 0, time.UTC).Unix()),
+ ApricotPhase4BlockTimestamp: big.NewInt(time.Date(2021, time.September, 22, 21, 0, 0, 0, time.UTC).Unix()),
+ ApricotPhase5BlockTimestamp: big.NewInt(time.Date(2021, time.December, 2, 18, 0, 0, 0, time.UTC).Unix()),
+ ApricotPhasePre6BlockTimestamp: big.NewInt(time.Date(2022, time.September, 5, 1, 30, 0, 0, time.UTC).Unix()),
+ ApricotPhase6BlockTimestamp: big.NewInt(time.Date(2022, time.September, 6, 20, 0, 0, 0, time.UTC).Unix()),
+ ApricotPhasePost6BlockTimestamp: big.NewInt(time.Date(2022, time.September, 7, 3, 0, 0, 0, time.UTC).Unix()),
// TODO Add Blueberry
}
// AvalancheFujiChainConfig is the configuration for the Fuji Test Network
AvalancheFujiChainConfig = &ChainConfig{
- ChainID: AvalancheFujiChainID,
- HomesteadBlock: big.NewInt(0),
- DAOForkBlock: big.NewInt(0),
- DAOForkSupport: true,
- EIP150Block: big.NewInt(0),
- EIP150Hash: common.HexToHash("0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0"),
- EIP155Block: big.NewInt(0),
- EIP158Block: big.NewInt(0),
- ByzantiumBlock: big.NewInt(0),
- ConstantinopleBlock: big.NewInt(0),
- PetersburgBlock: big.NewInt(0),
- IstanbulBlock: big.NewInt(0),
- MuirGlacierBlock: big.NewInt(0),
- ApricotPhase1BlockTimestamp: big.NewInt(time.Date(2021, time.March, 26, 14, 0, 0, 0, time.UTC).Unix()),
- ApricotPhase2BlockTimestamp: big.NewInt(time.Date(2021, time.May, 5, 14, 0, 0, 0, time.UTC).Unix()),
- ApricotPhase3BlockTimestamp: big.NewInt(time.Date(2021, time.August, 16, 19, 0, 0, 0, time.UTC).Unix()),
- ApricotPhase4BlockTimestamp: big.NewInt(time.Date(2021, time.September, 16, 21, 0, 0, 0, time.UTC).Unix()),
- ApricotPhase5BlockTimestamp: big.NewInt(time.Date(2021, time.November, 24, 15, 0, 0, 0, time.UTC).Unix()),
+ ChainID: AvalancheFujiChainID,
+ HomesteadBlock: big.NewInt(0),
+ DAOForkBlock: big.NewInt(0),
+ DAOForkSupport: true,
+ EIP150Block: big.NewInt(0),
+ EIP150Hash: common.HexToHash("0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0"),
+ EIP155Block: big.NewInt(0),
+ EIP158Block: big.NewInt(0),
+ ByzantiumBlock: big.NewInt(0),
+ ConstantinopleBlock: big.NewInt(0),
+ PetersburgBlock: big.NewInt(0),
+ IstanbulBlock: big.NewInt(0),
+ MuirGlacierBlock: big.NewInt(0),
+ ApricotPhase1BlockTimestamp: big.NewInt(time.Date(2021, time.March, 26, 14, 0, 0, 0, time.UTC).Unix()),
+ ApricotPhase2BlockTimestamp: big.NewInt(time.Date(2021, time.May, 5, 14, 0, 0, 0, time.UTC).Unix()),
+ ApricotPhase3BlockTimestamp: big.NewInt(time.Date(2021, time.August, 16, 19, 0, 0, 0, time.UTC).Unix()),
+ ApricotPhase4BlockTimestamp: big.NewInt(time.Date(2021, time.September, 16, 21, 0, 0, 0, time.UTC).Unix()),
+ ApricotPhase5BlockTimestamp: big.NewInt(time.Date(2021, time.November, 24, 15, 0, 0, 0, time.UTC).Unix()),
+ ApricotPhasePre6BlockTimestamp: big.NewInt(time.Date(2022, time.September, 6, 20, 0, 0, 0, time.UTC).Unix()),
+ ApricotPhase6BlockTimestamp: big.NewInt(time.Date(2022, time.September, 6, 20, 0, 0, 0, time.UTC).Unix()),
+ ApricotPhasePost6BlockTimestamp: big.NewInt(time.Date(2022, time.September, 7, 6, 0, 0, 0, time.UTC).Unix()),
// TODO Add Blueberry
}
// AvalancheLocalChainConfig is the configuration for the Avalanche Local Network
AvalancheLocalChainConfig = &ChainConfig{
- ChainID: AvalancheLocalChainID,
- HomesteadBlock: big.NewInt(0),
- DAOForkBlock: big.NewInt(0),
- DAOForkSupport: true,
- EIP150Block: big.NewInt(0),
- EIP150Hash: common.HexToHash("0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0"),
- EIP155Block: big.NewInt(0),
- EIP158Block: big.NewInt(0),
- ByzantiumBlock: big.NewInt(0),
- ConstantinopleBlock: big.NewInt(0),
- PetersburgBlock: big.NewInt(0),
- IstanbulBlock: big.NewInt(0),
- MuirGlacierBlock: big.NewInt(0),
- ApricotPhase1BlockTimestamp: big.NewInt(0),
- ApricotPhase2BlockTimestamp: big.NewInt(0),
- ApricotPhase3BlockTimestamp: big.NewInt(0),
- ApricotPhase4BlockTimestamp: big.NewInt(0),
- ApricotPhase5BlockTimestamp: big.NewInt(0),
- BlueberryBlockTimestamp: big.NewInt(0),
- }
-
- TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0)}
- TestLaunchConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, nil, nil}
- TestApricotPhase1Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, nil}
- TestApricotPhase2Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil}
- TestApricotPhase3Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil}
- TestApricotPhase4Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil}
- TestApricotPhase5Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil}
- TestBlueberryChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0)}
- TestRules = TestChainConfig.AvalancheRules(new(big.Int), new(big.Int))
+ ChainID: AvalancheLocalChainID,
+ HomesteadBlock: big.NewInt(0),
+ DAOForkBlock: big.NewInt(0),
+ DAOForkSupport: true,
+ EIP150Block: big.NewInt(0),
+ EIP150Hash: common.HexToHash("0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0"),
+ EIP155Block: big.NewInt(0),
+ EIP158Block: big.NewInt(0),
+ ByzantiumBlock: big.NewInt(0),
+ ConstantinopleBlock: big.NewInt(0),
+ PetersburgBlock: big.NewInt(0),
+ IstanbulBlock: big.NewInt(0),
+ MuirGlacierBlock: big.NewInt(0),
+ ApricotPhase1BlockTimestamp: big.NewInt(0),
+ ApricotPhase2BlockTimestamp: big.NewInt(0),
+ ApricotPhase3BlockTimestamp: big.NewInt(0),
+ ApricotPhase4BlockTimestamp: big.NewInt(0),
+ ApricotPhase5BlockTimestamp: big.NewInt(0),
+ ApricotPhasePre6BlockTimestamp: big.NewInt(0),
+ ApricotPhase6BlockTimestamp: big.NewInt(0),
+ ApricotPhasePost6BlockTimestamp: big.NewInt(0),
+ BlueberryBlockTimestamp: big.NewInt(0),
+ }
+
+ TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0)}
+ TestLaunchConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, nil, nil, nil, nil, nil}
+ TestApricotPhase1Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, nil, nil, nil, nil}
+ TestApricotPhase2Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, nil, nil, nil}
+ TestApricotPhase3Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, nil, nil}
+ TestApricotPhase4Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, nil}
+ TestApricotPhase5Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil}
+ TestApricotPhasePre6Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil}
+ TestApricotPhase6Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil}
+ TestApricotPhasePost6Config = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil}
+ TestBlueberryChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0)}
+ TestRules = TestChainConfig.AvalancheRules(new(big.Int), new(big.Int))
)
// ChainConfig is the core config which determines the blockchain settings.
@@ -167,6 +179,12 @@ type ChainConfig struct {
ApricotPhase4BlockTimestamp *big.Int `json:"apricotPhase4BlockTimestamp,omitempty"`
// Apricot Phase 5 introduces a batch of atomic transactions with a maximum atomic gas limit per block. (nil = no fork, 0 = already activated)
ApricotPhase5BlockTimestamp *big.Int `json:"apricotPhase5BlockTimestamp,omitempty"`
+ // Apricot Phase Pre-6 deprecates the NativeAssetCall precompile (soft). (nil = no fork, 0 = already activated)
+ ApricotPhasePre6BlockTimestamp *big.Int `json:"apricotPhasePre6BlockTimestamp,omitempty"`
+ // Apricot Phase 6 deprecates the NativeAssetBalance and NativeAssetCall precompiles. (nil = no fork, 0 = already activated)
+ ApricotPhase6BlockTimestamp *big.Int `json:"apricotPhase6BlockTimestamp,omitempty"`
+ // Apricot Phase Post-6 deprecates the NativeAssetCall precompile (soft). (nil = no fork, 0 = already activated)
+ ApricotPhasePost6BlockTimestamp *big.Int `json:"apricotPhasePost6BlockTimestamp,omitempty"`
// Blueberry TODO comment. (nil = no fork, 0 = already activated)
BlueberryBlockTimestamp *big.Int `json:"blueberryBlockTimestamp,omitempty"`
}
@@ -201,7 +219,10 @@ func (c *ChainConfig) String() string {
banner += fmt.Sprintf(" - Apricot Phase 3 Timestamp: %-8v (https://github.com/ava-labs/avalanchego/releases/tag/v1.5.0)\n", c.ApricotPhase3BlockTimestamp)
banner += fmt.Sprintf(" - Apricot Phase 4 Timestamp: %-8v (https://github.com/ava-labs/avalanchego/releases/tag/v1.6.0)\n", c.ApricotPhase4BlockTimestamp)
banner += fmt.Sprintf(" - Apricot Phase 5 Timestamp: %-8v (https://github.com/ava-labs/avalanchego/releases/tag/v1.7.0)\n", c.ApricotPhase5BlockTimestamp)
- banner += fmt.Sprintf(" - Blueberry Timestamp: %-8v (https://github.com/ava-labs/avalanchego/releases/tag/v1.8.0)\n", c.BlueberryBlockTimestamp)
+ banner += fmt.Sprintf(" - Apricot Phase P6 Timestamp %-8v (https://github.com/ava-labs/avalanchego/releases/tag/v1.8.0)\n", c.ApricotPhasePre6BlockTimestamp)
+ banner += fmt.Sprintf(" - Apricot Phase 6 Timestamp: %-8v (https://github.com/ava-labs/avalanchego/releases/tag/v1.8.0)\n", c.ApricotPhase6BlockTimestamp)
+ banner += fmt.Sprintf(" - Apricot Phase Post-6 Timestamp: %-8v (https://github.com/ava-labs/avalanchego/releases/tag/v1.8.0\n", c.ApricotPhasePost6BlockTimestamp)
+ banner += fmt.Sprintf(" - Blueberry Timestamp: %-8v (https://github.com/ava-labs/avalanchego/releases/tag/v1.10.0)\n", c.BlueberryBlockTimestamp)
banner += "\n"
return banner
}
@@ -290,6 +311,24 @@ func (c *ChainConfig) IsApricotPhase5(blockTimestamp *big.Int) bool {
return utils.IsForked(c.ApricotPhase5BlockTimestamp, blockTimestamp)
}
+// IsApricotPhasePre6 returns whether [blockTimestamp] represents a block
+// with a timestamp after the Apricot Phase Pre 6 upgrade time.
+func (c *ChainConfig) IsApricotPhasePre6(blockTimestamp *big.Int) bool {
+ return utils.IsForked(c.ApricotPhasePre6BlockTimestamp, blockTimestamp)
+}
+
+// IsApricotPhase6 returns whether [blockTimestamp] represents a block
+// with a timestamp after the Apricot Phase 6 upgrade time.
+func (c *ChainConfig) IsApricotPhase6(blockTimestamp *big.Int) bool {
+ return utils.IsForked(c.ApricotPhase6BlockTimestamp, blockTimestamp)
+}
+
+// IsApricotPhasePost6 returns whether [blockTimestamp] represents a block
+// with a timestamp after the Apricot Phase 6 Post upgrade time.
+func (c *ChainConfig) IsApricotPhasePost6(blockTimestamp *big.Int) bool {
+ return utils.IsForked(c.ApricotPhasePost6BlockTimestamp, blockTimestamp)
+}
+
// IsBlueberry returns whether [blockTimestamp] represents a block
// with a timestamp after the Blueberry upgrade time.
func (c *ChainConfig) IsBlueberry(blockTimestamp *big.Int) bool {
@@ -370,6 +409,10 @@ func (c *ChainConfig) CheckConfigForkOrder() error {
{name: "apricotPhase2BlockTimestamp", block: c.ApricotPhase2BlockTimestamp},
{name: "apricotPhase3BlockTimestamp", block: c.ApricotPhase3BlockTimestamp},
{name: "apricotPhase4BlockTimestamp", block: c.ApricotPhase4BlockTimestamp},
+ {name: "apricotPhase5BlockTimestamp", block: c.ApricotPhase5BlockTimestamp},
+ {name: "apricotPhasePre6BlockTimestamp", block: c.ApricotPhasePre6BlockTimestamp},
+ {name: "apricotPhase6BlockTimestamp", block: c.ApricotPhase6BlockTimestamp},
+ {name: "apricotPhasePost6BlockTimestamp", block: c.ApricotPhasePost6BlockTimestamp},
} {
if lastFork.name != "" {
// Next one must be higher number
@@ -452,6 +495,13 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, headHeight *big.Int,
if isForkIncompatible(c.ApricotPhase5BlockTimestamp, newcfg.ApricotPhase5BlockTimestamp, headTimestamp) {
return newCompatError("ApricotPhase5 fork block timestamp", c.ApricotPhase5BlockTimestamp, newcfg.ApricotPhase5BlockTimestamp)
}
+ if isForkIncompatible(c.ApricotPhasePre6BlockTimestamp, newcfg.ApricotPhasePre6BlockTimestamp, headTimestamp) {
+ return newCompatError("ApricotPhasePre6 fork block timestamp", c.ApricotPhasePre6BlockTimestamp, newcfg.ApricotPhasePre6BlockTimestamp)
+ }
+ if isForkIncompatible(c.ApricotPhase6BlockTimestamp, newcfg.ApricotPhase6BlockTimestamp, headTimestamp) {
+ return newCompatError("ApricotPhase6 fork block timestamp", c.ApricotPhase6BlockTimestamp, newcfg.ApricotPhase6BlockTimestamp)
+ }
+
if isForkIncompatible(c.BlueberryBlockTimestamp, newcfg.BlueberryBlockTimestamp, headTimestamp) {
return newCompatError("Blueberry fork block timestamp", c.BlueberryBlockTimestamp, newcfg.BlueberryBlockTimestamp)
}
@@ -517,6 +567,7 @@ type Rules struct {
// Rules for Avalanche releases
IsApricotPhase1, IsApricotPhase2, IsApricotPhase3, IsApricotPhase4, IsApricotPhase5 bool
+ IsApricotPhasePre6, IsApricotPhase6, IsApricotPhasePost6 bool
IsBlueberry bool
// Precompiles maps addresses to stateful precompiled contracts that are enabled
@@ -555,6 +606,9 @@ func (c *ChainConfig) AvalancheRules(blockNum, blockTimestamp *big.Int) Rules {
rules.IsApricotPhase3 = c.IsApricotPhase3(blockTimestamp)
rules.IsApricotPhase4 = c.IsApricotPhase4(blockTimestamp)
rules.IsApricotPhase5 = c.IsApricotPhase5(blockTimestamp)
+ rules.IsApricotPhasePre6 = c.IsApricotPhasePre6(blockTimestamp)
+ rules.IsApricotPhase6 = c.IsApricotPhase6(blockTimestamp)
+ rules.IsApricotPhasePost6 = c.IsApricotPhasePost6(blockTimestamp)
rules.IsBlueberry = c.IsBlueberry(blockTimestamp)
// Initialize the stateful precompiles that should be enabled at [blockTimestamp].
diff --git a/params/version.go b/params/version.go
index 27e0479450..1d7614e56a 100644
--- a/params/version.go
+++ b/params/version.go
@@ -33,7 +33,7 @@ import (
const (
VersionMajor = 1 // Major version component of the current release
VersionMinor = 10 // Minor version component of the current release
- VersionPatch = 21 // Patch version component of the current release
+ VersionPatch = 23 // Patch version component of the current release
VersionMeta = "stable" // Version metadata to append to the version string
)
diff --git a/plugin/evm/atomic_backend.go b/plugin/evm/atomic_backend.go
index 9fb5fb0295..76a6ffb55b 100644
--- a/plugin/evm/atomic_backend.go
+++ b/plugin/evm/atomic_backend.go
@@ -168,11 +168,11 @@ func (a *atomicBackend) initialize(lastAcceptedHeight uint64) error {
if err := a.atomicTrie.UpdateTrie(tr, height, combinedOps); err != nil {
return err
}
- root, _, err := tr.Commit(nil, false)
+ root, nodes, err := tr.Commit(false)
if err != nil {
return err
}
- if err := a.atomicTrie.InsertTrie(root); err != nil {
+ if err := a.atomicTrie.InsertTrie(nodes, root); err != nil {
return err
}
isCommit, err := a.atomicTrie.AcceptTrie(height, root)
@@ -198,7 +198,7 @@ func (a *atomicBackend) initialize(lastAcceptedHeight uint64) error {
// check if there are accepted blocks after the last block with accepted atomic txs.
if lastAcceptedHeight > height {
lastAcceptedRoot := a.atomicTrie.LastAcceptedRoot()
- if err := a.atomicTrie.InsertTrie(lastAcceptedRoot); err != nil {
+ if err := a.atomicTrie.InsertTrie(nil, lastAcceptedRoot); err != nil {
return err
}
if _, err := a.atomicTrie.AcceptTrie(lastAcceptedHeight, lastAcceptedRoot); err != nil {
@@ -390,11 +390,11 @@ func (a *atomicBackend) InsertTxs(blockHash common.Hash, blockHeight uint64, par
}
// get the new root and pin the atomic trie changes in memory.
- root, _, err := tr.Commit(nil, false)
+ root, nodes, err := tr.Commit(false)
if err != nil {
return common.Hash{}, err
}
- if err := a.atomicTrie.InsertTrie(root); err != nil {
+ if err := a.atomicTrie.InsertTrie(nodes, root); err != nil {
return common.Hash{}, err
}
// track this block so further blocks can be inserted on top
diff --git a/plugin/evm/atomic_syncer.go b/plugin/evm/atomic_syncer.go
index db28aa0d05..5c70d5517f 100644
--- a/plugin/evm/atomic_syncer.go
+++ b/plugin/evm/atomic_syncer.go
@@ -91,11 +91,11 @@ func (s *atomicSyncer) onLeafs(keys [][]byte, values [][]byte) error {
if height > lastHeight {
// If this key belongs to a new height, we commit
// the trie at the previous height before adding this key.
- root, _, err := s.trie.Commit(nil, false)
+ root, nodes, err := s.trie.Commit(false)
if err != nil {
return err
}
- if err := s.atomicTrie.InsertTrie(root); err != nil {
+ if err := s.atomicTrie.InsertTrie(nodes, root); err != nil {
return err
}
// AcceptTrie commits the trieDB and returns [isCommit] as true
@@ -125,11 +125,11 @@ func (s *atomicSyncer) onLeafs(keys [][]byte, values [][]byte) error {
// commit the trie to disk and perform the final checks that we synced the target root correctly.
func (s *atomicSyncer) onFinish() error {
// commit the trie on finish
- root, _, err := s.trie.Commit(nil, false)
+ root, nodes, err := s.trie.Commit(false)
if err != nil {
return err
}
- if err := s.atomicTrie.InsertTrie(root); err != nil {
+ if err := s.atomicTrie.InsertTrie(nodes, root); err != nil {
return err
}
if _, err := s.atomicTrie.AcceptTrie(s.targetHeight, root); err != nil {
diff --git a/plugin/evm/atomic_trie.go b/plugin/evm/atomic_trie.go
index 46efac65b9..88ef781170 100644
--- a/plugin/evm/atomic_trie.go
+++ b/plugin/evm/atomic_trie.go
@@ -70,9 +70,10 @@ type AtomicTrie interface {
// or the root it was initialized to if no new tries were accepted yet.
LastAcceptedRoot() common.Hash
- // InsertTrie adds a reference for root in the trieDB. Once InsertTrie
- // is called, it is expected either AcceptTrie or RejectTrie be called.
- InsertTrie(root common.Hash) error
+ // InsertTrie updates the trieDB with the provided node set and adds a reference
+ // to root in the trieDB. Once InsertTrie is called, it is expected either
+ // AcceptTrie or RejectTrie be called for the same root.
+ InsertTrie(nodes *trie.NodeSet, root common.Hash) error
// AcceptTrie marks root as the last accepted atomic trie root, and
// commits the trie to persistent storage if height is divisible by
@@ -306,13 +307,17 @@ func (a *atomicTrie) LastAcceptedRoot() common.Hash {
return a.lastAcceptedRoot
}
-func (a *atomicTrie) InsertTrie(root common.Hash) error {
- a.trieDB.Reference(root, common.Hash{}, true)
+func (a *atomicTrie) InsertTrie(nodes *trie.NodeSet, root common.Hash) error {
+ if nodes != nil {
+ if err := a.trieDB.Update(trie.NewWithNodeSet(nodes)); err != nil {
+ return err
+ }
+ }
+ a.trieDB.Reference(root, common.Hash{})
// The use of [Cap] in [insertTrie] prevents exceeding the configured memory
// limit (and OOM) in case there is a large backlog of processing (unaccepted) blocks.
- nodes, _ := a.trieDB.Size()
- if nodes <= a.memoryCap {
+ if nodeSize, _ := a.trieDB.Size(); nodeSize <= a.memoryCap {
return nil
}
if err := a.trieDB.Cap(a.memoryCap - ethdb.IdealBatchSize); err != nil {
diff --git a/plugin/evm/atomic_trie_iterator_test.go b/plugin/evm/atomic_trie_iterator_test.go
index b721a19dc6..944d2c4726 100644
--- a/plugin/evm/atomic_trie_iterator_test.go
+++ b/plugin/evm/atomic_trie_iterator_test.go
@@ -13,6 +13,7 @@ import (
"github.com/ava-labs/avalanchego/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func testSharedMemory() atomic.SharedMemory {
@@ -60,11 +61,12 @@ func TestIteratorCanIterate(t *testing.T) {
}
func TestIteratorHandlesInvalidData(t *testing.T) {
+ require := require.New(t)
lastAcceptedHeight := uint64(1000)
db := versiondb.New(memdb.New())
codec := testTxCodec()
repo, err := NewAtomicTxRepository(db, codec, lastAcceptedHeight, nil, nil, nil)
- assert.NoError(t, err)
+ require.NoError(err)
// create state with multiple transactions
// since each test transaction generates random ID for blockchainID we should get
@@ -74,35 +76,36 @@ func TestIteratorHandlesInvalidData(t *testing.T) {
// create an atomic trie
// on create it will initialize all the transactions from the above atomic repository
- atomicBackend, err := NewAtomicBackend(db, testSharedMemory(), nil, repo, lastAcceptedHeight, common.Hash{}, 100)
- assert.NoError(t, err)
- atomicTrie := atomicBackend.AtomicTrie().(*atomicTrie)
+ commitInterval := uint64(100)
+ atomicBackend, err := NewAtomicBackend(db, testSharedMemory(), nil, repo, lastAcceptedHeight, common.Hash{}, commitInterval)
+ require.NoError(err)
+ atomicTrie := atomicBackend.AtomicTrie()
lastCommittedHash, lastCommittedHeight := atomicTrie.LastCommitted()
- assert.NoError(t, err)
- assert.NotEqual(t, common.Hash{}, lastCommittedHash)
- assert.EqualValues(t, 1000, lastCommittedHeight)
+ require.NoError(err)
+ require.NotEqual(common.Hash{}, lastCommittedHash)
+ require.EqualValues(1000, lastCommittedHeight)
verifyOperations(t, atomicTrie, codec, lastCommittedHash, 1, 1000, operationsMap)
// Add a random key-value pair to the atomic trie in order to test that the iterator correctly
// handles an error when it runs into an unexpected key-value pair in the trie.
atomicTrieSnapshot, err := atomicTrie.OpenTrie(lastCommittedHash)
- if err != nil {
- t.Fatal(err)
- }
- assert.NoError(t, atomicTrieSnapshot.TryUpdate(utils.RandomBytes(50), utils.RandomBytes(50)))
+ require.NoError(err)
+ require.NoError(atomicTrieSnapshot.TryUpdate(utils.RandomBytes(50), utils.RandomBytes(50)))
- nextRoot, _, err := atomicTrieSnapshot.Commit(nil, false)
- if err != nil {
- t.Fatal(err)
- }
+ nextRoot, nodes, err := atomicTrieSnapshot.Commit(false)
+ require.NoError(err)
+ err = atomicTrie.InsertTrie(nodes, nextRoot)
+ require.NoError(err)
+ isCommit, err := atomicTrie.AcceptTrie(lastCommittedHeight+commitInterval, nextRoot)
+ require.NoError(err)
+ require.True(isCommit)
- assert.NoError(t, atomicTrie.commit(lastCommittedHeight+1, nextRoot))
corruptedHash, _ := atomicTrie.LastCommitted()
iter, err := atomicTrie.Iterator(corruptedHash, nil)
- assert.NoError(t, err)
+ require.NoError(err)
for iter.Next() {
}
- assert.Error(t, iter.Error())
+ require.Error(iter.Error())
}
diff --git a/plugin/evm/atomic_trie_test.go b/plugin/evm/atomic_trie_test.go
index 535fd1e08c..05e94cc72f 100644
--- a/plugin/evm/atomic_trie_test.go
+++ b/plugin/evm/atomic_trie_test.go
@@ -44,11 +44,11 @@ func indexAtomicTxs(tr AtomicTrie, height uint64, atomicOps map[ids.ID]*atomic.R
if err := tr.UpdateTrie(snapshot, height, atomicOps); err != nil {
return err
}
- root, _, err := snapshot.Commit(nil, false)
+ root, nodes, err := snapshot.Commit(false)
if err != nil {
return err
}
- if err := tr.InsertTrie(root); err != nil {
+ if err := tr.InsertTrie(nodes, root); err != nil {
return err
}
_, err = tr.AcceptTrie(height, root)
diff --git a/plugin/evm/version.go b/plugin/evm/version.go
index 63f3ce7922..0b6bf04928 100644
--- a/plugin/evm/version.go
+++ b/plugin/evm/version.go
@@ -11,7 +11,7 @@ var (
// GitCommit is set by the build script
GitCommit string
// Version is the version of Coreth
- Version string = "v0.8.17"
+ Version string = "v0.9.0"
)
func init() {
diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go
index 52264decca..1616258e0c 100644
--- a/plugin/evm/vm.go
+++ b/plugin/evm/vm.go
@@ -1089,10 +1089,12 @@ func (vm *VM) VerifyHeightIndex() error {
// GetBlockIDAtHeight retrieves the blkID of the canonical block at [blkHeight]
// if [blkHeight] is less than the height of the last accepted block, this will return
// a canonical block. Otherwise, it may return a blkID that has not yet been accepted.
+// Note: Engine's interface requires this function returns database.ErrNotFound
+// if a block is not found.
func (vm *VM) GetBlockIDAtHeight(blkHeight uint64) (ids.ID, error) {
ethBlock := vm.blockChain.GetBlockByNumber(blkHeight)
if ethBlock == nil {
- return ids.ID{}, fmt.Errorf("could not find block at height: %d", blkHeight)
+ return ids.ID{}, database.ErrNotFound
}
return ids.ID(ethBlock.Hash()), nil
diff --git a/plugin/evm/vm_test.go b/plugin/evm/vm_test.go
index fb280bcd2a..fc141cabb9 100644
--- a/plugin/evm/vm_test.go
+++ b/plugin/evm/vm_test.go
@@ -75,8 +75,13 @@ var (
genesisJSONApricotPhase3 = "{\"config\":{\"chainId\":43111,\"homesteadBlock\":0,\"daoForkBlock\":0,\"daoForkSupport\":true,\"eip150Block\":0,\"eip150Hash\":\"0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0\",\"eip155Block\":0,\"eip158Block\":0,\"byzantiumBlock\":0,\"constantinopleBlock\":0,\"petersburgBlock\":0,\"istanbulBlock\":0,\"muirGlacierBlock\":0,\"apricotPhase1BlockTimestamp\":0,\"apricotPhase2BlockTimestamp\":0,\"apricotPhase3BlockTimestamp\":0},\"nonce\":\"0x0\",\"timestamp\":\"0x0\",\"extraData\":\"0x00\",\"gasLimit\":\"0x5f5e100\",\"difficulty\":\"0x0\",\"mixHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"coinbase\":\"0x0000000000000000000000000000000000000000\",\"alloc\":{\"0100000000000000000000000000000000000000\":{\"code\":\"0x7300000000000000000000000000000000000000003014608060405260043610603d5760003560e01c80631e010439146042578063b6510bb314606e575b600080fd5b605c60048036036020811015605657600080fd5b503560b1565b60408051918252519081900360200190f35b818015607957600080fd5b5060af60048036036080811015608e57600080fd5b506001600160a01b03813516906020810135906040810135906060013560b6565b005b30cd90565b836001600160a01b031681836108fc8690811502906040516000604051808303818888878c8acf9550505050505015801560f4573d6000803e3d6000fd5b505050505056fea26469706673582212201eebce970fe3f5cb96bf8ac6ba5f5c133fc2908ae3dcd51082cfee8f583429d064736f6c634300060a0033\",\"balance\":\"0x0\"}},\"number\":\"0x0\",\"gasUsed\":\"0x0\",\"parentHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\"}"
genesisJSONApricotPhase4 = "{\"config\":{\"chainId\":43111,\"homesteadBlock\":0,\"daoForkBlock\":0,\"daoForkSupport\":true,\"eip150Block\":0,\"eip150Hash\":\"0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0\",\"eip155Block\":0,\"eip158Block\":0,\"byzantiumBlock\":0,\"constantinopleBlock\":0,\"petersburgBlock\":0,\"istanbulBlock\":0,\"muirGlacierBlock\":0,\"apricotPhase1BlockTimestamp\":0,\"apricotPhase2BlockTimestamp\":0,\"apricotPhase3BlockTimestamp\":0,\"apricotPhase4BlockTimestamp\":0},\"nonce\":\"0x0\",\"timestamp\":\"0x0\",\"extraData\":\"0x00\",\"gasLimit\":\"0x5f5e100\",\"difficulty\":\"0x0\",\"mixHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"coinbase\":\"0x0000000000000000000000000000000000000000\",\"alloc\":{\"0100000000000000000000000000000000000000\":{\"code\":\"0x7300000000000000000000000000000000000000003014608060405260043610603d5760003560e01c80631e010439146042578063b6510bb314606e575b600080fd5b605c60048036036020811015605657600080fd5b503560b1565b60408051918252519081900360200190f35b818015607957600080fd5b5060af60048036036080811015608e57600080fd5b506001600160a01b03813516906020810135906040810135906060013560b6565b005b30cd90565b836001600160a01b031681836108fc8690811502906040516000604051808303818888878c8acf9550505050505015801560f4573d6000803e3d6000fd5b505050505056fea26469706673582212201eebce970fe3f5cb96bf8ac6ba5f5c133fc2908ae3dcd51082cfee8f583429d064736f6c634300060a0033\",\"balance\":\"0x0\"}},\"number\":\"0x0\",\"gasUsed\":\"0x0\",\"parentHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\"}"
genesisJSONApricotPhase5 = "{\"config\":{\"chainId\":43111,\"homesteadBlock\":0,\"daoForkBlock\":0,\"daoForkSupport\":true,\"eip150Block\":0,\"eip150Hash\":\"0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0\",\"eip155Block\":0,\"eip158Block\":0,\"byzantiumBlock\":0,\"constantinopleBlock\":0,\"petersburgBlock\":0,\"istanbulBlock\":0,\"muirGlacierBlock\":0,\"apricotPhase1BlockTimestamp\":0,\"apricotPhase2BlockTimestamp\":0,\"apricotPhase3BlockTimestamp\":0,\"apricotPhase4BlockTimestamp\":0,\"apricotPhase5BlockTimestamp\":0},\"nonce\":\"0x0\",\"timestamp\":\"0x0\",\"extraData\":\"0x00\",\"gasLimit\":\"0x5f5e100\",\"difficulty\":\"0x0\",\"mixHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"coinbase\":\"0x0000000000000000000000000000000000000000\",\"alloc\":{\"0100000000000000000000000000000000000000\":{\"code\":\"0x7300000000000000000000000000000000000000003014608060405260043610603d5760003560e01c80631e010439146042578063b6510bb314606e575b600080fd5b605c60048036036020811015605657600080fd5b503560b1565b60408051918252519081900360200190f35b818015607957600080fd5b5060af60048036036080811015608e57600080fd5b506001600160a01b03813516906020810135906040810135906060013560b6565b005b30cd90565b836001600160a01b031681836108fc8690811502906040516000604051808303818888878c8acf9550505050505015801560f4573d6000803e3d6000fd5b505050505056fea26469706673582212201eebce970fe3f5cb96bf8ac6ba5f5c133fc2908ae3dcd51082cfee8f583429d064736f6c634300060a0033\",\"balance\":\"0x0\"}},\"number\":\"0x0\",\"gasUsed\":\"0x0\",\"parentHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\"}"
- genesisJSONBlueberry = "{\"config\":{\"chainId\":43111,\"homesteadBlock\":0,\"daoForkBlock\":0,\"daoForkSupport\":true,\"eip150Block\":0,\"eip150Hash\":\"0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0\",\"eip155Block\":0,\"eip158Block\":0,\"byzantiumBlock\":0,\"constantinopleBlock\":0,\"petersburgBlock\":0,\"istanbulBlock\":0,\"muirGlacierBlock\":0,\"apricotPhase1BlockTimestamp\":0,\"apricotPhase2BlockTimestamp\":0,\"apricotPhase3BlockTimestamp\":0,\"apricotPhase4BlockTimestamp\":0,\"apricotPhase5BlockTimestamp\":0,\"blueberryBlockTimestamp\":0},\"nonce\":\"0x0\",\"timestamp\":\"0x0\",\"extraData\":\"0x00\",\"gasLimit\":\"0x5f5e100\",\"difficulty\":\"0x0\",\"mixHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"coinbase\":\"0x0000000000000000000000000000000000000000\",\"alloc\":{\"0100000000000000000000000000000000000000\":{\"code\":\"0x7300000000000000000000000000000000000000003014608060405260043610603d5760003560e01c80631e010439146042578063b6510bb314606e575b600080fd5b605c60048036036020811015605657600080fd5b503560b1565b60408051918252519081900360200190f35b818015607957600080fd5b5060af60048036036080811015608e57600080fd5b506001600160a01b03813516906020810135906040810135906060013560b6565b005b30cd90565b836001600160a01b031681836108fc8690811502906040516000604051808303818888878c8acf9550505050505015801560f4573d6000803e3d6000fd5b505050505056fea26469706673582212201eebce970fe3f5cb96bf8ac6ba5f5c133fc2908ae3dcd51082cfee8f583429d064736f6c634300060a0033\",\"balance\":\"0x0\"}},\"number\":\"0x0\",\"gasUsed\":\"0x0\",\"parentHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\"}"
- genesisJSONLatest = genesisJSONBlueberry
+
+ genesisJSONApricotPhasePre6 = "{\"config\":{\"chainId\":43111,\"homesteadBlock\":0,\"daoForkBlock\":0,\"daoForkSupport\":true,\"eip150Block\":0,\"eip150Hash\":\"0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0\",\"eip155Block\":0,\"eip158Block\":0,\"byzantiumBlock\":0,\"constantinopleBlock\":0,\"petersburgBlock\":0,\"istanbulBlock\":0,\"muirGlacierBlock\":0,\"apricotPhase1BlockTimestamp\":0,\"apricotPhase2BlockTimestamp\":0,\"apricotPhase3BlockTimestamp\":0,\"apricotPhase4BlockTimestamp\":0,\"apricotPhase5BlockTimestamp\":0,\"apricotPhasePre6BlockTimestamp\":0},\"nonce\":\"0x0\",\"timestamp\":\"0x0\",\"extraData\":\"0x00\",\"gasLimit\":\"0x5f5e100\",\"difficulty\":\"0x0\",\"mixHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"coinbase\":\"0x0000000000000000000000000000000000000000\",\"alloc\":{\"0100000000000000000000000000000000000000\":{\"code\":\"0x7300000000000000000000000000000000000000003014608060405260043610603d5760003560e01c80631e010439146042578063b6510bb314606e575b600080fd5b605c60048036036020811015605657600080fd5b503560b1565b60408051918252519081900360200190f35b818015607957600080fd5b5060af60048036036080811015608e57600080fd5b506001600160a01b03813516906020810135906040810135906060013560b6565b005b30cd90565b836001600160a01b031681836108fc8690811502906040516000604051808303818888878c8acf9550505050505015801560f4573d6000803e3d6000fd5b505050505056fea26469706673582212201eebce970fe3f5cb96bf8ac6ba5f5c133fc2908ae3dcd51082cfee8f583429d064736f6c634300060a0033\",\"balance\":\"0x0\"}},\"number\":\"0x0\",\"gasUsed\":\"0x0\",\"parentHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\"}"
+ genesisJSONApricotPhase6 = "{\"config\":{\"chainId\":43111,\"homesteadBlock\":0,\"daoForkBlock\":0,\"daoForkSupport\":true,\"eip150Block\":0,\"eip150Hash\":\"0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0\",\"eip155Block\":0,\"eip158Block\":0,\"byzantiumBlock\":0,\"constantinopleBlock\":0,\"petersburgBlock\":0,\"istanbulBlock\":0,\"muirGlacierBlock\":0,\"apricotPhase1BlockTimestamp\":0,\"apricotPhase2BlockTimestamp\":0,\"apricotPhase3BlockTimestamp\":0,\"apricotPhase4BlockTimestamp\":0,\"apricotPhase5BlockTimestamp\":0,\"apricotPhasePre6BlockTimestamp\":0,\"apricotPhase6BlockTimestamp\":0},\"nonce\":\"0x0\",\"timestamp\":\"0x0\",\"extraData\":\"0x00\",\"gasLimit\":\"0x5f5e100\",\"difficulty\":\"0x0\",\"mixHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"coinbase\":\"0x0000000000000000000000000000000000000000\",\"alloc\":{\"0100000000000000000000000000000000000000\":{\"code\":\"0x7300000000000000000000000000000000000000003014608060405260043610603d5760003560e01c80631e010439146042578063b6510bb314606e575b600080fd5b605c60048036036020811015605657600080fd5b503560b1565b60408051918252519081900360200190f35b818015607957600080fd5b5060af60048036036080811015608e57600080fd5b506001600160a01b03813516906020810135906040810135906060013560b6565b005b30cd90565b836001600160a01b031681836108fc8690811502906040516000604051808303818888878c8acf9550505050505015801560f4573d6000803e3d6000fd5b505050505056fea26469706673582212201eebce970fe3f5cb96bf8ac6ba5f5c133fc2908ae3dcd51082cfee8f583429d064736f6c634300060a0033\",\"balance\":\"0x0\"}},\"number\":\"0x0\",\"gasUsed\":\"0x0\",\"parentHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\"}"
+ genesisJSONApricotPhasePost6 = "{\"config\":{\"chainId\":43111,\"homesteadBlock\":0,\"daoForkBlock\":0,\"daoForkSupport\":true,\"eip150Block\":0,\"eip150Hash\":\"0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0\",\"eip155Block\":0,\"eip158Block\":0,\"byzantiumBlock\":0,\"constantinopleBlock\":0,\"petersburgBlock\":0,\"istanbulBlock\":0,\"muirGlacierBlock\":0,\"apricotPhase1BlockTimestamp\":0,\"apricotPhase2BlockTimestamp\":0,\"apricotPhase3BlockTimestamp\":0,\"apricotPhase4BlockTimestamp\":0,\"apricotPhase5BlockTimestamp\":0,\"apricotPhasePre6BlockTimestamp\":0,\"apricotPhase6BlockTimestamp\":0,\"apricotPhasePost6BlockTimestamp\":0},\"nonce\":\"0x0\",\"timestamp\":\"0x0\",\"extraData\":\"0x00\",\"gasLimit\":\"0x5f5e100\",\"difficulty\":\"0x0\",\"mixHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"coinbase\":\"0x0000000000000000000000000000000000000000\",\"alloc\":{\"0100000000000000000000000000000000000000\":{\"code\":\"0x7300000000000000000000000000000000000000003014608060405260043610603d5760003560e01c80631e010439146042578063b6510bb314606e575b600080fd5b605c60048036036020811015605657600080fd5b503560b1565b60408051918252519081900360200190f35b818015607957600080fd5b5060af60048036036080811015608e57600080fd5b506001600160a01b03813516906020810135906040810135906060013560b6565b005b30cd90565b836001600160a01b031681836108fc8690811502906040516000604051808303818888878c8acf9550505050505015801560f4573d6000803e3d6000fd5b505050505056fea26469706673582212201eebce970fe3f5cb96bf8ac6ba5f5c133fc2908ae3dcd51082cfee8f583429d064736f6c634300060a0033\",\"balance\":\"0x0\"}},\"number\":\"0x0\",\"gasUsed\":\"0x0\",\"parentHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\"}"
+
+ genesisJSONBlueberry = "{\"config\":{\"chainId\":43111,\"homesteadBlock\":0,\"daoForkBlock\":0,\"daoForkSupport\":true,\"eip150Block\":0,\"eip150Hash\":\"0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0\",\"eip155Block\":0,\"eip158Block\":0,\"byzantiumBlock\":0,\"constantinopleBlock\":0,\"petersburgBlock\":0,\"istanbulBlock\":0,\"muirGlacierBlock\":0,\"apricotPhase1BlockTimestamp\":0,\"apricotPhase2BlockTimestamp\":0,\"apricotPhase3BlockTimestamp\":0,\"apricotPhase4BlockTimestamp\":0,\"apricotPhase5BlockTimestamp\":0,\"blueberryBlockTimestamp\":0},\"nonce\":\"0x0\",\"timestamp\":\"0x0\",\"extraData\":\"0x00\",\"gasLimit\":\"0x5f5e100\",\"difficulty\":\"0x0\",\"mixHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"coinbase\":\"0x0000000000000000000000000000000000000000\",\"alloc\":{\"0100000000000000000000000000000000000000\":{\"code\":\"0x7300000000000000000000000000000000000000003014608060405260043610603d5760003560e01c80631e010439146042578063b6510bb314606e575b600080fd5b605c60048036036020811015605657600080fd5b503560b1565b60408051918252519081900360200190f35b818015607957600080fd5b5060af60048036036080811015608e57600080fd5b506001600160a01b03813516906020810135906040810135906060013560b6565b005b30cd90565b836001600160a01b031681836108fc8690811502906040516000604051808303818888878c8acf9550505050505015801560f4573d6000803e3d6000fd5b505050505056fea26469706673582212201eebce970fe3f5cb96bf8ac6ba5f5c133fc2908ae3dcd51082cfee8f583429d064736f6c634300060a0033\",\"balance\":\"0x0\"}},\"number\":\"0x0\",\"gasUsed\":\"0x0\",\"parentHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\"}"
+ genesisJSONLatest = genesisJSONBlueberry
apricotRulesPhase0 = params.Rules{}
apricotRulesPhase1 = params.Rules{IsApricotPhase1: true}
@@ -387,6 +392,21 @@ func TestVMUpgrades(t *testing.T) {
genesis: genesisJSONApricotPhase5,
expectedGasPrice: big.NewInt(0),
},
+ {
+ name: "Apricot Phase Pre 6",
+ genesis: genesisJSONApricotPhasePre6,
+ expectedGasPrice: big.NewInt(0),
+ },
+ {
+ name: "Apricot Phase 6",
+ genesis: genesisJSONApricotPhase6,
+ expectedGasPrice: big.NewInt(0),
+ },
+ {
+ name: "Apricot Phase Post 6",
+ genesis: genesisJSONApricotPhasePost6,
+ expectedGasPrice: big.NewInt(0),
+ },
{
name: "Blueberry",
genesis: genesisJSONBlueberry,
diff --git a/rpc/http.go b/rpc/http.go
index a2c452bb45..528fd8be4a 100644
--- a/rpc/http.go
+++ b/rpc/http.go
@@ -101,6 +101,14 @@ type HTTPTimeouts struct {
// ReadHeaderTimeout. It is valid to use them both.
ReadTimeout time.Duration
+ // ReadHeaderTimeout is the amount of time allowed to read
+ // request headers. The connection's read deadline is reset
+ // after reading the headers and the Handler can decide what
+ // is considered too slow for the body. If ReadHeaderTimeout
+ // is zero, the value of ReadTimeout is used. If both are
+ // zero, there is no timeout.
+ ReadHeaderTimeout time.Duration
+
// WriteTimeout is the maximum duration before timing out
// writes of the response. It is reset whenever a new
// request's header is read. Like ReadTimeout, it does not
@@ -117,9 +125,10 @@ type HTTPTimeouts struct {
// DefaultHTTPTimeouts represents the default timeout values used if further
// configuration is not provided.
var DefaultHTTPTimeouts = HTTPTimeouts{
- ReadTimeout: 30 * time.Second,
- WriteTimeout: 30 * time.Second,
- IdleTimeout: 120 * time.Second,
+ ReadTimeout: 30 * time.Second,
+ ReadHeaderTimeout: 30 * time.Second,
+ WriteTimeout: 30 * time.Second,
+ IdleTimeout: 120 * time.Second,
}
// DialHTTPWithClient creates a new RPC client that connects to an RPC server over HTTP
diff --git a/rpc/server.go b/rpc/server.go
index 4e90545846..fa79a8b55f 100644
--- a/rpc/server.go
+++ b/rpc/server.go
@@ -181,7 +181,7 @@ type PeerInfo struct {
// Address of client. This will usually contain the IP address and port.
RemoteAddr string
- // Addditional information for HTTP and WebSocket connections.
+ // Additional information for HTTP and WebSocket connections.
HTTP struct {
// Protocol version, i.e. "HTTP/1.1". This is not set for WebSocket.
Version string
diff --git a/scripts/versions.sh b/scripts/versions.sh
index b3a6938295..5869853708 100644
--- a/scripts/versions.sh
+++ b/scripts/versions.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
# Set up the versions to be used
-coreth_version=${CORETH_VERSION:-'v0.8.17'}
+coreth_version=${CORETH_VERSION:-'v0.9.0'}
# Don't export them as they're used in the context of other calls
avalanche_version=${AVALANCHE_VERSION:-'v1.7.18'}
diff --git a/sync/statesync/sync_test.go b/sync/statesync/sync_test.go
index 885bfb4031..13d58c0caf 100644
--- a/sync/statesync/sync_test.go
+++ b/sync/statesync/sync_test.go
@@ -4,9 +4,11 @@
package statesync
import (
+ "bytes"
"context"
"errors"
"math/rand"
+ "runtime/pprof"
"sync/atomic"
"testing"
"time"
@@ -97,24 +99,34 @@ func waitFor(t *testing.T, result <-chan error, expected error, timeout time.Dur
t.Fatal("unexpected error waiting for sync result", err)
}
case <-time.After(timeout):
+ // print a stack trace to assist with debugging
+ // if the test times out.
+ var stackBuf bytes.Buffer
+ pprof.Lookup("goroutine").WriteTo(&stackBuf, 2)
+ t.Log(stackBuf.String())
+ // fail the test
t.Fatal("unexpected timeout waiting for sync result")
}
}
func TestSimpleSyncCases(t *testing.T) {
- clientErr := errors.New("dummy client error")
+ var (
+ numAccounts = 250
+ numAccountsSmall = 10
+ clientErr = errors.New("dummy client error")
+ )
tests := map[string]syncTest{
"accounts": {
prepareForTest: func(t *testing.T) (ethdb.Database, *trie.Database, common.Hash) {
serverTrieDB := trie.NewDatabase(memorydb.New())
- root, _ := trie.FillAccounts(t, serverTrieDB, common.Hash{}, 1000, nil)
+ root, _ := trie.FillAccounts(t, serverTrieDB, common.Hash{}, numAccounts, nil)
return memorydb.New(), serverTrieDB, root
},
},
"accounts with code": {
prepareForTest: func(t *testing.T) (ethdb.Database, *trie.Database, common.Hash) {
serverTrieDB := trie.NewDatabase(memorydb.New())
- root, _ := trie.FillAccounts(t, serverTrieDB, common.Hash{}, 1000, func(t *testing.T, index int, account types.StateAccount) types.StateAccount {
+ root, _ := trie.FillAccounts(t, serverTrieDB, common.Hash{}, numAccounts, func(t *testing.T, index int, account types.StateAccount) types.StateAccount {
if index%3 == 0 {
codeBytes := make([]byte, 256)
_, err := rand.Read(codeBytes)
@@ -134,14 +146,14 @@ func TestSimpleSyncCases(t *testing.T) {
"accounts with code and storage": {
prepareForTest: func(t *testing.T) (ethdb.Database, *trie.Database, common.Hash) {
serverTrieDB := trie.NewDatabase(memorydb.New())
- root := fillAccountsWithStorage(t, serverTrieDB, common.Hash{}, 1000)
+ root := fillAccountsWithStorage(t, serverTrieDB, common.Hash{}, numAccounts)
return memorydb.New(), serverTrieDB, root
},
},
"accounts with storage": {
prepareForTest: func(t *testing.T) (ethdb.Database, *trie.Database, common.Hash) {
serverTrieDB := trie.NewDatabase(memorydb.New())
- root, _ := trie.FillAccounts(t, serverTrieDB, common.Hash{}, 1000, func(t *testing.T, i int, account types.StateAccount) types.StateAccount {
+ root, _ := trie.FillAccounts(t, serverTrieDB, common.Hash{}, numAccounts, func(t *testing.T, i int, account types.StateAccount) types.StateAccount {
if i%5 == 0 {
account.Root, _, _ = trie.GenerateTrie(t, serverTrieDB, 16, common.HashLength)
}
@@ -154,14 +166,14 @@ func TestSimpleSyncCases(t *testing.T) {
"accounts with overlapping storage": {
prepareForTest: func(t *testing.T) (ethdb.Database, *trie.Database, common.Hash) {
serverTrieDB := trie.NewDatabase(memorydb.New())
- root, _ := FillAccountsWithOverlappingStorage(t, serverTrieDB, common.Hash{}, 1000, 3)
+ root, _ := FillAccountsWithOverlappingStorage(t, serverTrieDB, common.Hash{}, numAccounts, 3)
return memorydb.New(), serverTrieDB, root
},
},
"failed to fetch leafs": {
prepareForTest: func(t *testing.T) (ethdb.Database, *trie.Database, common.Hash) {
serverTrieDB := trie.NewDatabase(memorydb.New())
- root, _ := trie.FillAccounts(t, serverTrieDB, common.Hash{}, 100, nil)
+ root, _ := trie.FillAccounts(t, serverTrieDB, common.Hash{}, numAccountsSmall, nil)
return memorydb.New(), serverTrieDB, root
},
GetLeafsIntercept: func(_ message.LeafsRequest, _ message.LeafsResponse) (message.LeafsResponse, error) {
@@ -172,7 +184,7 @@ func TestSimpleSyncCases(t *testing.T) {
"failed to fetch code": {
prepareForTest: func(t *testing.T) (ethdb.Database, *trie.Database, common.Hash) {
serverTrieDB := trie.NewDatabase(memorydb.New())
- root := fillAccountsWithStorage(t, serverTrieDB, common.Hash{}, 100)
+ root := fillAccountsWithStorage(t, serverTrieDB, common.Hash{}, numAccountsSmall)
return memorydb.New(), serverTrieDB, root
},
GetCodeIntercept: func(_ []common.Hash, _ [][]byte) ([][]byte, error) {
diff --git a/trie/committer.go b/trie/committer.go
index 21f93b7df9..535f2cf0d4 100644
--- a/trie/committer.go
+++ b/trie/committer.go
@@ -27,61 +27,48 @@
package trie
import (
- "errors"
"fmt"
- "sync"
"github.com/ethereum/go-ethereum/common"
)
-// committer is a type used for the trie Commit operation. A committer has some
-// internal preallocated temp space, and also a callback that is invoked when
-// leaves are committed. The leafs are passed through the `leafCh`, to allow
-// some level of parallelism.
-// By 'some level' of parallelism, it's still the case that all leaves will be
-// processed sequentially - onleaf will never be called in parallel or out of order.
-type committer struct {
- onleaf LeafCallback
+// leaf represents a trie leaf node
+type leaf struct {
+ blob []byte // raw blob of leaf
+ parent common.Hash // the hash of parent node
}
-// committers live in a global sync.Pool
-var committerPool = sync.Pool{
- New: func() interface{} {
- return &committer{}
- },
+// committer is the tool used for the trie Commit operation. The committer will
+// capture all dirty nodes during the commit process and keep them cached in
+// insertion order.
+type committer struct {
+ nodes *NodeSet
+ collectLeaf bool
}
// newCommitter creates a new committer or picks one from the pool.
-func newCommitter(onleaf LeafCallback) *committer {
- committer := committerPool.Get().(*committer)
- committer.onleaf = onleaf
- return committer
-}
-
-func returnCommitterToPool(h *committer) {
- h.onleaf = nil
- committerPool.Put(h)
+func newCommitter(owner common.Hash, collectLeaf bool) *committer {
+ return &committer{
+ nodes: NewNodeSet(owner),
+ collectLeaf: collectLeaf,
+ }
}
// Commit collapses a node down into a hash node and inserts it into the database
-func (c *committer) Commit(n node, db *Database) (hashNode, int, error) {
- if db == nil {
- return nil, 0, errors.New("no db provided")
- }
-
- h, committed, err := c.commit(nil, n, db)
+func (c *committer) Commit(n node) (hashNode, *NodeSet, error) {
+ h, err := c.commit(nil, n)
if err != nil {
- return nil, 0, err
+ return nil, nil, err
}
- return h.(hashNode), committed, nil
+ return h.(hashNode), c.nodes, nil
}
// commit collapses a node down into a hash node and inserts it into the database
-func (c *committer) commit(path []byte, n node, db *Database) (node, int, error) {
+func (c *committer) commit(path []byte, n node) (node, error) {
// if this path is clean, use available cached data
hash, dirty := n.cache()
if hash != nil && !dirty {
- return hash, 0, nil
+ return hash, nil
}
// Commit children, then parent, and remove the dirty flag.
switch cn := n.(type) {
@@ -91,36 +78,35 @@ func (c *committer) commit(path []byte, n node, db *Database) (node, int, error)
// If the child is fullNode, recursively commit,
// otherwise it can only be hashNode or valueNode.
- var childCommitted int
if _, ok := cn.Val.(*fullNode); ok {
- childV, committed, err := c.commit(append(path, cn.Key...), cn.Val, db)
+ childV, err := c.commit(append(path, cn.Key...), cn.Val)
if err != nil {
- return nil, 0, err
+ return nil, err
}
- collapsed.Val, childCommitted = childV, committed
+ collapsed.Val = childV
}
// The key needs to be copied, since we're delivering it to database
collapsed.Key = hexToCompact(cn.Key)
- hashedNode := c.store(path, collapsed, db)
+ hashedNode := c.store(path, collapsed)
if hn, ok := hashedNode.(hashNode); ok {
- return hn, childCommitted + 1, nil
+ return hn, nil
}
- return collapsed, childCommitted, nil
+ return collapsed, nil
case *fullNode:
- hashedKids, childCommitted, err := c.commitChildren(path, cn, db)
+ hashedKids, err := c.commitChildren(path, cn)
if err != nil {
- return nil, 0, err
+ return nil, err
}
collapsed := cn.copy()
collapsed.Children = hashedKids
- hashedNode := c.store(path, collapsed, db)
+ hashedNode := c.store(path, collapsed)
if hn, ok := hashedNode.(hashNode); ok {
- return hn, childCommitted + 1, nil
+ return hn, nil
}
- return collapsed, childCommitted, nil
+ return collapsed, nil
case hashNode:
- return cn, 0, nil
+ return cn, nil
default:
// nil, valuenode shouldn't be committed
panic(fmt.Sprintf("%T: invalid node: %v", n, n))
@@ -128,11 +114,8 @@ func (c *committer) commit(path []byte, n node, db *Database) (node, int, error)
}
// commitChildren commits the children of the given fullnode
-func (c *committer) commitChildren(path []byte, n *fullNode, db *Database) ([17]node, int, error) {
- var (
- committed int
- children [17]node
- )
+func (c *committer) commitChildren(path []byte, n *fullNode) ([17]node, error) {
+ var children [17]node
for i := 0; i < 16; i++ {
child := n.Children[i]
if child == nil {
@@ -148,68 +131,62 @@ func (c *committer) commitChildren(path []byte, n *fullNode, db *Database) ([17]
// Commit the child recursively and store the "hashed" value.
// Note the returned node can be some embedded nodes, so it's
// possible the type is not hashNode.
- hashed, childCommitted, err := c.commit(append(path, byte(i)), child, db)
+ hashed, err := c.commit(append(path, byte(i)), child)
if err != nil {
- return children, 0, err
+ return children, err
}
children[i] = hashed
- committed += childCommitted
}
// For the 17th child, it's possible the type is valuenode.
if n.Children[16] != nil {
children[16] = n.Children[16]
}
- return children, committed, nil
+ return children, nil
}
// store hashes the node n and if we have a storage layer specified, it writes
// the key/value pair to it and tracks any node->child references as well as any
// node->external trie references.
-func (c *committer) store(path []byte, n node, db *Database) node {
+func (c *committer) store(path []byte, n node) node {
// Larger nodes are replaced by their hash and stored in the database.
- var (
- hashNode, _ = n.cache()
- size int
- )
- if hashNode == nil {
- // This was not generated - must be a small node stored in the parent.
- // In theory, we should apply the leafCall here if it's not nil(embedded
- // node usually contains value). But small value(less than 32bytes) is
- // not our target.
+ var hash, _ = n.cache()
+ // This was not generated - must be a small node stored in the parent.
+ // In theory, we should check if the node is leaf here (embedded node
+ // usually is leaf node). But small value(less than 32bytes) is not
+ // our target(leaves in account trie only).
+ if hash == nil {
return n
- } else {
- // We have the hash already, estimate the RLP encoding-size of the node.
- // The size is used for mem tracking, does not need to be exact
- size = estimateSize(n)
}
-
- hash := common.BytesToHash(hashNode)
- // Serially insert the nodes into the database
- if db != nil {
- db.insert(hash, size, n)
- }
-
- // Invoke the leaf callback if present
- if c.onleaf != nil {
- switch n := n.(type) {
- case *shortNode:
- if child, ok := n.Val.(valueNode); ok {
- c.onleaf(nil, nil, child, hash, nil)
- }
- case *fullNode:
- // For children in range [0, 15], it's impossible
- // to contain valueNode. Only check the 17th child.
- if n.Children[16] != nil {
- c.onleaf(nil, nil, n.Children[16].(valueNode), hash, nil)
+ // We have the hash already, estimate the RLP encoding-size of the node.
+ // The size is used for mem tracking, does not need to be exact
+ var (
+ size = estimateSize(n)
+ nhash = common.BytesToHash(hash)
+ mnode = &memoryNode{
+ hash: nhash,
+ node: simplifyNode(n),
+ size: uint16(size),
+ }
+ )
+ // Collect the dirty node to nodeset for return.
+ c.nodes.add(string(path), mnode)
+
+ // Collect the corresponding leaf node if it's required. We don't check
+ // full node since it's impossible to store value in fullNode. The key
+ // length of leaves should be exactly same.
+ if c.collectLeaf {
+ if sn, ok := n.(*shortNode); ok {
+ if val, ok := sn.Val.(valueNode); ok {
+ c.nodes.addLeaf(&leaf{blob: val, parent: nhash})
}
}
}
- return hashNode
+ return hash
}
// estimateSize estimates the size of an rlp-encoded node, without actually
// rlp-encoding it (zero allocs). This method has been experimentally tried, and with a trie
-// with 1000 leafs, the only errors above 1% are on small shortnodes, where this
+// with 1000 leaves, the only errors above 1% are on small shortnodes, where this
// method overestimates by 2 or 3 bytes (e.g. 37 instead of 35)
func estimateSize(n node) int {
switch n := n.(type) {
diff --git a/trie/database.go b/trie/database.go
index 666c91e41e..b98acd9267 100644
--- a/trie/database.go
+++ b/trie/database.go
@@ -36,6 +36,7 @@ import (
"github.com/VictoriaMetrics/fastcache"
"github.com/ava-labs/coreth/core/rawdb"
+ "github.com/ava-labs/coreth/core/types"
"github.com/ava-labs/coreth/ethdb"
"github.com/ava-labs/coreth/metrics"
"github.com/ethereum/go-ethereum/common"
@@ -43,10 +44,6 @@ import (
"github.com/ethereum/go-ethereum/rlp"
)
-const (
- defaultPreimagesLimit = 4 * 1024 * 1024 // 4 MB
-)
-
var (
memcacheCleanHitMeter = metrics.NewRegisteredMeter("trie/memcache/clean/hit", nil)
memcacheCleanMissMeter = metrics.NewRegisteredMeter("trie/memcache/clean/miss", nil)
@@ -87,15 +84,10 @@ var (
type Database struct {
diskdb ethdb.KeyValueStore // Persistent storage for matured trie nodes
- preimagesLock sync.RWMutex // Used to gate acess to [preimagesSize] and [preimages]
- preimagesSize common.StorageSize // Storage size of the preimages cache
- preimages map[common.Hash][]byte // Preimages of nodes from the secure trie
-
- dirtiesLock sync.RWMutex // Used to gate access to all trie node data structures and metrics (everything below)
- cleans *fastcache.Cache // GC friendly memory cache of clean node RLPs
- dirties map[common.Hash]*cachedNode // Data and references relationships of dirty trie nodes
- oldest common.Hash // Oldest tracked node, flush-list head
- newest common.Hash // Newest tracked node, flush-list tail
+ cleans *fastcache.Cache // GC friendly memory cache of clean node RLPs
+ dirties map[common.Hash]*cachedNode // Data and references relationships of dirty trie nodes
+ oldest common.Hash // Oldest tracked node, flush-list head
+ newest common.Hash // Newest tracked node, flush-list tail
gctime time.Duration // Time spent on garbage collection since last commit
gcnodes uint64 // Nodes garbage collected since last commit
@@ -107,6 +99,9 @@ type Database struct {
dirtiesSize common.StorageSize // Storage size of the dirty node cache (exc. metadata)
childrenSize common.StorageSize // Storage size of the external children tracking
+ preimages *preimageStore // The store for caching preimages
+
+ lock sync.RWMutex
}
// rawNode is a simple binary blob used to differentiate between collapsed trie
@@ -182,7 +177,10 @@ func (n *cachedNode) rlp() []byte {
// or by regenerating it from the rlp encoded blob.
func (n *cachedNode) obj(hash common.Hash) node {
if node, ok := n.node.(rawNode); ok {
- return mustDecodeNode(hash[:], node)
+ // The raw-blob format nodes are loaded either from the
+ // clean cache or the database, they are all in their own
+ // copy and safe to use unsafe decoder.
+ return mustDecodeNodeUnsafe(hash[:], node)
}
return expandNode(hash[:], n.node)
}
@@ -300,15 +298,17 @@ func NewDatabaseWithConfig(diskdb ethdb.KeyValueStore, config *Config) *Database
if config != nil && config.Cache > 0 {
cleans = fastcache.New(config.Cache * 1024 * 1024)
}
+ var preimage *preimageStore
+ if config != nil && config.Preimages {
+ preimage = newPreimageStore(diskdb)
+ }
db := &Database{
diskdb: diskdb,
cleans: cleans,
dirties: map[common.Hash]*cachedNode{{}: {
children: make(map[common.Hash]uint16),
}},
- }
- if config == nil || config.Preimages { // TODO(karalabe): Flip to default off in the future
- db.preimages = make(map[common.Hash][]byte)
+ preimages: preimage,
}
return db
}
@@ -318,11 +318,9 @@ func (db *Database) DiskDB() ethdb.KeyValueStore {
return db.diskdb
}
-// insert inserts a collapsed trie node into the memory database.
-// The blob size must be specified to allow proper size tracking.
+// insert inserts a simplified trie node into the memory database.
// All nodes inserted by this function will be reference tracked
// and in theory should only used for **trie nodes** insertion.
-// insert assumes that the dirtiesLock is held by the caller.
func (db *Database) insert(hash common.Hash, size int, node node) {
// If the node's already cached, skip
if _, ok := db.dirties[hash]; ok {
@@ -332,7 +330,7 @@ func (db *Database) insert(hash common.Hash, size int, node node) {
// Create the cached entry for this node
entry := &cachedNode{
- node: simplifyNode(node),
+ node: node,
size: uint16(size),
flushPrev: db.newest,
}
@@ -352,46 +350,6 @@ func (db *Database) insert(hash common.Hash, size int, node node) {
db.dirtiesSize += common.StorageSize(common.HashLength + entry.size)
}
-// InsertPreimages writes a map of new trie node preimages to the
-// memory database if it's yet unknown.
-//
-// The method will NOT make a copy of the provided slice,
-// only use if the preimage will NOT be changed later on.
-func (db *Database) InsertPreimages(preimages map[string][]byte) {
- // Short circuit if preimage collection is disabled
- if db.preimages == nil {
- return
- }
- // Track the preimage if a yet unknown one
- db.preimagesLock.Lock()
- defer db.preimagesLock.Unlock()
- for hk, preimage := range preimages {
- hash := common.BytesToHash([]byte(hk))
- if _, ok := db.preimages[hash]; ok {
- continue
- }
- db.preimages[hash] = preimage
- db.preimagesSize += common.StorageSize(common.HashLength + len(preimage))
- }
-}
-
-// Preimage retrieves a cached trie node pre-image from memory. If it cannot be
-// found cached, the method queries the persistent database for the content.
-func (db *Database) Preimage(hash common.Hash) []byte {
- // Short circuit if preimage collection is disabled
- if db.preimages == nil {
- return nil
- }
- // Retrieve the node from cache if available
- db.preimagesLock.RLock()
- preimage := db.preimages[hash]
- db.preimagesLock.RUnlock()
- if preimage != nil {
- return preimage
- }
- return rawdb.ReadPreimage(db.diskdb, hash)
-}
-
// RawNode retrieves an encoded cached trie node from memory. If it cannot be found
// cached, the method queries the persistent database for the content. This function
// will not return the metaroot.
@@ -437,9 +395,9 @@ func (db *Database) node(hash common.Hash) ([]byte, *cachedNode, error) {
}
}
// Retrieve the node from the dirty cache if available
- db.dirtiesLock.RLock()
+ db.lock.RLock()
dirty := db.dirties[hash]
- db.dirtiesLock.RUnlock()
+ db.lock.RUnlock()
if dirty != nil {
memcacheDirtyHitMeter.Mark(1)
@@ -465,8 +423,8 @@ func (db *Database) node(hash common.Hash) ([]byte, *cachedNode, error) {
// This method is extremely expensive and should only be used to validate internal
// states in test code.
func (db *Database) Nodes() []common.Hash {
- db.dirtiesLock.RLock()
- defer db.dirtiesLock.RUnlock()
+ db.lock.RLock()
+ defer db.lock.RUnlock()
var hashes = make([]common.Hash, 0, len(db.dirties))
for hash := range db.dirties {
@@ -481,12 +439,14 @@ func (db *Database) Nodes() []common.Hash {
// This function is used to add reference between internal trie node
// and external node(e.g. storage trie root), all internal trie nodes
// are referenced together by database itself.
-func (db *Database) Reference(child common.Hash, parent common.Hash, exclusive bool) {
- if exclusive {
- db.dirtiesLock.Lock()
- defer db.dirtiesLock.Unlock()
- }
+func (db *Database) Reference(child common.Hash, parent common.Hash) {
+ db.lock.Lock()
+ defer db.lock.Unlock()
+ db.reference(child, parent)
+}
+
+func (db *Database) reference(child common.Hash, parent common.Hash) {
// If the node does not exist, it's a node pulled from disk, skip
node, ok := db.dirties[child]
if !ok {
@@ -514,8 +474,8 @@ func (db *Database) Dereference(root common.Hash) {
return
}
- db.dirtiesLock.Lock()
- defer db.dirtiesLock.Unlock()
+ db.lock.Lock()
+ defer db.lock.Unlock()
nodes, storage, start := len(db.dirties), db.dirtiesSize, time.Now()
db.dereference(root, common.Hash{})
@@ -585,49 +545,6 @@ func (db *Database) dereference(child common.Hash, parent common.Hash) {
}
}
-// WritePreimages writes all preimages to disk if more than [limit] are currently in
-// memory.
-func (db *Database) WritePreimages(limit common.StorageSize) error {
- // Short circuit if preimage collection is disabled
- if db.preimages == nil {
- return nil
- }
-
- // If the preimage cache got large enough, push to disk. If it's still small
- // leave for later to deduplicate writes.
- db.preimagesLock.RLock()
- if db.preimagesSize <= limit {
- db.preimagesLock.RUnlock()
- return nil
- }
- toFlush := make(map[common.Hash][]byte)
- for k, v := range db.preimages {
- toFlush[k] = v
- }
- db.preimagesLock.RUnlock()
-
- // Short circuit if nothing to do
- if len(toFlush) == 0 {
- return nil
- }
-
- // Write preimages to disk
- batch := db.diskdb.NewBatch()
- rawdb.WritePreimages(batch, toFlush)
- if err := batch.Write(); err != nil {
- return err
- }
-
- // Write successful, clear out the flushed data
- db.preimagesLock.Lock()
- defer db.preimagesLock.Unlock()
- for hash, preimage := range toFlush {
- delete(db.preimages, hash)
- db.preimagesSize -= common.StorageSize(common.HashLength + len(preimage))
- }
- return nil
-}
-
// flushItem is used to track all [cachedNode]s that must be written to disk
type flushItem struct {
hash common.Hash
@@ -668,15 +585,19 @@ func (db *Database) writeFlushItems(toFlush []flushItem) error {
// memory usage goes below the given threshold.
func (db *Database) Cap(limit common.StorageSize) error {
start := time.Now()
- if err := db.WritePreimages(defaultPreimagesLimit); err != nil {
- return err
+ // If the preimage cache got large enough, push to disk. If it's still small
+ // leave for later to deduplicate writes.
+ if db.preimages != nil {
+ if err := db.preimages.commit(false); err != nil {
+ return err
+ }
}
// It is important that outside code doesn't see an inconsistent state
// (referenced data removed from memory cache during commit but not yet
// in persistent storage). This is ensured by only uncaching existing
// data when the database write finalizes.
- db.dirtiesLock.RLock()
+ db.lock.RLock()
lockStart := time.Now()
nodes, storage := len(db.dirties), db.dirtiesSize
@@ -686,7 +607,7 @@ func (db *Database) Cap(limit common.StorageSize) error {
pendingSize := db.dirtiesSize + common.StorageSize((len(db.dirties)-1)*cachedNodeSize)
pendingSize += db.childrenSize - common.StorageSize(len(db.dirties[common.Hash{}].children)*(common.HashLength+2))
if pendingSize <= limit {
- db.dirtiesLock.RUnlock()
+ db.lock.RUnlock()
return nil
}
@@ -707,7 +628,7 @@ func (db *Database) Cap(limit common.StorageSize) error {
}
oldest = node.flushNext
}
- db.dirtiesLock.RUnlock()
+ db.lock.RUnlock()
lockTime := time.Since(lockStart)
// Write nodes to disk
@@ -719,8 +640,8 @@ func (db *Database) Cap(limit common.StorageSize) error {
//
// NOTE: The order of the flushlist may have changed while the lock was not
// held, so we cannot just iterate to [oldest].
- db.dirtiesLock.Lock()
- defer db.dirtiesLock.Unlock()
+ db.lock.Lock()
+ defer db.lock.Unlock()
lockStart = time.Now()
for _, item := range toFlush {
// [item.rlp] is populated in [writeFlushItems]
@@ -750,14 +671,16 @@ func (db *Database) Cap(limit common.StorageSize) error {
// effect, all pre-images accumulated up to this point are also written.
func (db *Database) Commit(node common.Hash, report bool, callback func(common.Hash)) error {
start := time.Now()
- if err := db.WritePreimages(0); err != nil {
- return err
+ if db.preimages != nil {
+ if err := db.preimages.commit(true); err != nil {
+ return err
+ }
}
// It is important that outside code doesn't see an inconsistent state (referenced
// data removed from memory cache during commit but not yet in persistent storage).
// This is ensured by only uncaching existing data when the database write finalizes.
- db.dirtiesLock.RLock()
+ db.lock.RLock()
lockStart := time.Now()
nodes, storage := len(db.dirties), db.dirtiesSize
toFlush, err := db.commit(node, make([]flushItem, 0, 128), callback)
@@ -765,7 +688,7 @@ func (db *Database) Commit(node common.Hash, report bool, callback func(common.H
log.Error("Failed to commit trie from trie database", "err", err)
return err
}
- db.dirtiesLock.RUnlock()
+ db.lock.RUnlock()
lockTime := time.Since(lockStart)
// Write nodes to disk
@@ -774,8 +697,8 @@ func (db *Database) Commit(node common.Hash, report bool, callback func(common.H
}
// Flush all written items from dirites
- db.dirtiesLock.Lock()
- defer db.dirtiesLock.Unlock()
+ db.lock.Lock()
+ defer db.lock.Unlock()
lockStart = time.Now()
for _, item := range toFlush {
// [item.rlp] is populated in [writeFlushItems]
@@ -877,19 +800,98 @@ func (db *Database) removeFromDirties(hash common.Hash, rlp []byte) {
}
}
+// Update inserts the dirty nodes in provided nodeset into database and
+// links the account trie with multiple storage tries if necessary.
+func (db *Database) Update(nodes *MergedNodeSet) error {
+ db.lock.Lock()
+ defer db.lock.Unlock()
+
+ return db.update(nodes)
+}
+
+// UpdateAndReferenceRoot inserts the dirty nodes in provided nodeset into
+// database and links the account trie with multiple storage tries if necessary,
+// then adds a reference [from] root to the metaroot while holding the db's lock.
+func (db *Database) UpdateAndReferenceRoot(nodes *MergedNodeSet, root common.Hash) error {
+ db.lock.Lock()
+ defer db.lock.Unlock()
+
+ if err := db.update(nodes); err != nil {
+ return err
+ }
+ db.reference(root, common.Hash{})
+ return nil
+}
+
+func (db *Database) update(nodes *MergedNodeSet) error {
+ // Insert dirty nodes into the database. In the same tree, it must be
+ // ensured that children are inserted first, then parent so that children
+ // can be linked with their parent correctly.
+ //
+ // Note, the storage tries must be flushed before the account trie to
+ // retain the invariant that children go into the dirty cache first.
+ var order []common.Hash
+ for owner := range nodes.sets {
+ if owner == (common.Hash{}) {
+ continue
+ }
+ order = append(order, owner)
+ }
+ if _, ok := nodes.sets[common.Hash{}]; ok {
+ order = append(order, common.Hash{})
+ }
+ for _, owner := range order {
+ subset := nodes.sets[owner]
+ for _, path := range subset.paths {
+ n, ok := subset.nodes[path]
+ if !ok {
+ return fmt.Errorf("missing node %x %v", owner, path)
+ }
+ db.insert(n.hash, int(n.size), n.node)
+ }
+ }
+ // Link up the account trie and storage trie if the node points
+ // to an account trie leaf.
+ if set, present := nodes.sets[common.Hash{}]; present {
+ for _, n := range set.leaves {
+ var account types.StateAccount
+ if err := rlp.DecodeBytes(n.blob, &account); err != nil {
+ return err
+ }
+ if account.Root != emptyRoot {
+ db.reference(account.Root, n.parent)
+ }
+ }
+ }
+ return nil
+}
+
// Size returns the current storage size of the memory cache in front of the
// persistent database layer.
func (db *Database) Size() (common.StorageSize, common.StorageSize) {
- db.preimagesLock.RLock()
- preimagesSize := db.preimagesSize
- db.preimagesLock.RUnlock()
-
// db.dirtiesSize only contains the useful data in the cache, but when reporting
// the total memory consumption, the maintenance metadata is also needed to be
// counted.
- db.dirtiesLock.RLock()
- defer db.dirtiesLock.RUnlock()
+ db.lock.RLock()
+ defer db.lock.RUnlock()
var metadataSize = common.StorageSize((len(db.dirties) - 1) * cachedNodeSize)
var metarootRefs = common.StorageSize(len(db.dirties[common.Hash{}].children) * (common.HashLength + 2))
- return db.dirtiesSize + db.childrenSize + metadataSize - metarootRefs, preimagesSize
+ var preimageSize common.StorageSize
+ if db.preimages != nil {
+ preimageSize = db.preimages.size()
+ }
+ return db.dirtiesSize + db.childrenSize + metadataSize - metarootRefs, preimageSize
+}
+
+// CommitPreimages flushes the dangling preimages to disk. It is meant to be
+// called when closing the blockchain object, so that preimages are persisted
+// to the database.
+func (db *Database) CommitPreimages() error {
+ db.lock.Lock()
+ defer db.lock.Unlock()
+
+ if db.preimages == nil {
+ return nil
+ }
+ return db.preimages.commit(true)
}
diff --git a/trie/database_test.go b/trie/database_test.go
index 3fe9d31e2d..3a943a8a7c 100644
--- a/trie/database_test.go
+++ b/trie/database_test.go
@@ -27,13 +27,10 @@
package trie
import (
- "math/rand"
"testing"
- "time"
"github.com/ava-labs/coreth/ethdb/memorydb"
"github.com/ethereum/go-ethereum/common"
- "github.com/stretchr/testify/assert"
)
// Tests that the trie database returns a missing trie node error if attempting
@@ -44,64 +41,3 @@ func TestDatabaseMetarootFetch(t *testing.T) {
t.Fatalf("metaroot retrieval succeeded")
}
}
-
-// Tests that calling dereference does not interfere with a concurrent commit on
-// a trie with the same underlying trieDB.
-func TestDereferenceWhileCommit(t *testing.T) {
- var (
- numKeys = 10
- keyLen = 10
- db = NewDatabase(memorydb.New())
- )
-
- // set up a database with a small trie
- tr1 := NewEmpty(db)
- rand.Seed(1) // set random seed so we get deterministic key/values
- FillTrie(t, numKeys, keyLen, tr1)
- root, _, err := tr1.Commit(nil, false)
- assert.NoError(t, err)
- assert.NotZero(t, root)
- db.Reference(root, common.Hash{}, true)
-
- // call Dereference from onleafs to simulate
- // this occurring concurrently.
- // the second trie has one more leaf so it
- // does not share the same root as the first trie.
- firstLeaf := true
- tr2 := NewEmpty(db)
- rand.Seed(1) // set random seed so we get deterministic key/values
- FillTrie(t, numKeys+1, keyLen, tr2)
- done := make(chan struct{})
- onleaf := func([][]byte, []byte, []byte, common.Hash, []byte) error {
- if firstLeaf {
- go func() {
- db.Dereference(root)
- close(done)
- }()
- select {
- case <-done:
- t.Fatal("Dereference succeeded within leaf callback")
- case <-time.After(time.Second):
- }
- firstLeaf = false
- }
- return nil
- }
- root2, _, err := tr2.Commit(onleaf, false)
- assert.NoError(t, err)
- assert.NotEqual(t, root, root2)
- db.Reference(root2, common.Hash{}, true)
-
- // wait for the goroutine to exit.
- <-done
-
- // expected behavior is for root2 to
- // be present and the trie should iterate
- // without missing nodes.
- tr3, err := New(common.Hash{}, root2, db)
- assert.NoError(t, err)
- it := tr3.NodeIterator(nil)
- for it.Next(true) {
- }
- assert.NoError(t, it.Error())
-}
diff --git a/trie/hasher.go b/trie/hasher.go
index 5b6db8cd4e..69d4f6d6d1 100644
--- a/trie/hasher.go
+++ b/trie/hasher.go
@@ -201,7 +201,7 @@ func (h *hasher) hashData(data []byte) hashNode {
}
// proofHash is used to construct trie proofs, and returns the 'collapsed'
-// node (for later RLP encoding) aswell as the hashed node -- unless the
+// node (for later RLP encoding) as well as the hashed node -- unless the
// node is smaller than 32 bytes, in which case it will be returned as is.
// This method does not do anything on value- or hash-nodes.
func (h *hasher) proofHash(original node) (collapsed, hashed node) {
diff --git a/trie/iterator.go b/trie/iterator.go
index 98f38ef7d7..8742a7a595 100644
--- a/trie/iterator.go
+++ b/trie/iterator.go
@@ -385,8 +385,7 @@ func (it *nodeIterator) resolveHash(hash hashNode, path []byte) (node, error) {
}
}
}
- resolved, err := it.trie.resolveHash(hash, path)
- return resolved, err
+ return it.trie.resolveHash(hash, path)
}
func (it *nodeIterator) resolveBlob(hash hashNode, path []byte) ([]byte, error) {
diff --git a/trie/iterator_test.go b/trie/iterator_test.go
index ed0327385a..ac017cdfbb 100644
--- a/trie/iterator_test.go
+++ b/trie/iterator_test.go
@@ -41,7 +41,7 @@ import (
)
func TestEmptyIterator(t *testing.T) {
- trie := newEmpty()
+ trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
iter := trie.NodeIterator(nil)
seen := make(map[string]struct{})
@@ -54,7 +54,8 @@ func TestEmptyIterator(t *testing.T) {
}
func TestIterator(t *testing.T) {
- trie := newEmpty()
+ db := NewDatabase(rawdb.NewMemoryDatabase())
+ trie := NewEmpty(db)
vals := []struct{ k, v string }{
{"do", "verb"},
{"ether", "wookiedoo"},
@@ -69,8 +70,13 @@ func TestIterator(t *testing.T) {
all[val.k] = val.v
trie.Update([]byte(val.k), []byte(val.v))
}
- trie.Commit(nil, false)
+ root, nodes, err := trie.Commit(false)
+ if err != nil {
+ t.Fatalf("Failed to commit trie %v", err)
+ }
+ db.Update(NewWithNodeSet(nodes))
+ trie, _ = New(common.Hash{}, root, db)
found := make(map[string]string)
it := NewIterator(trie.NodeIterator(nil))
for it.Next() {
@@ -90,7 +96,7 @@ type kv struct {
}
func TestIteratorLargeData(t *testing.T) {
- trie := newEmpty()
+ trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
vals := make(map[string]*kv)
for i := byte(0); i < 255; i++ {
@@ -183,7 +189,7 @@ var testdata2 = []kvs{
}
func TestIteratorSeek(t *testing.T) {
- trie := newEmpty()
+ trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
for _, val := range testdata1 {
trie.Update([]byte(val.k), []byte(val.v))
}
@@ -224,17 +230,23 @@ func checkIteratorOrder(want []kvs, it *Iterator) error {
}
func TestDifferenceIterator(t *testing.T) {
- triea := newEmpty()
+ dba := NewDatabase(rawdb.NewMemoryDatabase())
+ triea := NewEmpty(dba)
for _, val := range testdata1 {
triea.Update([]byte(val.k), []byte(val.v))
}
- triea.Commit(nil, false)
+ rootA, nodesA, _ := triea.Commit(false)
+ dba.Update(NewWithNodeSet(nodesA))
+ triea, _ = New(common.Hash{}, rootA, dba)
- trieb := newEmpty()
+ dbb := NewDatabase(rawdb.NewMemoryDatabase())
+ trieb := NewEmpty(dbb)
for _, val := range testdata2 {
trieb.Update([]byte(val.k), []byte(val.v))
}
- trieb.Commit(nil, false)
+ rootB, nodesB, _ := trieb.Commit(false)
+ dbb.Update(NewWithNodeSet(nodesB))
+ trieb, _ = New(common.Hash{}, rootB, dbb)
found := make(map[string]string)
di, _ := NewDifferenceIterator(triea.NodeIterator(nil), trieb.NodeIterator(nil))
@@ -260,17 +272,23 @@ func TestDifferenceIterator(t *testing.T) {
}
func TestUnionIterator(t *testing.T) {
- triea := newEmpty()
+ dba := NewDatabase(rawdb.NewMemoryDatabase())
+ triea := NewEmpty(dba)
for _, val := range testdata1 {
triea.Update([]byte(val.k), []byte(val.v))
}
- triea.Commit(nil, false)
+ rootA, nodesA, _ := triea.Commit(false)
+ dba.Update(NewWithNodeSet(nodesA))
+ triea, _ = New(common.Hash{}, rootA, dba)
- trieb := newEmpty()
+ dbb := NewDatabase(rawdb.NewMemoryDatabase())
+ trieb := NewEmpty(dbb)
for _, val := range testdata2 {
trieb.Update([]byte(val.k), []byte(val.v))
}
- trieb.Commit(nil, false)
+ rootB, nodesB, _ := trieb.Commit(false)
+ dbb.Update(NewWithNodeSet(nodesB))
+ trieb, _ = New(common.Hash{}, rootB, dbb)
di, _ := NewUnionIterator([]NodeIterator{triea.NodeIterator(nil), trieb.NodeIterator(nil)})
it := NewIterator(di)
@@ -326,7 +344,8 @@ func testIteratorContinueAfterError(t *testing.T, memonly bool) {
for _, val := range testdata1 {
tr.Update([]byte(val.k), []byte(val.v))
}
- tr.Commit(nil, false)
+ _, nodes, _ := tr.Commit(false)
+ triedb.Update(NewWithNodeSet(nodes))
if !memonly {
triedb.Commit(tr.Hash(), true, nil)
}
@@ -417,7 +436,8 @@ func testIteratorContinueAfterSeekError(t *testing.T, memonly bool) {
for _, val := range testdata1 {
ctr.Update([]byte(val.k), []byte(val.v))
}
- root, _, _ := ctr.Commit(nil, false)
+ root, nodes, _ := ctr.Commit(false)
+ triedb.Update(NewWithNodeSet(nodes))
if !memonly {
triedb.Commit(root, true, nil)
}
@@ -516,11 +536,11 @@ func (l *loggingDb) Close() error {
}
// makeLargeTestTrie create a sample test trie
-func makeLargeTestTrie() (*Database, *SecureTrie, *loggingDb) {
+func makeLargeTestTrie() (*Database, *StateTrie, *loggingDb) {
// Create an empty trie
logDb := &loggingDb{0, memorydb.New()}
triedb := NewDatabase(logDb)
- trie, _ := NewSecure(common.Hash{}, common.Hash{}, triedb)
+ trie, _ := NewStateTrie(common.Hash{}, common.Hash{}, triedb)
// Fill it with some arbitrary data
for i := 0; i < 10000; i++ {
@@ -532,7 +552,8 @@ func makeLargeTestTrie() (*Database, *SecureTrie, *loggingDb) {
val = crypto.Keccak256(val)
trie.Update(key, val)
}
- trie.Commit(nil, false)
+ _, nodes, _ := trie.Commit(false)
+ triedb.Update(NewWithNodeSet(nodes))
// Return the generated trie
return triedb, trie, logDb
}
@@ -571,7 +592,8 @@ func TestIteratorNodeBlob(t *testing.T) {
all[val.k] = val.v
trie.Update([]byte(val.k), []byte(val.v))
}
- trie.Commit(nil, false)
+ _, nodes, _ := trie.Commit(false)
+ triedb.Update(NewWithNodeSet(nodes))
triedb.Cap(0)
found := make(map[common.Hash][]byte)
diff --git a/trie/node.go b/trie/node.go
index dc50acd890..7b8dd73e82 100644
--- a/trie/node.go
+++ b/trie/node.go
@@ -109,6 +109,7 @@ func (n valueNode) fstring(ind string) string {
return fmt.Sprintf("%x ", []byte(n))
}
+// mustDecodeNode is a wrapper of decodeNode and panic if any error is encountered.
func mustDecodeNode(hash, buf []byte) node {
n, err := decodeNode(hash, buf)
if err != nil {
@@ -117,8 +118,29 @@ func mustDecodeNode(hash, buf []byte) node {
return n
}
-// decodeNode parses the RLP encoding of a trie node.
+// mustDecodeNodeUnsafe is a wrapper of decodeNodeUnsafe and panic if any error is
+// encountered.
+func mustDecodeNodeUnsafe(hash, buf []byte) node {
+ n, err := decodeNodeUnsafe(hash, buf)
+ if err != nil {
+ panic(fmt.Sprintf("node %x: %v", hash, err))
+ }
+ return n
+}
+
+// decodeNode parses the RLP encoding of a trie node. It will deep-copy the passed
+// byte slice for decoding, so it's safe to modify the byte slice afterwards. The-
+// decode performance of this function is not optimal, but it is suitable for most
+// scenarios with low performance requirements and hard to determine whether the
+// byte slice be modified or not.
func decodeNode(hash, buf []byte) (node, error) {
+ return decodeNodeUnsafe(hash, common.CopyBytes(buf))
+}
+
+// decodeNodeUnsafe parses the RLP encoding of a trie node. The passed byte slice
+// will be directly referenced by node without bytes deep copy, so the input MUST
+// not be changed after.
+func decodeNodeUnsafe(hash, buf []byte) (node, error) {
if len(buf) == 0 {
return nil, io.ErrUnexpectedEOF
}
@@ -151,7 +173,7 @@ func decodeShort(hash, elems []byte) (node, error) {
if err != nil {
return nil, fmt.Errorf("invalid value node: %v", err)
}
- return &shortNode{key, append(valueNode{}, val...), flag}, nil
+ return &shortNode{key, valueNode(val), flag}, nil
}
r, _, err := decodeRef(rest)
if err != nil {
@@ -174,7 +196,7 @@ func decodeFull(hash, elems []byte) (*fullNode, error) {
return n, err
}
if len(val) > 0 {
- n.Children[16] = append(valueNode{}, val...)
+ n.Children[16] = valueNode(val)
}
return n, nil
}
@@ -200,7 +222,7 @@ func decodeRef(buf []byte) (node, []byte, error) {
// empty node
return nil, rest, nil
case kind == rlp.String && len(val) == 32:
- return append(hashNode{}, val...), rest, nil
+ return hashNode(val), rest, nil
default:
return nil, nil, fmt.Errorf("invalid RLP string size %d (want 0 or 32)", len(val))
}
diff --git a/trie/node_test.go b/trie/node_test.go
index f18aac4ca8..4671b798de 100644
--- a/trie/node_test.go
+++ b/trie/node_test.go
@@ -30,6 +30,7 @@ import (
"bytes"
"testing"
+ "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp"
)
@@ -102,3 +103,123 @@ func TestDecodeFullNode(t *testing.T) {
t.Fatalf("decode full node err: %v", err)
}
}
+
+// goos: darwin
+// goarch: arm64
+// pkg: github.com/ava-labs/coreth/trie
+// BenchmarkEncodeShortNode
+// BenchmarkEncodeShortNode-8 16878850 70.81 ns/op 48 B/op 1 allocs/op
+func BenchmarkEncodeShortNode(b *testing.B) {
+ node := &shortNode{
+ Key: []byte{0x1, 0x2},
+ Val: hashNode(randBytes(32)),
+ }
+ b.ResetTimer()
+ b.ReportAllocs()
+
+ for i := 0; i < b.N; i++ {
+ nodeToBytes(node)
+ }
+}
+
+// goos: darwin
+// goarch: arm64
+// pkg: github.com/ava-labs/coreth/trie
+// BenchmarkEncodeFullNode
+// BenchmarkEncodeFullNode-8 4323273 284.4 ns/op 576 B/op 1 allocs/op
+func BenchmarkEncodeFullNode(b *testing.B) {
+ node := &fullNode{}
+ for i := 0; i < 16; i++ {
+ node.Children[i] = hashNode(randBytes(32))
+ }
+ b.ResetTimer()
+ b.ReportAllocs()
+
+ for i := 0; i < b.N; i++ {
+ nodeToBytes(node)
+ }
+}
+
+// goos: darwin
+// goarch: arm64
+// pkg: github.com/ava-labs/coreth/trie
+// BenchmarkDecodeShortNode
+// BenchmarkDecodeShortNode-8 7925638 151.0 ns/op 157 B/op 4 allocs/op
+func BenchmarkDecodeShortNode(b *testing.B) {
+ node := &shortNode{
+ Key: []byte{0x1, 0x2},
+ Val: hashNode(randBytes(32)),
+ }
+ blob := nodeToBytes(node)
+ hash := crypto.Keccak256(blob)
+
+ b.ResetTimer()
+ b.ReportAllocs()
+
+ for i := 0; i < b.N; i++ {
+ mustDecodeNode(hash, blob)
+ }
+}
+
+// goos: darwin
+// goarch: arm64
+// pkg: github.com/ava-labs/coreth/trie
+// BenchmarkDecodeShortNodeUnsafe
+// BenchmarkDecodeShortNodeUnsafe-8 9027476 128.6 ns/op 109 B/op 3 allocs/op
+func BenchmarkDecodeShortNodeUnsafe(b *testing.B) {
+ node := &shortNode{
+ Key: []byte{0x1, 0x2},
+ Val: hashNode(randBytes(32)),
+ }
+ blob := nodeToBytes(node)
+ hash := crypto.Keccak256(blob)
+
+ b.ResetTimer()
+ b.ReportAllocs()
+
+ for i := 0; i < b.N; i++ {
+ mustDecodeNodeUnsafe(hash, blob)
+ }
+}
+
+// goos: darwin
+// goarch: arm64
+// pkg: github.com/ava-labs/coreth/trie
+// BenchmarkDecodeFullNode
+// BenchmarkDecodeFullNode-8 1597462 761.9 ns/op 1280 B/op 18 allocs/op
+func BenchmarkDecodeFullNode(b *testing.B) {
+ node := &fullNode{}
+ for i := 0; i < 16; i++ {
+ node.Children[i] = hashNode(randBytes(32))
+ }
+ blob := nodeToBytes(node)
+ hash := crypto.Keccak256(blob)
+
+ b.ResetTimer()
+ b.ReportAllocs()
+
+ for i := 0; i < b.N; i++ {
+ mustDecodeNode(hash, blob)
+ }
+}
+
+// goos: darwin
+// goarch: arm64
+// pkg: github.com/ava-labs/coreth/trie
+// BenchmarkDecodeFullNodeUnsafe
+// BenchmarkDecodeFullNodeUnsafe-8 1789070 687.1 ns/op 704 B/op 17 allocs/op
+func BenchmarkDecodeFullNodeUnsafe(b *testing.B) {
+ node := &fullNode{}
+ for i := 0; i < 16; i++ {
+ node.Children[i] = hashNode(randBytes(32))
+ }
+ blob := nodeToBytes(node)
+ hash := crypto.Keccak256(blob)
+
+ b.ResetTimer()
+ b.ReportAllocs()
+
+ for i := 0; i < b.N; i++ {
+ mustDecodeNodeUnsafe(hash, blob)
+ }
+}
diff --git a/trie/nodeset.go b/trie/nodeset.go
new file mode 100644
index 0000000000..421ad13435
--- /dev/null
+++ b/trie/nodeset.go
@@ -0,0 +1,104 @@
+// (c) 2022, Ava Labs, Inc.
+//
+// This file is a derived work, based on the go-ethereum library whose original
+// notices appear below.
+//
+// It is distributed under a license compatible with the licensing terms of the
+// original code from which it is derived.
+//
+// Much love to the original authors for their work.
+// **********
+// Copyright 2022 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package trie
+
+import (
+ "fmt"
+
+ "github.com/ethereum/go-ethereum/common"
+)
+
+// memoryNode is all the information we know about a single cached trie node
+// in the memory.
+type memoryNode struct {
+ hash common.Hash // Node hash, computed by hashing rlp value
+ size uint16 // Byte size of the useful cached data
+ node node // Cached collapsed trie node, or raw rlp data
+}
+
+// NodeSet contains all dirty nodes collected during the commit operation.
+// Each node is keyed by path. It's not thread-safe to use.
+type NodeSet struct {
+ owner common.Hash // the identifier of the trie
+ paths []string // the path of dirty nodes, sort by insertion order
+ nodes map[string]*memoryNode // the map of dirty nodes, keyed by node path
+ leaves []*leaf // the list of dirty leaves
+}
+
+// NewNodeSet initializes an empty node set to be used for tracking dirty nodes
+// from a specific account or storage trie. The owner is zero for the account
+// trie and the owning account address hash for storage tries.
+func NewNodeSet(owner common.Hash) *NodeSet {
+ return &NodeSet{
+ owner: owner,
+ nodes: make(map[string]*memoryNode),
+ }
+}
+
+// add caches node with provided path and node object.
+func (set *NodeSet) add(path string, node *memoryNode) {
+ set.paths = append(set.paths, path)
+ set.nodes[path] = node
+}
+
+// addLeaf caches the provided leaf node.
+func (set *NodeSet) addLeaf(node *leaf) {
+ set.leaves = append(set.leaves, node)
+}
+
+// Len returns the number of dirty nodes contained in the set.
+func (set *NodeSet) Len() int {
+ return len(set.nodes)
+}
+
+// MergedNodeSet represents a merged dirty node set for a group of tries.
+type MergedNodeSet struct {
+ sets map[common.Hash]*NodeSet
+}
+
+// NewMergedNodeSet initializes an empty merged set.
+func NewMergedNodeSet() *MergedNodeSet {
+ return &MergedNodeSet{sets: make(map[common.Hash]*NodeSet)}
+}
+
+// NewWithNodeSet constructs a merged nodeset with the provided single set.
+func NewWithNodeSet(set *NodeSet) *MergedNodeSet {
+ merged := NewMergedNodeSet()
+ merged.Merge(set)
+ return merged
+}
+
+// Merge merges the provided dirty nodes of a trie into the set. The assumption
+// is held that no duplicated set belonging to the same trie will be merged twice.
+func (set *MergedNodeSet) Merge(other *NodeSet) error {
+ _, present := set.sets[other.owner]
+ if present {
+ return fmt.Errorf("duplicate trie for owner %#x", other.owner)
+ }
+ set.sets[other.owner] = other
+ return nil
+}
diff --git a/trie/preimages.go b/trie/preimages.go
new file mode 100644
index 0000000000..4a11059e3d
--- /dev/null
+++ b/trie/preimages.go
@@ -0,0 +1,107 @@
+// (c) 2022, Ava Labs, Inc.
+//
+// This file is a derived work, based on the go-ethereum library whose original
+// notices appear below.
+//
+// It is distributed under a license compatible with the licensing terms of the
+// original code from which it is derived.
+//
+// Much love to the original authors for their work.
+// **********
+// Copyright 2022 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package trie
+
+import (
+ "sync"
+
+ "github.com/ava-labs/coreth/core/rawdb"
+ "github.com/ava-labs/coreth/ethdb"
+ "github.com/ethereum/go-ethereum/common"
+)
+
+const defaultPreimagesLimit = 4 * 1024 * 1024 // 4 MB
+
+// preimageStore is the store for caching preimages of node key.
+type preimageStore struct {
+ lock sync.RWMutex
+ disk ethdb.KeyValueStore
+ preimages map[common.Hash][]byte // Preimages of nodes from the secure trie
+ preimagesSize common.StorageSize // Storage size of the preimages cache
+}
+
+// newPreimageStore initializes the store for caching preimages.
+func newPreimageStore(disk ethdb.KeyValueStore) *preimageStore {
+ return &preimageStore{
+ disk: disk,
+ preimages: make(map[common.Hash][]byte),
+ }
+}
+
+// insertPreimage writes a new trie node pre-image to the memory database if it's
+// yet unknown. The method will NOT make a copy of the slice, only use if the
+// preimage will NOT be changed later on.
+func (store *preimageStore) insertPreimage(preimages map[common.Hash][]byte) {
+ store.lock.Lock()
+ defer store.lock.Unlock()
+
+ for hash, preimage := range preimages {
+ if _, ok := store.preimages[hash]; ok {
+ continue
+ }
+ store.preimages[hash] = preimage
+ store.preimagesSize += common.StorageSize(common.HashLength + len(preimage))
+ }
+}
+
+// preimage retrieves a cached trie node pre-image from memory. If it cannot be
+// found cached, the method queries the persistent database for the content.
+func (store *preimageStore) preimage(hash common.Hash) []byte {
+ store.lock.RLock()
+ preimage := store.preimages[hash]
+ store.lock.RUnlock()
+
+ if preimage != nil {
+ return preimage
+ }
+ return rawdb.ReadPreimage(store.disk, hash)
+}
+
+// commit flushes the cached preimages into the disk.
+func (store *preimageStore) commit(force bool) error {
+ store.lock.Lock()
+ defer store.lock.Unlock()
+
+ if store.preimagesSize <= defaultPreimagesLimit && !force {
+ return nil
+ }
+ batch := store.disk.NewBatch()
+ rawdb.WritePreimages(batch, store.preimages)
+ if err := batch.Write(); err != nil {
+ return err
+ }
+ store.preimages, store.preimagesSize = make(map[common.Hash][]byte), 0
+ return nil
+}
+
+// size returns the current storage size of accumulated preimages.
+func (store *preimageStore) size() common.StorageSize {
+ store.lock.RLock()
+ defer store.lock.RUnlock()
+
+ return store.preimagesSize
+}
diff --git a/trie/proof.go b/trie/proof.go
index e5d81bff99..329b9afe5c 100644
--- a/trie/proof.go
+++ b/trie/proof.go
@@ -31,6 +31,7 @@ import (
"errors"
"fmt"
+ "github.com/ava-labs/coreth/core/rawdb"
"github.com/ava-labs/coreth/ethdb"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
@@ -45,9 +46,12 @@ import (
// with the node that proves the absence of the key.
func (t *Trie) Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) error {
// Collect all nodes on the path to key.
+ var (
+ prefix []byte
+ nodes []node
+ tn = t.root
+ )
key = keybytesToHex(key)
- var nodes []node
- tn := t.root
for len(key) > 0 && tn != nil {
switch n := tn.(type) {
case *shortNode:
@@ -56,16 +60,18 @@ func (t *Trie) Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) e
tn = nil
} else {
tn = n.Val
+ prefix = append(prefix, n.Key...)
key = key[len(n.Key):]
}
nodes = append(nodes, n)
case *fullNode:
tn = n.Children[key[0]]
+ prefix = append(prefix, key[0])
key = key[1:]
nodes = append(nodes, n)
case hashNode:
var err error
- tn, err = t.resolveHash(n, nil)
+ tn, err = t.resolveHash(n, prefix)
if err != nil {
log.Error(fmt.Sprintf("Unhandled trie error: %v", err))
return err
@@ -104,7 +110,7 @@ func (t *Trie) Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) e
// If the trie does not contain a value for key, the returned proof contains all
// nodes of the longest existing prefix of the key (at least the root node), ending
// with the node that proves the absence of the key.
-func (t *SecureTrie) Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) error {
+func (t *StateTrie) Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) error {
return t.trie.Prove(key, fromLevel, proofDb)
}
@@ -563,7 +569,7 @@ func VerifyRangeProof(rootHash common.Hash, firstKey []byte, lastKey []byte, key
}
// Rebuild the trie with the leaf stream, the shape of trie
// should be same with the original one.
- tr := newWithRootNode(root)
+ tr := &Trie{root: root, db: NewDatabase(rawdb.NewMemoryDatabase())}
if empty {
tr.root = nil
}
diff --git a/trie/proof_test.go b/trie/proof_test.go
index d56667f34b..4875dfeb41 100644
--- a/trie/proof_test.go
+++ b/trie/proof_test.go
@@ -215,7 +215,7 @@ func TestRangeProofWithNonExistentProof(t *testing.T) {
proof := memorydb.New()
// Short circuit if the decreased key is same with the previous key
- first := decreseKey(common.CopyBytes(entries[start].k))
+ first := decreaseKey(common.CopyBytes(entries[start].k))
if start != 0 && bytes.Equal(first, entries[start-1].k) {
continue
}
@@ -224,7 +224,7 @@ func TestRangeProofWithNonExistentProof(t *testing.T) {
continue
}
// Short circuit if the increased key is same with the next key
- last := increseKey(common.CopyBytes(entries[end-1].k))
+ last := increaseKey(common.CopyBytes(entries[end-1].k))
if end != len(entries) && bytes.Equal(last, entries[end].k) {
continue
}
@@ -284,7 +284,7 @@ func TestRangeProofWithInvalidNonExistentProof(t *testing.T) {
// Case 1
start, end := 100, 200
- first := decreseKey(common.CopyBytes(entries[start].k))
+ first := decreaseKey(common.CopyBytes(entries[start].k))
proof := memorydb.New()
if err := trie.Prove(first, 0, proof); err != nil {
@@ -307,7 +307,7 @@ func TestRangeProofWithInvalidNonExistentProof(t *testing.T) {
// Case 2
start, end = 100, 200
- last := increseKey(common.CopyBytes(entries[end-1].k))
+ last := increaseKey(common.CopyBytes(entries[end-1].k))
proof = memorydb.New()
if err := trie.Prove(entries[start].k, 0, proof); err != nil {
t.Fatalf("Failed to prove the first node %v", err)
@@ -353,7 +353,7 @@ func TestOneElementRangeProof(t *testing.T) {
// One element with left non-existent edge proof
start = 1000
- first := decreseKey(common.CopyBytes(entries[start].k))
+ first := decreaseKey(common.CopyBytes(entries[start].k))
proof = memorydb.New()
if err := trie.Prove(first, 0, proof); err != nil {
t.Fatalf("Failed to prove the first node %v", err)
@@ -368,7 +368,7 @@ func TestOneElementRangeProof(t *testing.T) {
// One element with right non-existent edge proof
start = 1000
- last := increseKey(common.CopyBytes(entries[start].k))
+ last := increaseKey(common.CopyBytes(entries[start].k))
proof = memorydb.New()
if err := trie.Prove(entries[start].k, 0, proof); err != nil {
t.Fatalf("Failed to prove the first node %v", err)
@@ -383,7 +383,7 @@ func TestOneElementRangeProof(t *testing.T) {
// One element with two non-existent edge proofs
start = 1000
- first, last = decreseKey(common.CopyBytes(entries[start].k)), increseKey(common.CopyBytes(entries[start].k))
+ first, last = decreaseKey(common.CopyBytes(entries[start].k)), increaseKey(common.CopyBytes(entries[start].k))
proof = memorydb.New()
if err := trie.Prove(first, 0, proof); err != nil {
t.Fatalf("Failed to prove the first node %v", err)
@@ -651,9 +651,9 @@ func TestSameSideProofs(t *testing.T) {
sort.Sort(entries)
pos := 1000
- first := decreseKey(common.CopyBytes(entries[pos].k))
- first = decreseKey(first)
- last := decreseKey(common.CopyBytes(entries[pos].k))
+ first := decreaseKey(common.CopyBytes(entries[pos].k))
+ first = decreaseKey(first)
+ last := decreaseKey(common.CopyBytes(entries[pos].k))
proof := memorydb.New()
if err := trie.Prove(first, 0, proof); err != nil {
@@ -667,9 +667,9 @@ func TestSameSideProofs(t *testing.T) {
t.Fatalf("Expected error, got nil")
}
- first = increseKey(common.CopyBytes(entries[pos].k))
- last = increseKey(common.CopyBytes(entries[pos].k))
- last = increseKey(last)
+ first = increaseKey(common.CopyBytes(entries[pos].k))
+ last = increaseKey(common.CopyBytes(entries[pos].k))
+ last = increaseKey(last)
proof = memorydb.New()
if err := trie.Prove(first, 0, proof); err != nil {
@@ -775,7 +775,7 @@ func TestEmptyRangeProof(t *testing.T) {
}
for _, c := range cases {
proof := memorydb.New()
- first := increseKey(common.CopyBytes(entries[c.pos].k))
+ first := increaseKey(common.CopyBytes(entries[c.pos].k))
if err := trie.Prove(first, 0, proof); err != nil {
t.Fatalf("Failed to prove the first node %v", err)
}
@@ -914,7 +914,7 @@ func mutateByte(b []byte) {
}
}
-func increseKey(key []byte) []byte {
+func increaseKey(key []byte) []byte {
for i := len(key) - 1; i >= 0; i-- {
key[i]++
if key[i] != 0x0 {
@@ -924,7 +924,7 @@ func increseKey(key []byte) []byte {
return key
}
-func decreseKey(key []byte) []byte {
+func decreaseKey(key []byte) []byte {
for i := len(key) - 1; i >= 0; i-- {
key[i]--
if key[i] != 0xff {
diff --git a/trie/secure_trie.go b/trie/secure_trie.go
index 20c1afa413..68bcf22b46 100644
--- a/trie/secure_trie.go
+++ b/trie/secure_trie.go
@@ -35,24 +35,35 @@ import (
"github.com/ethereum/go-ethereum/rlp"
)
-// SecureTrie wraps a trie with key hashing. In a secure trie, all
+// SecureTrie is the old name of StateTrie.
+// Deprecated: use StateTrie.
+type SecureTrie = StateTrie
+
+// NewSecure creates a new StateTrie.
+// Deprecated: use NewStateTrie.
+func NewSecure(owner common.Hash, root common.Hash, db *Database) (*SecureTrie, error) {
+ return NewStateTrie(owner, root, db)
+}
+
+// StateTrie wraps a trie with key hashing. In a secure trie, all
// access operations hash the key using keccak256. This prevents
// calling code from creating long chains of nodes that
// increase the access time.
//
-// Contrary to a regular trie, a SecureTrie can only be created with
+// Contrary to a regular trie, a StateTrie can only be created with
// New and must have an attached database. The database also stores
// the preimage of each key.
//
-// SecureTrie is not safe for concurrent use.
-type SecureTrie struct {
+// StateTrie is not safe for concurrent use.
+type StateTrie struct {
trie Trie
+ preimages *preimageStore
hashKeyBuf [common.HashLength]byte
secKeyCache map[string][]byte
- secKeyCacheOwner *SecureTrie // Pointer to self, replace the key cache on mismatch
+ secKeyCacheOwner *StateTrie // Pointer to self, replace the key cache on mismatch
}
-// NewSecure creates a trie with an existing root node from a backing database
+// NewStateTrie creates a trie with an existing root node from a backing database
// and optional intermediate in-memory node pool.
//
// If root is the zero hash or the sha3 hash of an empty string, the
@@ -63,7 +74,7 @@ type SecureTrie struct {
// Loaded nodes are kept around until their 'cache generation' expires.
// A new cache generation is created by each call to Commit.
// cachelimit sets the number of past cache generations to keep.
-func NewSecure(owner common.Hash, root common.Hash, db *Database) (*SecureTrie, error) {
+func NewStateTrie(owner common.Hash, root common.Hash, db *Database) (*StateTrie, error) {
if db == nil {
panic("trie.NewSecure called without a database")
}
@@ -71,12 +82,12 @@ func NewSecure(owner common.Hash, root common.Hash, db *Database) (*SecureTrie,
if err != nil {
return nil, err
}
- return &SecureTrie{trie: *trie}, nil
+ return &StateTrie{trie: *trie, preimages: db.preimages}, nil
}
// Get returns the value for key stored in the trie.
// The value bytes must not be modified by the caller.
-func (t *SecureTrie) Get(key []byte) []byte {
+func (t *StateTrie) Get(key []byte) []byte {
res, err := t.TryGet(key)
if err != nil {
log.Error(fmt.Sprintf("Unhandled trie error: %v", err))
@@ -87,19 +98,50 @@ func (t *SecureTrie) Get(key []byte) []byte {
// TryGet returns the value for key stored in the trie.
// The value bytes must not be modified by the caller.
// If a node was not found in the database, a MissingNodeError is returned.
-func (t *SecureTrie) TryGet(key []byte) ([]byte, error) {
+func (t *StateTrie) TryGet(key []byte) ([]byte, error) {
return t.trie.TryGet(t.hashKey(key))
}
+func (t *StateTrie) TryGetAccount(key []byte) (*types.StateAccount, error) {
+ var ret types.StateAccount
+ res, err := t.trie.TryGet(t.hashKey(key))
+ if err != nil {
+ log.Error(fmt.Sprintf("Unhandled trie error: %v", err))
+ return &ret, err
+ }
+ if res == nil {
+ return nil, nil
+ }
+ err = rlp.DecodeBytes(res, &ret)
+ return &ret, err
+}
+
+// TryGetAccountWithPreHashedKey does the same thing as TryGetAccount, however
+// it expects a key that is already hashed. This constitutes an abstraction leak,
+// since the client code needs to know the key format.
+func (t *StateTrie) TryGetAccountWithPreHashedKey(key []byte) (*types.StateAccount, error) {
+ var ret types.StateAccount
+ res, err := t.trie.TryGet(key)
+ if err != nil {
+ log.Error(fmt.Sprintf("Unhandled trie error: %v", err))
+ return &ret, err
+ }
+ if res == nil {
+ return nil, nil
+ }
+ err = rlp.DecodeBytes(res, &ret)
+ return &ret, err
+}
+
// TryGetNode attempts to retrieve a trie node by compact-encoded path. It is not
// possible to use keybyte-encoding as the path might contain odd nibbles.
-func (t *SecureTrie) TryGetNode(path []byte) ([]byte, int, error) {
+func (t *StateTrie) TryGetNode(path []byte) ([]byte, int, error) {
return t.trie.TryGetNode(path)
}
// TryUpdateAccount account will abstract the write of an account to the
// secure trie.
-func (t *SecureTrie) TryUpdateAccount(key []byte, acc *types.StateAccount) error {
+func (t *StateTrie) TryUpdateAccount(key []byte, acc *types.StateAccount) error {
hk := t.hashKey(key)
data, err := rlp.EncodeToBytes(acc)
if err != nil {
@@ -118,7 +160,7 @@ func (t *SecureTrie) TryUpdateAccount(key []byte, acc *types.StateAccount) error
//
// The value bytes must not be modified by the caller while they are
// stored in the trie.
-func (t *SecureTrie) Update(key, value []byte) {
+func (t *StateTrie) Update(key, value []byte) {
if err := t.TryUpdate(key, value); err != nil {
log.Error(fmt.Sprintf("Unhandled trie error: %v", err))
}
@@ -132,7 +174,7 @@ func (t *SecureTrie) Update(key, value []byte) {
// stored in the trie.
//
// If a node was not found in the database, a MissingNodeError is returned.
-func (t *SecureTrie) TryUpdate(key, value []byte) error {
+func (t *StateTrie) TryUpdate(key, value []byte) error {
hk := t.hashKey(key)
err := t.trie.TryUpdate(hk, value)
if err != nil {
@@ -143,7 +185,7 @@ func (t *SecureTrie) TryUpdate(key, value []byte) error {
}
// Delete removes any existing value for key from the trie.
-func (t *SecureTrie) Delete(key []byte) {
+func (t *StateTrie) Delete(key []byte) {
if err := t.TryDelete(key); err != nil {
log.Error(fmt.Sprintf("Unhandled trie error: %v", err))
}
@@ -151,7 +193,14 @@ func (t *SecureTrie) Delete(key []byte) {
// TryDelete removes any existing value for key from the trie.
// If a node was not found in the database, a MissingNodeError is returned.
-func (t *SecureTrie) TryDelete(key []byte) error {
+func (t *StateTrie) TryDelete(key []byte) error {
+ hk := t.hashKey(key)
+ delete(t.getSecKeyCache(), string(hk))
+ return t.trie.TryDelete(hk)
+}
+
+// TryDeleteAccount abstracts an account deletion from the trie.
+func (t *StateTrie) TryDeleteAccount(key []byte) error {
hk := t.hashKey(key)
delete(t.getSecKeyCache(), string(hk))
return t.trie.TryDelete(hk)
@@ -159,52 +208,64 @@ func (t *SecureTrie) TryDelete(key []byte) error {
// GetKey returns the sha3 preimage of a hashed key that was
// previously used to store a value.
-func (t *SecureTrie) GetKey(shaKey []byte) []byte {
+func (t *StateTrie) GetKey(shaKey []byte) []byte {
if key, ok := t.getSecKeyCache()[string(shaKey)]; ok {
return key
}
- return t.trie.db.Preimage(common.BytesToHash(shaKey))
+ if t.preimages == nil {
+ return nil
+ }
+ return t.preimages.preimage(common.BytesToHash(shaKey))
}
-// Commit writes all nodes and the secure hash pre-images to the trie's database.
-// Nodes are stored with their sha3 hash as the key.
-//
-// Committing flushes nodes from memory. Subsequent Get calls will load nodes
-// from the database.
-func (t *SecureTrie) Commit(onleaf LeafCallback, referenceRoot bool) (common.Hash, int, error) {
+// Commit collects all dirty nodes in the trie and replace them with the
+// corresponding node hash. All collected nodes(including dirty leaves if
+// collectLeaf is true) will be encapsulated into a nodeset for return.
+// The returned nodeset can be nil if the trie is clean(nothing to commit).
+// All cached preimages will be also flushed if preimages recording is enabled.
+// Once the trie is committed, it's not usable anymore. A new trie must
+// be created with new root and updated trie database for following usage
+func (t *StateTrie) Commit(collectLeaf bool) (common.Hash, *NodeSet, error) {
// Write all the pre-images to the actual disk database
if len(t.getSecKeyCache()) > 0 {
- t.trie.db.InsertPreimages(t.secKeyCache) // if preimages are disabled, this returns immediately
+ if t.preimages != nil {
+ preimages := make(map[common.Hash][]byte)
+ for hk, key := range t.secKeyCache {
+ preimages[common.BytesToHash([]byte(hk))] = key
+ }
+ t.preimages.insertPreimage(preimages)
+ }
t.secKeyCache = make(map[string][]byte)
}
// Commit the trie to its intermediate node database
- return t.trie.Commit(onleaf, referenceRoot)
+ return t.trie.Commit(collectLeaf)
}
// Hash returns the root hash of SecureTrie. It does not write to the
// database and can be used even if the trie doesn't have one.
-func (t *SecureTrie) Hash() common.Hash {
+func (t *StateTrie) Hash() common.Hash {
return t.trie.Hash()
}
// Copy returns a copy of SecureTrie.
-func (t *SecureTrie) Copy() *SecureTrie {
- return &SecureTrie{
+func (t *StateTrie) Copy() *StateTrie {
+ return &StateTrie{
trie: *t.trie.Copy(),
+ preimages: t.preimages,
secKeyCache: t.secKeyCache,
}
}
// NodeIterator returns an iterator that returns nodes of the underlying trie. Iteration
// starts at the key after the given start key.
-func (t *SecureTrie) NodeIterator(start []byte) NodeIterator {
+func (t *StateTrie) NodeIterator(start []byte) NodeIterator {
return t.trie.NodeIterator(start)
}
// hashKey returns the hash of key as an ephemeral buffer.
// The caller must not hold onto the return value because it will become
// invalid on the next call to hashKey or secKey.
-func (t *SecureTrie) hashKey(key []byte) []byte {
+func (t *StateTrie) hashKey(key []byte) []byte {
h := newHasher(false)
h.sha.Reset()
h.sha.Write(key)
@@ -216,7 +277,7 @@ func (t *SecureTrie) hashKey(key []byte) []byte {
// getSecKeyCache returns the current secure key cache, creating a new one if
// ownership changed (i.e. the current secure trie is a copy of another owning
// the actual cache).
-func (t *SecureTrie) getSecKeyCache() map[string][]byte {
+func (t *StateTrie) getSecKeyCache() map[string][]byte {
if t != t.secKeyCacheOwner {
t.secKeyCacheOwner = t
t.secKeyCache = make(map[string][]byte)
diff --git a/trie/secure_trie_test.go b/trie/secure_trie_test.go
index 39aaf8541d..a08219279c 100644
--- a/trie/secure_trie_test.go
+++ b/trie/secure_trie_test.go
@@ -28,6 +28,7 @@ package trie
import (
"bytes"
+ "fmt"
"runtime"
"sync"
"testing"
@@ -37,16 +38,16 @@ import (
"github.com/ethereum/go-ethereum/crypto"
)
-func newEmptySecure() *SecureTrie {
- trie, _ := NewSecure(common.Hash{}, common.Hash{}, NewDatabase(memorydb.New()))
+func newEmptySecure() *StateTrie {
+ trie, _ := NewStateTrie(common.Hash{}, common.Hash{}, NewDatabase(memorydb.New()))
return trie
}
-// makeTestSecureTrie creates a large enough secure trie for testing.
-func makeTestSecureTrie() (*Database, *SecureTrie, map[string][]byte) {
+// makeTestStateTrie creates a large enough secure trie for testing.
+func makeTestStateTrie() (*Database, *StateTrie, map[string][]byte) {
// Create an empty trie
triedb := NewDatabase(memorydb.New())
- trie, _ := NewSecure(common.Hash{}, common.Hash{}, triedb)
+ trie, _ := NewStateTrie(common.Hash{}, common.Hash{}, triedb)
// Fill it with some arbitrary data
content := make(map[string][]byte)
@@ -67,9 +68,15 @@ func makeTestSecureTrie() (*Database, *SecureTrie, map[string][]byte) {
trie.Update(key, val)
}
}
- trie.Commit(nil, false)
-
- // Return the generated trie
+ root, nodes, err := trie.Commit(false)
+ if err != nil {
+ panic(fmt.Errorf("failed to commit trie %v", err))
+ }
+ if err := triedb.Update(NewWithNodeSet(nodes)); err != nil {
+ panic(fmt.Errorf("failed to commit db %v", err))
+ }
+ // Re-create the trie based on the new state
+ trie, _ = NewSecure(common.Hash{}, root, triedb)
return triedb, trie, content
}
@@ -115,12 +122,12 @@ func TestSecureGetKey(t *testing.T) {
}
}
-func TestSecureTrieConcurrency(t *testing.T) {
+func TestStateTrieConcurrency(t *testing.T) {
// Create an initial trie and copy if for concurrent access
- _, trie, _ := makeTestSecureTrie()
+ _, trie, _ := makeTestStateTrie()
threads := runtime.NumCPU()
- tries := make([]*SecureTrie, threads)
+ tries := make([]*StateTrie, threads)
for i := 0; i < threads; i++ {
tries[i] = trie.Copy()
}
@@ -145,7 +152,7 @@ func TestSecureTrieConcurrency(t *testing.T) {
tries[index].Update(key, val)
}
}
- tries[index].Commit(nil, false)
+ tries[index].Commit(false)
}(i)
}
// Wait for all threads to finish
diff --git a/trie/sync_test.go b/trie/sync_test.go
index 64eb3578cd..c04a928c0f 100644
--- a/trie/sync_test.go
+++ b/trie/sync_test.go
@@ -27,15 +27,17 @@
package trie
import (
+ "fmt"
+
"github.com/ava-labs/coreth/ethdb/memorydb"
"github.com/ethereum/go-ethereum/common"
)
// makeTestTrie create a sample test trie to test node-wise reconstruction.
-func makeTestTrie() (*Database, *SecureTrie, map[string][]byte) {
+func makeTestTrie() (*Database, *StateTrie, map[string][]byte) {
// Create an empty trie
triedb := NewDatabase(memorydb.New())
- trie, _ := NewSecure(common.Hash{}, common.Hash{}, triedb)
+ trie, _ := NewStateTrie(common.Hash{}, common.Hash{}, triedb)
// Fill it with some arbitrary data
content := make(map[string][]byte)
@@ -56,8 +58,14 @@ func makeTestTrie() (*Database, *SecureTrie, map[string][]byte) {
trie.Update(key, val)
}
}
- trie.Commit(nil, false)
-
- // Return the generated trie
+ root, nodes, err := trie.Commit(false)
+ if err != nil {
+ panic(fmt.Errorf("failed to commit trie %v", err))
+ }
+ if err := triedb.Update(NewWithNodeSet(nodes)); err != nil {
+ panic(fmt.Errorf("failed to commit db %v", err))
+ }
+ // Re-create the trie based on the new state
+ trie, _ = NewSecure(common.Hash{}, root, triedb)
return triedb, trie, content
}
diff --git a/trie/test_trie.go b/trie/test_trie.go
index 4106b968ef..4013d92a23 100644
--- a/trie/test_trie.go
+++ b/trie/test_trie.go
@@ -32,7 +32,9 @@ func GenerateTrie(t *testing.T, trieDB *Database, numKeys int, keySize int) (com
keys, values := FillTrie(t, numKeys, keySize, testTrie)
// Commit the root to [trieDB]
- root, _, err := testTrie.Commit(nil, false)
+ root, nodes, err := testTrie.Commit(false)
+ assert.NoError(t, err)
+ err = trieDB.Update(NewWithNodeSet(nodes))
assert.NoError(t, err)
err = trieDB.Commit(root, false, nil)
assert.NoError(t, err)
@@ -143,7 +145,7 @@ func FillAccounts(
accounts = make(map[*keystore.Key]*types.StateAccount, numAccounts)
)
- tr, err := NewSecure(common.Hash{}, root, trieDB)
+ tr, err := NewStateTrie(common.Hash{}, root, trieDB)
if err != nil {
t.Fatalf("error opening trie: %v", err)
}
@@ -174,10 +176,13 @@ func FillAccounts(
accounts[key] = &acc
}
- newRoot, _, err := tr.Commit(nil, false)
+ newRoot, nodes, err := tr.Commit(false)
if err != nil {
t.Fatalf("error committing trie: %v", err)
}
+ if err := trieDB.Update(NewWithNodeSet(nodes)); err != nil {
+ t.Fatalf("error updating trieDB: %v", err)
+ }
if err := trieDB.Commit(newRoot, false, nil); err != nil {
t.Fatalf("error committing trieDB: %v", err)
}
diff --git a/trie/trie.go b/trie/trie.go
index dfbe0c1959..c6433bd692 100644
--- a/trie/trie.go
+++ b/trie/trie.go
@@ -32,11 +32,8 @@ import (
"errors"
"fmt"
- "github.com/ava-labs/coreth/core/rawdb"
- "github.com/ava-labs/coreth/core/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/rlp"
)
var (
@@ -60,23 +57,28 @@ var (
// for extracting the raw states(leaf nodes) with corresponding paths.
type LeafCallback func(keys [][]byte, path []byte, leaf []byte, parent common.Hash, parentPath []byte) error
-// Trie is a Merkle Patricia Trie.
-// The zero value is an empty trie with no database.
-// Use New to create a trie that sits on top of a database.
+// Trie is a Merkle Patricia Trie. Use New to create a trie that sits on
+// top of a database. Whenever trie performs a commit operation, the generated
+// nodes will be gathered and returned in a set. Once the trie is committed,
+// it's not usable anymore. Callers have to re-create the trie with new root
+// based on the updated trie database.
//
// Trie is not safe for concurrent use.
type Trie struct {
- db *Database
root node
owner common.Hash
// Keep track of the number leaves which have been inserted since the last
// hashing operation. This number will not directly map to the number of
- // actually unhashed nodes
+ // actually unhashed nodes.
unhashed int
- // tracer is the state diff tracer can be used to track newly added/deleted
- // trie node. It will be reset after each commit operation.
+ // db is the handler trie can retrieve nodes from. It's
+ // only for reading purpose and not available for writing.
+ db *Database
+
+ // tracer is the tool to track the trie changes.
+ // It will be reset after each commit operation.
tracer *tracer
}
@@ -88,10 +90,10 @@ func (t *Trie) newFlag() nodeFlag {
// Copy returns a copy of Trie.
func (t *Trie) Copy() *Trie {
return &Trie{
- db: t.db,
root: t.root,
owner: t.owner,
unhashed: t.unhashed,
+ db: t.db,
tracer: t.tracer.copy(),
}
}
@@ -104,33 +106,9 @@ func (t *Trie) Copy() *Trie {
// New will panic if db is nil and returns a MissingNodeError if root does
// not exist in the database. Accessing the trie loads nodes from db on demand.
func New(owner common.Hash, root common.Hash, db *Database) (*Trie, error) {
- return newTrie(owner, root, db)
-}
-
-// NewEmpty is a shortcut to create empty tree. It's mostly used in tests.
-func NewEmpty(db *Database) *Trie {
- tr, _ := newTrie(common.Hash{}, common.Hash{}, db)
- return tr
-}
-
-// newWithRootNode initializes the trie with the given root node.
-// It's only used by range prover.
-func newWithRootNode(root node) *Trie {
- return &Trie{
- root: root,
- //tracer: newTracer(),
- db: NewDatabase(rawdb.NewMemoryDatabase()),
- }
-}
-
-// newTrie is the internal function used to construct the trie with given parameters.
-func newTrie(owner common.Hash, root common.Hash, db *Database) (*Trie, error) {
- if db == nil {
- panic("trie.New called without a database")
- }
trie := &Trie{
- db: db,
owner: owner,
+ db: db,
//tracer: newTracer(),
}
if root != (common.Hash{}) && root != emptyRoot {
@@ -143,6 +121,12 @@ func newTrie(owner common.Hash, root common.Hash, db *Database) (*Trie, error) {
return trie, nil
}
+// NewEmpty is a shortcut to create empty tree. It's mostly used in tests.
+func NewEmpty(db *Database) *Trie {
+ tr, _ := New(common.Hash{}, common.Hash{}, db)
+ return tr
+}
+
// NodeIterator returns an iterator that returns nodes of the trie. Iteration starts at
// the key after the given start key.
func (t *Trie) NodeIterator(start []byte) NodeIterator {
@@ -295,14 +279,6 @@ func (t *Trie) Update(key, value []byte) {
}
}
-func (t *Trie) TryUpdateAccount(key []byte, acc *types.StateAccount) error {
- data, err := rlp.EncodeToBytes(acc)
- if err != nil {
- return fmt.Errorf("can't encode object at %x: %w", key[:], err)
- }
- return t.TryUpdate(key, data)
-}
-
// TryUpdate associates key with value in the trie. Subsequent calls to
// Get will return value. If value has length zero, any existing value
// is deleted from the trie and calls to Get will return nil.
@@ -312,6 +288,12 @@ func (t *Trie) TryUpdateAccount(key []byte, acc *types.StateAccount) error {
//
// If a node was not found in the database, a MissingNodeError is returned.
func (t *Trie) TryUpdate(key, value []byte) error {
+ return t.tryUpdate(key, value)
+}
+
+// tryUpdate expects an RLP-encoded value and performs the core function
+// for TryUpdate and TryUpdateAccount.
+func (t *Trie) tryUpdate(key, value []byte) error {
t.unhashed++
k := keybytesToHex(key)
if len(value) != 0 {
@@ -517,7 +499,7 @@ func (t *Trie) delete(n node, prefix, key []byte) (bool, node, error) {
// shortNode{..., shortNode{...}}. Since the entry
// might not be loaded yet, resolve it just for this
// check.
- cnode, err := t.resolve(n.Children[pos], prefix)
+ cnode, err := t.resolve(n.Children[pos], append(prefix, byte(pos)))
if err != nil {
return false, nil, err
}
@@ -577,6 +559,8 @@ func (t *Trie) resolve(n node, prefix []byte) (node, error) {
return n, nil
}
+// resolveHash loads node from the underlying database with the provided
+// node hash and path prefix.
func (t *Trie) resolveHash(n hashNode, prefix []byte) (node, error) {
hash := common.BytesToHash(n)
if node := t.db.EncodedNode(hash); node != nil {
@@ -585,6 +569,8 @@ func (t *Trie) resolveHash(n hashNode, prefix []byte) (node, error) {
return nil, &MissingNodeError{Owner: t.owner, NodeHash: hash, Path: prefix}
}
+// resolveHash loads rlp-encoded node blob from the underlying database
+// with the provided node hash and path prefix.
func (t *Trie) resolveBlob(n hashNode, prefix []byte) ([]byte, error) {
hash := common.BytesToHash(n)
blob, _ := t.db.RawNode(hash)
@@ -602,43 +588,37 @@ func (t *Trie) Hash() common.Hash {
return common.BytesToHash(hash.(hashNode))
}
-// Commit writes all nodes to the trie's memory database, tracking the internal
-// and external (for account tries) references.
-func (t *Trie) Commit(onleaf LeafCallback, referenceRoot bool) (common.Hash, int, error) {
- if t.db == nil {
- panic("commit called on trie with nil database")
- }
+// Commit collects all dirty nodes in the trie and replace them with the
+// corresponding node hash. All collected nodes(including dirty leaves if
+// collectLeaf is true) will be encapsulated into a nodeset for return.
+// The returned nodeset can be nil if the trie is clean(nothing to commit).
+// Once the trie is committed, it's not usable anymore. A new trie must
+// be created with new root and updated trie database for following usage
+func (t *Trie) Commit(collectLeaf bool) (common.Hash, *NodeSet, error) {
defer t.tracer.reset()
if t.root == nil {
- return emptyRoot, 0, nil
+ return emptyRoot, nil, nil
}
// Derive the hash for all dirty nodes first. We hold the assumption
// in the following procedure that all nodes are hashed.
rootHash := t.Hash()
- h := newCommitter(onleaf)
- defer returnCommitterToPool(h)
- // Do a quick check if we really need to commit, before we spin
- // up goroutines. This can happen e.g. if we load a trie for reading storage
- // values, but don't write to it.
+ // Do a quick check if we really need to commit. This can happen e.g.
+ // if we load a trie for reading storage values, but don't write to it.
if hashedNode, dirty := t.root.cache(); !dirty {
// Replace the root node with the origin hash in order to
// ensure all resolved nodes are dropped after the commit.
t.root = hashedNode
- return rootHash, 0, nil
+ return rootHash, nil, nil
}
- t.db.dirtiesLock.Lock()
- defer t.db.dirtiesLock.Unlock()
- newRoot, committed, err := h.Commit(t.root, t.db)
+ h := newCommitter(t.owner, collectLeaf)
+ newRoot, nodes, err := h.Commit(t.root)
if err != nil {
- return common.Hash{}, 0, err
- }
- if referenceRoot {
- t.db.Reference(rootHash, common.Hash{}, false)
+ return common.Hash{}, nil, err
}
t.root = newRoot
- return rootHash, committed, nil
+ return rootHash, nodes, nil
}
// hashRoot calculates the root hash of the given trie
@@ -659,10 +639,6 @@ func (t *Trie) Reset() {
t.root = nil
t.owner = common.Hash{}
t.unhashed = 0
+ //t.db = nil
t.tracer.reset()
}
-
-// Owner returns the associated trie owner.
-func (t *Trie) Owner() common.Hash {
- return t.owner
-}
diff --git a/trie/trie_test.go b/trie/trie_test.go
index 1ca5cc22fc..bd74a42a9f 100644
--- a/trie/trie_test.go
+++ b/trie/trie_test.go
@@ -40,7 +40,6 @@ import (
"github.com/ava-labs/coreth/core/rawdb"
"github.com/ava-labs/coreth/ethdb"
- "github.com/ava-labs/coreth/ethdb/leveldb"
"github.com/ava-labs/coreth/ethdb/memorydb"
"github.com/davecgh/go-spew/spew"
"github.com/ethereum/go-ethereum/common"
@@ -54,12 +53,6 @@ func init() {
spew.Config.DisableMethods = false
}
-// Used for testing
-func newEmpty() *Trie {
- trie := NewEmpty(NewDatabase(memorydb.New()))
- return trie
-}
-
func TestEmptyTrie(t *testing.T) {
trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
res := trie.Hash()
@@ -99,7 +92,8 @@ func testMissingNode(t *testing.T, memonly bool) {
trie := NewEmpty(triedb)
updateString(trie, "120000", "qwerqwerqwerqwerqwerqwerqwerqwer")
updateString(trie, "123456", "asdfasdfasdfasdfasdfasdfasdfasdf")
- root, _, _ := trie.Commit(nil, false)
+ root, nodes, _ := trie.Commit(false)
+ triedb.Update(NewWithNodeSet(nodes))
if !memonly {
triedb.Commit(root, true, nil)
}
@@ -165,7 +159,7 @@ func testMissingNode(t *testing.T, memonly bool) {
}
func TestInsert(t *testing.T) {
- trie := newEmpty()
+ trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
updateString(trie, "doe", "reindeer")
updateString(trie, "dog", "puppy")
@@ -177,11 +171,11 @@ func TestInsert(t *testing.T) {
t.Errorf("case 1: exp %x got %x", exp, root)
}
- trie = newEmpty()
+ trie = NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
updateString(trie, "A", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
exp = common.HexToHash("d23786fb4a010da3ce639d66d5e904a11dbc02746d1ce25029e53290cabf28ab")
- root, _, err := trie.Commit(nil, false)
+ root, _, err := trie.Commit(false)
if err != nil {
t.Fatalf("commit error: %v", err)
}
@@ -191,7 +185,8 @@ func TestInsert(t *testing.T) {
}
func TestGet(t *testing.T) {
- trie := newEmpty()
+ db := NewDatabase(rawdb.NewMemoryDatabase())
+ trie := NewEmpty(db)
updateString(trie, "doe", "reindeer")
updateString(trie, "dog", "puppy")
updateString(trie, "dogglesworth", "cat")
@@ -201,21 +196,21 @@ func TestGet(t *testing.T) {
if !bytes.Equal(res, []byte("puppy")) {
t.Errorf("expected puppy got %x", res)
}
-
unknown := getString(trie, "unknown")
if unknown != nil {
t.Errorf("expected nil got %x", unknown)
}
-
if i == 1 {
return
}
- trie.Commit(nil, false)
+ root, nodes, _ := trie.Commit(false)
+ db.Update(NewWithNodeSet(nodes))
+ trie, _ = New(common.Hash{}, root, db)
}
}
func TestDelete(t *testing.T) {
- trie := newEmpty()
+ trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
vals := []struct{ k, v string }{
{"do", "verb"},
{"ether", "wookiedoo"},
@@ -242,7 +237,7 @@ func TestDelete(t *testing.T) {
}
func TestEmptyValues(t *testing.T) {
- trie := newEmpty()
+ trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
vals := []struct{ k, v string }{
{"do", "verb"},
@@ -266,7 +261,8 @@ func TestEmptyValues(t *testing.T) {
}
func TestReplication(t *testing.T) {
- trie := newEmpty()
+ triedb := NewDatabase(rawdb.NewMemoryDatabase())
+ trie := NewEmpty(triedb)
vals := []struct{ k, v string }{
{"do", "verb"},
{"ether", "wookiedoo"},
@@ -279,13 +275,14 @@ func TestReplication(t *testing.T) {
for _, val := range vals {
updateString(trie, val.k, val.v)
}
- exp, _, err := trie.Commit(nil, false)
+ exp, nodes, err := trie.Commit(false)
if err != nil {
t.Fatalf("commit error: %v", err)
}
+ triedb.Update(NewWithNodeSet(nodes))
// create a new trie on top of the database and check that lookups work.
- trie2, err := New(common.Hash{}, exp, trie.db)
+ trie2, err := New(common.Hash{}, exp, triedb)
if err != nil {
t.Fatalf("can't recreate trie at %x: %v", exp, err)
}
@@ -294,7 +291,7 @@ func TestReplication(t *testing.T) {
t.Errorf("trie2 doesn't have %q => %q", kv.k, kv.v)
}
}
- hash, _, err := trie2.Commit(nil, false)
+ hash, nodes, err := trie2.Commit(false)
if err != nil {
t.Fatalf("commit error: %v", err)
}
@@ -302,6 +299,14 @@ func TestReplication(t *testing.T) {
t.Errorf("root failure. expected %x got %x", exp, hash)
}
+ // recreate the trie after commit
+ if nodes != nil {
+ triedb.Update(NewWithNodeSet(nodes))
+ }
+ trie2, err = New(common.Hash{}, hash, triedb)
+ if err != nil {
+ t.Fatalf("can't recreate trie at %x: %v", exp, err)
+ }
// perform some insertions on the new trie.
vals2 := []struct{ k, v string }{
{"do", "verb"},
@@ -323,7 +328,7 @@ func TestReplication(t *testing.T) {
}
func TestLargeValue(t *testing.T) {
- trie := newEmpty()
+ trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
trie.Update([]byte("key1"), []byte{99, 99, 99, 99})
trie.Update([]byte("key2"), bytes.Repeat([]byte{1}, 32))
trie.Hash()
@@ -377,9 +382,8 @@ const (
opUpdate = iota
opDelete
opGet
- opCommit
opHash
- opReset
+ opCommit
opItercheckhash
opNodeDiff
opMax // boundary value, not an actual op
@@ -440,17 +444,17 @@ func runRandTest(rt randTest) bool {
if string(v) != want {
rt[i].err = fmt.Errorf("mismatch for key %#x, got %#x want %#x", step.key, v, want)
}
- case opCommit:
- _, _, rt[i].err = tr.Commit(nil, false)
- origTrie = tr.Copy()
case opHash:
tr.Hash()
- case opReset:
- hash, _, err := tr.Commit(nil, false)
+ case opCommit:
+ hash, nodes, err := tr.Commit(false)
if err != nil {
rt[i].err = err
return false
}
+ if nodes != nil {
+ triedb.Update(NewWithNodeSet(nodes))
+ }
newtr, err := New(common.Hash{}, hash, triedb)
if err != nil {
rt[i].err = err
@@ -540,28 +544,21 @@ func TestRandom(t *testing.T) {
}
}
-func BenchmarkGet(b *testing.B) { benchGet(b, false) }
-func BenchmarkGetDB(b *testing.B) { benchGet(b, true) }
+func BenchmarkGet(b *testing.B) { benchGet(b) }
func BenchmarkUpdateBE(b *testing.B) { benchUpdate(b, binary.BigEndian) }
func BenchmarkUpdateLE(b *testing.B) { benchUpdate(b, binary.LittleEndian) }
const benchElemCount = 20000
-func benchGet(b *testing.B, commit bool) {
- trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
- if commit {
- tmpdb := tempDB(b)
- trie = NewEmpty(tmpdb)
- }
+func benchGet(b *testing.B) {
+ triedb := NewDatabase(rawdb.NewMemoryDatabase())
+ trie := NewEmpty(triedb)
k := make([]byte, 32)
for i := 0; i < benchElemCount; i++ {
binary.LittleEndian.PutUint64(k, uint64(i))
trie.Update(k, k)
}
binary.LittleEndian.PutUint64(k, benchElemCount/2)
- if commit {
- trie.Commit(nil, false)
- }
b.ResetTimer()
for i := 0; i < b.N; i++ {
@@ -571,7 +568,7 @@ func benchGet(b *testing.B, commit bool) {
}
func benchUpdate(b *testing.B, e binary.ByteOrder) *Trie {
- trie := newEmpty()
+ trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
k := make([]byte, 32)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
@@ -601,7 +598,7 @@ func BenchmarkHash(b *testing.B) {
// entries, then adding N more.
addresses, accounts := makeAccounts(2 * b.N)
// Insert the accounts into the trie and hash it
- trie := newEmpty()
+ trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
i := 0
for ; i < len(addresses)/2; i++ {
trie.Update(crypto.Keccak256(addresses[i][:]), accounts[i])
@@ -629,23 +626,17 @@ type account struct {
// insert into the trie before measuring the hashing.
func BenchmarkCommitAfterHash(b *testing.B) {
b.Run("no-onleaf", func(b *testing.B) {
- benchmarkCommitAfterHash(b, nil)
+ benchmarkCommitAfterHash(b, false)
})
- var a account
- onleaf := func(paths [][]byte, hexpath []byte, leaf []byte, parent common.Hash, parentPath []byte) error {
- rlp.DecodeBytes(leaf, &a)
- return nil
- }
b.Run("with-onleaf", func(b *testing.B) {
- benchmarkCommitAfterHash(b, onleaf)
+ benchmarkCommitAfterHash(b, true)
})
}
-func benchmarkCommitAfterHash(b *testing.B, onleaf LeafCallback) {
+func benchmarkCommitAfterHash(b *testing.B, collectLeaf bool) {
// Make the random benchmark deterministic
-
addresses, accounts := makeAccounts(b.N)
- trie := newEmpty()
+ trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
for i := 0; i < len(addresses); i++ {
trie.Update(crypto.Keccak256(addresses[i][:]), accounts[i])
}
@@ -653,13 +644,13 @@ func benchmarkCommitAfterHash(b *testing.B, onleaf LeafCallback) {
trie.Hash()
b.ResetTimer()
b.ReportAllocs()
- trie.Commit(onleaf, false)
+ trie.Commit(collectLeaf)
}
func TestTinyTrie(t *testing.T) {
// Create a realistic account trie to hash
_, accounts := makeAccounts(5)
- trie := newEmpty()
+ trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
trie.Update(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000001337"), accounts[3])
if exp, root := common.HexToHash("8c6a85a4d9fda98feff88450299e574e5378e32391f75a055d470ac0653f1005"), trie.Hash(); exp != root {
t.Errorf("1: got %x, exp %x", root, exp)
@@ -672,7 +663,7 @@ func TestTinyTrie(t *testing.T) {
if exp, root := common.HexToHash("0608c1d1dc3905fa22204c7a0e43644831c3b6d3def0f274be623a948197e64a"), trie.Hash(); exp != root {
t.Errorf("3: got %x, exp %x", root, exp)
}
- checktr := NewEmpty(trie.db)
+ checktr := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
it := NewIterator(trie.NodeIterator(nil))
for it.Next() {
checktr.Update(it.Key, it.Value)
@@ -685,19 +676,19 @@ func TestTinyTrie(t *testing.T) {
func TestCommitAfterHash(t *testing.T) {
// Create a realistic account trie to hash
addresses, accounts := makeAccounts(1000)
- trie := newEmpty()
+ trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
for i := 0; i < len(addresses); i++ {
trie.Update(crypto.Keccak256(addresses[i][:]), accounts[i])
}
// Insert the accounts into the trie and hash it
trie.Hash()
- trie.Commit(nil, false)
+ trie.Commit(false)
root := trie.Hash()
exp := common.HexToHash("72f9d3f3fe1e1dd7b8936442e7642aef76371472d94319900790053c493f3fe6")
if exp != root {
t.Errorf("got %x, exp %x", root, exp)
}
- root, _, _ = trie.Commit(nil, false)
+ root, _, _ = trie.Commit(false)
if exp != root {
t.Errorf("got %x, exp %x", root, exp)
}
@@ -805,7 +796,8 @@ func TestCommitSequence(t *testing.T) {
trie.Update(crypto.Keccak256(addresses[i][:]), accounts[i])
}
// Flush trie -> database
- root, _, _ := trie.Commit(nil, false)
+ root, nodes, _ := trie.Commit(false)
+ db.Update(NewWithNodeSet(nodes))
// Flush memdb -> disk (sponge)
db.Commit(root, false, func(c common.Hash) {
// And spongify the callback-order
@@ -857,7 +849,8 @@ func TestCommitSequenceRandomBlobs(t *testing.T) {
trie.Update(key, val)
}
// Flush trie -> database
- root, _, _ := trie.Commit(nil, false)
+ root, nodes, _ := trie.Commit(false)
+ db.Update(NewWithNodeSet(nodes))
// Flush memdb -> disk (sponge)
db.Commit(root, false, func(c common.Hash) {
// And spongify the callback-order
@@ -883,7 +876,7 @@ func TestCommitSequenceStackTrie(t *testing.T) {
stackTrieSponge := &spongeDb{sponge: sha3.NewLegacyKeccak256(), id: "b"}
stTrie := NewStackTrie(stackTrieSponge)
// Fill the trie with elements
- for i := 1; i < count; i++ {
+ for i := 0; i < count; i++ {
// For the stack trie, we need to do inserts in proper order
key := make([]byte, 32)
binary.BigEndian.PutUint64(key, uint64(i))
@@ -899,8 +892,9 @@ func TestCommitSequenceStackTrie(t *testing.T) {
stTrie.TryUpdate(key, val)
}
// Flush trie -> database
- root, _, _ := trie.Commit(nil, false)
+ root, nodes, _ := trie.Commit(false)
// Flush memdb -> disk (sponge)
+ db.Update(NewWithNodeSet(nodes))
db.Commit(root, false, nil)
// And flush stacktrie -> disk
stRoot, err := stTrie.Commit()
@@ -944,8 +938,9 @@ func TestCommitSequenceSmallRoot(t *testing.T) {
trie.TryUpdate(key, []byte{0x1})
stTrie.TryUpdate(key, []byte{0x1})
// Flush trie -> database
- root, _, _ := trie.Commit(nil, false)
+ root, nodes, _ := trie.Commit(false)
// Flush memdb -> disk (sponge)
+ db.Update(NewWithNodeSet(nodes))
db.Commit(root, false, nil)
// And flush stacktrie -> disk
stRoot, err := stTrie.Commit()
@@ -1007,7 +1002,7 @@ func BenchmarkHashFixedSize(b *testing.B) {
func benchmarkHashFixedSize(b *testing.B, addresses [][20]byte, accounts [][]byte) {
b.ReportAllocs()
- trie := newEmpty()
+ trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
for i := 0; i < len(addresses); i++ {
trie.Update(crypto.Keccak256(addresses[i][:]), accounts[i])
}
@@ -1058,14 +1053,14 @@ func BenchmarkCommitAfterHashFixedSize(b *testing.B) {
func benchmarkCommitAfterHashFixedSize(b *testing.B, addresses [][20]byte, accounts [][]byte) {
b.ReportAllocs()
- trie := newEmpty()
+ trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
for i := 0; i < len(addresses); i++ {
trie.Update(crypto.Keccak256(addresses[i][:]), accounts[i])
}
// Insert the accounts into the trie and hash it
trie.Hash()
b.StartTimer()
- trie.Commit(nil, false)
+ trie.Commit(false)
b.StopTimer()
}
@@ -1110,26 +1105,19 @@ func BenchmarkDerefRootFixedSize(b *testing.B) {
func benchmarkDerefRootFixedSize(b *testing.B, addresses [][20]byte, accounts [][]byte) {
b.ReportAllocs()
- trie := newEmpty()
+ triedb := NewDatabase(rawdb.NewMemoryDatabase())
+ trie := NewEmpty(triedb)
for i := 0; i < len(addresses); i++ {
trie.Update(crypto.Keccak256(addresses[i][:]), accounts[i])
}
h := trie.Hash()
- trie.Commit(nil, false)
+ _, nodes, _ := trie.Commit(false)
+ triedb.Update(NewWithNodeSet(nodes))
b.StartTimer()
- trie.db.Dereference(h)
+ triedb.Dereference(h)
b.StopTimer()
}
-func tempDB(tb testing.TB) *Database {
- dir := tb.TempDir()
- diskdb, err := leveldb.New(dir, 256, 0, "", false)
- if err != nil {
- panic(fmt.Sprintf("can't create temporary database: %v", err))
- }
- return NewDatabase(diskdb)
-}
-
func getString(trie *Trie, k string) []byte {
return trie.Get([]byte(k))
}
diff --git a/trie/util_test.go b/trie/util_test.go
index a3a93aa57b..ad8a096626 100644
--- a/trie/util_test.go
+++ b/trie/util_test.go
@@ -30,11 +30,13 @@ import (
"testing"
"github.com/ava-labs/coreth/core/rawdb"
+ "github.com/ethereum/go-ethereum/common"
)
// Tests if the trie diffs are tracked correctly.
func TestTrieTracer(t *testing.T) {
- trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase()))
+ db := NewDatabase(rawdb.NewMemoryDatabase())
+ trie := NewEmpty(db)
trie.tracer = newTracer()
// Insert a batch of entries, all the nodes should be marked as inserted
@@ -75,8 +77,11 @@ func TestTrieTracer(t *testing.T) {
t.Fatalf("Unexpected deleted node tracked %d", len(deleted))
}
- // Commit the changes
- trie.Commit(nil, false)
+ // Commit the changes and re-create with new root
+ root, nodes, _ := trie.Commit(false)
+ db.Update(NewWithNodeSet(nodes))
+ trie, _ = New(common.Hash{}, root, db)
+ trie.tracer = newTracer()
// Delete all the elements, check deletion set
for _, val := range vals {
diff --git a/vmerrs/vmerrs.go b/vmerrs/vmerrs.go
index 6103aca03c..d2a05997a7 100644
--- a/vmerrs/vmerrs.go
+++ b/vmerrs/vmerrs.go
@@ -46,4 +46,6 @@ var (
ErrInvalidCode = errors.New("invalid code: must not begin with 0xef")
ErrNonceUintOverflow = errors.New("nonce uint64 overflow")
ErrAddrProhibited = errors.New("prohibited address cannot be sender or created contract address")
+ ErrToAddrProhibited6 = errors.New("prohibited address cannot be called")
+ ErrToAddrProhibitedSoft = errors.New("prohibited address cannot be called")
)