feat(rpc): block header and block details endpoints
Co-Authored-By: Charon <charon@lethean.io> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
efbace75a8
commit
e008dc5cfe
2 changed files with 234 additions and 0 deletions
79
rpc/blocks.go
Normal file
79
rpc/blocks.go
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
// Copyright (c) 2017-2026 Lethean (https://lt.hn)
|
||||
//
|
||||
// Licensed under the European Union Public Licence (EUPL) version 1.2.
|
||||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package rpc
|
||||
|
||||
import "fmt"
|
||||
|
||||
// GetLastBlockHeader returns the header of the most recent block.
|
||||
func (c *Client) GetLastBlockHeader() (*BlockHeader, error) {
|
||||
var resp struct {
|
||||
BlockHeader BlockHeader `json:"block_header"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
if err := c.call("getlastblockheader", struct{}{}, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.Status != "OK" {
|
||||
return nil, fmt.Errorf("getlastblockheader: status %q", resp.Status)
|
||||
}
|
||||
return &resp.BlockHeader, nil
|
||||
}
|
||||
|
||||
// GetBlockHeaderByHeight returns the block header at the given height.
|
||||
func (c *Client) GetBlockHeaderByHeight(height uint64) (*BlockHeader, error) {
|
||||
params := struct {
|
||||
Height uint64 `json:"height"`
|
||||
}{Height: height}
|
||||
var resp struct {
|
||||
BlockHeader BlockHeader `json:"block_header"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
if err := c.call("getblockheaderbyheight", params, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.Status != "OK" {
|
||||
return nil, fmt.Errorf("getblockheaderbyheight: status %q", resp.Status)
|
||||
}
|
||||
return &resp.BlockHeader, nil
|
||||
}
|
||||
|
||||
// GetBlockHeaderByHash returns the block header with the given hash.
|
||||
func (c *Client) GetBlockHeaderByHash(hash string) (*BlockHeader, error) {
|
||||
params := struct {
|
||||
Hash string `json:"hash"`
|
||||
}{Hash: hash}
|
||||
var resp struct {
|
||||
BlockHeader BlockHeader `json:"block_header"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
if err := c.call("getblockheaderbyhash", params, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.Status != "OK" {
|
||||
return nil, fmt.Errorf("getblockheaderbyhash: status %q", resp.Status)
|
||||
}
|
||||
return &resp.BlockHeader, nil
|
||||
}
|
||||
|
||||
// GetBlocksDetails returns full block details starting at the given height.
|
||||
func (c *Client) GetBlocksDetails(heightStart, count uint64) ([]BlockDetails, error) {
|
||||
params := struct {
|
||||
HeightStart uint64 `json:"height_start"`
|
||||
Count uint64 `json:"count"`
|
||||
IgnoreTransactions bool `json:"ignore_transactions"`
|
||||
}{HeightStart: heightStart, Count: count}
|
||||
var resp struct {
|
||||
Blocks []BlockDetails `json:"blocks"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
if err := c.call("get_blocks_details", params, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.Status != "OK" {
|
||||
return nil, fmt.Errorf("get_blocks_details: status %q", resp.Status)
|
||||
}
|
||||
return resp.Blocks, nil
|
||||
}
|
||||
155
rpc/blocks_test.go
Normal file
155
rpc/blocks_test.go
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
// Copyright (c) 2017-2026 Lethean (https://lt.hn)
|
||||
//
|
||||
// Licensed under the European Union Public Licence (EUPL) version 1.2.
|
||||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var testBlockHeaderJSON = `{
|
||||
"major_version": 1,
|
||||
"minor_version": 0,
|
||||
"timestamp": 1770897600,
|
||||
"prev_hash": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"nonce": 101011010221,
|
||||
"orphan_status": false,
|
||||
"height": 0,
|
||||
"depth": 6300,
|
||||
"hash": "cb9d5455ccb79451931003672c405f5e2ac51bff54021aa30bc4499b1ffc4963",
|
||||
"difficulty": "1",
|
||||
"reward": 1000000000000
|
||||
}`
|
||||
|
||||
func blockHeaderResponse() jsonRPCResponse {
|
||||
return jsonRPCResponse{
|
||||
JSONRPC: "2.0",
|
||||
ID: json.RawMessage(`"0"`),
|
||||
Result: json.RawMessage(`{"block_header":` + testBlockHeaderJSON + `,"status":"OK"}`),
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetLastBlockHeader_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(blockHeaderResponse())
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
c := NewClient(srv.URL)
|
||||
hdr, err := c.GetLastBlockHeader()
|
||||
if err != nil {
|
||||
t.Fatalf("GetLastBlockHeader: %v", err)
|
||||
}
|
||||
if hdr.Height != 0 {
|
||||
t.Errorf("height: got %d, want 0", hdr.Height)
|
||||
}
|
||||
if hdr.Hash != "cb9d5455ccb79451931003672c405f5e2ac51bff54021aa30bc4499b1ffc4963" {
|
||||
t.Errorf("hash: got %q", hdr.Hash)
|
||||
}
|
||||
if hdr.MajorVersion != 1 {
|
||||
t.Errorf("major_version: got %d, want 1", hdr.MajorVersion)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetBlockHeaderByHeight_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(blockHeaderResponse())
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
c := NewClient(srv.URL)
|
||||
hdr, err := c.GetBlockHeaderByHeight(0)
|
||||
if err != nil {
|
||||
t.Fatalf("GetBlockHeaderByHeight: %v", err)
|
||||
}
|
||||
if hdr.Height != 0 {
|
||||
t.Errorf("height: got %d, want 0", hdr.Height)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetBlockHeaderByHash_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(blockHeaderResponse())
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
c := NewClient(srv.URL)
|
||||
hdr, err := c.GetBlockHeaderByHash("cb9d5455ccb79451931003672c405f5e2ac51bff54021aa30bc4499b1ffc4963")
|
||||
if err != nil {
|
||||
t.Fatalf("GetBlockHeaderByHash: %v", err)
|
||||
}
|
||||
if hdr.Reward != 1000000000000 {
|
||||
t.Errorf("reward: got %d, want 1000000000000", hdr.Reward)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetBlocksDetails_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(jsonRPCResponse{
|
||||
JSONRPC: "2.0",
|
||||
ID: json.RawMessage(`"0"`),
|
||||
Result: json.RawMessage(`{
|
||||
"blocks": [{
|
||||
"height": 0,
|
||||
"timestamp": 1770897600,
|
||||
"base_reward": 1000000000000,
|
||||
"id": "cb9d5455ccb79451931003672c405f5e2ac51bff54021aa30bc4499b1ffc4963",
|
||||
"type": 1,
|
||||
"is_orphan": false,
|
||||
"transactions_details": []
|
||||
}],
|
||||
"status": "OK"
|
||||
}`),
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
c := NewClient(srv.URL)
|
||||
blocks, err := c.GetBlocksDetails(0, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("GetBlocksDetails: %v", err)
|
||||
}
|
||||
if len(blocks) != 1 {
|
||||
t.Fatalf("blocks: got %d, want 1", len(blocks))
|
||||
}
|
||||
if blocks[0].Height != 0 {
|
||||
t.Errorf("height: got %d, want 0", blocks[0].Height)
|
||||
}
|
||||
if blocks[0].ID != "cb9d5455ccb79451931003672c405f5e2ac51bff54021aa30bc4499b1ffc4963" {
|
||||
t.Errorf("id: got %q", blocks[0].ID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetBlockHeaderByHeight_Bad_TooBig(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(jsonRPCResponse{
|
||||
JSONRPC: "2.0",
|
||||
ID: json.RawMessage(`"0"`),
|
||||
Error: &jsonRPCError{Code: -2, Message: "TOO_BIG_HEIGHT"},
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
c := NewClient(srv.URL)
|
||||
_, err := c.GetBlockHeaderByHeight(999999999)
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
rpcErr, ok := err.(*RPCError)
|
||||
if !ok {
|
||||
t.Fatalf("expected *RPCError, got %T", err)
|
||||
}
|
||||
if rpcErr.Code != -2 {
|
||||
t.Errorf("code: got %d, want -2", rpcErr.Code)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue