diff --git a/README.md b/README.md index dcbaf4c..c3a14c1 100644 --- a/README.md +++ b/README.md @@ -50,29 +50,7 @@ if err := config.Parse("config.yaml", &cfg); err != nil { ### `node` -Simple Tezos RPC API wrapper. - -```go -import "github.com/dipdup-net/go-lib/node" - -rpc := node.NewNodeRPC(url, node.WithTimeout(timeout)) - -ctx, cancel := context.WithCancel(context.Background()) -defer cancel() - -// example with context -constants, err := rpc.Constants(node.WithContext(ctx)) -if err != nil { - panic(err) -} - -// example without context -constants, err := rpc.Constants() -if err != nil { - panic(err) -} - -``` +Simple Tezos RPC API wrapper. Docs you can find [here](node/README.md) ### `database` diff --git a/node/README.md b/node/README.md new file mode 100644 index 0000000..a700c24 --- /dev/null +++ b/node/README.md @@ -0,0 +1,55 @@ +# Tezos RPC client + +The library realize almost all RPC methods of Tezos node. + +## Usage + +### Simple example + +```go +rpc := node.NewRPC("https://rpc.tzkt.io/mainnet", "main") +ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) +defer cancel() + +block, err := rpc.Block(ctx, "head") +if err != nil { + panic(err) +} +log.Printf("%##v", block) +``` + +You can use main RPC constructor where chain id set by default to `main` + +```go +rpc := node.NewMainRPC("https://rpc.tzkt.io/mainnet") +``` + +### Usage certain API part + +RPC struct contains some internal parts: `Chain`, `Block`, `Context`, `Config`, `General`, `Protocols`, `Inject` and `Network`. You can use it without creation of full RPC client. + +```go +rpc := node.NewMainBlockRPC("https://rpc.tzkt.io/mainnet") +ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) +defer cancel() + +block, err := rpc.Block(ctx, "head") +if err != nil { + panic(err) +} +log.Printf("%##v", block) +``` + +### Interfaces + +For testing purpose RPC was wrapped by interfaces. Also each part of RPC has interface. You can mock it with code generation tools. + +Interfaces list: +* `BlockAPI` +* `ChainAPI` +* `ContextAPI` +* `ConfigAPI` +* `GeneralAPI` +* `ProtocolsAPI` +* `NetworkAPI` +* `InjectAPI` \ No newline at end of file diff --git a/node/api.go b/node/api.go new file mode 100644 index 0000000..ad9f14b --- /dev/null +++ b/node/api.go @@ -0,0 +1,57 @@ +package node + +import jsoniter "github.com/json-iterator/go" + +var json = jsoniter.ConfigCompatibleWithStandardLibrary + +// API - +type API interface { + BlockAPI + ChainAPI + ContextAPI + ConfigAPI + GeneralAPI + ProtocolsAPI + NetworkAPI + InjectAPI +} + +// RPC - +type RPC struct { + *BlockRPC + *Chain + *Context + *Config + *General + *Protocols + *Network + *Inject +} + +// NewRPC - +func NewRPC(baseURL, chainID string) *RPC { + return &RPC{ + BlockRPC: NewBlockRPC(baseURL, chainID), + Chain: NewChain(baseURL, chainID), + Context: NewContext(baseURL, chainID), + Config: NewConfig(baseURL), + General: NewGeneral(baseURL), + Protocols: NewProtocols(baseURL), + Network: NewNetwork(baseURL), + Inject: NewInject(baseURL), + } +} + +// NewMainRPC - +func NewMainRPC(baseURL string) *RPC { + return &RPC{ + BlockRPC: NewMainBlockRPC(baseURL), + Chain: NewMainChain(baseURL), + Context: NewMainContext(baseURL), + Config: NewConfig(baseURL), + General: NewGeneral(baseURL), + Protocols: NewProtocols(baseURL), + Network: NewNetwork(baseURL), + Inject: NewInject(baseURL), + } +} diff --git a/node/block_api.go b/node/block_api.go new file mode 100644 index 0000000..8ae78aa --- /dev/null +++ b/node/block_api.go @@ -0,0 +1,358 @@ +package node + +import ( + "context" + "fmt" + "net/url" + "strings" +) + +// BlockAPI - +type BlockAPI interface { + Blocks(ctx context.Context, args BlocksArgs) ([][]string, error) + Block(ctx context.Context, blockID string) (Block, error) + Head(ctx context.Context) (Block, error) + Header(ctx context.Context, blockID string) (Header, error) + HeaderRaw(ctx context.Context, blockID string) (string, error) + HeaderShell(ctx context.Context, blockID string) (HeaderShell, error) + Metadata(ctx context.Context, blockID string) (BlockMetadata, error) + MetadataHash(ctx context.Context, blockID string) (string, error) + Hash(ctx context.Context, blockID string) (string, error) + ProtocolData(ctx context.Context, blockID string) (ProtocolData, error) + ProtocolDataRaw(ctx context.Context, blockID string) (string, error) + OperationHashes(ctx context.Context, blockID string) ([][]string, error) + OperationMetadataHash(ctx context.Context, blockID string) (string, error) + OperationMetadataHashes(ctx context.Context, blockID string) ([][]string, error) + Operations(ctx context.Context, blockID string) ([][]OperationGroup, error) + OperationsOffset(ctx context.Context, blockID string, listOffset int) ([]OperationGroup, error) + Operation(ctx context.Context, blockID string, listOffset, operationOffset int) (OperationGroup, error) + BlockProtocols(ctx context.Context, blockID string) (BlockProtocols, error) + VotesBallotList(ctx context.Context, blockID string) ([]BlockBallot, error) + VotesBallots(ctx context.Context, blockID string) (BlockBallots, error) + VotesCurrentPeriod(ctx context.Context, blockID string) (VotingPeriod, error) + VotesCurrentProposal(ctx context.Context, blockID string) (string, error) + VotesQuorum(ctx context.Context, blockID string) (int, error) + VotesListing(ctx context.Context, blockID string) ([]Rolls, error) + VotesProposals(ctx context.Context, blockID string) ([]string, error) + VotesSuccessorPeriod(ctx context.Context, blockID string) (VotingPeriod, error) + VotesTotalVotingPower(ctx context.Context, blockID string) (int, error) +} + +// BlockRPC - +type BlockRPC struct { + baseURL string + chainID string + client *client +} + +// NewChain - +func NewBlockRPC(baseURL, chainID string) *BlockRPC { + return &BlockRPC{ + baseURL: strings.TrimSuffix(baseURL, "/"), + chainID: chainID, + client: newClient(), + } +} + +// NewMainBlockRPC - +func NewMainBlockRPC(baseURL string) *BlockRPC { + return NewBlockRPC(baseURL, "main") +} + +// Blocks - +func (api *BlockRPC) Blocks(ctx context.Context, args BlocksArgs) ([][]string, error) { + queryArgs := make(url.Values) + if args.HeadHash != "" { + queryArgs.Add("head", args.HeadHash) + } + if args.Length > 0 { + queryArgs.Add("length", fmt.Sprintf("%d", args.Length)) + } + + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/blocks/", api.chainID), queryArgs) + if err != nil { + return nil, err + } + var blocks [][]string + err = req.doWithJSONResponse(ctx, api.client, &blocks) + return blocks, err +} + +// Block - +func (api *BlockRPC) Block(ctx context.Context, blockID string) (Block, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/blocks/%s", api.chainID, blockID), nil) + if err != nil { + return Block{}, err + } + var block Block + err = req.doWithJSONResponse(ctx, api.client, &block) + return block, err +} + +// Head - +func (api *BlockRPC) Head(ctx context.Context) (Block, error) { + return api.Block(ctx, "head") +} + +// Header - +func (api *BlockRPC) Header(ctx context.Context, blockID string) (Header, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/blocks/%s/header", api.chainID, blockID), nil) + if err != nil { + return Header{}, err + } + var block Header + err = req.doWithJSONResponse(ctx, api.client, &block) + return block, err +} + +// HeaderRaw - +func (api *BlockRPC) HeaderRaw(ctx context.Context, blockID string) (string, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/blocks/%s/header/raw", api.chainID, blockID), nil) + if err != nil { + return "", err + } + var block string + err = req.doWithJSONResponse(ctx, api.client, &block) + return block, err +} + +// HeaderShell - +func (api *BlockRPC) HeaderShell(ctx context.Context, blockID string) (HeaderShell, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/blocks/%s/header/shell", api.chainID, blockID), nil) + if err != nil { + return HeaderShell{}, err + } + var block HeaderShell + err = req.doWithJSONResponse(ctx, api.client, &block) + return block, err +} + +// Metadata - +func (api *BlockRPC) Metadata(ctx context.Context, blockID string) (BlockMetadata, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/blocks/%s/metadata", api.chainID, blockID), nil) + if err != nil { + return BlockMetadata{}, err + } + var block BlockMetadata + err = req.doWithJSONResponse(ctx, api.client, &block) + return block, err +} + +// MetadataHash - +func (api *BlockRPC) MetadataHash(ctx context.Context, blockID string) (string, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/blocks/%s/metadata_hash", api.chainID, blockID), nil) + if err != nil { + return "", err + } + var block string + err = req.doWithJSONResponse(ctx, api.client, &block) + return block, err +} + +// Hash - +func (api *BlockRPC) Hash(ctx context.Context, blockID string) (string, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/blocks/%s/hash", api.chainID, blockID), nil) + if err != nil { + return "", err + } + var block string + err = req.doWithJSONResponse(ctx, api.client, &block) + return block, err +} + +// ProtocolData - +func (api *BlockRPC) ProtocolData(ctx context.Context, blockID string) (ProtocolData, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/blocks/%s/header/protocol_data", api.chainID, blockID), nil) + if err != nil { + return ProtocolData{}, err + } + var block ProtocolData + err = req.doWithJSONResponse(ctx, api.client, &block) + return block, err +} + +// ProtocolDataRaw - +func (api *BlockRPC) ProtocolDataRaw(ctx context.Context, blockID string) (string, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/blocks/%s/header/protocol_data/raw", api.chainID, blockID), nil) + if err != nil { + return "", err + } + var block string + err = req.doWithJSONResponse(ctx, api.client, &block) + return block, err +} + +// OperationHashes - +func (api *BlockRPC) OperationHashes(ctx context.Context, blockID string) ([][]string, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/blocks/%s/header/protocol_data/raw", api.chainID, blockID), nil) + if err != nil { + return nil, err + } + var block [][]string + err = req.doWithJSONResponse(ctx, api.client, &block) + return block, err +} + +// OperationMetadataHash - +func (api *BlockRPC) OperationMetadataHash(ctx context.Context, blockID string) (string, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/blocks/%s/operations_metadata_hash", api.chainID, blockID), nil) + if err != nil { + return "", err + } + var block string + err = req.doWithJSONResponse(ctx, api.client, &block) + return block, err +} + +// OperationMetadataHashes - +func (api *BlockRPC) OperationMetadataHashes(ctx context.Context, blockID string) ([][]string, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/blocks/%s/operation_metadata_hashes", api.chainID, blockID), nil) + if err != nil { + return nil, err + } + var block [][]string + err = req.doWithJSONResponse(ctx, api.client, &block) + return block, err +} + +// Operations - +func (api *BlockRPC) Operations(ctx context.Context, blockID string) ([][]OperationGroup, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/blocks/%s/operations", api.chainID, blockID), nil) + if err != nil { + return nil, err + } + var block [][]OperationGroup + err = req.doWithJSONResponse(ctx, api.client, &block) + return block, err +} + +// OperationsOffset - +func (api *BlockRPC) OperationsOffset(ctx context.Context, blockID string, listOffset int) ([]OperationGroup, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/blocks/%s/operations/%d", api.chainID, blockID, listOffset), nil) + if err != nil { + return nil, err + } + var block []OperationGroup + err = req.doWithJSONResponse(ctx, api.client, &block) + return block, err +} + +// Operation - +func (api *BlockRPC) Operation(ctx context.Context, blockID string, listOffset, operationOffset int) (OperationGroup, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/blocks/%s/operations/%d/%d", api.chainID, blockID, listOffset, operationOffset), nil) + if err != nil { + return OperationGroup{}, err + } + var block OperationGroup + err = req.doWithJSONResponse(ctx, api.client, &block) + return block, err +} + +// BlockProtocols - +func (api *BlockRPC) BlockProtocols(ctx context.Context, blockID string) (BlockProtocols, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/blocks/%s/protocols", api.chainID, blockID), nil) + if err != nil { + return BlockProtocols{}, err + } + var block BlockProtocols + err = req.doWithJSONResponse(ctx, api.client, &block) + return block, err +} + +func (api *BlockRPC) VotesBallotList(ctx context.Context, blockID string) ([]BlockBallot, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/blocks/%s/votes/ballot_list", api.chainID, blockID), nil) + if err != nil { + return nil, err + } + var block []BlockBallot + err = req.doWithJSONResponse(ctx, api.client, &block) + return block, err +} + +// VotesBallots - +func (api *BlockRPC) VotesBallots(ctx context.Context, blockID string) (BlockBallots, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/blocks/%s/votes/ballots", api.chainID, blockID), nil) + if err != nil { + return BlockBallots{}, err + } + var block BlockBallots + err = req.doWithJSONResponse(ctx, api.client, &block) + return block, err +} + +// VotesCurrentPeriod - +func (api *BlockRPC) VotesCurrentPeriod(ctx context.Context, blockID string) (VotingPeriod, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/blocks/%s/votes/current_period", api.chainID, blockID), nil) + if err != nil { + return VotingPeriod{}, err + } + var block VotingPeriod + err = req.doWithJSONResponse(ctx, api.client, &block) + return block, err +} + +// VotesCurrentProposal - +func (api *BlockRPC) VotesCurrentProposal(ctx context.Context, blockID string) (string, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/blocks/%s/votes/current_proposal", api.chainID, blockID), nil) + if err != nil { + return "", err + } + var block string + err = req.doWithJSONResponse(ctx, api.client, &block) + return block, err +} + +// VotesQuorum - +func (api *BlockRPC) VotesQuorum(ctx context.Context, blockID string) (int, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/blocks/%s/votes/current_quorum", api.chainID, blockID), nil) + if err != nil { + return 0, err + } + var block int + err = req.doWithJSONResponse(ctx, api.client, &block) + return block, err +} + +// VotesListing +func (api *BlockRPC) VotesListing(ctx context.Context, blockID string) ([]Rolls, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/blocks/%s/votes/listings", api.chainID, blockID), nil) + if err != nil { + return nil, err + } + var block []Rolls + err = req.doWithJSONResponse(ctx, api.client, &block) + return block, err +} + +// VotesProposals - +func (api *BlockRPC) VotesProposals(ctx context.Context, blockID string) ([]string, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/blocks/%s/votes/proposals", api.chainID, blockID), nil) + if err != nil { + return nil, err + } + var block []string + err = req.doWithJSONResponse(ctx, api.client, &block) + return block, err +} + +// VotesSuccessorPeriod - +func (api *BlockRPC) VotesSuccessorPeriod(ctx context.Context, blockID string) (VotingPeriod, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/blocks/%s/votes/successor_period", api.chainID, blockID), nil) + if err != nil { + return VotingPeriod{}, err + } + var block VotingPeriod + err = req.doWithJSONResponse(ctx, api.client, &block) + return block, err +} + +// VotesTotalVotingPower - +func (api *BlockRPC) VotesTotalVotingPower(ctx context.Context, blockID string) (int, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/blocks/%s/votes/total_voting_power", api.chainID, blockID), nil) + if err != nil { + return 0, err + } + var block int + err = req.doWithJSONResponse(ctx, api.client, &block) + return block, err +} diff --git a/node/block_data.go b/node/block_data.go new file mode 100644 index 0000000..ac80775 --- /dev/null +++ b/node/block_data.go @@ -0,0 +1,163 @@ +package node + +import ( + stdJSON "encoding/json" + "time" +) + +// Header - +type Header struct { + Protocol string `json:"protocol"` + ChainID string `json:"chain_id"` + Hash string `json:"hash"` + Level uint64 `json:"level"` + Proto int `json:"proto"` + Predecessor string `json:"predecessor"` + Timestamp time.Time `json:"timestamp"` + ValidationPass int `json:"validation_pass"` + OperationsHash string `json:"operations_hash"` + Fitness []string `json:"fitness"` + Context string `json:"context"` + Priority int `json:"priority"` + ProofOfWorkNonce string `json:"proof_of_work_nonce"` + Signature string `json:"signature"` +} + +// BlockMetadata - +type BlockMetadata struct { + Protocol string `json:"protocol"` + NextProtocol string `json:"next_protocol"` + TestChainStatus struct { + Status string `json:"status"` + } `json:"test_chain_status"` + MaxOperationsTTL int `json:"max_operations_ttl"` + MaxOperationDataLength int `json:"max_operation_data_length"` + MaxBlockHeaderLength int `json:"max_block_header_length"` + MaxOperationListLength []struct { + MaxSize int `json:"max_size"` + MaxOp int `json:"max_op,omitempty"` + } `json:"max_operation_list_length"` + Baker string `json:"baker"` + LevelInfo LevelInfo `json:"level_info"` + VotingPeriodInfo struct { + VotingPeriod struct { + Index int `json:"index"` + Kind string `json:"kind"` + StartPosition int `json:"start_position"` + } `json:"voting_period"` + Position int `json:"position"` + Remaining int `json:"remaining"` + } `json:"voting_period_info"` + NonceHash string `json:"nonce_hash"` + ConsumedGas string `json:"consumed_gas"` + Deactivated []interface{} `json:"deactivated"` + BalanceUpdates []BalanceUpdate `json:"balance_updates"` + LiquidityBakingEscapeEma int `json:"liquidity_baking_escape_ema"` + ImplicitOperationsResults []ImplicitOperationsResult `json:"implicit_operations_results"` +} + +// Block - +type Block struct { + Protocol string `json:"protocol"` + ChainID string `json:"chain_id"` + Hash string `json:"hash"` + Header Header `json:"header"` + Metadata BlockMetadata `json:"metadata"` + Operations [][]OperationGroup +} + +// BalanceUpdate - +type BalanceUpdate struct { + Kind string `json:"kind"` + Contract string `json:"contract,omitempty"` + Change string `json:"change"` + Category string `json:"category,omitempty"` + Origin string `json:"origin,omitempty"` + Delegate string `json:"delegate,omitempty"` + Cycle uint64 `json:"cycle,omitempty"` + Level uint64 `json:"level,omitempty"` +} + +// ImplicitOperationsResult - +type ImplicitOperationsResult struct { + Kind string `json:"kind"` + BalanceUpdates []BalanceUpdate `json:"balance_updates"` + OriginatedContracts []string `json:"originated_contracts,omitempty"` + StorageSize int64 `json:"storage_size,string"` + PaidStorageSizeDiff int64 `json:"paid_storage_size_diff,string"` + Storage stdJSON.RawMessage `json:"storage,omitempty"` + ConsumedGas int64 `json:"consumed_gas,string,omitempty"` + ConsumedMilligas int64 `json:"consumed_milligas,string,omitempty"` +} + +// LevelInfo - +type LevelInfo struct { + Level int64 `json:"level"` + LevelPosition int64 `json:"level_position"` + Cycle int64 `json:"cycle"` + CyclePosition int64 `json:"cycle_position"` + ExpectedCommitment bool `json:"expected_commitment"` +} + +// ProtocolData - +type ProtocolData struct { + Protocol string `json:"protocol"` + Priority int `json:"priority"` + ProofOfWorkNonce string `json:"proof_of_work_nonce"` + LiquidityBakingEscapeVote bool `json:"liquidity_baking_escape_vote"` + Signature string `json:"signature"` +} + +// HeaderShell - +type HeaderShell struct { + Level int64 `json:"level"` + Proto int `json:"proto"` + Predecessor string `json:"predecessor"` + Timestamp time.Time `json:"timestamp"` + ValidationPass int `json:"validation_pass"` + OperationsHash string `json:"operations_hash"` + Fitness []string `json:"fitness"` + Context string `json:"context"` +} + +// BlockProtocols - +type BlockProtocols struct { + Protocol string `json:"protocol"` + NextProtocol string `json:"next_protocol"` +} + +// BlockBallot - +type BlockBallot struct { + Pkh string `json:"pkh"` + Ballot string `json:"ballot"` +} + +// BlockBallots - +type BlockBallots struct { + Yay int `json:"yay"` + Nay int `json:"nay"` + Pass int `json:"pass"` +} + +// VotingPeriod - +type VotingPeriod struct { + VotingPeriod struct { + Index int `json:"index"` + Kind string `json:"kind"` + StartPosition int `json:"start_position"` + } `json:"voting_period"` + Position int `json:"position"` + Remaining int `json:"remaining"` +} + +// Rolls - +type Rolls struct { + Pkh string `json:"pkh"` + Rolls int `json:"rolls"` +} + +// BlocksArgs - +type BlocksArgs struct { + Length uint + HeadHash string +} diff --git a/node/chain_api.go b/node/chain_api.go new file mode 100644 index 0000000..737cf09 --- /dev/null +++ b/node/chain_api.go @@ -0,0 +1,128 @@ +package node + +import ( + "context" + "fmt" + "strings" +) + +// ChainAPI - +type ChainAPI interface { + ChainID(ctx context.Context) (string, error) + InvalidBlocks(ctx context.Context) ([]InvalidBlock, error) + InvalidBlock(ctx context.Context, blockHash string) (InvalidBlock, error) + IsBootstrapped(ctx context.Context) (Bootstrapped, error) + LevelsCaboose(ctx context.Context) (Caboose, error) + LevelsCheckpoint(ctx context.Context) (Checkpoint, error) + LevelsSavepoint(ctx context.Context) (Savepoint, error) + PendingOperations(ctx context.Context) (MempoolResponse, error) +} + +// Chain - +type Chain struct { + baseURL string + chainID string + client *client +} + +// NewChain - +func NewChain(baseURL, chainID string) *Chain { + return &Chain{ + baseURL: strings.TrimSuffix(baseURL, "/"), + chainID: chainID, + client: newClient(), + } +} + +// NewMainChain - +func NewMainChain(baseURL string) *Chain { + return NewChain(baseURL, "main") +} + +// ChainID - +func (api *Chain) ChainID(ctx context.Context) (string, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/chain_id", api.chainID), nil) + if err != nil { + return "", err + } + var chainID string + err = req.doWithJSONResponse(ctx, api.client, &chainID) + return chainID, err +} + +// InvalidBlocks - +func (api *Chain) InvalidBlocks(ctx context.Context) ([]InvalidBlock, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/invalid_blocks", api.chainID), nil) + if err != nil { + return nil, err + } + var result []InvalidBlock + err = req.doWithJSONResponse(ctx, api.client, &result) + return result, err +} + +// InvalidBlock - +func (api *Chain) InvalidBlock(ctx context.Context, blockHash string) (InvalidBlock, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/invalid_blocks/%s", api.chainID, blockHash), nil) + if err != nil { + return InvalidBlock{}, err + } + var result InvalidBlock + err = req.doWithJSONResponse(ctx, api.client, &result) + return result, err +} + +// IsBootstrapped - +func (api *Chain) IsBootstrapped(ctx context.Context) (Bootstrapped, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/is_bootstrapped", api.chainID), nil) + if err != nil { + return Bootstrapped{}, err + } + var result Bootstrapped + err = req.doWithJSONResponse(ctx, api.client, &result) + return result, err +} + +// LevelsCaboose - +func (api *Chain) LevelsCaboose(ctx context.Context) (Caboose, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/levels/caboose", api.chainID), nil) + if err != nil { + return Caboose{}, err + } + var result Caboose + err = req.doWithJSONResponse(ctx, api.client, &result) + return result, err +} + +// LevelsCheckpoint - +func (api *Chain) LevelsCheckpoint(ctx context.Context) (Checkpoint, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/levels/checkpoint", api.chainID), nil) + if err != nil { + return Checkpoint{}, err + } + var result Checkpoint + err = req.doWithJSONResponse(ctx, api.client, &result) + return result, err +} + +// LevelsSavepoint - +func (api *Chain) LevelsSavepoint(ctx context.Context) (Savepoint, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/levels/savepoint", api.chainID), nil) + if err != nil { + return Savepoint{}, err + } + var result Savepoint + err = req.doWithJSONResponse(ctx, api.client, &result) + return result, err +} + +// PendingOperations - +func (api *Chain) PendingOperations(ctx context.Context) (MempoolResponse, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/mempool/pending_operations", api.chainID), nil) + if err != nil { + return MempoolResponse{}, err + } + var result MempoolResponse + err = req.doWithJSONResponse(ctx, api.client, &result) + return result, err +} diff --git a/node/chain_data.go b/node/chain_data.go new file mode 100644 index 0000000..2367516 --- /dev/null +++ b/node/chain_data.go @@ -0,0 +1,138 @@ +package node + +import ( + stdJSON "encoding/json" + + "github.com/pkg/errors" +) + +// InvalidBlock - +type InvalidBlock struct { + Hash string `json:"block"` + Level uint64 `json:"level"` + Errors []interface{} `json:"errors"` +} + +// Bootstrapped - +type Bootstrapped struct { + Bootstrapped bool `json:"bootstrapped"` + SyncState string `json:"sync_state"` +} + +// Caboose - +type Caboose struct { + Hash string `json:"block_hash"` + Level uint64 `json:"level"` +} + +// Checkpoint - +type Checkpoint struct { + Hash string `json:"block_hash"` + Level uint64 `json:"level"` +} + +// Savepoint - +type Savepoint struct { + Hash string `json:"block_hash"` + Level uint64 `json:"level"` +} + +// MempoolResponse - +type MempoolResponse struct { + Applied []Applied `json:"applied"` + Refused []Failed `json:"refused"` + BranchRefused []Failed `json:"branch_refused"` + BranchDelayed []Failed `json:"branch_delayed"` +} + +// Applied - +type Applied struct { + Hash string `json:"hash"` + Branch string `json:"branch"` + Signature string `json:"signature"` + Contents []Content `json:"contents"` + Raw stdJSON.RawMessage `json:"raw"` +} + +// UnmarshalJSON - +func (a *Applied) UnmarshalJSON(data []byte) error { + type buf Applied + if err := json.Unmarshal(data, (*buf)(a)); err != nil { + return err + } + a.Raw = data + return nil +} + +// Failed - +type Failed struct { + Hash string `json:"-"` + Protocol string `json:"protocol"` + Branch string `json:"branch"` + Contents []Content `json:"contents"` + Signature string `json:"signature,omitempty"` + Error stdJSON.RawMessage `json:"error,omitempty"` + Raw stdJSON.RawMessage `json:"raw"` +} + +// UnmarshalJSON - +func (f *Failed) UnmarshalJSON(data []byte) error { + var body []stdJSON.RawMessage + if err := json.Unmarshal(data, &body); err != nil { + return err + } + if len(body) != 2 { + return errors.Errorf("Invalid failed operation body %s", string(data)) + } + if err := json.Unmarshal(body[0], &f.Hash); err != nil { + return err + } + type buf Failed + if err := json.Unmarshal(body[1], (*buf)(f)); err != nil { + return err + } + f.Raw = data + return nil +} + +// FailedMonitor - +type FailedMonitor struct { + Hash string `json:"hash"` + Protocol string `json:"protocol"` + Branch string `json:"branch"` + Contents []Content `json:"contents"` + Signature string `json:"signature,omitempty"` + Error stdJSON.RawMessage `json:"error,omitempty"` + Raw stdJSON.RawMessage `json:"raw"` +} + +// UnmarshalJSON - +func (f *FailedMonitor) UnmarshalJSON(data []byte) error { + type buf FailedMonitor + if err := json.Unmarshal(data, (*buf)(f)); err != nil { + return err + } + f.Raw = data + return nil +} + +// Contents - +type Content struct { + Kind string `json:"kind"` + Body stdJSON.RawMessage `json:"-"` +} + +// UnmarshalJSON - +func (c *Content) UnmarshalJSON(data []byte) error { + type buf Content + if err := json.Unmarshal(data, (*buf)(c)); err != nil { + return err + } + c.Body = data + return nil +} + +// IsManager - +func IsManager(kind string) bool { + return kind == KindDelegation || kind == KindOrigination || kind == KindReveal || kind == KindTransaction +} diff --git a/node/client.go b/node/client.go new file mode 100644 index 0000000..d4f0fa5 --- /dev/null +++ b/node/client.go @@ -0,0 +1,24 @@ +package node + +import ( + "net/http" + "time" +) + +type client struct { + *http.Client +} + +func newClient() *client { + t := http.DefaultTransport.(*http.Transport).Clone() + t.MaxIdleConns = 100 + t.MaxConnsPerHost = 100 + t.MaxIdleConnsPerHost = 100 + + return &client{ + &http.Client{ + Timeout: time.Minute, + Transport: t, + }, + } +} diff --git a/node/config_api.go b/node/config_api.go new file mode 100644 index 0000000..f173010 --- /dev/null +++ b/node/config_api.go @@ -0,0 +1,57 @@ +package node + +import "context" + +// ConfigAPI - +type ConfigAPI interface { + HistoryMode(ctx context.Context) (HistoryMode, error) + UserActivatedProtocols(ctx context.Context) ([]ActivatedProtocol, error) + UserActivatedUpgrades(ctx context.Context) ([]ActivatedUpgrades, error) +} + +// Config - +type Config struct { + baseURL string + client *client +} + +// NewConfig - +func NewConfig(baseURL string) *Config { + return &Config{ + baseURL: baseURL, + client: newClient(), + } +} + +// HistoryMode - +func (api *Config) HistoryMode(ctx context.Context) (HistoryMode, error) { + req, err := newGetRequest(api.baseURL, "config/history_mode", nil) + if err != nil { + return HistoryMode{}, err + } + var result HistoryMode + err = req.doWithJSONResponse(ctx, api.client, &result) + return result, err +} + +// UserActivatedProtocols - +func (api *Config) UserActivatedProtocols(ctx context.Context) ([]ActivatedProtocol, error) { + req, err := newGetRequest(api.baseURL, "config/network/user_activated_protocol_overrides", nil) + if err != nil { + return nil, err + } + var result []ActivatedProtocol + err = req.doWithJSONResponse(ctx, api.client, &result) + return result, err +} + +// UserActivatedUpgrades - +func (api *Config) UserActivatedUpgrades(ctx context.Context) ([]ActivatedUpgrades, error) { + req, err := newGetRequest(api.baseURL, "config/network/user_activated_upgrades", nil) + if err != nil { + return nil, err + } + var result []ActivatedUpgrades + err = req.doWithJSONResponse(ctx, api.client, &result) + return result, err +} diff --git a/node/config_data.go b/node/config_data.go new file mode 100644 index 0000000..63b75c8 --- /dev/null +++ b/node/config_data.go @@ -0,0 +1,18 @@ +package node + +// HistoryMode - +type HistoryMode struct { + Mode string `json:"history_mode"` +} + +// ActivatedProtocol - +type ActivatedProtocol struct { + ReplacedProtocol string `json:"replaced_protocol"` + ReplacementProtocol string `json:"replacement_protocol"` +} + +// ActivatedUpgrades - +type ActivatedUpgrades struct { + Level int `json:"level"` + ReplacementProtocol string `json:"replacement_protocol"` +} diff --git a/node/context_api.go b/node/context_api.go new file mode 100644 index 0000000..cae96b8 --- /dev/null +++ b/node/context_api.go @@ -0,0 +1,321 @@ +package node + +import ( + "context" + stdJSON "encoding/json" + "fmt" + "strings" + + "github.com/pkg/errors" +) + +// ContextAPI - +type ContextAPI interface { + BigMap(ctx context.Context, blockID string, bigMapID uint64) ([]byte, error) + BigMapKey(ctx context.Context, blockID string, bigMapID uint64, key string) (interface{}, error) + CacheContracts(ctx context.Context, blockID string) (interface{}, error) + CacheContractsSize(ctx context.Context, blockID string) (uint64, error) + CacheContractsSizeLimit(ctx context.Context, blockID string) (uint64, error) + Constants(ctx context.Context, blockID string) (Constants, error) + Contracts(ctx context.Context, blockID string) ([]string, error) + Contract(ctx context.Context, blockID, contract string) (ContractInfo, error) + ContractBalance(ctx context.Context, blockID, contract string) (string, error) + ContractCounter(ctx context.Context, blockID, contract string) (string, error) + ContractDelegate(ctx context.Context, blockID, contract string) (string, error) + ContractEntrypoints(ctx context.Context, blockID, contract string) (Entrypoints, error) + ContractEntrypoint(ctx context.Context, blockID, contract, entrypoint string) (stdJSON.RawMessage, error) + ContractScript(ctx context.Context, blockID, contract string) (Script, error) + ContractStorage(ctx context.Context, blockID, contract string) (stdJSON.RawMessage, error) + Delegates(ctx context.Context, blockID string, active DelegateType) ([]string, error) + Delegate(ctx context.Context, blockID, pkh string) (Delegate, error) + DelegateDeactivated(ctx context.Context, blockID, pkh string) (bool, error) + DelegateBalance(ctx context.Context, blockID, pkh string) (string, error) + DelegateContracts(ctx context.Context, blockID, pkh string) ([]string, error) + DelegateGracePeriod(ctx context.Context, blockID, pkh string) (int, error) + DelegateStakingBalance(ctx context.Context, blockID, pkh string) (string, error) + DelegateVotingPower(ctx context.Context, blockID, pkh string) (int, error) + ActiveDelegatesWithRolls(ctx context.Context, blockID string) ([]string, error) + LiquidityBakingCPMMAddress(ctx context.Context, blockID string) (string, error) +} + +// Context - +type Context struct { + baseURL string + chainID string + client *client +} + +// NewContext - +func NewContext(baseURL, chainID string) *Context { + return &Context{ + baseURL: strings.TrimSuffix(baseURL, "/"), + chainID: chainID, + client: newClient(), + } +} + +// NewMainContext - +func NewMainContext(baseURL string) *Context { + return NewContext(baseURL, "main") +} + +// BigMap - +func (api *Context) BigMap(ctx context.Context, blockID string, bigMapID uint64) ([]byte, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/blocks/%s/context/big_maps/%d", api.chainID, blockID, bigMapID), nil) + if err != nil { + return nil, err + } + return req.doWithBytesResponse(ctx, api.client) +} + +// BigMapKey - +func (api *Context) BigMapKey(ctx context.Context, blockID string, bigMapID uint64, key string) (interface{}, error) { + return nil, errors.New("not implemented") +} + +// CacheContracts - +func (api *Context) CacheContracts(ctx context.Context, blockID string) (interface{}, error) { + return nil, errors.New("not implemented") +} + +// CacheContractsSize - +func (api *Context) CacheContractsSize(ctx context.Context, blockID string) (uint64, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/blocks/%s/context/cache/contracts/size", api.chainID, blockID), nil) + if err != nil { + return 0, err + } + var result uint64 + err = req.doWithJSONResponse(ctx, api.client, &result) + return result, err +} + +// CacheContractsSizeLimit - +func (api *Context) CacheContractsSizeLimit(ctx context.Context, blockID string) (uint64, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/blocks/%s/context/cache/contracts/size_limit", api.chainID, blockID), nil) + if err != nil { + return 0, err + } + var result uint64 + err = req.doWithJSONResponse(ctx, api.client, &result) + return result, err +} + +// Constants - +func (api *Context) Constants(ctx context.Context, blockID string) (Constants, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/blocks/%s/context/constants", api.chainID, blockID), nil) + if err != nil { + return Constants{}, err + } + var result Constants + err = req.doWithJSONResponse(ctx, api.client, &result) + return result, err +} + +// Contracts - +func (api *Context) Contracts(ctx context.Context, blockID string) ([]string, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/blocks/%s/context/contracts", api.chainID, blockID), nil) + if err != nil { + return nil, err + } + var result []string + err = req.doWithJSONResponse(ctx, api.client, &result) + return result, err +} + +// Contract - +func (api *Context) Contract(ctx context.Context, blockID, contract string) (ContractInfo, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/blocks/%s/context/contracts/%s", api.chainID, blockID, contract), nil) + if err != nil { + return ContractInfo{}, err + } + var result ContractInfo + err = req.doWithJSONResponse(ctx, api.client, &result) + return result, err +} + +// ContractBalance - +func (api *Context) ContractBalance(ctx context.Context, blockID, contract string) (string, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/blocks/%s/context/contracts/%s/balance", api.chainID, blockID, contract), nil) + if err != nil { + return "", err + } + var result string + err = req.doWithJSONResponse(ctx, api.client, &result) + return result, err +} + +// ContractCounter - +func (api *Context) ContractCounter(ctx context.Context, blockID, contract string) (string, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/blocks/%s/context/contracts/%s/counter", api.chainID, blockID, contract), nil) + if err != nil { + return "", err + } + var result string + err = req.doWithJSONResponse(ctx, api.client, &result) + return result, err +} + +// ContractDelegate - +func (api *Context) ContractDelegate(ctx context.Context, blockID, contract string) (string, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/blocks/%s/context/contracts/%s/delegate", api.chainID, blockID, contract), nil) + if err != nil { + return "", err + } + var result string + err = req.doWithJSONResponse(ctx, api.client, &result) + return result, err +} + +// ContractEntrypoints - +func (api *Context) ContractEntrypoints(ctx context.Context, blockID, contract string) (Entrypoints, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/blocks/%s/context/contracts/%s/entrypoints", api.chainID, blockID, contract), nil) + if err != nil { + return Entrypoints{}, err + } + var result Entrypoints + err = req.doWithJSONResponse(ctx, api.client, &result) + return result, err +} + +// ContractEntrypoint - +func (api *Context) ContractEntrypoint(ctx context.Context, blockID, contract, entrypoint string) (stdJSON.RawMessage, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/blocks/%s/context/contracts/%s/entrypoint", api.chainID, blockID, contract), nil) + if err != nil { + return nil, err + } + return req.doWithBytesResponse(ctx, api.client) +} + +// ContractScript - +func (api *Context) ContractScript(ctx context.Context, blockID, contract string) (Script, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/blocks/%s/context/contracts/%s/script", api.chainID, blockID, contract), nil) + if err != nil { + return Script{}, err + } + var result Script + err = req.doWithJSONResponse(ctx, api.client, &result) + return result, err +} + +// ContractStorage - +func (api *Context) ContractStorage(ctx context.Context, blockID, contract string) (stdJSON.RawMessage, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/blocks/%s/context/contracts/%s/storage", api.chainID, blockID, contract), nil) + if err != nil { + return nil, err + } + return req.doWithBytesResponse(ctx, api.client) +} + +// Delegates - Lists all registered delegates. `active` can get `active` or `inactive` values, othewise it skip. +func (api *Context) Delegates(ctx context.Context, blockID string, active DelegateType) ([]string, error) { + url := fmt.Sprintf("chains/%s/blocks/%s/context/delegates", api.chainID, blockID) + if active == ActiveDelegateType || active == InactiveDelegateType { + url += fmt.Sprintf("?%s", active) + } + req, err := newGetRequest(api.baseURL, url, nil) + if err != nil { + return nil, err + } + var result []string + err = req.doWithJSONResponse(ctx, api.client, &result) + return result, err +} + +// Delegate - +func (api *Context) Delegate(ctx context.Context, blockID, pkh string) (Delegate, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/blocks/%s/context/delegates/%s", api.chainID, blockID, pkh), nil) + if err != nil { + return Delegate{}, err + } + var result Delegate + err = req.doWithJSONResponse(ctx, api.client, &result) + return result, err +} + +// DelegateDeactivated - +func (api *Context) DelegateDeactivated(ctx context.Context, blockID, pkh string) (bool, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/blocks/%s/context/delegates/%s/deactivated", api.chainID, blockID, pkh), nil) + if err != nil { + return false, err + } + var result bool + err = req.doWithJSONResponse(ctx, api.client, &result) + return result, err +} + +// DelegateBalance - +func (api *Context) DelegateBalance(ctx context.Context, blockID, pkh string) (string, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/blocks/%s/context/delegates/%s/delegated_balance", api.chainID, blockID, pkh), nil) + if err != nil { + return "", err + } + var result string + err = req.doWithJSONResponse(ctx, api.client, &result) + return result, err +} + +// DelegateContracts - +func (api *Context) DelegateContracts(ctx context.Context, blockID, pkh string) ([]string, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/blocks/%s/context/delegates/%s/delegated_contracts", api.chainID, blockID, pkh), nil) + if err != nil { + return nil, err + } + var result []string + err = req.doWithJSONResponse(ctx, api.client, &result) + return result, err +} + +// DelegateGracePeriod - +func (api *Context) DelegateGracePeriod(ctx context.Context, blockID, pkh string) (int, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/blocks/%s/context/delegates/%s/grace_period", api.chainID, blockID, pkh), nil) + if err != nil { + return 0, err + } + var result int + err = req.doWithJSONResponse(ctx, api.client, &result) + return result, err +} + +// DelegateStakingBalance - +func (api *Context) DelegateStakingBalance(ctx context.Context, blockID, pkh string) (string, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/blocks/%s/context/delegates/%s/staking_balance", api.chainID, blockID, pkh), nil) + if err != nil { + return "", err + } + var result string + err = req.doWithJSONResponse(ctx, api.client, &result) + return result, err +} + +// DelegateVotingPower - +func (api *Context) DelegateVotingPower(ctx context.Context, blockID, pkh string) (int, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/blocks/%s/context/delegates/%s/voting_power", api.chainID, blockID, pkh), nil) + if err != nil { + return 0, err + } + var result int + err = req.doWithJSONResponse(ctx, api.client, &result) + return result, err +} + +// ActiveDelegatesWithRolls - +func (api *Context) ActiveDelegatesWithRolls(ctx context.Context, blockID string) ([]string, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/blocks/%s/context/raw/json/active_delegates_with_rolls", api.chainID, blockID), nil) + if err != nil { + return nil, err + } + var result []string + err = req.doWithJSONResponse(ctx, api.client, &result) + return result, err +} + +// LiquidityBakingCPMMAddress - +func (api *Context) LiquidityBakingCPMMAddress(ctx context.Context, blockID string) (string, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("chains/%s/blocks/%s/context/liquidity_baking/cpmm_address", api.chainID, blockID), nil) + if err != nil { + return "", err + } + var result string + err = req.doWithJSONResponse(ctx, api.client, &result) + return result, err +} diff --git a/node/context_data.go b/node/context_data.go new file mode 100644 index 0000000..18d7b6e --- /dev/null +++ b/node/context_data.go @@ -0,0 +1,113 @@ +package node + +import ( + stdJSON "encoding/json" + "strconv" +) + +// Constants - +type Constants struct { + ProofOfWorkNonceSize int64 `json:"proof_of_work_nonce_size"` + NonceLength int64 `json:"nonce_length"` + MaxAnonOpsPerBlock int64 `json:"max_anon_ops_per_block"` + MaxOperationDataLength int64 `json:"max_operation_data_length"` + MaxProposalsPerDelegate int64 `json:"max_proposals_per_delegate"` + PreservedCycles uint64 `json:"preserved_cycles"` + BlocksPerCycle uint64 `json:"blocks_per_cycle"` + BlocksPerCommitment int64 `json:"blocks_per_commitment"` + BlocksPerRollSnapshot int64 `json:"blocks_per_roll_snapshot"` + BlocksPerVotingPeriod int64 `json:"blocks_per_voting_period"` + TimeBetweenBlocks Int64StringSlice `json:"time_between_blocks"` + EndorsersPerBlock int64 `json:"endorsers_per_block"` + HardGasLimitPerOperation int64 `json:"hard_gas_limit_per_operation,string"` + HardGasLimitPerBlock int64 `json:"hard_gas_limit_per_block,string"` + ProofOfWorkThreshold int64 `json:"proof_of_work_threshold,string"` + TokensPerRoll int64 `json:"tokens_per_roll,string"` + MichelsonMaximumTypeSize int64 `json:"michelson_maximum_type_size"` + SeedNonceRevelationTip int64 `json:"seed_nonce_revelation_tip,string"` + OriginationSize int64 `json:"origination_size"` + BlockSecurityDeposit int64 `json:"block_security_deposit,string"` + EndorsementSecurityDeposit int64 `json:"endorsement_security_deposit,string"` + BakingRewardPerEndorsement Int64StringSlice `json:"baking_reward_per_endorsement"` + EndorsementReward Int64StringSlice `json:"endorsement_reward"` + CostPerByte int64 `json:"cost_per_byte,string"` + HardStorageLimitPerOperation int64 `json:"hard_storage_limit_per_operation,string"` + TestChainDuration int64 `json:"test_chain_duration,string"` + QuorumMin int64 `json:"quorum_min"` + QuorumMax int64 `json:"quorum_max"` + MinProposalQuorum int64 `json:"min_proposal_quorum"` + InitialEndorsers int64 `json:"initial_endorsers"` + DelayPerMissingEndorsement int64 `json:"delay_per_missing_endorsement,string"` +} + +// Int64StringSlice - +type Int64StringSlice []int64 + +// UnmarshalJSON - +func (slice *Int64StringSlice) UnmarshalJSON(data []byte) error { + s := make([]string, 0) + if err := json.Unmarshal(data, &s); err != nil { + return err + } + *slice = make([]int64, len(s)) + for i := range s { + value, err := strconv.ParseInt(s[i], 10, 64) + if err != nil { + return err + } + (*slice)[i] = value + } + return nil +} + +// ContractInfo - +type ContractInfo struct { + Balance string `json:"balance"` + Delegate string `json:"delegate,omitempty"` + Script stdJSON.RawMessage `json:"script,omitempty"` + Counter string `json:"counter,omitempty"` + Spendable bool `json:"spendable,omitempty"` + Manager string `json:"manager,omitempty"` +} + +// Entrypoints - +type Entrypoints struct { + Entrypoints map[string]stdJSON.RawMessage `json:"entrypoints"` +} + +// Script - +type Script struct { + Code stdJSON.RawMessage `json:"code"` + Strage stdJSON.RawMessage `json:"storage"` +} + +// Delegate - +type Delegate struct { + Balance string `json:"balance"` + FrozenBalance string `json:"frozen_balance"` + FrozenBalanceByCycle FrozenBalanceByCycle `json:"frozen_balance_by_cycle"` + StakingBalance string `json:"staking_balance"` + DelegatedContracts []string `json:"delegated_contracts"` + DelegatedBalance string `json:"delegated_balance"` + Deactivated bool `json:"deactivated"` + GracePeriod int `json:"grace_period"` + VotingPower int `json:"voting_power"` +} + +// FrozenBalanceByCycle - +type FrozenBalanceByCycle struct { + Cycle int `json:"cycle"` + Deposits string `json:"deposits"` + Fees string `json:"fees"` + Rewards string `json:"rewards"` +} + +// DelegateType - +type DelegateType string + +// delegate types +const ( + ActiveDelegateType DelegateType = "active" + InactiveDelegateType DelegateType = "inactive" + AllDelegateType DelegateType = "all" +) diff --git a/node/data.go b/node/data.go deleted file mode 100644 index a703357..0000000 --- a/node/data.go +++ /dev/null @@ -1,277 +0,0 @@ -package node - -import ( - stdJSON "encoding/json" - "strconv" - "time" - - "github.com/pkg/errors" -) - -// Errors -var ( - ErrUnknownKind = errors.New("Unknown operation kind") -) - -// MempoolResponse - -type MempoolResponse struct { - Applied []Applied `json:"applied"` - Refused []Failed `json:"refused"` - BranchRefused []Failed `json:"branch_refused"` - BranchDelayed []Failed `json:"branch_delayed"` -} - -// Applied - -type Applied struct { - Hash string `json:"hash"` - Branch string `json:"branch"` - Signature string `json:"signature"` - Contents []Content `json:"contents"` - Raw stdJSON.RawMessage `json:"raw"` -} - -// UnmarshalJSON - -func (a *Applied) UnmarshalJSON(data []byte) error { - type buf Applied - if err := json.Unmarshal(data, (*buf)(a)); err != nil { - return err - } - a.Raw = data - return nil -} - -// Failed - -type Failed struct { - Hash string `json:"-"` - Protocol string `json:"protocol"` - Branch string `json:"branch"` - Contents []Content `json:"contents"` - Signature string `json:"signature,omitempty"` - Error stdJSON.RawMessage `json:"error,omitempty"` - Raw stdJSON.RawMessage `json:"raw"` -} - -// UnmarshalJSON - -func (f *Failed) UnmarshalJSON(data []byte) error { - var body []stdJSON.RawMessage - if err := json.Unmarshal(data, &body); err != nil { - return err - } - if len(body) != 2 { - return errors.Errorf("Invalid failed operation body %s", string(data)) - } - if err := json.Unmarshal(body[0], &f.Hash); err != nil { - return err - } - type buf Failed - if err := json.Unmarshal(body[1], (*buf)(f)); err != nil { - return err - } - f.Raw = data - return nil -} - -// FailedMonitor - -type FailedMonitor struct { - Hash string `json:"hash"` - Protocol string `json:"protocol"` - Branch string `json:"branch"` - Contents []Content `json:"contents"` - Signature string `json:"signature,omitempty"` - Error stdJSON.RawMessage `json:"error,omitempty"` - Raw stdJSON.RawMessage `json:"raw"` -} - -// UnmarshalJSON - -func (f *FailedMonitor) UnmarshalJSON(data []byte) error { - type buf FailedMonitor - if err := json.Unmarshal(data, (*buf)(f)); err != nil { - return err - } - f.Raw = data - return nil -} - -// Contents - -type Content struct { - Kind string `json:"kind"` - Body stdJSON.RawMessage `json:"-"` -} - -// UnmarshalJSON - -func (c *Content) UnmarshalJSON(data []byte) error { - type buf Content - if err := json.Unmarshal(data, (*buf)(c)); err != nil { - return err - } - c.Body = data - return nil -} - -// Constants - -type Constants struct { - ProofOfWorkNonceSize int64 `json:"proof_of_work_nonce_size"` - NonceLength int64 `json:"nonce_length"` - MaxAnonOpsPerBlock int64 `json:"max_anon_ops_per_block"` - MaxOperationDataLength int64 `json:"max_operation_data_length"` - MaxProposalsPerDelegate int64 `json:"max_proposals_per_delegate"` - PreservedCycles uint64 `json:"preserved_cycles"` - BlocksPerCycle uint64 `json:"blocks_per_cycle"` - BlocksPerCommitment int64 `json:"blocks_per_commitment"` - BlocksPerRollSnapshot int64 `json:"blocks_per_roll_snapshot"` - BlocksPerVotingPeriod int64 `json:"blocks_per_voting_period"` - TimeBetweenBlocks Int64StringSlice `json:"time_between_blocks"` - EndorsersPerBlock int64 `json:"endorsers_per_block"` - HardGasLimitPerOperation int64 `json:"hard_gas_limit_per_operation,string"` - HardGasLimitPerBlock int64 `json:"hard_gas_limit_per_block,string"` - ProofOfWorkThreshold int64 `json:"proof_of_work_threshold,string"` - TokensPerRoll int64 `json:"tokens_per_roll,string"` - MichelsonMaximumTypeSize int64 `json:"michelson_maximum_type_size"` - SeedNonceRevelationTip int64 `json:"seed_nonce_revelation_tip,string"` - OriginationSize int64 `json:"origination_size"` - BlockSecurityDeposit int64 `json:"block_security_deposit,string"` - EndorsementSecurityDeposit int64 `json:"endorsement_security_deposit,string"` - BakingRewardPerEndorsement Int64StringSlice `json:"baking_reward_per_endorsement"` - EndorsementReward Int64StringSlice `json:"endorsement_reward"` - CostPerByte int64 `json:"cost_per_byte,string"` - HardStorageLimitPerOperation int64 `json:"hard_storage_limit_per_operation,string"` - TestChainDuration int64 `json:"test_chain_duration,string"` - QuorumMin int64 `json:"quorum_min"` - QuorumMax int64 `json:"quorum_max"` - MinProposalQuorum int64 `json:"min_proposal_quorum"` - InitialEndorsers int64 `json:"initial_endorsers"` - DelayPerMissingEndorsement int64 `json:"delay_per_missing_endorsement,string"` -} - -// Int64StringSlice - -type Int64StringSlice []int64 - -// UnmarshalJSON - -func (slice *Int64StringSlice) UnmarshalJSON(data []byte) error { - s := make([]string, 0) - if err := json.Unmarshal(data, &s); err != nil { - return err - } - *slice = make([]int64, len(s)) - for i := range s { - value, err := strconv.ParseInt(s[i], 10, 64) - if err != nil { - return err - } - (*slice)[i] = value - } - return nil -} - -// Header - -type Header struct { - Protocol string `json:"protocol"` - ChainID string `json:"chain_id"` - Hash string `json:"hash"` - Level uint64 `json:"level"` - Proto int `json:"proto"` - Predecessor string `json:"predecessor"` - Timestamp time.Time `json:"timestamp"` - ValidationPass int `json:"validation_pass"` - OperationsHash string `json:"operations_hash"` - Fitness []string `json:"fitness"` - Context string `json:"context"` - Priority int `json:"priority"` - ProofOfWorkNonce string `json:"proof_of_work_nonce"` - Signature string `json:"signature"` -} - -// HeadMetadata - -type HeadMetadata struct { - Protocol string `json:"protocol"` - NextProtocol string `json:"next_protocol"` - TestChainStatus struct { - Status string `json:"status"` - } `json:"test_chain_status"` - MaxOperationsTTL uint64 `json:"max_operations_ttl"` - MaxOperationDataLength uint64 `json:"max_operation_data_length"` - MaxBlockHeaderLength uint64 `json:"max_block_header_length"` - MaxOperationListLength []struct { - MaxSize uint64 `json:"max_size"` - MaxOp uint64 `json:"max_op,omitempty"` - } `json:"max_operation_list_length"` - Baker string `json:"baker"` - Level struct { - Level uint64 `json:"level"` - LevelPosition uint64 `json:"level_position"` - Cycle uint64 `json:"cycle"` - CyclePosition uint64 `json:"cycle_position"` - VotingPeriod uint64 `json:"voting_period"` - VotingPeriodPosition uint64 `json:"voting_period_position"` - ExpectedCommitment bool `json:"expected_commitment"` - } `json:"level"` - LevelInfo struct { - Level uint64 `json:"level"` - LevelPosition uint64 `json:"level_position"` - Cycle uint64 `json:"cycle"` - CyclePosition uint64 `json:"cycle_position"` - ExpectedCommitment bool `json:"expected_commitment"` - } `json:"level_info"` - VotingPeriodKind string `json:"voting_period_kind"` - VotingPeriodInfo struct { - VotingPeriod struct { - Index uint64 `json:"index"` - Kind string `json:"kind"` - StartPosition uint64 `json:"start_position"` - } `json:"voting_period"` - Position int `json:"position"` - Remaining int `json:"remaining"` - } `json:"voting_period_info"` - NonceHash interface{} `json:"nonce_hash"` - ConsumedGas string `json:"consumed_gas"` - Deactivated []interface{} `json:"deactivated"` - BalanceUpdates []BalanceUpdate `json:"balance_updates"` -} - -// IsManager - -func IsManager(kind string) bool { - return kind == KindDelegation || kind == KindOrigination || kind == KindReveal || kind == KindTransaction -} - -// InjectOperationRequest - -type InjectOperationRequest struct { - Operation string - ChainID string - Async bool -} - -// NetworkPointWithURI - -type NetworkPointWithURI struct { - URI string - NetworkPoint -} - -// UnmarshalJSON - -func (n *NetworkPointWithURI) UnmarshalJSON(buf []byte) error { - tmp := []interface{}{&n.URI, &n.NetworkPoint} - wantLen := len(tmp) - if err := json.Unmarshal(buf, &tmp); err != nil { - return err - } - if g, e := len(tmp), wantLen; g != e { - return errors.Errorf("wrong number of fields in NetworkPointWithURI: %d != %d", g, e) - } - return nil -} - -// NetworkPoint - -type NetworkPoint struct { - Trusted bool `json:"trusted"` - GreylistedUntil string `json:"greylisted_until"` - State struct { - EventKind string `json:"event_kind"` - P2PPeerID string `json:"p2p_peer_id"` - } `json:"state"` - P2PPeerID string `json:"p2p_peer_id"` - LastFailedConnection string `json:"last_failed_connection"` - LastRejectedConnection []string `json:"last_rejected_connection"` - LastEstablishedConnection []string `json:"last_established_connection"` - LastDisconnection []string `json:"last_disconnection"` - LastSeen []string `json:"last_seen"` - LastMiss string `json:"last_miss"` -} diff --git a/node/errors.go b/node/errors.go index 227ae7e..bbb0e9d 100644 --- a/node/errors.go +++ b/node/errors.go @@ -2,6 +2,13 @@ package node import ( "fmt" + + "github.com/pkg/errors" +) + +// Errors +var ( + ErrUnknownKind = errors.New("unknown operation kind") ) // RequestError - diff --git a/node/general.go b/node/general.go new file mode 100644 index 0000000..ac1c5aa --- /dev/null +++ b/node/general.go @@ -0,0 +1,57 @@ +package node + +import "context" + +// GeneralAPI - +type GeneralAPI interface { + Version(ctx context.Context) (Version, error) + StatsGC(ctx context.Context) (StatsGC, error) + StatsMemory(ctx context.Context) (StatsMemory, error) +} + +// General - +type General struct { + baseURL string + client *client +} + +// NewGeneral - +func NewGeneral(baseURL string) *General { + return &General{ + baseURL: baseURL, + client: newClient(), + } +} + +// Version - +func (api *General) Version(ctx context.Context) (Version, error) { + req, err := newGetRequest(api.baseURL, "version", nil) + if err != nil { + return Version{}, err + } + var result Version + err = req.doWithJSONResponse(ctx, api.client, &result) + return result, err +} + +// StatsGC - +func (api *General) StatsGC(ctx context.Context) (StatsGC, error) { + req, err := newGetRequest(api.baseURL, "stats/gc", nil) + if err != nil { + return StatsGC{}, err + } + var result StatsGC + err = req.doWithJSONResponse(ctx, api.client, &result) + return result, err +} + +// StatsMemory - +func (api *General) StatsMemory(ctx context.Context) (StatsMemory, error) { + req, err := newGetRequest(api.baseURL, "stats/memory", nil) + if err != nil { + return StatsMemory{}, err + } + var result StatsMemory + err = req.doWithJSONResponse(ctx, api.client, &result) + return result, err +} diff --git a/node/general_data.go b/node/general_data.go new file mode 100644 index 0000000..2b004d7 --- /dev/null +++ b/node/general_data.go @@ -0,0 +1,52 @@ +package node + +// Version - +type Version struct { + Version struct { + Major int `json:"major"` + Minor int `json:"minor"` + AdditionalInfo string `json:"additional_info"` + } `json:"version"` + NetworkVersion struct { + ChainName string `json:"chain_name"` + DistributedDbVersion int `json:"distributed_db_version"` + P2PVersion int `json:"p2p_version"` + } `json:"network_version"` + CommitInfo struct { + CommitHash string `json:"commit_hash"` + CommitDate string `json:"commit_date"` + } `json:"commit_info"` +} + +// StatsGC - +type StatsGC struct { + MinorWords int64 `json:"minor_words"` + PromotedWords int64 `json:"promoted_words"` + MajorWords int64 `json:"major_words"` + MinorCollections int64 `json:"minor_collections"` + MajorCollections int64 `json:"major_collections"` + ForcedMajorCollections int64 `json:"forced_major_collections"` + HeapWords int64 `json:"heap_words"` + HeapChunks int64 `json:"heap_chunks"` + LiveWords int64 `json:"live_words"` + LiveBlocks int64 `json:"live_blocks"` + FreeWords int64 `json:"free_words"` + FreeBlocks int64 `json:"free_blocks"` + LargestFree int64 `json:"largest_free"` + Fragments int64 `json:"fragments"` + Compactions int64 `json:"compactions"` + TopHeapWords int64 `json:"top_heap_words"` + StackSize int64 `json:"stack_size"` +} + +// StatsMemory - +type StatsMemory struct { + PageSize int64 `json:"page_size"` + Size string `json:"size"` + Resident string `json:"resident"` + Shared string `json:"shared"` + Text string `json:"text"` + Lib string `json:"lib"` + Data string `json:"data"` + Dt string `json:"dt"` +} diff --git a/node/inject_api.go b/node/inject_api.go new file mode 100644 index 0000000..7ba78d3 --- /dev/null +++ b/node/inject_api.go @@ -0,0 +1,42 @@ +package node + +import ( + "context" + "net/url" +) + +// InjectAPI - +type InjectAPI interface { + InjectOperation(ctx context.Context, request InjectOperationRequest) (string, error) +} + +type Inject struct { + baseURL string + client *client +} + +// NewInject - +func NewInject(baseURL string) *Inject { + return &Inject{ + baseURL: baseURL, + client: newClient(), + } +} + +// InjectOperation - +func (api *Inject) InjectOperation(ctx context.Context, request InjectOperationRequest) (string, error) { + queryArgs := make(url.Values) + if request.Async { + queryArgs.Add("async", "true") + } + if request.ChainID != "" { + queryArgs.Add("chain", request.ChainID) + } + req, err := newPostRequest(api.baseURL, "injection/operation", queryArgs, request.Operation) + if err != nil { + return "", err + } + var hash string + err = req.doWithJSONResponse(ctx, api.client, &hash) + return hash, err +} diff --git a/node/inject_data.go b/node/inject_data.go new file mode 100644 index 0000000..7b0f92f --- /dev/null +++ b/node/inject_data.go @@ -0,0 +1,8 @@ +package node + +// InjectOperationRequest - +type InjectOperationRequest struct { + Operation string + ChainID string + Async bool +} diff --git a/node/network_api.go b/node/network_api.go new file mode 100644 index 0000000..3be7567 --- /dev/null +++ b/node/network_api.go @@ -0,0 +1,72 @@ +package node + +import ( + "context" + "fmt" +) + +// NetworkAPI - +type NetworkAPI interface { + Connections(ctx context.Context) ([]Connection, error) + Connection(ctx context.Context, peerID string) (Connection, error) + Points(ctx context.Context) ([]NetworkPointWithURI, error) + ConnectionVersion(ctx context.Context) (ConnectionVersion, error) +} + +// Network - +type Network struct { + baseURL string + client *client +} + +// NewNetwork - +func NewNetwork(baseURL string) *Network { + return &Network{ + baseURL: baseURL, + client: newClient(), + } +} + +// Connections - +func (api *Network) Connections(ctx context.Context) ([]Connection, error) { + req, err := newGetRequest(api.baseURL, "network/connections", nil) + if err != nil { + return nil, err + } + var result []Connection + err = req.doWithJSONResponse(ctx, api.client, &result) + return result, err +} + +// Connection - +func (api *Network) Connection(ctx context.Context, peerID string) (Connection, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("network/connections/%s", peerID), nil) + if err != nil { + return Connection{}, err + } + var result Connection + err = req.doWithJSONResponse(ctx, api.client, &result) + return result, err +} + +// Points - +func (api *Network) Points(ctx context.Context) ([]NetworkPointWithURI, error) { + req, err := newGetRequest(api.baseURL, "network/points", nil) + if err != nil { + return nil, err + } + var result []NetworkPointWithURI + err = req.doWithJSONResponse(ctx, api.client, &result) + return result, err +} + +// ConnectionVersion - +func (api *Network) ConnectionVersion(ctx context.Context) (ConnectionVersion, error) { + req, err := newGetRequest(api.baseURL, "network/version", nil) + if err != nil { + return ConnectionVersion{}, err + } + var result ConnectionVersion + err = req.doWithJSONResponse(ctx, api.client, &result) + return result, err +} diff --git a/node/network_data.go b/node/network_data.go new file mode 100644 index 0000000..629fd5f --- /dev/null +++ b/node/network_data.go @@ -0,0 +1,70 @@ +package node + +import "github.com/pkg/errors" + +// Connection - +type Connection struct { + PeerID string `json:"peer_id"` + Point Point `json:"id_point"` + RemoteSocketPort int `json:"remote_socket_port"` + AnnouncedVersion ConnectionVersion `json:"announced_version"` + LocalMetadata ConnectionMetadata `json:"local_metadata"` + RemoteMetadata ConnectionMetadata `json:"remote_metadata"` + Incoming bool `json:"incoming"` + Private bool `json:"private"` +} + +// Point - +type Point struct { + Addr string `json:"addr"` + Port int `json:"port"` +} + +// ConnectionMetadata - +type ConnectionMetadata struct { + DisableMempool bool `json:"disable_mempool"` + PrivateNode bool `json:"private_node"` +} + +// ConnectionVersion - +type ConnectionVersion struct { + ChainName string `json:"chain_name"` + DistributedDbVersion int `json:"distributed_db_version"` + P2PVersion int `json:"p2p_version"` +} + +// NetworkPointWithURI - +type NetworkPointWithURI struct { + URI string + NetworkPoint +} + +// UnmarshalJSON - +func (n *NetworkPointWithURI) UnmarshalJSON(buf []byte) error { + tmp := []interface{}{&n.URI, &n.NetworkPoint} + wantLen := len(tmp) + if err := json.Unmarshal(buf, &tmp); err != nil { + return err + } + if g, e := len(tmp), wantLen; g != e { + return errors.Errorf("wrong number of fields in NetworkPointWithURI: %d != %d", g, e) + } + return nil +} + +// NetworkPoint - +type NetworkPoint struct { + Trusted bool `json:"trusted"` + GreylistedUntil string `json:"greylisted_until"` + State struct { + EventKind string `json:"event_kind"` + P2PPeerID string `json:"p2p_peer_id"` + } `json:"state"` + P2PPeerID string `json:"p2p_peer_id"` + LastFailedConnection string `json:"last_failed_connection"` + LastRejectedConnection []string `json:"last_rejected_connection"` + LastEstablishedConnection []string `json:"last_established_connection"` + LastDisconnection []string `json:"last_disconnection"` + LastSeen []string `json:"last_seen"` + LastMiss string `json:"last_miss"` +} diff --git a/node/node.go b/node/node.go deleted file mode 100644 index ff2137a..0000000 --- a/node/node.go +++ /dev/null @@ -1,216 +0,0 @@ -package node - -import ( - "bytes" - "fmt" - "io" - "io/ioutil" - "net/http" - "net/url" - "path" - "strings" - "time" - - jsoniter "github.com/json-iterator/go" - "github.com/pkg/errors" -) - -var json = jsoniter.ConfigCompatibleWithStandardLibrary - -// NodeRPC - -type NodeRPC struct { - baseURL string - client *http.Client -} - -// NewNodeRPC - -func NewNodeRPC(baseURL string) *NodeRPC { - t := http.DefaultTransport.(*http.Transport).Clone() - t.MaxIdleConns = 100 - t.MaxConnsPerHost = 100 - t.MaxIdleConnsPerHost = 100 - - return &NodeRPC{ - baseURL: strings.TrimSuffix(baseURL, "/"), - client: &http.Client{ - Timeout: time.Second * 15, - Transport: t, - }, - } -} - -// URL - -func (rpc *NodeRPC) URL() string { - return rpc.baseURL -} - -func (rpc *NodeRPC) parseResponse(resp *http.Response, response interface{}) error { - if resp.StatusCode != http.StatusOK { - data, err := ioutil.ReadAll(resp.Body) - if err != nil { - return RequestError{ - Code: resp.StatusCode, - Body: resp.Status, - Err: err, - } - } - return RequestError{ - Code: resp.StatusCode, - Body: string(data), - } - } - return json.NewDecoder(resp.Body).Decode(response) -} - -func (rpc *NodeRPC) makeRequest(method, uri string, queryArgs url.Values, body interface{}, opts RequestOpts) (*http.Response, error) { - link, err := url.Parse(rpc.baseURL) - if err != nil { - return nil, err - } - if uri != "" { - link.Path = path.Join(link.Path, uri) - } - if len(queryArgs) > 0 { - link.RawQuery = queryArgs.Encode() - } - - var bodyReader io.ReadWriter - if body != nil { - bodyReader = new(bytes.Buffer) - if err := json.NewEncoder(bodyReader).Encode(body); err != nil { - return nil, err - } - } - - req, err := http.NewRequestWithContext(opts.ctx, method, link.String(), bodyReader) - if err != nil { - return nil, errors.Errorf("makeGetRequest.NewRequest: %v", err) - } - return rpc.client.Do(req) -} - -//nolint -func (rpc *NodeRPC) get(uri string, queryArgs url.Values, opts RequestOpts, response interface{}) error { - resp, err := rpc.makeRequest(http.MethodGet, uri, queryArgs, nil, opts) - if err != nil { - return err - } - defer resp.Body.Close() - - return rpc.parseResponse(resp, response) -} - -//nolint -func (rpc *NodeRPC) post(uri string, queryArgs url.Values, body interface{}, opts RequestOpts, response interface{}) error { - resp, err := rpc.makeRequest(http.MethodPost, uri, queryArgs, body, opts) - if err != nil { - return err - } - defer resp.Body.Close() - - return rpc.parseResponse(resp, response) -} - -// PendingOperations - -func (rpc *NodeRPC) PendingOperations(opts ...RequestOption) (res MempoolResponse, err error) { - options := newRequestOpts(opts...) - err = rpc.get("chains/main/mempool/pending_operations", nil, options, &res) - return -} - -// Constants - -func (rpc *NodeRPC) Constants(opts ...RequestOption) (constants Constants, err error) { - options := newRequestOpts(opts...) - err = rpc.get("chains/main/blocks/head/context/constants", nil, options, &constants) - return -} - -// ActiveDelegatesWithRolls - -func (rpc *NodeRPC) ActiveDelegatesWithRolls(opts ...RequestOption) (delegates []string, err error) { - options := newRequestOpts(opts...) - err = rpc.get("chains/main/blocks/head/context/raw/json/active_delegates_with_rolls", nil, options, &delegates) - return -} - -// Delegates - -func (rpc *NodeRPC) Delegates(active *bool, opts ...RequestOption) (delegates []string, err error) { - queryArgs := make(url.Values) - if active != nil && *active { - queryArgs.Add("active", "true") - } - options := newRequestOpts(opts...) - err = rpc.get("chains/main/blocks/head/context/delegates", queryArgs, options, &delegates) - return -} - -// StakingBalance - -func (rpc *NodeRPC) StakingBalance(address string, opts ...RequestOption) (balance string, err error) { - options := newRequestOpts(opts...) - uri := fmt.Sprintf("chains/main/blocks/head/context/delegates/%s/staking_balance", address) - err = rpc.get(uri, nil, options, &balance) - return -} - -// InjectOperaiton - -func (rpc *NodeRPC) InjectOperaiton(request InjectOperationRequest, opts ...RequestOption) (hash string, err error) { - queryArgs := make(url.Values) - if request.Async { - queryArgs.Add("async", "true") - } - if request.ChainID != "" { - queryArgs.Add("chain", request.ChainID) - } - options := newRequestOpts(opts...) - err = rpc.post("injection/operation", queryArgs, request.Operation, options, &hash) - return -} - -// Counter - -func (rpc *NodeRPC) Counter(contract string, block string, opts ...RequestOption) (counter string, err error) { - options := newRequestOpts(opts...) - uri := fmt.Sprintf("chains/main/blocks/%s/context/contracts/%s/counter", block, contract) - err = rpc.get(uri, nil, options, &counter) - return -} - -// Header - -func (rpc *NodeRPC) Header(block string, opts ...RequestOption) (head Header, err error) { - options := newRequestOpts(opts...) - err = rpc.get(fmt.Sprintf("chains/main/blocks/%s/header", block), nil, options, &head) - return -} - -// HeadMetadata - -func (rpc *NodeRPC) HeadMetadata(block string, opts ...RequestOption) (head HeadMetadata, err error) { - options := newRequestOpts(opts...) - err = rpc.get(fmt.Sprintf("chains/main/blocks/%s/metadata", block), nil, options, &head) - return -} - -// Operations - -func (rpc *NodeRPC) Operations(block string, opts ...RequestOption) (operations [][]Operation, err error) { - options := newRequestOpts(opts...) - err = rpc.get(fmt.Sprintf("chains/main/blocks/%s/operations", block), nil, options, &operations) - return -} - -// ManagerOperations - -func (rpc *NodeRPC) ManagerOperations(block string, opts ...RequestOption) (operations []Operation, err error) { - options := newRequestOpts(opts...) - err = rpc.get(fmt.Sprintf("chains/main/blocks/%s/operations/3", block), nil, options, &operations) - return -} - -// ContractStorage - -func (rpc *NodeRPC) ContractStorage(block, contract string, output interface{}, opts ...RequestOption) (err error) { - options := newRequestOpts(opts...) - err = rpc.get(fmt.Sprintf("chains/main/blocks/%s/context/contracts/%s/storage", block, contract), nil, options, &output) - return -} - -// NetworkPoints - -func (rpc *NodeRPC) NetworkPoints(opts ...RequestOption) (output []NetworkPointWithURI, err error) { - options := newRequestOpts(opts...) - err = rpc.get("network/points", nil, options, &output) - return -} diff --git a/node/operations.go b/node/operations.go index 450ab02..62c3409 100644 --- a/node/operations.go +++ b/node/operations.go @@ -448,18 +448,6 @@ type EndorsementMetadata struct { Slots []int `json:"slots"` } -// BalanceUpdate - -type BalanceUpdate struct { - Kind string `json:"kind"` - Contract string `json:"contract,omitempty"` - Change string `json:"change"` - Category string `json:"category,omitempty"` - Origin string `json:"origin,omitempty"` - Delegate string `json:"delegate,omitempty"` - Cycle uint64 `json:"cycle,omitempty"` - Level uint64 `json:"level,omitempty"` -} - // OperationResult - type OperationResult struct { Status string `json:"status"` diff --git a/node/options.go b/node/options.go deleted file mode 100644 index f420270..0000000 --- a/node/options.go +++ /dev/null @@ -1,29 +0,0 @@ -package node - -import ( - "context" -) - -// RequestOpts - -type RequestOpts struct { - ctx context.Context -} - -func newRequestOpts(opts ...RequestOption) RequestOpts { - req := RequestOpts{context.Background()} - - for i := range opts { - opts[i](&req) - } - return req -} - -// RequestOption - -type RequestOption func(*RequestOpts) - -// WithContext - -func WithContext(ctx context.Context) RequestOption { - return func(opts *RequestOpts) { - opts.ctx = ctx - } -} diff --git a/node/protocols_api.go b/node/protocols_api.go new file mode 100644 index 0000000..f1a1edc --- /dev/null +++ b/node/protocols_api.go @@ -0,0 +1,60 @@ +package node + +import ( + "context" + "fmt" +) + +// ProtocolsAPI - +type ProtocolsAPI interface { + Protocols(ctx context.Context) ([]string, error) + Protocol(ctx context.Context, hash string) (ProtocolInfo, error) + Environment(ctx context.Context, hash string) (int, error) +} + +// Protocols - +type Protocols struct { + baseURL string + client *client +} + +// NewProtocols - +func NewProtocols(baseURL string) *Protocols { + return &Protocols{ + baseURL: baseURL, + client: newClient(), + } +} + +// Protocols - +func (api *Protocols) Protocols(ctx context.Context) ([]string, error) { + req, err := newGetRequest(api.baseURL, "protocols", nil) + if err != nil { + return nil, err + } + var result []string + err = req.doWithJSONResponse(ctx, api.client, &result) + return result, err +} + +// Protocol - +func (api *Protocols) Protocol(ctx context.Context, hash string) (ProtocolInfo, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("protocols/%s", hash), nil) + if err != nil { + return ProtocolInfo{}, err + } + var result ProtocolInfo + err = req.doWithJSONResponse(ctx, api.client, &result) + return result, err +} + +// Environment - +func (api *Protocols) Environment(ctx context.Context, hash string) (int, error) { + req, err := newGetRequest(api.baseURL, fmt.Sprintf("protocols/%s/environment", hash), nil) + if err != nil { + return 0, err + } + var result int + err = req.doWithJSONResponse(ctx, api.client, &result) + return result, err +} diff --git a/node/protocols_data.go b/node/protocols_data.go new file mode 100644 index 0000000..e3570a6 --- /dev/null +++ b/node/protocols_data.go @@ -0,0 +1,14 @@ +package node + +// ProtocolInfo - +type ProtocolInfo struct { + ExpectedEnvVersion int `json:"expected_env_version"` + Components []ProtocolComponent `json:"components"` +} + +// ProtocolComponent - +type ProtocolComponent struct { + Name string `json:"name"` + Interface string `json:"interface,omitempty"` + Implementation string `json:"implementation"` +} diff --git a/node/request.go b/node/request.go new file mode 100644 index 0000000..9d8aac5 --- /dev/null +++ b/node/request.go @@ -0,0 +1,107 @@ +package node + +import ( + "bytes" + "context" + "io" + "io/ioutil" + "net/http" + "net/url" + "path" + + "github.com/pkg/errors" +) + +type request struct { + link *url.URL + method string + bodyReader io.ReadWriter +} + +func newRequest(baseURL, uri, method string, query url.Values, body interface{}) (*request, error) { + req := new(request) + link, err := url.Parse(baseURL) + if err != nil { + return nil, err + } + if uri != "" { + link.Path = path.Join(link.Path, uri) + } + if len(query) > 0 { + link.RawQuery = query.Encode() + } + req.link = link + + if body != nil { + bodyReader := new(bytes.Buffer) + if err := json.NewEncoder(bodyReader).Encode(body); err != nil { + return nil, err + } + req.bodyReader = bodyReader + } + + req.method = method + return req, nil +} + +func newGetRequest(baseURL, uri string, query url.Values) (*request, error) { + return newRequest(baseURL, uri, http.MethodGet, query, nil) +} + +func newPostRequest(baseURL, uri string, query url.Values, body interface{}) (*request, error) { + return newRequest(baseURL, uri, http.MethodPost, query, body) +} + +func (r *request) do(ctx context.Context, client *client) (*http.Response, error) { + req, err := http.NewRequestWithContext(ctx, r.method, r.link.String(), r.bodyReader) + if err != nil { + return nil, errors.Errorf("request.do: %v", err) + } + return client.Do(req) +} + +func (r *request) checkStatusCode(resp *http.Response) error { + if resp.StatusCode == http.StatusOK { + return nil + } + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + return RequestError{ + Code: resp.StatusCode, + Body: resp.Status, + Err: err, + } + } + return RequestError{ + Code: resp.StatusCode, + Body: string(data), + } +} + +func (r *request) doWithJSONResponse(ctx context.Context, client *client, response interface{}) error { + resp, err := r.do(ctx, client) + if err != nil { + return err + } + defer resp.Body.Close() + + if err := r.checkStatusCode(resp); err != nil { + return err + } + + return json.NewDecoder(resp.Body).Decode(response) +} + +func (r *request) doWithBytesResponse(ctx context.Context, client *client) ([]byte, error) { + resp, err := r.do(ctx, client) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if err := r.checkStatusCode(resp); err != nil { + return nil, err + } + + return ioutil.ReadAll(resp.Body) +}