From cb90aaa4c7a6246b02a74f3e8b448f65c9621f4e Mon Sep 17 00:00:00 2001
From: Leon Hudak <33522493+leohhhn@users.noreply.github.com>
Date: Mon, 29 Apr 2024 15:38:07 +0200
Subject: [PATCH] feat(gnoclient): support fetching blocks, block results,
latest block number (#1910)
## Description
This PR adds gnoclient support for fetching blocks, block results, and
the latest block number.
Contributors' checklist...
- [x] Added new tests, or not needed, or not feasible
- [x] Provided an example (e.g. screenshot) to aid review or the PR is
self-explanatory
- [x] Updated the official documentation or not needed
- [x] No breaking changes were made, or a `BREAKING CHANGE: xxx` message
was included in the description
- [x] Added references to related issues and PRs
- [x] Provided any useful hints for running manual tests
- [ ] Added new benchmarks to [generated
graphs](https://gnoland.github.io/benchmarks), if any. More info
[here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md).
---
docs/reference/gnoclient/client.md | 24 +++
gno.land/pkg/gnoclient/client_queries.go | 54 +++++++
gno.land/pkg/gnoclient/client_test.go | 187 +++++++++++++++++++++++
3 files changed, 265 insertions(+)
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)
+ })
+ }
+}