github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/bft/rpc/core/blocks.go (about)

     1  package core
     2  
     3  import (
     4  	"fmt"
     5  
     6  	ctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types"
     7  	rpctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/lib/types"
     8  	sm "github.com/gnolang/gno/tm2/pkg/bft/state"
     9  	"github.com/gnolang/gno/tm2/pkg/bft/types"
    10  )
    11  
    12  // Get block headers for minHeight <= height <= maxHeight.
    13  // Block headers are returned in descending order (highest first).
    14  //
    15  // ```shell
    16  // curl 'localhost:26657/blockchain?minHeight=10&maxHeight=10'
    17  // ```
    18  //
    19  // ```go
    20  // client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket")
    21  // err := client.Start()
    22  //
    23  //	if err != nil {
    24  //	  // handle error
    25  //	}
    26  //
    27  // defer client.Stop()
    28  // info, err := client.BlockchainInfo(10, 10)
    29  // ```
    30  //
    31  // > The above command returns JSON structured like this:
    32  //
    33  // ```json
    34  //
    35  //	{
    36  //		"error": "",
    37  //		"result": {
    38  //			"block_metas": [
    39  //				{
    40  //					"header": {
    41  //						"app_hash": "",
    42  //						"chain_id": "test-chain-6UTNIN",
    43  //						"height": "10",
    44  //						"time": "2017-05-29T15:05:53.877Z",
    45  //						"num_txs": "0",
    46  //						"last_block_id": {
    47  //							"parts": {
    48  //								"hash": "3C78F00658E06744A88F24FF97A0A5011139F34A",
    49  //								"total": "1"
    50  //							},
    51  //							"hash": "F70588DAB36BDA5A953D548A16F7D48C6C2DFD78"
    52  //						},
    53  //						"last_commit_hash": "F31CC4282E50B3F2A58D763D233D76F26D26CABE",
    54  //						"data_hash": "",
    55  //						"validators_hash": "9365FC80F234C967BD233F5A3E2AB2F1E4B0E5AA"
    56  //					},
    57  //					"block_id": {
    58  //						"parts": {
    59  //							"hash": "277A4DBEF91483A18B85F2F5677ABF9694DFA40F",
    60  //							"total": "1"
    61  //						},
    62  //						"hash": "96B1D2F2D201BA4BC383EB8224139DB1294944E5"
    63  //					}
    64  //				}
    65  //			],
    66  //			"last_height": "5493"
    67  //		},
    68  //		"id": "",
    69  //		"jsonrpc": "2.0"
    70  //	}
    71  //
    72  // ```
    73  //
    74  // <aside class="notice">Returns at most 20 items.</aside>
    75  func BlockchainInfo(ctx *rpctypes.Context, minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) {
    76  	// maximum 20 block metas
    77  	const limit int64 = 20
    78  	var err error
    79  	minHeight, maxHeight, err = filterMinMax(blockStore.Height(), minHeight, maxHeight, limit)
    80  	if err != nil {
    81  		return nil, err
    82  	}
    83  	logger.Debug("BlockchainInfoHandler", "maxHeight", maxHeight, "minHeight", minHeight)
    84  
    85  	blockMetas := []*types.BlockMeta{}
    86  	for height := maxHeight; height >= minHeight; height-- {
    87  		blockMeta := blockStore.LoadBlockMeta(height)
    88  		blockMetas = append(blockMetas, blockMeta)
    89  	}
    90  
    91  	return &ctypes.ResultBlockchainInfo{
    92  		LastHeight: blockStore.Height(),
    93  		BlockMetas: blockMetas,
    94  	}, nil
    95  }
    96  
    97  // error if either low or high are negative or low > high
    98  // if low is 0 it defaults to 1, if high is 0 it defaults to height (block height).
    99  // limit sets the maximum amounts of values included within [low,high] (inclusive),
   100  // increasing low as necessary.
   101  func filterMinMax(height, low, high, limit int64) (int64, int64, error) {
   102  	// filter negatives
   103  	if low < 0 || high < 0 {
   104  		return low, high, fmt.Errorf("heights must be non-negative")
   105  	}
   106  
   107  	// adjust for default values
   108  	if low == 0 {
   109  		low = 1
   110  	}
   111  	if high == 0 {
   112  		high = height
   113  	}
   114  
   115  	// limit high to the height
   116  	high = min(height, high)
   117  
   118  	// limit low to within `limit` of max
   119  	// so the total number of blocks returned will be `limit`
   120  	low = max(low, high-limit+1)
   121  
   122  	if low > high {
   123  		return low, high, fmt.Errorf("min height %d can't be greater than max height %d", low, high)
   124  	}
   125  	return low, high, nil
   126  }
   127  
   128  // Get block at a given height.
   129  // If no height is provided, it will fetch the latest block.
   130  //
   131  // ```shell
   132  // curl 'localhost:26657/block?height=10'
   133  // ```
   134  //
   135  // ```go
   136  // client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket")
   137  // err := client.Start()
   138  //
   139  //	if err != nil {
   140  //	  // handle error
   141  //	}
   142  //
   143  // defer client.Stop()
   144  // info, err := client.Block(10)
   145  // ```
   146  //
   147  // > The above command returns JSON structured like this:
   148  //
   149  // ```json
   150  //
   151  //	{
   152  //	  "error": "",
   153  //	  "result": {
   154  //	    "block": {
   155  //	      "last_commit": {
   156  //	        "precommits": [
   157  //	          {
   158  //	            "signature": {
   159  //	              "data": "12C0D8893B8A38224488DC1DE6270DF76BB1A5E9DB1C68577706A6A97C6EC34FFD12339183D5CA8BC2F46148773823DE905B7F6F5862FD564038BB7AE03BF50D",
   160  //	              "type": "ed25519"
   161  //	            },
   162  //	            "block_id": {
   163  //	              "parts": {
   164  //	                "hash": "3C78F00658E06744A88F24FF97A0A5011139F34A",
   165  //	                "total": "1"
   166  //	              },
   167  //	              "hash": "F70588DAB36BDA5A953D548A16F7D48C6C2DFD78"
   168  //	            },
   169  //	            "type": "2",
   170  //	            "round": "0",
   171  //	            "height": "9",
   172  //	            "validator_index": "0",
   173  //	            "validator_address": "E89A51D60F68385E09E716D353373B11F8FACD62"
   174  //	          }
   175  //	        ],
   176  //	        "blockID": {
   177  //	          "parts": {
   178  //	            "hash": "3C78F00658E06744A88F24FF97A0A5011139F34A",
   179  //	            "total": "1"
   180  //	          },
   181  //	          "hash": "F70588DAB36BDA5A953D548A16F7D48C6C2DFD78"
   182  //	        }
   183  //	      },
   184  //	      "data": {
   185  //	        "txs": []
   186  //	      },
   187  //	      "header": {
   188  //	        "app_hash": "",
   189  //	        "chain_id": "test-chain-6UTNIN",
   190  //	        "height": "10",
   191  //	        "time": "2017-05-29T15:05:53.877Z",
   192  //	        "num_txs": "0",
   193  //	        "last_block_id": {
   194  //	          "parts": {
   195  //	            "hash": "3C78F00658E06744A88F24FF97A0A5011139F34A",
   196  //	            "total": "1"
   197  //	          },
   198  //	          "hash": "F70588DAB36BDA5A953D548A16F7D48C6C2DFD78"
   199  //	        },
   200  //	        "last_commit_hash": "F31CC4282E50B3F2A58D763D233D76F26D26CABE",
   201  //	        "data_hash": "",
   202  //	        "validators_hash": "9365FC80F234C967BD233F5A3E2AB2F1E4B0E5AA"
   203  //	      }
   204  //	    },
   205  //	    "block_meta": {
   206  //	      "header": {
   207  //	        "app_hash": "",
   208  //	        "chain_id": "test-chain-6UTNIN",
   209  //	        "height": "10",
   210  //	        "time": "2017-05-29T15:05:53.877Z",
   211  //	        "num_txs": "0",
   212  //	        "last_block_id": {
   213  //	          "parts": {
   214  //	            "hash": "3C78F00658E06744A88F24FF97A0A5011139F34A",
   215  //	            "total": "1"
   216  //	          },
   217  //	          "hash": "F70588DAB36BDA5A953D548A16F7D48C6C2DFD78"
   218  //	        },
   219  //	        "last_commit_hash": "F31CC4282E50B3F2A58D763D233D76F26D26CABE",
   220  //	        "data_hash": "",
   221  //	        "validators_hash": "9365FC80F234C967BD233F5A3E2AB2F1E4B0E5AA"
   222  //	      },
   223  //	      "block_id": {
   224  //	        "parts": {
   225  //	          "hash": "277A4DBEF91483A18B85F2F5677ABF9694DFA40F",
   226  //	          "total": "1"
   227  //	        },
   228  //	        "hash": "96B1D2F2D201BA4BC383EB8224139DB1294944E5"
   229  //	      }
   230  //	    }
   231  //	  },
   232  //	  "id": "",
   233  //	  "jsonrpc": "2.0"
   234  //	}
   235  //
   236  // ```
   237  func Block(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultBlock, error) {
   238  	storeHeight := blockStore.Height()
   239  	height, err := getHeight(storeHeight, heightPtr)
   240  	if err != nil {
   241  		return nil, err
   242  	}
   243  
   244  	blockMeta := blockStore.LoadBlockMeta(height)
   245  	block := blockStore.LoadBlock(height)
   246  	return &ctypes.ResultBlock{BlockMeta: blockMeta, Block: block}, nil
   247  }
   248  
   249  // Get block commit at a given height.
   250  // If no height is provided, it will fetch the commit for the latest block.
   251  //
   252  // ```shell
   253  // curl 'localhost:26657/commit?height=11'
   254  // ```
   255  //
   256  // ```go
   257  // client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket")
   258  // err := client.Start()
   259  //
   260  //	if err != nil {
   261  //	  // handle error
   262  //	}
   263  //
   264  // defer client.Stop()
   265  // info, err := client.Commit(11)
   266  // ```
   267  //
   268  // > The above command returns JSON structured like this:
   269  //
   270  // ```json
   271  //
   272  //	{
   273  //	  "error": "",
   274  //	  "result": {
   275  //	    "canonical": true,
   276  //	    "commit": {
   277  //	      "precommits": [
   278  //	        {
   279  //	          "signature": {
   280  //	            "data": "00970429FEC652E9E21D106A90AE8C5413759A7488775CEF4A3F44DC46C7F9D941070E4FBE9ED54DF247FA3983359A0C3A238D61DE55C75C9116D72ABC9CF50F",
   281  //	            "type": "ed25519"
   282  //	          },
   283  //	          "block_id": {
   284  //	            "parts": {
   285  //	              "hash": "9E37CBF266BC044A779E09D81C456E653B89E006",
   286  //	              "total": "1"
   287  //	            },
   288  //	            "hash": "CC6E861E31CA4334E9888381B4A9137D1458AB6A"
   289  //	          },
   290  //	          "type": "2",
   291  //	          "round": "0",
   292  //	          "height": "11",
   293  //	          "validator_index": "0",
   294  //	          "validator_address": "E89A51D60F68385E09E716D353373B11F8FACD62"
   295  //	        }
   296  //	      ],
   297  //	      "blockID": {
   298  //	        "parts": {
   299  //	          "hash": "9E37CBF266BC044A779E09D81C456E653B89E006",
   300  //	          "total": "1"
   301  //	        },
   302  //	        "hash": "CC6E861E31CA4334E9888381B4A9137D1458AB6A"
   303  //	      }
   304  //	    },
   305  //	    "header": {
   306  //	      "app_hash": "",
   307  //	      "chain_id": "test-chain-6UTNIN",
   308  //	      "height": "11",
   309  //	      "time": "2017-05-29T15:05:54.893Z",
   310  //	      "num_txs": "0",
   311  //	      "last_block_id": {
   312  //	        "parts": {
   313  //	          "hash": "277A4DBEF91483A18B85F2F5677ABF9694DFA40F",
   314  //	          "total": "1"
   315  //	        },
   316  //	        "hash": "96B1D2F2D201BA4BC383EB8224139DB1294944E5"
   317  //	      },
   318  //	      "last_commit_hash": "3CE0C9727CE524BA9CB7C91E28F08E2B94001087",
   319  //	      "data_hash": "",
   320  //	      "validators_hash": "9365FC80F234C967BD233F5A3E2AB2F1E4B0E5AA"
   321  //	    }
   322  //	  },
   323  //	  "id": "",
   324  //	  "jsonrpc": "2.0"
   325  //	}
   326  //
   327  // ```
   328  func Commit(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultCommit, error) {
   329  	storeHeight := blockStore.Height()
   330  	height, err := getHeight(storeHeight, heightPtr)
   331  	if err != nil {
   332  		return nil, err
   333  	}
   334  
   335  	header := blockStore.LoadBlockMeta(height).Header
   336  
   337  	// If the next block has not been committed yet,
   338  	// use a non-canonical commit
   339  	if height == storeHeight {
   340  		commit := blockStore.LoadSeenCommit(height)
   341  		return ctypes.NewResultCommit(&header, commit, false), nil
   342  	}
   343  
   344  	// Return the canonical commit (comes from the block at height+1)
   345  	commit := blockStore.LoadBlockCommit(height)
   346  	return ctypes.NewResultCommit(&header, commit, true), nil
   347  }
   348  
   349  // BlockResults gets ABCIResults at a given height.
   350  // If no height is provided, it will fetch results for the latest block.
   351  //
   352  // Results are for the height of the block containing the txs.
   353  // Thus response.results.deliver_tx[5] is the results of executing
   354  // getBlock(h).Txs[5]
   355  //
   356  // ```shell
   357  // curl 'localhost:26657/block_results?height=10'
   358  // ```
   359  //
   360  // ```go
   361  // client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket")
   362  // err := client.Start()
   363  //
   364  //	if err != nil {
   365  //	  // handle error
   366  //	}
   367  //
   368  // defer client.Stop()
   369  // info, err := client.BlockResults(10)
   370  // ```
   371  //
   372  // > The above command returns JSON structured like this:
   373  //
   374  // ```json
   375  //
   376  //	{
   377  //	  "jsonrpc": "2.0",
   378  //	  "id": "",
   379  //	  "result": {
   380  //	    "height": "39",
   381  //	    "results": {
   382  //	      "deliver_tx": [
   383  //	        {
   384  //	          "tags": [
   385  //	            {
   386  //	              "key": "YXBwLmNyZWF0b3I=",
   387  //	              "value": "Q29zbW9zaGkgTmV0b3dva28="
   388  //	            }
   389  //	          ]
   390  //	        }
   391  //	      ],
   392  //	      "end_block": {
   393  //	        "validator_updates": null
   394  //	      },
   395  //	      "begin_block": {}
   396  //	    }
   397  //	  }
   398  //	}
   399  //
   400  // ```
   401  func BlockResults(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultBlockResults, error) {
   402  	storeHeight := blockStore.Height()
   403  	height, err := getHeight(storeHeight, heightPtr)
   404  	if err != nil {
   405  		return nil, err
   406  	}
   407  
   408  	results, err := sm.LoadABCIResponses(stateDB, height)
   409  	if err != nil {
   410  		return nil, err
   411  	}
   412  
   413  	res := &ctypes.ResultBlockResults{
   414  		Height:  height,
   415  		Results: results,
   416  	}
   417  	return res, nil
   418  }
   419  
   420  func getHeight(currentHeight int64, heightPtr *int64) (int64, error) {
   421  	if heightPtr != nil {
   422  		height := *heightPtr
   423  		if height <= 0 {
   424  			return 0, fmt.Errorf("height must be greater than 0")
   425  		}
   426  		if height > currentHeight {
   427  			return 0, fmt.Errorf("height must be less than or equal to the current blockchain height")
   428  		}
   429  		return height, nil
   430  	}
   431  	return currentHeight, nil
   432  }