diff --git a/account/account.go b/account/account.go index 2c63d82d..6a70fe88 100644 --- a/account/account.go +++ b/account/account.go @@ -639,6 +639,10 @@ func (account *Account) BlockWithTxs(ctx context.Context, blockID rpc.BlockID) ( return account.provider.BlockWithTxs(ctx, blockID) } +func (account *Account) BlockWithReceipts(ctx context.Context, blockID rpc.BlockID) (interface{}, error) { + return account.provider.BlockWithReceipts(ctx, blockID) +} + // Call is a function that performs a function call on an Account. // // Parameters: diff --git a/mocks/mock_rpc_provider.go b/mocks/mock_rpc_provider.go index c11ef581..5b6b4923 100644 --- a/mocks/mock_rpc_provider.go +++ b/mocks/mock_rpc_provider.go @@ -130,6 +130,21 @@ func (mr *MockRpcProviderMockRecorder) BlockTransactionCount(ctx, blockID any) * return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BlockTransactionCount", reflect.TypeOf((*MockRpcProvider)(nil).BlockTransactionCount), ctx, blockID) } +// BlockWithReceipts mocks base method. +func (m *MockRpcProvider) BlockWithReceipts(ctx context.Context, blockID rpc.BlockID) (any, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BlockWithReceipts", ctx, blockID) + ret0, _ := ret[0].(any) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// BlockWithReceipts indicates an expected call of BlockWithReceipts. +func (mr *MockRpcProviderMockRecorder) BlockWithReceipts(ctx, blockID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BlockWithReceipts", reflect.TypeOf((*MockRpcProvider)(nil).BlockWithReceipts), ctx, blockID) +} + // BlockWithTxHashes mocks base method. func (m *MockRpcProvider) BlockWithTxHashes(ctx context.Context, blockID rpc.BlockID) (any, error) { m.ctrl.T.Helper() diff --git a/rpc/block.go b/rpc/block.go index fe730e27..e901b8e4 100644 --- a/rpc/block.go +++ b/rpc/block.go @@ -2,7 +2,9 @@ package rpc import ( "context" + "encoding/json" "errors" + "fmt" "github.com/NethermindEth/juno/core/felt" ) @@ -34,8 +36,8 @@ func (provider *Provider) BlockNumber(ctx context.Context) (uint64, error) { // - error: An error if any func (provider *Provider) BlockHashAndNumber(ctx context.Context) (*BlockHashAndNumberOutput, error) { var block BlockHashAndNumberOutput - if err := do(ctx, provider.c, "starknet_blockHashAndNumber", &block); err != nil { - return nil, tryUnwrapToRPCErr(err, ErrNoBlocks ) + if err := do(ctx, provider.c, "starknet_blockHashAndNumber", &block); err != nil { + return nil, tryUnwrapToRPCErr(err, ErrNoBlocks) } return &block, nil } @@ -44,6 +46,7 @@ func (provider *Provider) BlockHashAndNumber(ctx context.Context) (*BlockHashAnd // // Parameters: // - n: The block number to use for the BlockID. +// // Returns: // - BlockID: A BlockID struct with the specified block number func WithBlockNumber(n uint64) BlockID { @@ -115,8 +118,8 @@ func (provider *Provider) BlockWithTxHashes(ctx context.Context, blockID BlockID // - error: An error, if any func (provider *Provider) StateUpdate(ctx context.Context, blockID BlockID) (*StateUpdateOutput, error) { var state StateUpdateOutput - if err := do(ctx, provider.c, "starknet_getStateUpdate", &state, blockID); err != nil { - return nil,tryUnwrapToRPCErr(err,ErrBlockNotFound ) + if err := do(ctx, provider.c, "starknet_getStateUpdate", &state, blockID); err != nil { + return nil, tryUnwrapToRPCErr(err, ErrBlockNotFound) } return &state, nil } @@ -151,7 +154,7 @@ func (provider *Provider) BlockTransactionCount(ctx context.Context, blockID Blo func (provider *Provider) BlockWithTxs(ctx context.Context, blockID BlockID) (interface{}, error) { var result Block if err := do(ctx, provider.c, "starknet_getBlockWithTxs", &result, blockID); err != nil { - return nil, tryUnwrapToRPCErr(err,ErrBlockNotFound ) + return nil, tryUnwrapToRPCErr(err, ErrBlockNotFound) } // if header.Hash == nil it's a pending block if result.BlockHeader.BlockHash == nil { @@ -165,3 +168,39 @@ func (provider *Provider) BlockWithTxs(ctx context.Context, blockID BlockID) (in } return &result, nil } + +// Get block information with full transactions and receipts given the block id +func (provider *Provider) BlockWithReceipts(ctx context.Context, blockID BlockID) (interface{}, error) { + var result BlockWithReceipts + if err := do(ctx, provider.c, "starknet_getBlockWithReceipts", &result, blockID); err != nil { + return nil, tryUnwrapToRPCErr(err, ErrBlockNotFound) + } + + resultBytes, err := json.Marshal(result) + if err != nil { + return nil, err + } + fmt.Println(result) + + resultMap, ok := result.(map[string]interface{}) + if !ok { + return nil, errors.New("result is not a map[string]interface{}") + } + + // if result.Status == nil it's a pending block + if resultMap["BlockStatus"] == nil { + var pendingBlock PendingBlockWithReceipts + err := json.Unmarshal(resultBytes, &pendingBlock) + if err != nil { + return nil, err + } + return &pendingBlock, nil + } + + var block BlockWithReceipts + err = json.Unmarshal(resultBytes, &block) + if err != nil { + return nil, err + } + return &block, nil +} diff --git a/rpc/mock_test.go b/rpc/mock_test.go index f749db42..2b21b7f6 100644 --- a/rpc/mock_test.go +++ b/rpc/mock_test.go @@ -63,6 +63,8 @@ func (r *rpcMock) CallContext(ctx context.Context, result interface{}, method st return mock_starknet_getBlockTransactionCount(result, method, args...) case "starknet_getBlockWithTxHashes": return mock_starknet_getBlockWithTxHashes(result, method, args...) + case "starknet_getBlockWithReceipts": + return mock_starknet_getBlockWithReceipts(result, method, args...) case "starknet_getClass": return mock_starknet_getClass(result, method, args...) case "starknet_getClassAt": @@ -964,6 +966,71 @@ func mock_starknet_getBlockWithTxHashes(result interface{}, method string, args return nil } +func mock_starknet_getBlockWithReceipts(result interface{}, method string, args ...interface{}) error { + r, ok := result.(*json.RawMessage) + if !ok || r == nil { + return errWrongType + } + if len(args) != 1 { + return errWrongArgs + } + blockId, ok := args[0].(BlockID) + if !ok { + fmt.Printf("args[0] should be BlockID, got %T\n", args[0]) + return errWrongArgs + } + + if blockId.Tag == "pending" { + fmt.Println("MOCK IN PENDING") + pBlock, err := json.Marshal( + PendingBlockWithReceipts{ + PendingBlockHeader{ + ParentHash: &felt.Zero, + Timestamp: 123, + SequencerAddress: &felt.Zero, + }, + BlockBodyWithReceipts{}, + }, + ) + if err != nil { + return err + } + + return json.Unmarshal(pBlock, &r) + } else { + fmt.Println("MOCK IN REAL") + block, err := json.Marshal( + BlockWithReceipts{ + BlockStatus: BlockStatus_AcceptedOnL1, + BlockHeader: BlockHeader{ + BlockHash: new(felt.Felt).SetUint64(1), + ParentHash: new(felt.Felt).SetUint64(0), + Timestamp: 124, + SequencerAddress: new(felt.Felt).SetUint64(42)}, + BlockBodyWithReceipts: BlockBodyWithReceipts{ + Transactions: []TransactionWithReceipt{ + { + Transaction: InvokeTxnV0{ + Type: TransactionType_Invoke, + }, + Receipt: InvokeTransactionReceipt{ + TransactionHash: new(felt.Felt).SetUint64(1), + }, + }, + }, + }, + }, + ) + if err != nil { + return err + } + + return json.Unmarshal(block, &r) + } + + return nil +} + // mock_starknet_traceBlockTransactions is a function that traces the transactions of a block in the StarkNet network. // The function first checks the type of the result parameter and returns an error if it is not of type *json.RawMessage. // It then checks the length of the args parameter and returns an error if it is not equal to 1. Next, it checks the diff --git a/rpc/provider.go b/rpc/provider.go index 36b33a38..2ab70dde 100644 --- a/rpc/provider.go +++ b/rpc/provider.go @@ -36,6 +36,7 @@ type RpcProvider interface { BlockTransactionCount(ctx context.Context, blockID BlockID) (uint64, error) BlockWithTxHashes(ctx context.Context, blockID BlockID) (interface{}, error) BlockWithTxs(ctx context.Context, blockID BlockID) (interface{}, error) + BlockWithReceipts(ctx context.Context, blockID BlockID) (interface{}, error) Call(ctx context.Context, call FunctionCall, block BlockID) ([]*felt.Felt, error) ChainID(ctx context.Context) (string, error) Class(ctx context.Context, blockID BlockID, classHash *felt.Felt) (ClassOutput, error) diff --git a/rpc/types_block.go b/rpc/types_block.go index 34150cfe..de8eb55f 100644 --- a/rpc/types_block.go +++ b/rpc/types_block.go @@ -123,6 +123,27 @@ type PendingBlock struct { BlockTransactions } +type BlockWithReceipts struct { + BlockStatus `json:"status"` + BlockHeader + BlockBodyWithReceipts +} + +type BlockBodyWithReceipts struct { + Transactions []TransactionWithReceipt `json:"transactions"` +} + +type TransactionWithReceipt struct { + Transaction Transaction `json:"transaction,omitempty"` + Receipt TransactionReceipt `json:"receipt"` +} + +// The dynamic block being constructed by the sequencer. Note that this object will be deprecated upon decentralization. +type PendingBlockWithReceipts struct { + PendingBlockHeader + BlockBodyWithReceipts +} + type BlockTxHashes struct { BlockHeader Status BlockStatus `json:"status"` diff --git a/rpc/types_block_test.go b/rpc/types_block_test.go index db568b7a..7ab8ec9b 100644 --- a/rpc/types_block_test.go +++ b/rpc/types_block_test.go @@ -1,12 +1,14 @@ package rpc import ( + "context" _ "embed" "encoding/json" "errors" "testing" "github.com/NethermindEth/juno/core/felt" + "github.com/test-go/testify/require" ) // TestBlockID_Marshal tests the MarshalJSON method of the BlockID struct. @@ -22,7 +24,8 @@ import ( // Parameters: // - t: the testing object for running the test cases // Returns: -// none +// +// none func TestBlockID_Marshal(t *testing.T) { blockNumber := uint64(420) for _, tc := range []struct { @@ -78,7 +81,8 @@ func TestBlockID_Marshal(t *testing.T) { // Parameters: // - t: A testing.T object used for reporting test failures and logging. // Returns: -// none +// +// none func TestBlockStatus(t *testing.T) { for _, tc := range []struct { status string @@ -115,10 +119,32 @@ var rawBlock []byte // Parameters: // - t: the testing object for running the test // Returns: -// none +// +// none func TestBlock_Unmarshal(t *testing.T) { b := Block{} if err := json.Unmarshal(rawBlock, &b); err != nil { t.Fatalf("Unmarshalling block: %v", err) } } + +func TestBlockWithReceipts(t *testing.T) { + testConfig := beforeEach(t) + + ctx := context.Background() + + t.Run("BlockWithReceipts - block", func(t *testing.T) { + blockID := BlockID{Tag: "greatest block"} + block, err := testConfig.provider.BlockWithReceipts(ctx, blockID) + require.Nil(t, err) + require.NotNil(t, block) + }) + + t.Run("BlockWithReceipts - pending", func(t *testing.T) { + blockID := BlockID{Tag: "pending"} + block, err := testConfig.provider.BlockWithReceipts(ctx, blockID) + require.Nil(t, err) + require.NotNil(t, block) + }) + +}