From 1fd6d91bdb74bb353177b4ccfa8d3eeeb7388abb Mon Sep 17 00:00:00 2001 From: Cameron Cooper Date: Tue, 16 Dec 2025 20:57:24 -0600 Subject: [PATCH] enable using a height in place of a header hash --- .../cmd/coinset/get_additions_and_removals.go | 16 +++--- internal/cmd/coinset/get_block.go | 18 +++--- internal/cmd/coinset/get_block_record.go | 18 +++--- internal/cmd/coinset/get_block_spends.go | 16 +++--- .../get_block_spends_with_conditions.go | 16 +++--- internal/cmd/coinset/util.go | 57 +++++++++++++++++-- 6 files changed, 94 insertions(+), 47 deletions(-) diff --git a/internal/cmd/coinset/get_additions_and_removals.go b/internal/cmd/coinset/get_additions_and_removals.go index 19a601e..8cc760d 100644 --- a/internal/cmd/coinset/get_additions_and_removals.go +++ b/internal/cmd/coinset/get_additions_and_removals.go @@ -1,8 +1,6 @@ package cmd import ( - "fmt" - "github.com/spf13/cobra" ) @@ -11,21 +9,23 @@ func init() { } var getAdditionsAndRemovalsCmd = &cobra.Command{ - Use: "get_additions_and_removals ", + Use: "get_additions_and_removals ", Args: func(cmd *cobra.Command, args []string) error { if err := cobra.ExactArgs(1)(cmd, args); err != nil { return err } - if isHex(args[0]) { - return nil + _, err := convertHeightOrHeaderHash(args[0]) + if err != nil { + return err } - return fmt.Errorf("invalid hex value specified: %s", args[0]) + return nil }, Short: "Retrieves the additions and removals for a certain block", - Long: "Retrieves the additions and removals for a certain block. Returns coin records for each addition and removal. Blocks that are not transaction blocks will have empty removal and addition lists. To get the actual puzzles and solutions for spent coins, use the get_puzzle_and_solution api.", + Long: "Retrieves the additions and removals for a certain block by height or header hash. Returns coin records for each addition and removal. Blocks that are not transaction blocks will have empty removal and addition lists. To get the actual puzzles and solutions for spent coins, use the get_puzzle_and_solution api.", Run: func(cmd *cobra.Command, args []string) { + headerHash, _ := convertHeightOrHeaderHash(args[0]) jsonData := map[string]interface{}{} - jsonData["header_hash"] = formatHex(args[0]) + jsonData["header_hash"] = headerHash makeRequest("get_additions_and_removals", jsonData) }, } diff --git a/internal/cmd/coinset/get_block.go b/internal/cmd/coinset/get_block.go index 118c4b0..3c3e96a 100644 --- a/internal/cmd/coinset/get_block.go +++ b/internal/cmd/coinset/get_block.go @@ -1,8 +1,6 @@ package cmd import ( - "fmt" - "github.com/spf13/cobra" ) @@ -11,21 +9,23 @@ func init() { } var getBlockCmd = &cobra.Command{ - Use: "get_block ", + Use: "get_block ", Args: func(cmd *cobra.Command, args []string) error { if err := cobra.ExactArgs(1)(cmd, args); err != nil { return err } - if isHex(args[0]) { - return nil + _, err := convertHeightOrHeaderHash(args[0]) + if err != nil { + return err } - return fmt.Errorf("invalid hex value specified: %s", args[0]) + return nil }, - Short: "Retrieves an entire block as a block by header hash", - Long: `Retrieves an entire block as a block by header hash`, + Short: "Retrieves an entire block by height or header hash", + Long: `Retrieves an entire block by height or header hash`, Run: func(cmd *cobra.Command, args []string) { + headerHash, _ := convertHeightOrHeaderHash(args[0]) jsonData := map[string]interface{}{} - jsonData["header_hash"] = formatHex(args[0]) + jsonData["header_hash"] = headerHash makeRequest("get_block", jsonData) }, } diff --git a/internal/cmd/coinset/get_block_record.go b/internal/cmd/coinset/get_block_record.go index d036f58..30b606c 100644 --- a/internal/cmd/coinset/get_block_record.go +++ b/internal/cmd/coinset/get_block_record.go @@ -1,8 +1,6 @@ package cmd import ( - "fmt" - "github.com/spf13/cobra" ) @@ -11,21 +9,23 @@ func init() { } var getBlockRecordCmd = &cobra.Command{ - Use: "get_block_record ", + Use: "get_block_record ", Args: func(cmd *cobra.Command, args []string) error { if err := cobra.ExactArgs(1)(cmd, args); err != nil { return err } - if isHex(args[0]) { - return nil + _, err := convertHeightOrHeaderHash(args[0]) + if err != nil { + return err } - return fmt.Errorf("invalid hex value specified: %s", args[0]) + return nil }, - Short: "Retrieves a block record by header hash", - Long: "Retrieves a block record by header hash", + Short: "Retrieves a block record by height or header hash", + Long: "Retrieves a block record by height or header hash", Run: func(cmd *cobra.Command, args []string) { + headerHash, _ := convertHeightOrHeaderHash(args[0]) jsonData := map[string]interface{}{} - jsonData["header_hash"] = formatHex(args[0]) + jsonData["header_hash"] = headerHash makeRequest("get_block_record", jsonData) }, } diff --git a/internal/cmd/coinset/get_block_spends.go b/internal/cmd/coinset/get_block_spends.go index 0ecc065..58d55f1 100644 --- a/internal/cmd/coinset/get_block_spends.go +++ b/internal/cmd/coinset/get_block_spends.go @@ -1,8 +1,6 @@ package cmd import ( - "fmt" - "github.com/spf13/cobra" ) @@ -11,21 +9,23 @@ func init() { } var getBlockSpendsCmd = &cobra.Command{ - Use: "get_block_spends ", + Use: "get_block_spends ", Args: func(cmd *cobra.Command, args []string) error { if err := cobra.ExactArgs(1)(cmd, args); err != nil { return err } - if isHex(args[0]) { - return nil + _, err := convertHeightOrHeaderHash(args[0]) + if err != nil { + return err } - return fmt.Errorf("invalid hex value specified: %s", args[0]) + return nil }, Short: "Retrieves every coin that was spent in a block", - Long: `Retrieves every coin that was spent in a block`, + Long: `Retrieves every coin that was spent in a block by height or header hash`, Run: func(cmd *cobra.Command, args []string) { + headerHash, _ := convertHeightOrHeaderHash(args[0]) jsonData := map[string]interface{}{} - jsonData["header_hash"] = formatHex(args[0]) + jsonData["header_hash"] = headerHash makeRequest("get_block_spends", jsonData) }, } diff --git a/internal/cmd/coinset/get_block_spends_with_conditions.go b/internal/cmd/coinset/get_block_spends_with_conditions.go index 6d903d1..907265f 100644 --- a/internal/cmd/coinset/get_block_spends_with_conditions.go +++ b/internal/cmd/coinset/get_block_spends_with_conditions.go @@ -1,8 +1,6 @@ package cmd import ( - "fmt" - "github.com/spf13/cobra" ) @@ -11,21 +9,23 @@ func init() { } var getBlockSpendsWithConditionsCmd = &cobra.Command{ - Use: "get_block_spends_with_conditions ", + Use: "get_block_spends_with_conditions ", Args: func(cmd *cobra.Command, args []string) error { if err := cobra.ExactArgs(1)(cmd, args); err != nil { return err } - if isHex(args[0]) { - return nil + _, err := convertHeightOrHeaderHash(args[0]) + if err != nil { + return err } - return fmt.Errorf("invalid hex value specified: %s", args[0]) + return nil }, Short: "Retrieves every coin that was spent in a block with the returned conditions", - Long: `Retrieves every coin that was spent in a block with the returned conditions`, + Long: `Retrieves every coin that was spent in a block with the returned conditions by height or header hash`, Run: func(cmd *cobra.Command, args []string) { + headerHash, _ := convertHeightOrHeaderHash(args[0]) jsonData := map[string]interface{}{} - jsonData["header_hash"] = formatHex(args[0]) + jsonData["header_hash"] = headerHash makeRequest("get_block_spends_with_conditions", jsonData) }, } diff --git a/internal/cmd/coinset/util.go b/internal/cmd/coinset/util.go index ac68f7d..391018f 100644 --- a/internal/cmd/coinset/util.go +++ b/internal/cmd/coinset/util.go @@ -6,6 +6,7 @@ import ( "log" "net/url" "regexp" + "strconv" "github.com/TylerBrock/colorjson" "github.com/chia-network/go-chia-libs/pkg/bech32m" @@ -46,6 +47,44 @@ func convertAddressOrPuzzleHash(input string) (string, error) { } } +func convertHeightOrHeaderHash(input string) (string, error) { + if height, err := strconv.Atoi(input); err == nil { + return getHeaderHashByHeight(height) + } else if isHex(input) { + return formatHex(input), nil + } else { + return "", fmt.Errorf("invalid input: must be either a block height (number) or hex header hash") + } +} + +func getHeaderHashByHeight(height int) (string, error) { + jsonData := map[string]interface{}{ + "height": height, + } + + jsonResponse, err := doRpc("get_block_record_by_height", jsonData) + if err != nil { + return "", fmt.Errorf("failed to get block record by height %d: %v", height, err) + } + + var response map[string]interface{} + if err := json.Unmarshal(jsonResponse, &response); err != nil { + return "", fmt.Errorf("failed to parse response: %v", err) + } + + blockRecord, ok := response["block_record"].(map[string]interface{}) + if !ok { + return "", fmt.Errorf("invalid response format from get_block_record_by_height") + } + + headerHash, ok := blockRecord["header_hash"].(string) + if !ok { + return "", fmt.Errorf("header_hash not found in block record") + } + + return headerHash, nil +} + func apiHost() string { baseUrl, err := url.Parse(apiRoot()) if err != nil { @@ -64,13 +103,13 @@ func apiRoot() string { return "https://api.coinset.org" } -func makeRequest(path string, jsonData map[string]interface{}) { +func doRpc(path string, jsonData map[string]interface{}) (json.RawMessage, error) { var client *rpc.Client var err error baseUrl, err := url.Parse(apiRoot()) if err != nil { - log.Fatal(err.Error()) + return nil, fmt.Errorf("failed to parse API URL: %v", err) } if local { @@ -80,20 +119,28 @@ func makeRequest(path string, jsonData map[string]interface{}) { } if err != nil { - log.Fatal(err.Error()) + return nil, fmt.Errorf("failed to create RPC client: %v", err) } req, err := client.FullNodeService.NewRequest(rpcinterface.Endpoint(path), jsonData) if err != nil { - log.Fatal(err.Error()) + return nil, fmt.Errorf("failed to create request: %v", err) } jsonResponse := json.RawMessage{} _, err = client.FullNodeService.Do(req, &jsonResponse) if err != nil { - log.Fatal(err.Error()) + return nil, fmt.Errorf("request failed: %v", err) } + return jsonResponse, nil +} + +func makeRequest(path string, jsonData map[string]interface{}) { + jsonResponse, err := doRpc(path, jsonData) + if err != nil { + log.Fatal(err.Error()) + } printJson(jsonResponse) }