Skip to content
8 changes: 3 additions & 5 deletions sae/rpc/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
})
}
Expand Down
206 changes: 156 additions & 50 deletions sae/rpc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What's the difference between this call and the one above?? To be clear, out of curiosity - these seem like the same API but with different names

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Geth weirdness I think. eth_getRawTransactionByHash is an undocumented API while debug_getRawTransaction is documented, but would only be available here when EnableDBInspecting is enabled.

They both end up calling GetTransaction -> rawdb.ReadTransaction. 🤷

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
Expand Down Expand Up @@ -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 (
Expand Down Expand Up @@ -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
Expand All @@ -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),
})
}

Expand All @@ -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...)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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",
Expand All @@ -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() {
Expand Down Expand Up @@ -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),
},
}...)
}

Expand Down Expand Up @@ -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"),
},
}...)
}

Expand All @@ -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",
Expand All @@ -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() {
Expand Down Expand Up @@ -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),
},
}...)
}

Expand Down
Loading