diff --git a/sae/rpc/server.go b/sae/rpc/server.go index 941a0a8b..d62948da 100644 --- a/sae/rpc/server.go +++ b/sae/rpc/server.go @@ -124,14 +124,12 @@ func (b *backend) server(filter *filters.FilterAPI) (*rpc.Server, error) { // - debug_dbAncient // - debug_dbAncients // - debug_dbGet - // - debug_getRawTransaction - // - debug_printBlock - // - debug_setHead (no-op, logs info) - // - // TODO: implement once BlockByNumberOrHash and GetReceipts exist: // - debug_getRawBlock // - debug_getRawHeader // - debug_getRawReceipts + // - debug_getRawTransaction + // - debug_printBlock + // - debug_setHead (no-op, logs info) "debug", ethapi.NewDebugAPI(b), }) } diff --git a/sae/rpc_test.go b/sae/rpc_test.go index 6e1114d4..4c840e6a 100644 --- a/sae/rpc_test.go +++ b/sae/rpc_test.go @@ -29,6 +29,7 @@ import ( "github.com/ava-labs/libevm/libevm/hookstest" "github.com/ava-labs/libevm/libevm/options" "github.com/ava-labs/libevm/params" + "github.com/ava-labs/libevm/rlp" "github.com/ava-labs/libevm/rpc" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" @@ -529,7 +530,7 @@ func TestEthGetters(t *testing.T) { timeOpt, vmTime := withVMTime(t, time.Unix(saeparams.TauSeconds, 0)) blockingPrecompile := common.Address{'b', 'l', 'o', 'c', 'k'} precompileOpt, unblock := withBlockingPrecompile(blockingPrecompile) - ctx, sut := newSUT(t, 1, timeOpt, precompileOpt) + ctx, sut := newSUT(t, 1, timeOpt, precompileOpt, withDebugAPI()) t.Cleanup(unblock) t.Run("unknown_hashes", func(t *testing.T) { @@ -604,6 +605,39 @@ func TestEthGetters(t *testing.T) { }) } +func TestMempoolTxGetters(t *testing.T) { + ctx, sut := newSUT(t, 1, withDebugAPI()) + + mempoolTx := sut.wallet.SetNonceAndSign(t, 0, &types.DynamicFeeTx{ + To: &zeroAddr, + Gas: params.TxGas, + GasFeeCap: big.NewInt(1), + }) + sut.mustSendTx(t, mempoolTx) + sut.syncMempool(t) + + marshaled, err := mempoolTx.MarshalBinary() + require.NoErrorf(t, err, "%T.MarshalBinary()", mempoolTx) + + sut.testRPC(ctx, t, []rpcTest{ + { + method: "eth_getTransactionByHash", + args: []any{mempoolTx.Hash()}, + want: mempoolTx, + }, + { + method: "eth_getRawTransactionByHash", + args: []any{mempoolTx.Hash()}, + want: hexutil.Bytes(marshaled), + }, + { + method: "debug_getRawTransaction", + args: []any{mempoolTx.Hash()}, + want: hexutil.Bytes(marshaled), + }, + }...) +} + func TestGetLogs(t *testing.T) { // We shorten section size to reduce number of required blocks in the test. const bloomSectionSize = 8 @@ -788,7 +822,7 @@ func TestGetReceipts(t *testing.T) { timeOpt, vmTime := withVMTime(t, time.Unix(saeparams.TauSeconds, 0)) precompileOpt, unblock := withBlockingPrecompile(blockingPrecompile) - ctx, sut := newSUT(t, 1, timeOpt, precompileOpt) + ctx, sut := newSUT(t, 1, timeOpt, precompileOpt, withDebugAPI()) t.Cleanup(unblock) var ( @@ -841,6 +875,16 @@ func TestGetReceipts(t *testing.T) { GasPrice: big.NewInt(1), })) + marshalReceipts := func(rs []*types.Receipt) []hexutil.Bytes { + raw := make([]hexutil.Bytes, len(rs)) + for i, r := range rs { + buf, err := r.MarshalBinary() + require.NoErrorf(t, err, "receipts[%d].MarshalBinary()", i) + raw[i] = buf + } + return raw + } + var tests []rpcTest for _, tc := range []struct { id rpc.BlockNumberOrHash @@ -858,15 +902,39 @@ func TestGetReceipts(t *testing.T) { id: rpc.BlockNumberOrHashWithHash(unsettled.Hash(), true), want: wantUnsettled, }, + { + id: rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(onDisk.Height())), //nolint:gosec // Test block heights won't overflow + want: wantOnDisk, + }, + { + id: rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(settled.Height())), //nolint:gosec // Test block heights won't overflow + want: wantSettled, + }, + { + id: rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(unsettled.Height())), //nolint:gosec // Test block heights won't overflow + want: wantUnsettled, + }, { id: rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber), want: wantUnsettled, }, + { + id: rpc.BlockNumberOrHashWithNumber(rpc.SafeBlockNumber), + want: wantSettled, + }, + { + id: rpc.BlockNumberOrHashWithNumber(rpc.FinalizedBlockNumber), + want: wantSettled, + }, } { tests = append(tests, rpcTest{ method: "eth_getBlockReceipts", args: []any{tc.id.String()}, want: tc.want, + }, rpcTest{ + method: "debug_getRawReceipts", + args: []any{tc.id.String()}, + want: marshalReceipts(tc.want), }) } @@ -892,21 +960,41 @@ func TestGetReceipts(t *testing.T) { args: []any{common.Hash{}}, want: ([]*types.Receipt)(nil), }, + { + method: "debug_getRawReceipts", + args: []any{common.Hash{}}, + want: []hexutil.Bytes{}, + }, { method: "eth_getBlockReceipts", args: []any{genesis.Hash()}, want: []*types.Receipt{}, }, + { + method: "debug_getRawReceipts", + args: []any{genesis.Hash()}, + want: []hexutil.Bytes{}, + }, { method: "eth_getBlockReceipts", args: []any{pending.Hash()}, want: ([]*types.Receipt)(nil), }, + { + method: "debug_getRawReceipts", + args: []any{pending.Hash()}, + want: []hexutil.Bytes{}, + }, { method: "eth_getBlockReceipts", args: []any{hexutil.Uint64(pending.Height())}, want: ([]*types.Receipt)(nil), }, + { + method: "debug_getRawReceipts", + args: []any{hexutil.Uint64(pending.Height())}, + want: []hexutil.Bytes{}, + }, }...) sut.testRPC(ctx, t, tests...) @@ -1159,6 +1247,11 @@ func TestDebugRPCs(t *testing.T) { method: "debug_dbAncients", wantErr: testerr.Contains("not supported"), }, + { + method: "debug_printBlock", + args: []any{uint64(1)}, // SUT only has genesis, so block 1 doesn't exist. + wantErr: testerr.Contains("not found"), + }, }...) // The profiling debug namespace is handled entirely by upstream code @@ -1189,58 +1282,16 @@ func TestDebugRPCs(t *testing.T) { }) } -func TestDebugGetRawTransaction(t *testing.T) { - ctx, sut := newSUT(t, 1, withDebugAPI()) - - tx := sut.wallet.SetNonceAndSign(t, 0, &types.DynamicFeeTx{ - To: &common.Address{}, - Gas: params.TxGas, - GasFeeCap: big.NewInt(1), - }) - b := sut.runConsensusLoop(t, tx) - require.NoErrorf(t, b.WaitUntilExecuted(ctx), "%T.WaitUntilExecuted()", b) - - marshaled, err := tx.MarshalBinary() - require.NoErrorf(t, err, "%T.MarshalBinary()", tx) - - // Mempool tx: send without building a block, then query. - mempoolTx := sut.wallet.SetNonceAndSign(t, 0, &types.DynamicFeeTx{ - To: &common.Address{}, - Gas: params.TxGas, - GasFeeCap: big.NewInt(1), - }) - sut.mustSendTx(t, mempoolTx) - sut.syncMempool(t) - - mempoolMarshaled, err := mempoolTx.MarshalBinary() - require.NoErrorf(t, err, "%T.MarshalBinary()", mempoolTx) - - t.Logf("Tx in block: %#x", tx.Hash()) - t.Logf("Tx in mempool: %#x", mempoolTx.Hash()) - - sut.testRPC(ctx, t, []rpcTest{ - { - method: "debug_getRawTransaction", - args: []any{tx.Hash()}, - want: hexutil.Bytes(marshaled), - }, - { - method: "debug_getRawTransaction", - args: []any{common.Hash{}}, - want: hexutil.Bytes(nil), - }, - { - method: "debug_getRawTransaction", - args: []any{mempoolTx.Hash()}, - want: hexutil.Bytes(mempoolMarshaled), - }, - }...) -} - func (s *SUT) testGetByHash(ctx context.Context, t *testing.T, want *types.Block) { t.Helper() testRPCGetter(ctx, t, "eth_getBlockByHash", s.BlockByHash, want.Hash(), want) + + wantBlockRLP, err := rlp.EncodeToBytes(want) + require.NoErrorf(t, err, "rlp.EncodeToBytes(%T)", want) + wantHeaderRLP, err := rlp.EncodeToBytes(want.Header()) + require.NoErrorf(t, err, "rlp.EncodeToBytes(%T)", want.Header()) + s.testRPC(ctx, t, []rpcTest{ { method: "eth_getBlockByHash", @@ -1262,6 +1313,16 @@ func (s *SUT) testGetByHash(ctx context.Context, t *testing.T, want *types.Block args: []any{want.Hash()}, want: hexutil.Uint(0), // SAE never has uncles (no reorgs) }, + { + method: "debug_getRawBlock", + args: []any{want.Hash()}, + want: hexutil.Bytes(wantBlockRLP), + }, + { + method: "debug_getRawHeader", + args: []any{want.Hash()}, + want: hexutil.Bytes(wantHeaderRLP), + }, }...) for i, wantTx := range want.Transactions() { @@ -1290,6 +1351,11 @@ func (s *SUT) testGetByHash(ctx context.Context, t *testing.T, want *types.Block args: []any{wantTx.Hash()}, want: hexutil.Bytes(marshaled), }, + { + method: "debug_getRawTransaction", + args: []any{wantTx.Hash()}, + want: hexutil.Bytes(marshaled), + }, }...) } @@ -1347,6 +1413,21 @@ func (s *SUT) testGetByUnknownHash(ctx context.Context, t *testing.T) { args: []any{common.Hash{}}, want: hexutil.Bytes(nil), }, + { + method: "debug_getRawTransaction", + args: []any{common.Hash{}}, + want: hexutil.Bytes(nil), + }, + { + method: "debug_getRawBlock", + args: []any{common.Hash{}}, + wantErr: testerr.Contains("not found"), + }, + { + method: "debug_getRawHeader", + args: []any{common.Hash{}}, + wantErr: testerr.Contains("not found"), + }, }...) } @@ -1357,6 +1438,11 @@ func (s *SUT) testGetByNumber(ctx context.Context, t *testing.T, want *types.Blo t.Helper() testRPCGetter(ctx, t, "eth_getBlockByNumber", s.BlockByNumber, big.NewInt(n.Int64()), want) + wantBlockRLP, err := rlp.EncodeToBytes(want) + require.NoErrorf(t, err, "rlp.EncodeToBytes(%T)", want) + wantHeaderRLP, err := rlp.EncodeToBytes(want.Header()) + require.NoErrorf(t, err, "rlp.EncodeToBytes(%T)", want.Header()) + s.testRPC(ctx, t, []rpcTest{ { method: "eth_getBlockByNumber", @@ -1378,6 +1464,16 @@ func (s *SUT) testGetByNumber(ctx context.Context, t *testing.T, want *types.Blo args: []any{n}, want: hexutil.Uint(0), // SAE never has uncles (no reorgs) }, + { + method: "debug_getRawBlock", + args: []any{n}, + want: hexutil.Bytes(wantBlockRLP), + }, + { + method: "debug_getRawHeader", + args: []any{n}, + want: hexutil.Bytes(wantHeaderRLP), + }, }...) for i, wantTx := range want.Transactions() { @@ -1444,6 +1540,16 @@ func (s *SUT) testGetByUnknownNumber(ctx context.Context, t *testing.T) { args: []any{n, hexutil.Uint(0)}, want: hexutil.Bytes(nil), }, + { + method: "debug_getRawBlock", + args: []any{n}, + want: hexutil.Bytes(nil), + }, + { + method: "debug_getRawHeader", + args: []any{n}, + want: hexutil.Bytes(nil), + }, }...) }