diff --git a/docs/reference/gnoclient/client.md b/docs/reference/gnoclient/client.md index 3f258fa683e..0fbef3f5f93 100644 --- a/docs/reference/gnoclient/client.md +++ b/docs/reference/gnoclient/client.md @@ -25,6 +25,30 @@ func (c *Client) AddPackage(cfg BaseTxCfg, msgs ...MsgAddPackage) (*ctypes.Resul `AddPackage` executes one or more [AddPackage](#type-msgaddpackage) calls on the blockchain. +### func \(\*Client\) [Block]() + +```go +func (c *Client) Block(height int64) (*ctypes.ResultBlock, error) +``` + +`Block` gets the latest block at height, if any. Height must be larger than 0. + +### func \(\*Client\) [BlockResult]() + +```go +func (c *Client) BlockResult(height int64) (*ctypes.ResultBlockResults, error) +``` + +`BlockResult` gets the block results at height, if any. Height must be larger than 0. + +### func \(\*Client\) [LatestBlockHeight]() + +```go +func (c *Client) LatestBlockHeight() (int64, error) +``` + +`LatestBlockHeight` gets the latest block height on the chain. + ### func \(\*Client\) [Call]() ```go diff --git a/gno.land/pkg/gnoclient/client_queries.go b/gno.land/pkg/gnoclient/client_queries.go index 8ceb8352e34..a6c8ea60475 100644 --- a/gno.land/pkg/gnoclient/client_queries.go +++ b/gno.land/pkg/gnoclient/client_queries.go @@ -11,6 +11,8 @@ import ( "github.com/gnolang/gno/tm2/pkg/std" ) +var ErrInvalidBlockHeight = errors.New("invalid block height provided") + // QueryCfg contains configuration options for performing ABCI queries. type QueryCfg struct { Path string // Query path @@ -123,3 +125,55 @@ func (c *Client) QEval(pkgPath string, expression string) (string, *ctypes.Resul return string(qres.Response.Data), qres, nil } + +// Block gets the latest block at height, if any +// Height must be larger than 0 +func (c *Client) Block(height int64) (*ctypes.ResultBlock, error) { + if err := c.validateRPCClient(); err != nil { + return nil, ErrMissingRPCClient + } + + if height <= 0 { + return nil, ErrInvalidBlockHeight + } + + block, err := c.RPCClient.Block(&height) + if err != nil { + return nil, fmt.Errorf("block query failed: %w", err) + } + + return block, nil +} + +// BlockResult gets the block results at height, if any +// Height must be larger than 0 +func (c *Client) BlockResult(height int64) (*ctypes.ResultBlockResults, error) { + if err := c.validateRPCClient(); err != nil { + return nil, ErrMissingRPCClient + } + + if height <= 0 { + return nil, ErrInvalidBlockHeight + } + + blockResults, err := c.RPCClient.BlockResults(&height) + if err != nil { + return nil, fmt.Errorf("block query failed: %w", err) + } + + return blockResults, nil +} + +// LatestBlockHeight gets the latest block height on the chain +func (c *Client) LatestBlockHeight() (int64, error) { + if err := c.validateRPCClient(); err != nil { + return 0, ErrMissingRPCClient + } + + status, err := c.RPCClient.Status() + if err != nil { + return 0, fmt.Errorf("block number query failed: %w", err) + } + + return status.SyncInfo.LatestBlockHeight, nil +} diff --git a/gno.land/pkg/gnoclient/client_test.go b/gno.land/pkg/gnoclient/client_test.go index d68a209dd26..04ebb8e27b8 100644 --- a/gno.land/pkg/gnoclient/client_test.go +++ b/gno.land/pkg/gnoclient/client_test.go @@ -1079,3 +1079,190 @@ func TestAddPackageErrors(t *testing.T) { }) } } + +// Block tests +func TestBlock(t *testing.T) { + t.Parallel() + + height := int64(5) + client := &Client{ + Signer: &mockSigner{}, + RPCClient: &mockRPCClient{ + block: func(height *int64) (*ctypes.ResultBlock, error) { + return &ctypes.ResultBlock{ + BlockMeta: &types.BlockMeta{ + BlockID: types.BlockID{}, + Header: types.Header{}, + }, + Block: &types.Block{ + Header: types.Header{ + Height: *height, + }, + Data: types.Data{}, + LastCommit: nil, + }, + }, nil + }, + }, + } + + block, err := client.Block(height) + require.NoError(t, err) + assert.Equal(t, height, block.Block.GetHeight()) +} + +func TestBlockResults(t *testing.T) { + t.Parallel() + + height := int64(5) + client := &Client{ + Signer: &mockSigner{}, + RPCClient: &mockRPCClient{ + blockResults: func(height *int64) (*ctypes.ResultBlockResults, error) { + return &ctypes.ResultBlockResults{ + Height: *height, + Results: nil, + }, nil + }, + }, + } + + blockResult, err := client.BlockResult(height) + require.NoError(t, err) + assert.Equal(t, height, blockResult.Height) +} + +func TestLatestBlockHeight(t *testing.T) { + t.Parallel() + + latestHeight := int64(5) + + client := &Client{ + Signer: &mockSigner{}, + RPCClient: &mockRPCClient{ + status: func() (*ctypes.ResultStatus, error) { + return &ctypes.ResultStatus{ + SyncInfo: ctypes.SyncInfo{ + LatestBlockHeight: latestHeight, + }, + }, nil + }, + }, + } + + head, err := client.LatestBlockHeight() + require.NoError(t, err) + assert.Equal(t, latestHeight, head) +} + +func TestBlockErrors(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + client Client + height int64 + expectedError error + }{ + { + name: "Invalid RPCClient", + client: Client{ + &mockSigner{}, + nil, + }, + height: 1, + expectedError: ErrMissingRPCClient, + }, + { + name: "Invalid height", + client: Client{ + &mockSigner{}, + &mockRPCClient{}, + }, + height: 0, + expectedError: ErrInvalidBlockHeight, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + res, err := tc.client.Block(tc.height) + assert.Nil(t, res) + assert.ErrorIs(t, err, tc.expectedError) + }) + } +} + +func TestBlockResultErrors(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + client Client + height int64 + expectedError error + }{ + { + name: "Invalid RPCClient", + client: Client{ + &mockSigner{}, + nil, + }, + height: 1, + expectedError: ErrMissingRPCClient, + }, + { + name: "Invalid height", + client: Client{ + &mockSigner{}, + &mockRPCClient{}, + }, + height: 0, + expectedError: ErrInvalidBlockHeight, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + res, err := tc.client.BlockResult(tc.height) + assert.Nil(t, res) + assert.ErrorIs(t, err, tc.expectedError) + }) + } +} + +func TestLatestBlockHeightErrors(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + client Client + expectedError error + }{ + { + name: "Invalid RPCClient", + client: Client{ + &mockSigner{}, + nil, + }, + expectedError: ErrMissingRPCClient, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + res, err := tc.client.LatestBlockHeight() + assert.Equal(t, int64(0), res) + assert.ErrorIs(t, err, tc.expectedError) + }) + } +}