From b6440eea1e2d5c8eb36ab176e275e0b0c12cb99d Mon Sep 17 00:00:00 2001 From: Levi Aul Date: Fri, 15 Jul 2022 07:04:23 -0700 Subject: [PATCH] Add erigon_getBalanceChangesInBlock RPC endpoint (#4609) * Add eth_getBalanceChangesInBlock RPC endpoint * Fix lints * added assertion for one test * moved balance change api from eth to erigon Co-authored-by: fatemebagherii --- cmd/rpcdaemon/commands/erigon_api.go | 2 + cmd/rpcdaemon/commands/erigon_block.go | 68 ++++++++++++++++++++++++++ cmd/rpcdaemon/commands/eth_api_test.go | 25 ++++++++++ 3 files changed, 95 insertions(+) diff --git a/cmd/rpcdaemon/commands/erigon_api.go b/cmd/rpcdaemon/commands/erigon_api.go index f976cf31f05..87781938642 100644 --- a/cmd/rpcdaemon/commands/erigon_api.go +++ b/cmd/rpcdaemon/commands/erigon_api.go @@ -5,6 +5,7 @@ import ( "github.com/ledgerwatch/erigon-lib/kv" "github.com/ledgerwatch/erigon/common" + "github.com/ledgerwatch/erigon/common/hexutil" "github.com/ledgerwatch/erigon/core/types" "github.com/ledgerwatch/erigon/p2p" "github.com/ledgerwatch/erigon/rpc" @@ -20,6 +21,7 @@ type ErigonAPI interface { GetHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) GetHeaderByHash(_ context.Context, hash common.Hash) (*types.Header, error) GetBlockByTimestamp(ctx context.Context, timeStamp rpc.Timestamp, fullTx bool) (map[string]interface{}, error) + GetBalanceChangesInBlock(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (map[common.Address]*hexutil.Big, error) // Receipt related (see ./erigon_receipts.go) GetLogsByHash(ctx context.Context, hash common.Hash) ([][]*types.Log, error) diff --git a/cmd/rpcdaemon/commands/erigon_block.go b/cmd/rpcdaemon/commands/erigon_block.go index 7a5bf1bda1d..e56cba2a1d3 100644 --- a/cmd/rpcdaemon/commands/erigon_block.go +++ b/cmd/rpcdaemon/commands/erigon_block.go @@ -1,15 +1,21 @@ package commands import ( + "bytes" "context" "errors" "fmt" "sort" + "github.com/holiman/uint256" "github.com/ledgerwatch/erigon-lib/kv" "github.com/ledgerwatch/erigon/common" + "github.com/ledgerwatch/erigon/common/changeset" + "github.com/ledgerwatch/erigon/common/dbutils" + "github.com/ledgerwatch/erigon/common/hexutil" "github.com/ledgerwatch/erigon/core/rawdb" "github.com/ledgerwatch/erigon/core/types" + "github.com/ledgerwatch/erigon/core/types/accounts" "github.com/ledgerwatch/erigon/internal/ethapi" "github.com/ledgerwatch/erigon/rpc" "github.com/ledgerwatch/erigon/turbo/rpchelper" @@ -168,3 +174,65 @@ func buildBlockResponse(db kv.Tx, blockNum uint64, fullTx bool) (map[string]inte } return response, err } + +func (api *ErigonImpl) GetBalanceChangesInBlock(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (map[common.Address]*hexutil.Big, error) { + tx, err := api.db.BeginRo(ctx) + if err != nil { + return nil, err + } + defer tx.Rollback() + + blockNumber, _, _, err := rpchelper.GetBlockNumber(blockNrOrHash, tx, api.filters) + if err != nil { + return nil, err + } + + c, err := tx.Cursor(kv.AccountChangeSet) + if err != nil { + return nil, err + } + defer c.Close() + + startkey := dbutils.EncodeBlockNumber(blockNumber) + + decodeFn := changeset.Mapper[kv.AccountChangeSet].Decode + + balancesMapping := make(map[common.Address]*hexutil.Big) + + newReader, err := rpchelper.CreateStateReader(ctx, tx, blockNrOrHash, api.filters, api.stateCache) + if err != nil { + return nil, err + } + + for dbKey, dbValue, _ := c.Seek(startkey); bytes.Equal(dbKey, startkey) && dbKey != nil; dbKey, dbValue, _ = c.Next() { + _, addressBytes, v, err := decodeFn(dbKey, dbValue) + if err != nil { + return nil, err + } + + var oldAcc accounts.Account + if err = oldAcc.DecodeForStorage(v); err != nil { + return nil, err + } + oldBalance := oldAcc.Balance + + address := common.BytesToAddress(addressBytes) + + newAcc, err := newReader.ReadAccountData(address) + if err != nil { + return nil, err + } + + newBalance := uint256.NewInt(0) + if newAcc != nil { + newBalance = &newAcc.Balance + } + + if !oldBalance.Eq(newBalance) { + newBalanceDesc := (*hexutil.Big)(newBalance.ToBig()) + balancesMapping[address] = newBalanceDesc + } + } + + return balancesMapping, nil +} diff --git a/cmd/rpcdaemon/commands/eth_api_test.go b/cmd/rpcdaemon/commands/eth_api_test.go index cc940c43e66..043f620db7b 100644 --- a/cmd/rpcdaemon/commands/eth_api_test.go +++ b/cmd/rpcdaemon/commands/eth_api_test.go @@ -5,6 +5,8 @@ import ( "fmt" "testing" + "github.com/holiman/uint256" + "github.com/ledgerwatch/erigon/common/hexutil" "github.com/ledgerwatch/erigon/core" "github.com/ledgerwatch/erigon/internal/ethapi" "github.com/ledgerwatch/erigon/rpc" @@ -16,6 +18,29 @@ import ( "github.com/ledgerwatch/erigon/common" ) +func TestGetBalanceChangesInBlock(t *testing.T) { + assert := assert.New(t) + myBlockNum := rpc.BlockNumberOrHashWithNumber(0) + + db := rpcdaemontest.CreateTestKV(t) + stateCache := kvcache.New(kvcache.DefaultCoherentConfig) + api := NewErigonAPI(NewBaseApi(nil, stateCache, snapshotsync.NewBlockReader(), false), db, nil) + balances, err := api.GetBalanceChangesInBlock(context.Background(), myBlockNum) + if err != nil { + t.Errorf("calling GetBalanceChangesInBlock resulted in an error: %v", err) + } + expected := map[common.Address]*hexutil.Big{ + common.HexToAddress("0x0D3ab14BBaD3D99F4203bd7a11aCB94882050E7e"): (*hexutil.Big)(uint256.NewInt(200000000000000000).ToBig()), + common.HexToAddress("0x703c4b2bD70c169f5717101CaeE543299Fc946C7"): (*hexutil.Big)(uint256.NewInt(300000000000000000).ToBig()), + common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7"): (*hexutil.Big)(uint256.NewInt(9000000000000000000).ToBig()), + } + assert.Equal(len(expected), len(balances)) + for i := range balances { + assert.Contains(expected, i, "%s is not expected to be present in the output.", i) + assert.Equal(balances[i], expected[i], "the value for %s is expected to be %v, but got %v.", i, expected[i], balances[i]) + } +} + func TestGetTransactionReceipt(t *testing.T) { db := rpcdaemontest.CreateTestKV(t) stateCache := kvcache.New(kvcache.DefaultCoherentConfig)