From a33a1c91420ad529a5c7a8edf4881c8353870115 Mon Sep 17 00:00:00 2001 From: aodhgan Date: Wed, 1 Oct 2025 17:41:47 -0700 Subject: [PATCH] add happy and timeout unit tests --- internal/ethapi/api_test.go | 142 +++++++++++++++++++++++++++++++++++- 1 file changed, 140 insertions(+), 2 deletions(-) diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index d3278c04e7..04ac249e4b 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -440,6 +440,14 @@ type testBackend struct { pending *types.Block pendingReceipts types.Receipts + + // test-only fields for SendRawTransactionSync + autoMine bool + mined bool + syncDefaultTO time.Duration + syncMaxTO time.Duration + sentTx *types.Transaction + sentTxHash common.Hash } func newTestBackend(t *testing.T, n int, gspec *core.Genesis, engine consensus.Engine, generator func(i int, b *core.BlockGen)) *testBackend { @@ -592,14 +600,38 @@ func (b testBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscr func (b testBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription { panic("implement me") } -func (b testBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error { - panic("implement me") +func (b *testBackend) SendTx(ctx context.Context, tx *types.Transaction) error { + b.sentTx = tx + b.sentTxHash = tx.Hash() + if b.autoMine { + b.mined = true + } + return nil } func (b testBackend) GetCanonicalTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) { + // in-memory fast path for tests + if b.mined && txHash == b.sentTxHash { + return true, b.sentTx, common.HexToHash("0x01"), 1, 0 + } + // fallback to existing DB-backed path tx, blockHash, blockNumber, index := rawdb.ReadCanonicalTransaction(b.db, txHash) return tx != nil, tx, blockHash, blockNumber, index } func (b testBackend) GetCanonicalReceipt(tx *types.Transaction, blockHash common.Hash, blockNumber, blockIndex uint64) (*types.Receipt, error) { + // In-memory fast path used by tests + if b.mined && tx != nil && tx.Hash() == b.sentTxHash && blockHash == common.HexToHash("0x01") && blockNumber == 1 && blockIndex == 0 { + return &types.Receipt{ + Type: tx.Type(), + Status: types.ReceiptStatusSuccessful, + CumulativeGasUsed: 21000, + GasUsed: 21000, + EffectiveGasPrice: big.NewInt(1), + BlockHash: blockHash, + BlockNumber: new(big.Int).SetUint64(blockNumber), + TransactionIndex: 0, + }, nil + } + // Fallback: use the chain's canonical receipt (DB-backed) return b.chain.GetCanonicalReceipt(tx, blockHash, blockNumber, blockIndex) } func (b testBackend) TxIndexDone() bool { @@ -3889,3 +3921,109 @@ func (b configTimeBackend) HeaderByNumber(_ context.Context, n rpc.BlockNumber) func (b configTimeBackend) CurrentHeader() *types.Header { return &types.Header{Time: b.time} } + +func (b *testBackend) RPCTxSyncDefaultTimeout() time.Duration { + if b.syncDefaultTO != 0 { + return b.syncDefaultTO + } + return 2 * time.Second +} +func (b *testBackend) RPCTxSyncMaxTimeout() time.Duration { + if b.syncMaxTO != 0 { + return b.syncMaxTO + } + return 5 * time.Minute +} +func (b *backendMock) RPCTxSyncDefaultTimeout() time.Duration { return 2 * time.Second } +func (b *backendMock) RPCTxSyncMaxTimeout() time.Duration { return 5 * time.Minute } + +func makeSignedRaw(t *testing.T, api *TransactionAPI, from, to common.Address, value *big.Int) (hexutil.Bytes, *types.Transaction) { + t.Helper() + + fillRes, err := api.FillTransaction(context.Background(), TransactionArgs{ + From: &from, + To: &to, + Value: (*hexutil.Big)(value), + }) + if err != nil { + t.Fatalf("FillTransaction failed: %v", err) + } + signRes, err := api.SignTransaction(context.Background(), argsFromTransaction(fillRes.Tx, from)) + if err != nil { + t.Fatalf("SignTransaction failed: %v", err) + } + return signRes.Raw, signRes.Tx +} + +// makeSelfSignedRaw is a convenience for a 0-ETH self-transfer. +func makeSelfSignedRaw(t *testing.T, api *TransactionAPI, addr common.Address) (hexutil.Bytes, *types.Transaction) { + return makeSignedRaw(t, api, addr, addr, big.NewInt(0)) +} + +func TestSendRawTransactionSync_Success(t *testing.T) { + t.Parallel() + genesis := &core.Genesis{ + Config: params.TestChainConfig, + Alloc: types.GenesisAlloc{}, + } + b := newTestBackend(t, 0, genesis, ethash.NewFaker(), nil) + b.autoMine = true // immediately “mines” the tx in-memory + + api := NewTransactionAPI(b, new(AddrLocker)) + + raw, _ := makeSelfSignedRaw(t, api, b.acc.Address) + + receipt, err := api.SendRawTransactionSync(context.Background(), raw, nil) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if receipt == nil { + t.Fatalf("expected non-nil receipt") + } + if _, ok := receipt["blockNumber"]; !ok { + t.Fatalf("expected blockNumber in receipt, got %#v", receipt) + } +} + +func TestSendRawTransactionSync_Timeout(t *testing.T) { + t.Parallel() + + genesis := &core.Genesis{ + Config: params.TestChainConfig, + Alloc: types.GenesisAlloc{}, + } + b := newTestBackend(t, 0, genesis, ethash.NewFaker(), nil) + b.autoMine = false // don't mine, should time out + + api := NewTransactionAPI(b, new(AddrLocker)) + + raw, _ := makeSelfSignedRaw(t, api, b.acc.Address) + + timeout := hexutil.Uint64(200) // 200ms + receipt, err := api.SendRawTransactionSync(context.Background(), raw, &timeout) + + if receipt != nil { + t.Fatalf("expected nil receipt, got %#v", receipt) + } + if err == nil { + t.Fatalf("expected timeout error, got nil") + } + // assert error shape & data (hash) + var de interface { + ErrorCode() int + ErrorData() interface{} + } + if !errors.As(err, &de) { + t.Fatalf("expected data error with code/data, got %T %v", err, err) + } + if de.ErrorCode() != errCodeTxSyncTimeout { + t.Fatalf("expected code %d, got %d", errCodeTxSyncTimeout, de.ErrorCode()) + } + tx := new(types.Transaction) + if e := tx.UnmarshalBinary(raw); e != nil { + t.Fatal(e) + } + if got, want := de.ErrorData(), tx.Hash().Hex(); got != want { + t.Fatalf("expected ErrorData=%s, got %v", want, got) + } +}