Skip to content
Merged
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
4 changes: 4 additions & 0 deletions eth/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,10 @@ func (s *Ethereum) ResetWithGenesisBlock(gb *types.Block) {
s.blockchain.ResetWithGenesisBlock(gb)
}

func (s *Ethereum) PublicBlockChainAPI() *ethapi.PublicBlockChainAPI {
return s.handler.ethAPI
}

func (s *Ethereum) Etherbase() (eb common.Address, err error) {
s.lock.RLock()
etherbase := s.etherbase
Expand Down
5 changes: 4 additions & 1 deletion internal/ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,7 @@ func (s *PublicBlockChainAPI) GetTransactionReceiptsByBlock(ctx context.Context,
"contractAddress": nil,
"logs": receipt.Logs,
"logsBloom": receipt.Bloom,
"type": hexutil.Uint(tx.Type()),
}

// Assign receipt status or post state.
Expand All @@ -681,9 +682,11 @@ func (s *PublicBlockChainAPI) GetTransactionReceiptsByBlock(ctx context.Context,
if receipt.Logs == nil {
fields["logs"] = [][]*types.Log{}
}
if borReceipt != nil {

if borReceipt != nil && idx == len(receipts)-1 {
fields["transactionHash"] = txHash
}

// If the ContractAddress is 20 0x0 bytes, assume it is not a contract creation
if receipt.ContractAddress != (common.Address{}) {
fields["contractAddress"] = receipt.ContractAddress
Expand Down
292 changes: 168 additions & 124 deletions tests/bor/bor_api_test.go
Original file line number Diff line number Diff line change
@@ -1,168 +1,212 @@
//go:build integration

package bor

import (
"context"
"math/big"
"testing"

"github.com/golang/mock/gomock"
"github.com/stretchr/testify/require"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/bor"
"github.com/ethereum/go-ethereum/consensus/bor/clerk"
"github.com/ethereum/go-ethereum/consensus/bor/heimdall/checkpoint"
"github.com/ethereum/go-ethereum/consensus/bor/valset"
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/tests/bor/mocks"

"github.com/stretchr/testify/assert"
)

func TestGetTransactionReceiptsByBlock(t *testing.T) {
init := buildEthereumInstance(t, rawdb.NewMemoryDatabase())
chain := init.ethereum.BlockChain()
engine := init.ethereum.Engine()

ctrl := gomock.NewController(t)
defer ctrl.Finish()

_bor := engine.(*bor.Bor)
defer _bor.Close()

// Mock /bor/span/1
res, _ := loadSpanFromFile(t)

h := mocks.NewMockIHeimdallClient(ctrl)

h.EXPECT().Span(gomock.Any(), uint64(1)).Return(&res.Result, nil).MinTimes(1)
h.EXPECT().Close().MinTimes(1)
h.EXPECT().FetchCheckpoint(gomock.Any(), int64(-1)).Return(&checkpoint.Checkpoint{
Proposer: res.Result.SelectedProducers[0].Address,
StartBlock: big.NewInt(0),
EndBlock: big.NewInt(int64(spanSize)),
}, nil).AnyTimes()

// Mock State Sync events
// at # sprintSize, events are fetched for [fromID, (block-sprint).Time)
fromID := uint64(1)
to := int64(chain.GetHeaderByNumber(0).Time)
sample := getSampleEventRecord(t)

// First query will be from [id=1, (block-sprint).Time]
eventRecords := []*clerk.EventRecordWithTime{
buildStateEvent(sample, 1, 1),
buildStateEvent(sample, 2, 2),
buildStateEvent(sample, 3, 3),
func duplicateInArray(arr []common.Hash) bool {
visited := make(map[common.Hash]bool, 0)
for i := 0; i < len(arr); i++ {
if visited[arr[i]] == true {
return true
} else {
visited[arr[i]] = true
}
}

h.EXPECT().StateSyncEvents(gomock.Any(), fromID, to).Return(eventRecords, nil).MinTimes(1)
_bor.SetHeimdallClient(h)
return false
}

// Insert blocks for 0th sprint
db := init.ethereum.ChainDb()
block := init.genesis.ToBlock(db)
func areDifferentHashes(receipts []map[string]interface{}) bool {
addresses := []common.Hash{}
for i := 0; i < len(receipts); i++ {
addresses = append(addresses, receipts[i]["transactionHash"].(common.Hash))
if duplicateInArray(addresses) {
return false
}
}

signer := types.LatestSigner(init.genesis.Config)
toAddress := common.HexToAddress("0x000000000000000000000000000000000000aaaa")
return true
}

currentValidators := []*valset.Validator{valset.NewValidator(addr, 10)}
txHashes := map[int]common.Hash{} // blockNumber -> txHash
func TestGetTransactionReceiptsByBlock(t *testing.T) {
t.Parallel()

var (
err error
nonce uint64
tx *types.Transaction
txs []*types.Transaction
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
addr = crypto.PubkeyToAddress(key1.PublicKey)
stack, _ = node.New(&node.DefaultConfig)
backend, _ = eth.New(stack, &ethconfig.Defaults)
db = backend.ChainDb()
hash1 = common.BytesToHash([]byte("topic1"))
hash2 = common.BytesToHash([]byte("topic2"))
hash3 = common.BytesToHash([]byte("topic3"))
hash4 = common.BytesToHash([]byte("topic4"))
hash5 = common.BytesToHash([]byte("topic5"))
)

for i := uint64(1); i <= sprintSize; i++ {
if IsSpanEnd(i) {
currentValidators = []*valset.Validator{valset.NewValidator(addr, 10)}
defer func() {
if err := stack.Close(); err != nil {
t.Error(err)
}
}()

if i%3 == 0 {
txdata := &types.LegacyTx{
Nonce: nonce,
To: &toAddress,
Gas: 30000,
GasPrice: newGwei(5),
}
genesis := core.GenesisBlockForTesting(db, addr, big.NewInt(1000000))
sprint := params.TestChainConfig.Bor.Sprint

nonce++
chain, receipts := core.GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), db, 6, func(i int, gen *core.BlockGen) {
switch i {

tx = types.NewTx(txdata)
tx, err = types.SignTx(tx, signer, key)
require.Nil(t, err, "an incorrect transaction or signer")

txs = []*types.Transaction{tx}
} else {
txs = nil
}

block = buildNextBlock(t, _bor, chain, block, nil, init.genesis.Config.Bor, txs, currentValidators)
insertNewBlock(t, chain, block)
case 1: // 1 normal transaction on block 2
receipt := types.NewReceipt(nil, false, 0)
receipt.Logs = []*types.Log{
{
Address: addr,
Topics: []common.Hash{hash1},
},
}
gen.AddUncheckedReceipt(receipt)
gen.AddUncheckedTx(types.NewTransaction(24, common.HexToAddress("0x24"), big.NewInt(24), 24, gen.BaseFee(), nil))

case 2: // 2 normal transactions on block 3
receipt := types.NewReceipt(nil, false, 0)
receipt.Logs = []*types.Log{
{
Address: addr,
Topics: []common.Hash{hash2},
},
}
gen.AddUncheckedReceipt(receipt)
gen.AddUncheckedTx(types.NewTransaction(992, common.HexToAddress("0x992"), big.NewInt(992), 992, gen.BaseFee(), nil))

receipt2 := types.NewReceipt(nil, false, 0)
receipt2.Logs = []*types.Log{
{
Address: addr,
Topics: []common.Hash{hash3},
},
}
gen.AddUncheckedReceipt(receipt2)
gen.AddUncheckedTx(types.NewTransaction(993, common.HexToAddress("0x993"), big.NewInt(993), 993, gen.BaseFee(), nil))

case 3: // 1 normal transaction, 1 state-sync transaction on block 4
receipt := types.NewReceipt(nil, false, 0)
receipt.Logs = []*types.Log{
{
Address: addr,
Topics: []common.Hash{hash4},
},
}
gen.AddUncheckedReceipt(receipt)
gen.AddUncheckedTx(types.NewTransaction(1000, common.HexToAddress("0x0"), big.NewInt(1000), 1000, gen.BaseFee(), nil))

// state-sync transaction
receipt2 := types.NewReceipt(nil, false, 0)
receipt2.Logs = []*types.Log{
{
Address: addr,
Topics: []common.Hash{hash5},
},
}
gen.AddUncheckedReceipt(receipt2)
// not adding unchecked tx as it will be added as a state-sync tx later

if len(txs) != 0 {
txHashes[int(block.Number().Uint64())] = tx.Hash()
}
}
})

// state 6 was not written
//
fromID = uint64(4)
to = int64(chain.GetHeaderByNumber(sprintSize).Time)
for i, block := range chain {
// write the block to database
rawdb.WriteBlock(db, block)
rawdb.WriteCanonicalHash(db, block.Hash(), block.NumberU64())
rawdb.WriteHeadBlockHash(db, block.Hash())

eventRecords = []*clerk.EventRecordWithTime{
buildStateEvent(sample, 4, 4),
buildStateEvent(sample, 5, 5),
}
h.EXPECT().StateSyncEvents(gomock.Any(), fromID, to).Return(eventRecords, nil).MinTimes(1)
blockBatch := db.NewBatch()

for i := sprintSize + 1; i <= spanSize; i++ {
block = buildNextBlock(t, _bor, chain, block, nil, init.genesis.Config.Bor, nil, currentValidators)
insertNewBlock(t, chain, block)
}

ethAPI := ethapi.NewPublicBlockChainAPI(init.ethereum.APIBackend)
txPoolAPI := ethapi.NewPublicTransactionPoolAPI(init.ethereum.APIBackend, nil)
if i%int(sprint-1) != 0 {
// if it is not sprint start write all the transactions as normal transactions.
rawdb.WriteReceipts(db, block.Hash(), block.NumberU64(), receipts[i])
} else {
// check for blocks with receipts. Since in state-sync block, we have 1 normal txn and 1 state-sync txn.
if len(receipts[i]) > 0 {
// We write receipts for the normal transaction.
rawdb.WriteReceipts(db, block.Hash(), block.NumberU64(), receipts[i][:1])

// write the state-sync receipts to database => receipts[i][1:] => receipts[i][1]
// State sync logs don't have tx index, tx hash and other necessary fields, DeriveFieldsForBorLogs will fill those fields for websocket subscriptions
// DeriveFieldsForBorLogs argurments:
// 1. State-sync logs
// 2. Block Hash
// 3. Block Number
// 4. Transactions in the block(except state-sync) i.e. 1 in our case
// 5. AllLogs -(minus) StateSyncLogs ; since we only have state-sync tx, it will be 1
types.DeriveFieldsForBorLogs(receipts[i][1].Logs, block.Hash(), block.NumberU64(), uint(1), uint(1))

rawdb.WriteBorReceipt(blockBatch, block.Hash(), block.NumberU64(), &types.ReceiptForStorage{
Status: types.ReceiptStatusSuccessful, // make receipt status successful
Logs: receipts[i][1].Logs,
})

rawdb.WriteBorTxLookupEntry(blockBatch, block.Hash(), block.NumberU64())

for n := 0; n < int(spanSize)+1; n++ {
rpcNumber := rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(n))
}

txs, err := ethAPI.GetTransactionReceiptsByBlock(context.Background(), rpcNumber)
require.Nil(t, err)
}

tx := txPoolAPI.GetTransactionByBlockNumberAndIndex(context.Background(), rpc.BlockNumber(n), 0)
if err := blockBatch.Write(); err != nil {
t.Error("Failed to write block into disk", "err", err)
}
}

blockMap, err := ethAPI.GetBlockByNumber(context.Background(), rpc.BlockNumber(n), true)
require.Nil(t, err)
publicBlockchainAPI := backend.PublicBlockChainAPI()

expectedTxHash, ok := txHashes[n]
// FIXME: add `IsSprintStart(uint64(n)) || IsSpanStart(uint64(n))` after adding a full state receiver contract
if ok {
require.Len(t, txs, 1)
// check 1 : zero transactions
receiptsOut, err := publicBlockchainAPI.GetTransactionReceiptsByBlock(context.Background(), rpc.BlockNumberOrHashWithNumber(1))
if err != nil {
t.Error(err)
}

require.NotNil(t, tx, "not nil receipt expected")
assert.Equal(t, 0, len(receiptsOut))

require.Equal(t, expectedTxHash, tx.Hash, "got different from expected receipt")
// check 2 : one transactions ( normal )
receiptsOut, err = publicBlockchainAPI.GetTransactionReceiptsByBlock(context.Background(), rpc.BlockNumberOrHashWithNumber(2))
if err != nil {
t.Error(err)
}

blockTxs, ok := blockMap["transactions"].([]interface{})
require.Len(t, blockTxs, 1)
assert.Equal(t, 1, len(receiptsOut))
assert.True(t, areDifferentHashes(receiptsOut))

blockTx, ok := blockTxs[0].(*ethapi.RPCTransaction)
require.True(t, ok)
require.Equal(t, expectedTxHash, blockTx.Hash)
} else {
require.Len(t, txs, 0)
// check 3 : two transactions ( both normal )
receiptsOut, err = publicBlockchainAPI.GetTransactionReceiptsByBlock(context.Background(), rpc.BlockNumberOrHashWithNumber(3))
if err != nil {
t.Error(err)
}
Comment thread
0xsharma marked this conversation as resolved.

require.Nil(t, tx, "nil receipt expected")
assert.Equal(t, 2, len(receiptsOut))
assert.True(t, areDifferentHashes(receiptsOut))

blockTxs, _ := blockMap["transactions"].([]interface{})
require.Len(t, blockTxs, 0)
}
// check 4 : two transactions ( one normal + one state-sync)
receiptsOut, err = publicBlockchainAPI.GetTransactionReceiptsByBlock(context.Background(), rpc.BlockNumberOrHashWithNumber(4))
if err != nil {
t.Error(err)
}

assert.Equal(t, 2, len(receiptsOut))
assert.True(t, areDifferentHashes(receiptsOut))
}
2 changes: 1 addition & 1 deletion tests/bor/bor_filter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ func TestBorFilters(t *testing.T) {
// since all the transactions are state-sync, we will not include them as normal receipts
rawdb.WriteReceipts(db, block.Hash(), block.NumberU64(), []*types.Receipt{})

// check for blocks with receipts. Since the only receipt is state-sync, we can chack the length of receipts
// check for blocks with receipts. Since the only receipt is state-sync, we can check the length of receipts
if len(receipts[i]) > 0 {
// write the state-sync receipts to database
// State sync logs don't have tx index, tx hash and other necessary fields, DeriveFieldsForBorLogs will fill those fields for websocket subscriptions
Expand Down