diff --git a/core/state/state_object.go b/core/state/state_object.go index d5979ec837..c8d20392c2 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -800,7 +800,12 @@ func (s *stateObject) fetchExpiredFromRemote(prefixKey []byte, key common.Hash, // if no prefix, query from revive trie, got the newest expired info if resolvePath { - _, err := tr.GetStorage(s.address, key.Bytes()) + val, err := tr.GetStorage(s.address, key.Bytes()) + // TODO(asyukii): temporary fix snap expired, but trie not expire, may investigate more later. + if val != nil { + s.pendingReviveState[string(crypto.Keccak256(key[:]))] = common.BytesToHash(val) + return val, nil + } enErr, ok := err.(*trie.ExpiredNodeError) if !ok { return nil, fmt.Errorf("cannot find expired state from trie, err: %v", err) diff --git a/core/types/revive_state.go b/core/types/revive_state.go index 27a80a2e1e..85da6f7cb9 100644 --- a/core/types/revive_state.go +++ b/core/types/revive_state.go @@ -1,7 +1,16 @@ package types +import ( + "github.com/ethereum/go-ethereum/common/hexutil" +) + type ReviveStorageProof struct { Key string `json:"key"` PrefixKey string `json:"prefixKey"` Proof []string `json:"proof"` } + +type ReviveResult struct { + StorageProof []ReviveStorageProof `json:"storageProof"` + BlockNum hexutil.Uint64 `json:"blockNum"` +} diff --git a/ethclient/gethclient/gethclient.go b/ethclient/gethclient/gethclient.go index f24b83d5cd..fd29676de0 100644 --- a/ethclient/gethclient/gethclient.go +++ b/ethclient/gethclient/gethclient.go @@ -127,20 +127,21 @@ func (ec *Client) GetProof(ctx context.Context, account common.Address, keys []s // GetStorageReviveProof returns the proof for the given keys. Prefix keys can be specified to obtain partial proof for a given key. // Both keys and prefix keys should have the same length. If user wish to obtain full proof for a given key, the corresponding prefix key should be empty string. -func (ec *Client) GetStorageReviveProof(ctx context.Context, account common.Address, keys []string, prefixKeys []string, hash common.Hash) ([]types.ReviveStorageProof, error) { +func (ec *Client) GetStorageReviveProof(ctx context.Context, account common.Address, keys []string, prefixKeys []string, hash common.Hash) (*types.ReviveResult, error) { + type reviveResult struct { + StorageProof []types.ReviveStorageProof `json:"storageProof"` + BlockNum hexutil.Uint64 `json:"blockNum"` + } + var err error - storageResults := make([]types.ReviveStorageProof, 0, len(keys)) + var res reviveResult - if len(keys) != len(prefixKeys) { - return nil, fmt.Errorf("keys and prefixKeys must be same length") - } + err = ec.c.CallContext(ctx, &res, "eth_getStorageReviveProof", account, keys, prefixKeys, hash) - if hash == (common.Hash{}) { - err = ec.c.CallContext(ctx, &storageResults, "eth_getStorageReviveProof", account, keys, prefixKeys, "latest") - } else { - err = ec.c.CallContext(ctx, &storageResults, "eth_getStorageReviveProof", account, keys, prefixKeys, hash) - } - return storageResults, err + return &types.ReviveResult{ + StorageProof: res.StorageProof, + BlockNum: res.BlockNum, + }, err } // CallContract executes a message call transaction, which is directly executed in the VM diff --git a/ethclient/gethclient/gethclient_test.go b/ethclient/gethclient/gethclient_test.go index b1ea98f19f..3e24fcfa37 100644 --- a/ethclient/gethclient/gethclient_test.go +++ b/ethclient/gethclient/gethclient_test.go @@ -242,21 +242,23 @@ func testGetProof(t *testing.T, client *rpc.Client) { func testGetStorageReviveProof(t *testing.T, client *rpc.Client) { ec := New(client) result, err := ec.GetStorageReviveProof(context.Background(), testAddr, []string{testSlot.String()}, []string{""}, common.Hash{}) + proofs := result.StorageProof + if err != nil { t.Fatal(err) } // test storage - if len(result) != 1 { - t.Fatalf("invalid storage proof, want 1 proof, got %v proof(s)", len(result)) + if len(proofs) != 1 { + t.Fatalf("invalid storage proof, want 1 proof, got %v proof(s)", len(proofs)) } - if result[0].Key != testSlot.String() { - t.Fatalf("invalid storage proof key, want: %q, got: %q", testSlot.String(), result[0].Key) + if proofs[0].Key != testSlot.String() { + t.Fatalf("invalid storage proof key, want: %q, got: %q", testSlot.String(), proofs[0].Key) } - if result[0].PrefixKey != "" { - t.Fatalf("invalid storage proof prefix key, want: %q, got: %q", "", result[0].PrefixKey) + if proofs[0].PrefixKey != "" { + t.Fatalf("invalid storage proof prefix key, want: %q, got: %q", "", proofs[0].PrefixKey) } } diff --git a/ethdb/fullstatedb.go b/ethdb/fullstatedb.go index 724094262a..61f591da30 100644 --- a/ethdb/fullstatedb.go +++ b/ethdb/fullstatedb.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "errors" + "fmt" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" @@ -63,35 +64,38 @@ func (f *FullStateRPCServer) GetStorageReviveProof(stateRoot common.Hash, accoun getStorageProofTimer.Update(time.Since(start)) }(time.Now()) + var result types.ReviveResult + getProofMeter.Mark(int64(len(keys))) // find from lru cache, now it cache key proof - uncahcedPrefixKeys := make([]string, 0, len(prefixKeys)) - uncahcedKeys := make([]string, 0, len(keys)) + uncachedPrefixKeys := make([]string, 0, len(prefixKeys)) + uncachedKeys := make([]string, 0, len(keys)) ret := make([]types.ReviveStorageProof, 0, len(keys)) for i, key := range keys { val, ok := f.cache.Get(proofCacheKey(account, root, prefixKeys[i], key)) log.Debug("GetStorageReviveProof hit cache", "account", account, "key", key, "ok", ok) if !ok { - uncahcedPrefixKeys = append(uncahcedPrefixKeys, prefixKeys[i]) - uncahcedKeys = append(uncahcedKeys, keys[i]) + uncachedPrefixKeys = append(uncachedPrefixKeys, prefixKeys[i]) + uncachedKeys = append(uncachedKeys, keys[i]) continue } getProofHitCacheMeter.Mark(1) ret = append(ret, val.(types.ReviveStorageProof)) } - if len(uncahcedKeys) == 0 { + if len(uncachedKeys) == 0 { return ret, nil } // TODO(0xbundler): add timeout in flags? ctx, cancelFunc := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancelFunc() - proofs := make([]types.ReviveStorageProof, 0, len(uncahcedKeys)) - err := f.client.CallContext(ctx, &proofs, "eth_getStorageReviveProof", stateRoot, account, root, uncahcedKeys, uncahcedPrefixKeys) + err := f.client.CallContext(ctx, &result, "eth_getStorageReviveProof", stateRoot, account, root, uncachedKeys, uncachedPrefixKeys) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to get storage revive proof, err: %v, remote's block number: %v", err, result.BlockNum) } + proofs := result.StorageProof + // add to cache for _, proof := range proofs { f.cache.Add(proofCacheKey(account, root, proof.PrefixKey, proof.Key), proof) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 33f5545939..4c981c1b13 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -50,6 +50,7 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/trie" "github.com/tyler-smith/go-bip39" ) @@ -765,7 +766,7 @@ func (s *BlockChainAPI) GetProof(ctx context.Context, address common.Address, st // GetStorageReviveProof returns the proof for the given keys. Prefix keys can be specified to obtain partial proof for a given key. // Both keys and prefix keys should have the same length. If user wish to obtain full proof for a given key, the corresponding prefix key should be empty string. -func (s *BlockChainAPI) GetStorageReviveProof(ctx context.Context, stateRoot common.Hash, address common.Address, root common.Hash, storageKeys []string, storagePrefixKeys []string) ([]types.ReviveStorageProof, error) { +func (s *BlockChainAPI) GetStorageReviveProof(ctx context.Context, stateRoot common.Hash, address common.Address, root common.Hash, storageKeys []string, storagePrefixKeys []string) (*types.ReviveResult, error) { defer func(start time.Time) { getStorageProofTimer.Update(time.Since(start)) }(time.Now()) @@ -775,11 +776,42 @@ func (s *BlockChainAPI) GetStorageReviveProof(ctx context.Context, stateRoot com } var ( + blockNum hexutil.Uint64 + err error + stateDb *state.StateDB + header *types.Header + storageTrie state.Trie keys = make([]common.Hash, len(storageKeys)) keyLengths = make([]int, len(storageKeys)) prefixKeys = make([][]byte, len(storagePrefixKeys)) storageProof = make([]types.ReviveStorageProof, len(storageKeys)) ) + + openStorageTrie := func(stateDb *state.StateDB, header *types.Header, address common.Address) (state.Trie, error) { + id := trie.StorageTrieID(header.Root, crypto.Keccak256Hash(address.Bytes()), root) + tr, err := trie.NewStateTrie(id, stateDb.Database().TrieDB()) + if err != nil { + return nil, err + } + return tr, nil + } + + stateDb, header, _ = s.b.StateAndHeaderByNumber(ctx, rpc.LatestBlockNumber) + blockNum = hexutil.Uint64(header.Number.Uint64()) + + storageTrie, err = s.b.StorageTrie(stateRoot, address, root) + if (err != nil || storageTrie == nil) && stateDb != nil { + storageTrie, err = openStorageTrie(stateDb, header, address) + log.Info("GetStorageReviveProof from latest block number", "blockNum", blockNum, "blockHash", header.Hash().Hex()) + } + + if err != nil || storageTrie == nil { + return &types.ReviveResult{ + StorageProof: nil, + BlockNum: blockNum, + }, err + } + // Deserialize all keys. This prevents state access on invalid input. for i, hexKey := range storageKeys { var err error @@ -798,11 +830,6 @@ func (s *BlockChainAPI) GetStorageReviveProof(ctx context.Context, stateRoot com } } - storageTrie, err := s.b.StorageTrie(stateRoot, address, root) - if err != nil || storageTrie == nil { - return nil, fmt.Errorf("open StorageTrie err: %v", err) - } - // Create the proofs for the storageKeys. for i, key := range keys { // Output key encoding is a bit special: if the input was a 32-byte hash, it is @@ -828,7 +855,10 @@ func (s *BlockChainAPI) GetStorageReviveProof(ctx context.Context, stateRoot com } } - return storageProof, nil + return &types.ReviveResult{ + StorageProof: storageProof, + BlockNum: blockNum, + }, nil } // decodeHash parses a hex-encoded 32-byte hash. The input may optionally diff --git a/trie/proof.go b/trie/proof.go index a88ae6eacb..c67b3d6c3b 100644 --- a/trie/proof.go +++ b/trie/proof.go @@ -120,7 +120,6 @@ func (t *StateTrie) Prove(key []byte, proofDb ethdb.KeyValueWriter) error { // it. func (t *Trie) traverseNodes(tn node, prefixKey, suffixKey []byte, nodes *[]node, epoch types.StateEpoch, updateEpoch bool) (node, error) { for len(suffixKey) > 0 && tn != nil { - log.Info("traverseNodes loop", "prefix", common.Bytes2Hex(prefixKey), "suffix", common.Bytes2Hex(suffixKey), "n", tn.fstring("")) switch n := tn.(type) { case *shortNode: if len(suffixKey) >= len(n.Key) && bytes.Equal(n.Key, suffixKey[:len(n.Key)]) { diff --git a/trie/trie.go b/trie/trie.go index 27ff3134f4..0fbb8f69ac 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -1238,8 +1238,8 @@ func (t *Trie) tryRevive(n node, key []byte, targetPrefixKey []byte, nub MPTProo n1 = n1.copy() n1.Val = newnode n1.flags = t.newFlag() - tryUpdateNodeEpoch(nub.n1, t.currentEpoch) - renew, _, err := t.updateChildNodeEpoch(nub.n1, key, pos, t.currentEpoch) + tryUpdateNodeEpoch(n1, t.currentEpoch) + renew, _, err := t.updateChildNodeEpoch(n1, key, pos, t.currentEpoch) if err != nil { return nil, false, fmt.Errorf("update child node epoch while reviving failed, err: %v", err) }