Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RPC API: get receipts by block number #19721

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions ethclient/ethclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,14 @@ func (ec *Client) TransactionReceipt(ctx context.Context, txHash common.Hash) (*
return r, err
}

// BlockReceipts returns the receipts of a transaction by block number.
func (ec *Client) BlockReceipts(ctx context.Context, number *big.Int) (types.Receipts, error) {
var result types.Receipts
err := ec.c.CallContext(ctx, &result, "eth_getBlockReceipts", toBlockNumArg(number))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that both web3ext.go and web3.js need to be modified to bind "eth_getBlockReceipts" and "GetBlockReceipts", and expose "eth_getBlockReceipts".


return result, err
}

func toBlockNumArg(number *big.Int) string {
if number == nil {
return "latest"
Expand Down
108 changes: 100 additions & 8 deletions ethclient/ethclient_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,15 +168,24 @@ var (
testBalance = big.NewInt(2e10)
)

func newTestBackend(t *testing.T) (*node.Node, []*types.Block) {
// Generate test chain.
genesis, blocks := generateTestChain()
func newTestBackend(t *testing.T, withTx bool) (*node.Node, []*types.Block) {
var (
genesis *core.Genesis
blocks []*types.Block
)

// Generate test chain
if withTx {
genesis, blocks = generateTestChainWithTx(t)
} else {
genesis, blocks = generateTestChain()
}

// Start Ethereum service.
var ethservice *eth.Ethereum
n, err := node.New(&node.Config{})
n.Register(func(ctx *node.ServiceContext) (node.Service, error) {
config := &eth.Config{Genesis: genesis}
config := &eth.Config{Genesis: genesis, NoPrefetch: true}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

without this parameter it produces nonce too high error. For details see #19723

config.Ethash.PowMode = ethash.ModeFake
ethservice, err = eth.New(ctx, config)
return ethservice, err
Expand Down Expand Up @@ -212,8 +221,47 @@ func generateTestChain() (*core.Genesis, []*types.Block) {
return genesis, blocks
}

func generateTestChainWithTx(t *testing.T) (*core.Genesis, []*types.Block) {
db := rawdb.NewMemoryDatabase()
config := params.AllEthashProtocolChanges
signer := types.NewEIP155Signer(config.ChainID)
genesis := &core.Genesis{
Config: config,
Alloc: core.GenesisAlloc{testAddr: {Balance: testBalance}},
ExtraData: []byte("test genesis"),
Timestamp: 9000,
}
generate := func(i int, g *core.BlockGen) {
g.OffsetTime(5)
g.SetExtra([]byte("test"))

switch i {
case 0:
tx, err := types.SignTx(types.NewTransaction(g.TxNonce(testAddr), common.HexToAddress("0x1"), big.NewInt(1), 21000, big.NewInt(1), nil), signer, testKey)
if err != nil {
t.Fatal(err)
}

g.AddTx(tx)
case 1:
tx, err := types.SignTx(types.NewTransaction(g.TxNonce(testAddr), common.HexToAddress("0x2"), big.NewInt(2), 21000, big.NewInt(1), nil), signer, testKey)
if err != nil {
t.Fatal(err)
}

g.AddTx(tx)
}
}
gblock := genesis.ToBlock(db)
engine := ethash.NewFaker()
blocks, _ := core.GenerateChain(config, gblock, engine, db, 2, generate)
blocks = append([]*types.Block{gblock}, blocks...)

return genesis, blocks
}

func TestHeader(t *testing.T) {
backend, chain := newTestBackend(t)
backend, chain := newTestBackend(t, false)
client, _ := backend.Attach()
defer backend.Stop()
defer client.Close()
Expand Down Expand Up @@ -257,7 +305,7 @@ func TestHeader(t *testing.T) {
}

func TestBalanceAt(t *testing.T) {
backend, _ := newTestBackend(t)
backend, _ := newTestBackend(t, false)
client, _ := backend.Attach()
defer backend.Stop()
defer client.Close()
Expand Down Expand Up @@ -303,7 +351,7 @@ func TestBalanceAt(t *testing.T) {
}

func TestTransactionInBlockInterrupted(t *testing.T) {
backend, _ := newTestBackend(t)
backend, _ := newTestBackend(t, false)
client, _ := backend.Attach()
defer backend.Stop()
defer client.Close()
Expand All @@ -320,8 +368,51 @@ func TestTransactionInBlockInterrupted(t *testing.T) {
}
}

func TestTransactionInBlock(t *testing.T) {
backend, blocks := newTestBackend(t, true)
client, _ := backend.Attach()
defer backend.Stop()
defer client.Close()

ec := NewClient(client)
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()

tx, err := ec.TransactionInBlock(ctx, blocks[1].Hash(), 0)
if err != nil {
t.Fatalf("TransactionInBlock error = %q", err)
}
if tx == nil {
t.Fatal("transaction should not be nil")
}
}

func TestBlockReceipts(t *testing.T) {
backend, blocks := newTestBackend(t, true)
client, _ := backend.Attach()
defer backend.Stop()
defer client.Close()

ec := NewClient(client)
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()

receipts, err := ec.BlockReceipts(ctx, big.NewInt(1))
if err != nil {
t.Fatalf("BlockReceipts error = %q", err)
}

if len(receipts) == 0 {
t.Fatal("receipts should not be 0 len")
}

if receipts[0].BlockHash != blocks[1].Hash() {
t.Fatalf("BlockReceipts block hash mismatch, got %v, want %v", receipts[0].BlockHash, blocks[1].Hash())
}
}

func TestChainID(t *testing.T) {
backend, _ := newTestBackend(t)
backend, _ := newTestBackend(t, false)
client, _ := backend.Attach()
defer backend.Stop()
defer client.Close()
Expand All @@ -333,5 +424,6 @@ func TestChainID(t *testing.T) {
}
if id == nil || id.Cmp(params.AllEthashProtocolChanges.ChainID) != 0 {
t.Fatalf("ChainID returned wrong number: %+v", id)

}
}
65 changes: 65 additions & 0 deletions internal/ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -1246,6 +1246,71 @@ func (s *PublicTransactionPoolAPI) GetTransactionReceipt(ctx context.Context, ha
return fields, nil
}

// GetBlockReceipts returns the block receipts for the given block number.
func (s *PublicTransactionPoolAPI) GetBlockReceipts(ctx context.Context, blockNr rpc.BlockNumber) ([]map[string]interface{}, error) {
block, err := s.b.BlockByNumber(ctx, blockNr)
if err != nil {
return nil, err
}
if block == nil {
return nil, nil
}

txs := block.Transactions()

receipts, err := s.b.GetReceipts(ctx, block.Hash())
if err != nil {
return nil, err
}

if len(receipts) != len(txs) {
return nil, fmt.Errorf("receipt and transaction count mismatch")
}

result := make([]map[string]interface{}, 0, len(receipts))

for index, receipt := range receipts {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

receipt.TransactionIndex could stand for index, so index here makes no sense. Suggest to replace it with blank identifier "_"

tx := txs[index]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Directly using index of receipts to look for corresponding tx is not so rigorous. Suggest to modify it to:

tx := txs[receipt.TransactionIndex]

var signer types.Signer = types.FrontierSigner{}
if tx.Protected() {
signer = types.NewEIP155Signer(tx.ChainId())
}
from, _ := types.Sender(signer, tx)

fields := map[string]interface{}{
"blockHash": block.Hash(),
"blockNumber": hexutil.Uint64(blockNr),
"transactionHash": receipt.TxHash,
"transactionIndex": hexutil.Uint64(index),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest to replace index with receipt.TransactionIndex

"from": from,
"to": tx.To(),
"gasUsed": hexutil.Uint64(receipt.GasUsed),
"cumulativeGasUsed": hexutil.Uint64(receipt.CumulativeGasUsed),
"contractAddress": nil,
"logs": receipt.Logs,
"logsBloom": receipt.Bloom,
}

// Assign receipt status or post state.
if len(receipt.PostState) > 0 {
fields["root"] = hexutil.Bytes(receipt.PostState)
} else {
fields["status"] = hexutil.Uint(receipt.Status)
}
if receipt.Logs == nil {
fields["logs"] = [][]*types.Log{}
}
// If the ContractAddress is 20 0x0 bytes, assume it is not a contract creation
if receipt.ContractAddress != (common.Address{}) {
fields["contractAddress"] = receipt.ContractAddress
}

result = append(result, fields)
}

return result, nil
}

// sign is a helper function that signs a transaction with the private key of the given address.
func (s *PublicTransactionPoolAPI) sign(addr common.Address, tx *types.Transaction) (*types.Transaction, error) {
// Look up the wallet containing the requested signer
Expand Down