Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions cmd/goal/ledger.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,20 @@ package main

import (
"fmt"
"strconv"

"github.com/spf13/cobra"
)

var (
rawFilename string
)

func init() {
ledgerCmd.AddCommand(supplyCmd)
ledgerCmd.AddCommand(rawBlockCmd)

rawBlockCmd.Flags().StringVarP(&rawFilename, "out", "o", stdoutFilenameValue, "The filename to dump the raw block to (if not set, use stdout)")
Comment thread
justicz marked this conversation as resolved.
Outdated
}

var ledgerCmd = &cobra.Command{
Expand Down Expand Up @@ -52,3 +60,29 @@ var supplyCmd = &cobra.Command{
fmt.Printf("Round: %v\nTotal Money: %v microAlgos\nOnline Money: %v microAlgos\n", response.Round, response.TotalMoney, response.OnlineMoney)
},
}

var rawBlockCmd = &cobra.Command{
Use: "rawblock [round number]",
Short: "Dump the raw, encoded msgpack block to a file or stdout",
Long: "Dump the raw, encoded msgpack block to a file or stdout",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
round, err := strconv.ParseUint(args[0], 10, 64)
if err != nil {
reportErrorf(errParsingRoundNumber, err)
}

dataDir := ensureSingleDataDir()
client := ensureAlgodClient(dataDir)
response, err := client.RawBlock(round)
if err != nil {
reportErrorf(errorRequestFail, err)
}

// If rawFilename flag was not set, the default value '-' will write to stdout
err = writeFile(rawFilename, response, 0600)
if err != nil {
reportErrorf(fileWriteError, rawFilename, err)
}
},
}
3 changes: 3 additions & 0 deletions cmd/goal/messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,4 +146,7 @@ const (
errWalletNotFound = "Wallet '%s' not found"
errDefaultWalletNotFound = "Wallet with ID '%s' not found. Was the default wallet deleted?"
errGettingToken = "Couldn't get token for wallet '%s' (ID: %s): %s"

// Ledger
errParsingRoundNumber = "Error parsing round number: %s"
)
46 changes: 40 additions & 6 deletions daemon/algod/api/client/restClient.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const (
authHeader = "X-Algo-API-Token"
healthCheckEndpoint = "/health"
apiVersionPathPrefix = "/v1"
maxRawResponseBytes = 50e6
)

// unversionedPaths ais a set of paths that should not be prefixed by the API version
Expand Down Expand Up @@ -87,7 +88,7 @@ func stripTransaction(tx string) string {
}

// submitForm is a helper used for submitting (ex.) GETs and POSTs to the server
func (client RestClient) submitForm(response interface{}, path string, request interface{}, requestMethod string, encodeJSON bool) error {
func (client RestClient) submitForm(response interface{}, path string, request interface{}, requestMethod string, encodeJSON bool, decodeJSON bool) error {
var err error
queryURL := client.serverURL
queryURL.Path = path
Expand Down Expand Up @@ -134,32 +135,55 @@ func (client RestClient) submitForm(response interface{}, path string, request i

httpClient := &http.Client{}
resp, err := httpClient.Do(req)

if err != nil {
return err
}

// Ensure response isn't too large
resp.Body = http.MaxBytesReader(nil, resp.Body, maxRawResponseBytes)
defer resp.Body.Close()

err = extractError(resp)
if err != nil {
return err
}

dec := json.NewDecoder(resp.Body)
return dec.Decode(&response)
if decodeJSON {
dec := json.NewDecoder(resp.Body)
return dec.Decode(&response)
}

// Response must implement RawResponse
raw, ok := response.(v1.RawResponse)
if !ok {
return fmt.Errorf("can only decode raw response into type implementing v1.RawResponse")
}

bodyBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}

raw.SetBytes(bodyBytes)
return nil
}

// get performs a GET request to the specific path against the server
func (client RestClient) get(response interface{}, path string, request interface{}) error {
return client.submitForm(response, path, request, "GET", false /* encodeJSON */)
return client.submitForm(response, path, request, "GET", false /* encodeJSON */, true /* decodeJSON */)
}

// getRaw behaves identically to get but doesn't json decode the response, and
// the response must implement the v1.RawResponse interface
func (client RestClient) getRaw(response v1.RawResponse, path string, request interface{}) error {
return client.submitForm(response, path, request, "GET", false /* encodeJSON */, false /* decodeJSON */)
}

// post sends a POST request to the given path with the given request object.
// No query parameters will be sent if request is nil.
// response must be a pointer to an object as post writes the response there.
func (client RestClient) post(response interface{}, path string, request interface{}) error {
return client.submitForm(response, path, request, "POST", true /* encodeJSON */)
return client.submitForm(response, path, request, "POST", true /* encodeJSON */, true /* decodeJSON */)
}

// Status retrieves the StatusResponse from the running node
Expand Down Expand Up @@ -219,6 +243,10 @@ type assetsParams struct {
Max uint64 `url:"max"`
}

type rawblockParams struct {
Raw uint64 `url:"raw"`
}

// TransactionsByAddr returns all transactions for a PK [addr] in the [first,
// last] rounds range.
func (client RestClient) TransactionsByAddr(addr string, first, last, max uint64) (response v1.TransactionList, err error) {
Expand Down Expand Up @@ -303,6 +331,12 @@ func (client RestClient) Block(round uint64) (response v1.Block, err error) {
return
}

// RawBlock gets the encoded, raw msgpack block for the given round
func (client RestClient) RawBlock(round uint64) (response v1.RawBlock, err error) {
err = client.getRaw(&response, fmt.Sprintf("/block/%d", round), rawblockParams{1})
return
}

// GetGoRoutines gets a dump of the goroutines from pprof
// Not supported
func (client RestClient) GetGoRoutines(ctx context.Context) (goRoutines string, err error) {
Expand Down
12 changes: 12 additions & 0 deletions daemon/algod/api/server/v1/handlers/responses.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,18 @@ func (r TransactionParamsResponse) getBody() interface{} {
return r.Body
}

// RawBlockResponse contains encoded, raw block information
//
// swagger:response RawBlockResponse
type RawBlockResponse struct {
// in: body
Body *v1.RawBlock
}

func (r RawBlockResponse) getBody() interface{} {
return r.Body
}

// BlockResponse contains block information
//
// swagger:response BlockResponse
Expand Down
15 changes: 15 additions & 0 deletions daemon/algod/api/spec/v1/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,21 @@ type TransactionParams struct {
MinTxnFee uint64 `json:"minFee"`
}

// RawResponse is fulfilled by responses that should not be decoded as msgpack
type RawResponse interface {
SetBytes([]byte)
}

// RawBlock represents an encoded msgpack block
// swagger:model RawBlock
// swagger:strfmt byte
type RawBlock []byte

// SetBytes fulfills the RawResponse interface on RawBlock
func (rb *RawBlock) SetBytes(b []byte) {
*rb = b
}

// Block contains a block information
// swagger:model Block
type Block struct {
Expand Down
9 changes: 9 additions & 0 deletions libgoal/libgoal.go
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,15 @@ func (c *Client) Block(round uint64) (resp v1.Block, err error) {
return
}

// RawBlock takes a round and returns its block
func (c *Client) RawBlock(round uint64) (resp v1.RawBlock, err error) {
algod, err := c.ensureAlgodClient()
if err == nil {
resp, err = algod.RawBlock(round)
}
return
}

// HealthCheck returns an error if something is wrong
func (c *Client) HealthCheck() error {
algod, err := c.ensureAlgodClient()
Expand Down