diff --git a/README.md b/README.md index ef6a70a..e401827 100644 --- a/README.md +++ b/README.md @@ -17,11 +17,11 @@ package main import ( "fmt" - goTezos "github.com/goat-systems/go-tezos/v4/rpc" + "github.com/goat-systems/go-tezos/v4/rpc" ) func main() { - rpc, err := client.New("http://127.0.0.1:8732") + client, err := rpc.New("http://127.0.0.1:8732") if err != nil { fmt.Printf("failed tp connect to network: %v", err) } @@ -31,7 +31,7 @@ func main() { fmt.Printf("failed to get (%s) head block: %s\n", resp.Status(), err.Error()) os.Exit(1) } - fmt.Println(block) + fmt.Println("Current Head Hash:", head.Hash) } ``` diff --git a/internal/crypto/crypto.go b/crypto/crypto.go similarity index 100% rename from internal/crypto/crypto.go rename to crypto/crypto.go diff --git a/rpc/block.go b/rpc/block.go index e529f95..95ba4de 100644 --- a/rpc/block.go +++ b/rpc/block.go @@ -212,6 +212,38 @@ type Operations struct { Signature string `json:"signature,omitempty"` } +/* +OperationsAlt represents a JSON array containing an opHash at index 0, and +an Operation object at index 1. The RPC does not properly objectify Refused, +BranchRefused, BranchDelayed, and Unprocessed sections of the mempool, +so we must parse them manually. +Code hints used from github.com/blockwatch-cc/tzindex/rpc/mempool.go +*/ +type OperationsAlt Operations + +func (o *OperationsAlt) UnmarshalJSON(buf []byte) error { + return unmarshalNamedJSONArray(buf, &o.Hash, (*Operations)(o)) +} + +func unmarshalNamedJSONArray(data []byte, v ...interface{}) error { + var raw []json.RawMessage + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + if len(raw) < len(v) { + return fmt.Errorf("JSON array is too short, expected %d, got %d", len(v), len(raw)) + } + + for i, vv := range v { + if err := json.Unmarshal(raw[i], vv); err != nil { + return err + } + } + + return nil +} + /* OrganizedContents represents the contents in Tezos operations orginized by kind. @@ -2017,6 +2049,22 @@ type MinimalValidTimeInput struct { EndorsingPower int } +func (i *MinimalValidTimeInput) contructRPCOptions() []rpcOptions { + var opts []rpcOptions + opts = append(opts, rpcOptions{ + "priority", + strconv.Itoa(i.Priority), + }) + + // Endorsing power + opts = append(opts, rpcOptions{ + "endorsing_power", + strconv.Itoa(i.EndorsingPower), + }) + return opts +} + + /* MinimalValidTime returns the minimal valid time for a block given a priority and an endorsing power. @@ -2026,7 +2074,7 @@ RPC https://tezos.gitlab.io/008/rpc.html#get-block-id-minimal-valid-time */ func (c *Client) MinimalValidTime(input MinimalValidTimeInput) (*resty.Response, time.Time, error) { - resp, err := c.get(fmt.Sprintf("/chains/%s/blocks/%s/minimal_valid_time", c.chain, input.BlockID.ID())) + resp, err := c.get(fmt.Sprintf("/chains/%s/blocks/%s/minimal_valid_time", c.chain, input.BlockID.ID()), input.contructRPCOptions()...) if err != nil { return resp, time.Time{}, errors.Wrapf(err, "failed to get minimal valid time at '%s'", input.BlockID.ID()) } diff --git a/rpc/client.go b/rpc/client.go index 62cf828..3f62d06 100644 --- a/rpc/client.go +++ b/rpc/client.go @@ -82,8 +82,8 @@ func (c *Client) SetChain(chain string) { c.chain = chain } -// CurrentContstants returns the constants used on the client -func (c *Client) CurrentContstants() Constants { +// CurrentConstants returns the constants used on the client +func (c *Client) CurrentConstants() Constants { return *c.networkConstants } diff --git a/rpc/client_test.go b/rpc/client_test.go index 4332065..f30808b 100644 --- a/rpc/client_test.go +++ b/rpc/client_test.go @@ -40,7 +40,7 @@ func Test_New(t *testing.T) { r, err := rpc.New(server.URL) checkErr(t, tt.wantErr, "", err) if err == nil { - assert.Equal(t, *tt.wantConstants, r.CurrentContstants()) + assert.Equal(t, *tt.wantConstants, r.CurrentConstants()) } }) } @@ -51,7 +51,7 @@ func Test_SetConstants(t *testing.T) { var constants rpc.Constants r.SetConstants(constants) - assert.Equal(t, constants, r.CurrentContstants()) + assert.Equal(t, constants, r.CurrentConstants()) } func gtGoldenHTTPMock(next http.Handler) http.Handler { diff --git a/rpc/context.go b/rpc/context.go index ee23df0..79602af 100644 --- a/rpc/context.go +++ b/rpc/context.go @@ -125,11 +125,11 @@ type Constants struct { BlocksPerCommitment int `json:"blocks_per_commitment"` BlocksPerRollSnapshot int `json:"blocks_per_roll_snapshot"` BlocksPerVotingPeriod int `json:"blocks_per_voting_period"` - TimeBetweenBlocks []string `json:"time_between_blocks"` + TimeBetweenBlocks IntArray `json:"time_between_blocks"` EndorsersPerBlock int `json:"endorsers_per_block"` HardGasLimitPerOperation int `json:"hard_gas_limit_per_operation,string"` HardGasLimitPerBlock int `json:"hard_gas_limit_per_block,string"` - ProofOfWorkThreshold string `json:"proof_of_work_threshold"` + ProofOfWorkThreshold uint64 `json:"proof_of_work_threshold,string"` TokensPerRoll string `json:"tokens_per_roll"` MichelsonMaximumTypeSize int `json:"michelson_maximum_type_size"` SeedNonceRevelationTip string `json:"seed_nonce_revelation_tip"` diff --git a/rpc/helpers.go b/rpc/helpers.go index 53ae7c3..297956e 100644 --- a/rpc/helpers.go +++ b/rpc/helpers.go @@ -460,7 +460,7 @@ RPC: */ type ForgeBlockHeaderBody struct { Level int `json:"level"` - Proto string `json:"proto"` + Proto int `json:"proto"` Predecessor string `json:"predecessor"` Timestamp time.Time `json:"timestamp"` ValidationPass int `json:"validation_pass"` @@ -697,14 +697,14 @@ func (p *PreapplyBlockInput) constructRPCOptions() []rpcOptions { if p.Sort { options = append(options, rpcOptions{ "sort", - "True", + "true", }) } if p.Timestamp != nil { options = append(options, rpcOptions{ "timestamp", - p.Timestamp.String(), + strconv.FormatInt(p.Timestamp.Unix(), 10), }) } @@ -732,7 +732,7 @@ type PreapplyBlockProtocolData struct { Protocol string `json:"protocol"` Priority int `json:"priority"` ProofOfWorkNonce string `json:"proof_of_work_nonce"` - SeedNonceHash string `json:"seed_nonce_hash"` + SeedNonceHash string `json:"seed_nonce_hash,omitempty"` Signature string `json:"signature"` } @@ -744,7 +744,7 @@ RPC: */ type PreappliedBlock struct { ShellHeader HeaderShell `json:"shell_header"` - Operations []PreappliedBlockOperations `json:"oeprations"` + Operations []PreappliedBlockOperations `json:"operations"` } /* diff --git a/rpc/independent.go b/rpc/independent.go index 4134867..2f6885f 100644 --- a/rpc/independent.go +++ b/rpc/independent.go @@ -2,6 +2,7 @@ package rpc import ( "encoding/json" + "fmt" "time" validator "github.com/go-playground/validator/v10" @@ -90,8 +91,13 @@ RPC: https://tezos.gitlab.io/shell/rpc.html#post-injection-block */ type InjectionBlockInput struct { - // Block to inject - Block *Block `validate:"required"` + + // Block header signature + SignedBlock string `validate:"required"` + + // Operations to include in the block. + // This is not the same as operations found in mempool and also not like preapply result + Operations [][]interface{} `validate:"required"` // If ?async is true, the function returns immediately. Async bool @@ -150,10 +156,25 @@ func (c *Client) InjectionBlock(input InjectionBlockInput) (*resty.Response, err return nil, errors.Wrap(err, "failed to inject block: invalid input") } - resp, err := c.post("/injection/block", *input.Block, input.contructRPCOptions()...) + // Create an anonymous struct containing the data required by RPC + newBlock := struct { + SignedBlock string `json:"data"` + Ops [][]interface{} `json:"operations"` + }{ + input.SignedBlock, + input.Operations, + } + + v, err := json.Marshal(newBlock) + if err != nil { + return nil, errors.Wrap(err, "failed to marshal new block") + } + + resp, err := c.post("/injection/block", v, input.contructRPCOptions()...) if err != nil { return resp, errors.Wrap(err, "failed to inject block") } + return resp, nil } @@ -250,3 +271,104 @@ func (c *Client) ActiveChains() (*resty.Response, ActiveChains, error) { return resp, activeChains, nil } + +/* +MempoolInput is the input for the goTezos.Mempool function. +Function: + func (c *Client) Mempool(input *MempoolInput) (Mempool, error) {} +*/ +type MempoolInput struct { + // Mempool filters + Applied bool + BranchDelayed bool + Refused bool + BranchRefused bool +} + +/* +Mempool represents the contents of the Tezos mempool. +RPC: + /chains//mempool/pending_operations (GET) +*/ +type Mempool struct { + Applied []Operations `json:"applied"` + Refused []OperationsAlt `json:"refused"` + BranchRefused []OperationsAlt `json:"branch_refused"` + BranchDelayed []OperationsAlt `json:"branch_delayed"` + Unprocessed []OperationsAlt `json:"unprocessed"` +} + +/* +Mempool fetches the current contents of main the chain mempool. +Path: + /chains//mempool/pending_operations (GET) +Parameters: + None +*/ +func (c *Client) Mempool(input MempoolInput) (*resty.Response, *Mempool, error) { + resp, err := c.get(fmt.Sprintf("/chains/%s/mempool/pending_operations", c.chain), input.constructRPCOptions()...) + if err != nil { + return resp, &Mempool{}, errors.Wrap(err, "failed to fetch mempool contents") + } + + var mempool Mempool + err = json.Unmarshal(resp.Body(), &mempool) + if err != nil { + return resp, &mempool, errors.Wrap(err, "failed to unmarshal mempool contents") + } + + return resp, &mempool, nil +} + +func (m *MempoolInput) constructRPCOptions() []rpcOptions { + var opts []rpcOptions + if m.Applied { + opts = append(opts, rpcOptions{ + "applied", + "true", + }) + } else { + opts = append(opts, rpcOptions{ + "applied", + "false", + }) + } + + if m.BranchDelayed { + opts = append(opts, rpcOptions{ + "branch_delayed", + "true", + }) + } else { + opts = append(opts, rpcOptions{ + "branch_delayed", + "false", + }) + } + + if m.Refused { + opts = append(opts, rpcOptions{ + "refused", + "true", + }) + } else { + opts = append(opts, rpcOptions{ + "refused", + "false", + }) + } + + if m.BranchRefused { + opts = append(opts, rpcOptions{ + "branch_refused", + "true", + }) + } else { + opts = append(opts, rpcOptions{ + "branch_refused", + "false", + }) + } + + return opts +}