From 7ac165d462e83b9b7f415ff0732ae74e1dc7fb29 Mon Sep 17 00:00:00 2001 From: primal_concrete_sledge Date: Thu, 13 Jan 2022 14:05:30 +0300 Subject: [PATCH] issue/2028-support_parity_listStorageKeys (#3235) --- cmd/rpcdaemon/commands/daemon.go | 8 ++ cmd/rpcdaemon/commands/parity_api.go | 72 +++++++++++++++ cmd/rpcdaemon/commands/parity_api_test.go | 102 ++++++++++++++++++++++ 3 files changed, 182 insertions(+) create mode 100644 cmd/rpcdaemon/commands/parity_api.go create mode 100644 cmd/rpcdaemon/commands/parity_api_test.go diff --git a/cmd/rpcdaemon/commands/daemon.go b/cmd/rpcdaemon/commands/daemon.go index 9be12b4be..05b881892 100644 --- a/cmd/rpcdaemon/commands/daemon.go +++ b/cmd/rpcdaemon/commands/daemon.go @@ -36,6 +36,7 @@ func APIList(ctx context.Context, db kv.RoDB, dbImpl := NewDBAPIImpl() /* deprecated */ engineImpl := NewEngineAPI(base, db, eth) adminImpl := NewAdminAPI(eth) + parityImpl := NewParityAPIImpl(db) for _, enabledAPI := range cfg.API { switch enabledAPI { @@ -116,6 +117,13 @@ func APIList(ctx context.Context, db kv.RoDB, Service: AdminAPI(adminImpl), Version: "1.0", }) + case "parity": + defaultAPIList = append(defaultAPIList, rpc.API{ + Namespace: "parity", + Public: false, + Service: ParityAPI(parityImpl), + Version: "1.0", + }) } } diff --git a/cmd/rpcdaemon/commands/parity_api.go b/cmd/rpcdaemon/commands/parity_api.go new file mode 100644 index 000000000..443593acb --- /dev/null +++ b/cmd/rpcdaemon/commands/parity_api.go @@ -0,0 +1,72 @@ +package commands + +import ( + "context" + "encoding/binary" + "fmt" + + "github.com/ledgerwatch/erigon-lib/kv" + "github.com/ledgerwatch/erigon/common" + "github.com/ledgerwatch/erigon/common/hexutil" + "github.com/ledgerwatch/erigon/core/state" +) + +// ParityAPI the interface for the parity_ RPC commands +type ParityAPI interface { + ListStorageKeys(ctx context.Context, account common.Address, quantity int, offset *hexutil.Bytes) ([]hexutil.Bytes, error) +} + +// ParityAPIImpl data structure to store things needed for parity_ commands +type ParityAPIImpl struct { + db kv.RoDB +} + +// NewParityAPIImpl returns ParityAPIImpl instance +func NewParityAPIImpl(db kv.RoDB) *ParityAPIImpl { + return &ParityAPIImpl{ + db: db, + } +} + +// ListStorageKeys implements parity_listStorageKeys. Returns all storage keys of the given address +func (api *ParityAPIImpl) ListStorageKeys(ctx context.Context, account common.Address, quantity int, offset *hexutil.Bytes) ([]hexutil.Bytes, error) { + tx, err := api.db.BeginRo(ctx) + if err != nil { + return nil, fmt.Errorf("listStorageKeys cannot open tx: %w", err) + } + defer tx.Rollback() + a, err := state.NewPlainStateReader(tx).ReadAccountData(account) + if err != nil { + return nil, err + } else if a == nil { + return nil, fmt.Errorf("acc not found") + } + + b := make([]byte, 8) + binary.BigEndian.PutUint64(b, a.GetIncarnation()) + seekBytes := append(account.Bytes(), b...) + + c, err := tx.CursorDupSort(kv.PlainState) + if err != nil { + return nil, err + } + defer c.Close() + keys := make([]hexutil.Bytes, 0) + var v []byte + var seekVal []byte + if offset != nil { + seekVal = *offset + } + + for v, err = c.SeekBothRange(seekBytes, seekVal); v != nil && len(keys) != quantity && err == nil; _, v, err = c.NextDup() { + if len(v) > common.HashLength { + keys = append(keys, v[:common.HashLength]) + } else { + keys = append(keys, v) + } + } + if err != nil { + return nil, err + } + return keys, nil +} diff --git a/cmd/rpcdaemon/commands/parity_api_test.go b/cmd/rpcdaemon/commands/parity_api_test.go new file mode 100644 index 000000000..ed4cc29c2 --- /dev/null +++ b/cmd/rpcdaemon/commands/parity_api_test.go @@ -0,0 +1,102 @@ +package commands + +import ( + "context" + "fmt" + "testing" + + "github.com/ledgerwatch/erigon/cmd/rpcdaemon/rpcdaemontest" + "github.com/ledgerwatch/erigon/common" + "github.com/ledgerwatch/erigon/common/hexutil" + "github.com/stretchr/testify/assert" +) + +func TestParityAPIImpl_ListStorageKeys_NoOffset(t *testing.T) { + assert := assert.New(t) + db := rpcdaemontest.CreateTestKV(t) + api := NewParityAPIImpl(db) + answers := []string{ + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000002", + "0a2127994676ca91e4eb3d2a1e46ec9dcee074dc2643bb5ebd4e9ac6541a3148", + "0fe673b4bc06161f39bc26f4e8831c810a72ffe69e5c8cb26f7f54752618e696", + "120e23dcb7e4437386073613853db77b10011a2404eefc716b97c7767e37f8eb", + } + addr := common.HexToAddress("0x920fd5070602feaea2e251e9e7238b6c376bcae5") + result, err := api.ListStorageKeys(context.Background(), addr, 5, nil) + if err != nil { + t.Errorf("calling ListStorageKeys: %v", err) + } + assert.Equal(len(answers), len(result)) + for k, v := range result { + assert.Equal(answers[k], common.Bytes2Hex(v)) + } +} + +func TestParityAPIImpl_ListStorageKeys_WithOffset_ExistingPrefix(t *testing.T) { + assert := assert.New(t) + db := rpcdaemontest.CreateTestKV(t) + api := NewParityAPIImpl(db) + answers := []string{ + "29d05770ca9ee7088a64e18c8e5160fc62c3c2179dc8ef9b4dbc970c9e51b4d8", + "29edc84535d98b29835079d685b97b41ee8e831e343cc80793057e462353a26d", + "2c05ac60f9aa2df5e64ef977f271e4b9a2d13951f123a2cb5f5d4ad5eb344f1a", + "4644be453c81744b6842ddf615d7fca0e14a23b09734be63d44c23452de95631", + "4974416255391052161ba8184fe652f3bf8c915592c65f7de127af8e637dce5d", + } + addr := common.HexToAddress("0x920fd5070602feaea2e251e9e7238b6c376bcae5") + offset := common.Hex2Bytes("29") + b := hexutil.Bytes(offset) + result, err := api.ListStorageKeys(context.Background(), addr, 5, &b) + if err != nil { + t.Errorf("calling ListStorageKeys: %v", err) + } + assert.Equal(len(answers), len(result)) + for k, v := range result { + assert.Equal(answers[k], common.Bytes2Hex(v)) + } +} + +func TestParityAPIImpl_ListStorageKeys_WithOffset_NonExistingPrefix(t *testing.T) { + assert := assert.New(t) + db := rpcdaemontest.CreateTestKV(t) + api := NewParityAPIImpl(db) + answers := []string{ + "4644be453c81744b6842ddf615d7fca0e14a23b09734be63d44c23452de95631", + "4974416255391052161ba8184fe652f3bf8c915592c65f7de127af8e637dce5d", + } + addr := common.HexToAddress("0x920fd5070602feaea2e251e9e7238b6c376bcae5") + offset := common.Hex2Bytes("30") + b := hexutil.Bytes(offset) + result, err := api.ListStorageKeys(context.Background(), addr, 2, &b) + if err != nil { + t.Errorf("calling ListStorageKeys: %v", err) + } + assert.Equal(len(answers), len(result)) + for k, v := range result { + assert.Equal(answers[k], common.Bytes2Hex(v)) + } +} + +func TestParityAPIImpl_ListStorageKeys_WithOffset_EmptyResponse(t *testing.T) { + assert := assert.New(t) + db := rpcdaemontest.CreateTestKV(t) + api := NewParityAPIImpl(db) + addr := common.HexToAddress("0x920fd5070602feaea2e251e9e7238b6c376bcae5") + offset := common.Hex2Bytes("ff") + b := hexutil.Bytes(offset) + result, err := api.ListStorageKeys(context.Background(), addr, 2, &b) + if err != nil { + t.Errorf("calling ListStorageKeys: %v", err) + } + assert.Equal(0, len(result)) +} + +func TestParityAPIImpl_ListStorageKeys_AccNotFound(t *testing.T) { + assert := assert.New(t) + db := rpcdaemontest.CreateTestKV(t) + api := NewParityAPIImpl(db) + addr := common.HexToAddress("0x920fd5070602feaea2e251e9e7238b6c376bcaef") + _, err := api.ListStorageKeys(context.Background(), addr, 2, nil) + assert.Error(err, fmt.Errorf("acc not found")) +}