diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go index aedf2814ad..2fe4288892 100644 --- a/ethclient/ethclient.go +++ b/ethclient/ethclient.go @@ -268,6 +268,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)) + + return result, err +} + func toBlockNumArg(number *big.Int) string { if number == nil { return "latest" diff --git a/ethclient/ethclient_test.go b/ethclient/ethclient_test.go index 2e464cd42b..66f5b22a5e 100644 --- a/ethclient/ethclient_test.go +++ b/ethclient/ethclient_test.go @@ -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 := ð.Config{Genesis: genesis} + config := ð.Config{Genesis: genesis, NoPrefetch: true} config.Ethash.PowMode = ethash.ModeFake ethservice, err = eth.New(ctx, config) return ethservice, err @@ -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() @@ -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() @@ -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() @@ -319,3 +367,46 @@ func TestTransactionInBlockInterrupted(t *testing.T) { t.Fatal("error should not be nil") } } + +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()) + } +} diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 07142d66be..677907f070 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1241,6 +1241,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 { + tx := txs[index] + 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), + "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