mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-17 18:30:45 +00:00
parent
45aadcd311
commit
436166e447
3 changed files with 158 additions and 0 deletions
|
|
@ -55,6 +55,7 @@ import (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultGasPrice = 50 * params.Shannon
|
defaultGasPrice = 50 * params.Shannon
|
||||||
|
|
||||||
// statuses of candidates
|
// statuses of candidates
|
||||||
statusMasternode = "MASTERNODE"
|
statusMasternode = "MASTERNODE"
|
||||||
statusSlashed = "SLASHED"
|
statusSlashed = "SLASHED"
|
||||||
|
|
@ -66,6 +67,10 @@ const (
|
||||||
fieldEpoch = "epoch"
|
fieldEpoch = "epoch"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// maxGetStorageSlots is the maximum total number of storage slots that can
|
||||||
|
// be requested in a single eth_getStorageValues call.
|
||||||
|
const maxGetStorageSlots = 1024
|
||||||
|
|
||||||
var errEmptyHeader = errors.New("empty header")
|
var errEmptyHeader = errors.New("empty header")
|
||||||
|
|
||||||
// EthereumAPI provides an API to access Ethereum related information.
|
// EthereumAPI provides an API to access Ethereum related information.
|
||||||
|
|
@ -517,6 +522,41 @@ func (api *BlockChainAPI) GetStorageAt(ctx context.Context, address common.Addre
|
||||||
return res[:], state.Error()
|
return res[:], state.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetStorageValues returns multiple storage slot values for multiple accounts
|
||||||
|
// at the given block.
|
||||||
|
func (api *BlockChainAPI) GetStorageValues(ctx context.Context, requests map[common.Address][]common.Hash, blockNrOrHash rpc.BlockNumberOrHash) (map[common.Address][]hexutil.Bytes, error) {
|
||||||
|
// Count total slots requested.
|
||||||
|
var totalSlots int
|
||||||
|
for _, keys := range requests {
|
||||||
|
totalSlots += len(keys)
|
||||||
|
if totalSlots > maxGetStorageSlots {
|
||||||
|
return nil, &clientLimitExceededError{message: fmt.Sprintf("too many slots (max %d)", maxGetStorageSlots)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if totalSlots == 0 {
|
||||||
|
return nil, &invalidParamsError{message: "empty request"}
|
||||||
|
}
|
||||||
|
|
||||||
|
state, _, err := api.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
|
||||||
|
if state == nil || err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make(map[common.Address][]hexutil.Bytes, len(requests))
|
||||||
|
for addr, keys := range requests {
|
||||||
|
vals := make([]hexutil.Bytes, len(keys))
|
||||||
|
for i, key := range keys {
|
||||||
|
v := state.GetState(addr, key)
|
||||||
|
vals[i] = v[:]
|
||||||
|
}
|
||||||
|
if err := state.Error(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result[addr] = vals
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetBlockReceipts returns the block receipts for the given block hash or number or tag.
|
// GetBlockReceipts returns the block receipts for the given block hash or number or tag.
|
||||||
func (api *BlockChainAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) ([]map[string]interface{}, error) {
|
func (api *BlockChainAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) ([]map[string]interface{}, error) {
|
||||||
block, err := api.b.BlockByNumberOrHash(ctx, blockNrOrHash)
|
block, err := api.b.BlockByNumberOrHash(ctx, blockNrOrHash)
|
||||||
|
|
|
||||||
|
|
@ -17,15 +17,20 @@
|
||||||
package ethapi
|
package ethapi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"hash"
|
"hash"
|
||||||
"math/big"
|
"math/big"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/common"
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/core"
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/core/state"
|
||||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||||
"github.com/XinFinOrg/XDPoSChain/crypto/keccak"
|
"github.com/XinFinOrg/XDPoSChain/crypto/keccak"
|
||||||
"github.com/XinFinOrg/XDPoSChain/params"
|
"github.com/XinFinOrg/XDPoSChain/params"
|
||||||
|
"github.com/XinFinOrg/XDPoSChain/rpc"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -271,3 +276,110 @@ func TestRPCMarshalBlock(t *testing.T) {
|
||||||
require.JSONEqf(t, tc.want, string(out), "test %d", i)
|
require.JSONEqf(t, tc.want, string(out), "test %d", i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type storageBackendMock struct {
|
||||||
|
*backendMock
|
||||||
|
stateDB *state.StateDB
|
||||||
|
header *types.Header
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *storageBackendMock) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error) {
|
||||||
|
return b.stateDB, b.header, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetStorageValues(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var (
|
||||||
|
addr1 = common.HexToAddress("0x1111")
|
||||||
|
addr2 = common.HexToAddress("0x2222")
|
||||||
|
slot0 = common.Hash{}
|
||||||
|
slot1 = common.BigToHash(big.NewInt(1))
|
||||||
|
slot2 = common.BigToHash(big.NewInt(2))
|
||||||
|
val0 = common.BigToHash(big.NewInt(42))
|
||||||
|
val1 = common.BigToHash(big.NewInt(100))
|
||||||
|
val2 = common.BigToHash(big.NewInt(200))
|
||||||
|
|
||||||
|
genesis = &core.Genesis{
|
||||||
|
Config: params.MergedTestChainConfig,
|
||||||
|
Alloc: types.GenesisAlloc{
|
||||||
|
addr1: {
|
||||||
|
Balance: big.NewInt(params.Ether),
|
||||||
|
Storage: map[common.Hash]common.Hash{
|
||||||
|
slot0: val0,
|
||||||
|
slot1: val1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
addr2: {
|
||||||
|
Balance: big.NewInt(params.Ether),
|
||||||
|
Storage: map[common.Hash]common.Hash{
|
||||||
|
slot2: val2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
db := rawdb.NewMemoryDatabase()
|
||||||
|
block := genesis.MustCommit(db)
|
||||||
|
stateDB, err := state.New(block.Root(), state.NewDatabase(db))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create state db: %v", err)
|
||||||
|
}
|
||||||
|
backend := &storageBackendMock{
|
||||||
|
backendMock: newBackendMock(),
|
||||||
|
stateDB: stateDB,
|
||||||
|
header: block.Header(),
|
||||||
|
}
|
||||||
|
api := NewBlockChainAPI(backend, nil)
|
||||||
|
latest := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber)
|
||||||
|
|
||||||
|
// Happy path: multiple addresses, multiple slots.
|
||||||
|
result, err := api.GetStorageValues(context.Background(), map[common.Address][]common.Hash{
|
||||||
|
addr1: {slot0, slot1},
|
||||||
|
addr2: {slot2},
|
||||||
|
}, latest)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if len(result) != 2 {
|
||||||
|
t.Fatalf("expected 2 addresses in result, got %d", len(result))
|
||||||
|
}
|
||||||
|
if got := common.BytesToHash(result[addr1][0]); got != val0 {
|
||||||
|
t.Errorf("addr1 slot0: want %x, got %x", val0, got)
|
||||||
|
}
|
||||||
|
if got := common.BytesToHash(result[addr1][1]); got != val1 {
|
||||||
|
t.Errorf("addr1 slot1: want %x, got %x", val1, got)
|
||||||
|
}
|
||||||
|
if got := common.BytesToHash(result[addr2][0]); got != val2 {
|
||||||
|
t.Errorf("addr2 slot2: want %x, got %x", val2, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Missing slot returns zero.
|
||||||
|
result, err = api.GetStorageValues(context.Background(), map[common.Address][]common.Hash{
|
||||||
|
addr1: {common.HexToHash("0xff")},
|
||||||
|
}, latest)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if got := common.BytesToHash(result[addr1][0]); got != (common.Hash{}) {
|
||||||
|
t.Errorf("missing slot: want zero, got %x", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty request returns error.
|
||||||
|
_, err = api.GetStorageValues(context.Background(), map[common.Address][]common.Hash{}, latest)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error for empty request")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exceeding slot limit returns error.
|
||||||
|
tooMany := make([]common.Hash, maxGetStorageSlots+1)
|
||||||
|
for i := range tooMany {
|
||||||
|
tooMany[i] = common.BigToHash(big.NewInt(int64(i)))
|
||||||
|
}
|
||||||
|
_, err = api.GetStorageValues(context.Background(), map[common.Address][]common.Hash{
|
||||||
|
addr1: tooMany,
|
||||||
|
}, latest)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error for exceeding slot limit")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -543,6 +543,12 @@ web3._extend({
|
||||||
params: 2,
|
params: 2,
|
||||||
inputFormatter: [web3._extend.formatters.inputAddressFormatter, web3._extend.formatters.inputBlockNumberFormatter]
|
inputFormatter: [web3._extend.formatters.inputAddressFormatter, web3._extend.formatters.inputBlockNumberFormatter]
|
||||||
}),
|
}),
|
||||||
|
new web3._extend.Method({
|
||||||
|
name: 'getStorageValues',
|
||||||
|
call: 'eth_getStorageValues',
|
||||||
|
params: 2,
|
||||||
|
inputFormatter: [null, web3._extend.formatters.inputBlockNumberFormatter]
|
||||||
|
}),
|
||||||
new web3._extend.Method({
|
new web3._extend.Method({
|
||||||
name: 'createAccessList',
|
name: 'createAccessList',
|
||||||
call: 'eth_createAccessList',
|
call: 'eth_createAccessList',
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue