Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement QueryBalanceDetails RPC method #86

Merged
Merged
Show file tree
Hide file tree
Changes from 5 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
25 changes: 21 additions & 4 deletions rpc/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@ type ClientPOS interface {
// The response should be identical, regardless of the node queried,
// as the information in question is objective and common to all nodes within a network.
type ClientInformational interface {
// GetAccountBalance returns a purse's balance from a network.
// GetBalance returns a purse's balance from a network.
// The request takes in the formatted representation of a purse URef as a parameter.
// If the param stateRootHash is nil, the client will make an additional RPC call to retrieve the latest stateRootHash.
GetAccountBalance(ctx context.Context, stateRootHash *string, purseURef string) (StateGetBalanceResult, error)
GetBalance(ctx context.Context, purseURef string, stateRootHash *string) (StateGetBalanceResult, error)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just realized. Should we have GetLatestBalance and GetBalanceByStateRootHash methods?

// GetDeploy retrieves a Deploy from a network. It requires a deploy_hash to query the Deploy.
GetDeploy(ctx context.Context, hash string) (InfoGetDeployResult, error)
// GetDeployFinalizedApproval returns Deploy with the finalized approvals substituted.
Expand Down Expand Up @@ -117,8 +117,25 @@ type ClientInformational interface {
// GetPeers return a list of peers connected to the node on a Casper network.
// The responses return information specific to the queried node, and as such, will vary.
GetPeers(ctx context.Context) (InfoGetPeerResult, error)
// QueryBalance queries for balances under a given PurseIdentifier
QueryBalance(ctx context.Context, identifier PurseIdentifier) (QueryBalanceResult, error)

// QueryLatestBalance queries for balances under a given PurseIdentifier
QueryLatestBalance(ctx context.Context, identifier PurseIdentifier) (QueryBalanceResult, error)
// QueryBalanceByBlockHeight query for balance information using a purse identifier and block height
QueryBalanceByBlockHeight(ctx context.Context, purseIdentifier PurseIdentifier, height uint64) (QueryBalanceResult, error)
// QueryBalanceByBlockHash query for balance information using a purse identifier and block hash
QueryBalanceByBlockHash(ctx context.Context, purseIdentifier PurseIdentifier, blockHash string) (QueryBalanceResult, error)
// QueryBalanceByStateRootHash query for full balance information using a purse identifier and state root hash
QueryBalanceByStateRootHash(ctx context.Context, purseIdentifier PurseIdentifier, stateRootHash string) (QueryBalanceResult, error)

// QueryLatestBalanceDetails query for full balance information using a purse identifier
QueryLatestBalanceDetails(ctx context.Context, purseIdentifier PurseIdentifier) (QueryBalanceDetailsResult, error)
// QueryBalanceDetailsByBlockHeight query for full balance information using a purse identifier and block height
QueryBalanceDetailsByBlockHeight(ctx context.Context, purseIdentifier PurseIdentifier, height uint64) (QueryBalanceDetailsResult, error)
// QueryBalanceDetailsByBlockHash query for full balance information using a purse identifier and block hash
QueryBalanceDetailsByBlockHash(ctx context.Context, purseIdentifier PurseIdentifier, blockHash string) (QueryBalanceDetailsResult, error)
// QueryBalanceDetailsByStateRootHash query for full balance information using a purse identifier and state root hash
QueryBalanceDetailsByStateRootHash(ctx context.Context, purseIdentifier PurseIdentifier, stateRootHash string) (QueryBalanceDetailsResult, error)

// GetChainspec returns the raw bytes of the chainspec.toml, accounts.toml and global_state.toml files as read at node startup.
GetChainspec(ctx context.Context) (InfoGetChainspecResult, error)
}
Expand Down
44 changes: 41 additions & 3 deletions rpc/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const (
MethodPutDeploy Method = "account_put_deploy"
MethodSpeculativeExec Method = "speculative_exec"
MethodQueryBalance Method = "query_balance"
MethodQueryBalanceDetails Method = "query_balance_details"
MethodInfoGetChainspec Method = "info_get_chainspec"
)

Expand Down Expand Up @@ -111,6 +112,12 @@ type BlockIdentifier struct {
Height *uint64 `json:"Height,omitempty"`
}

type GlobalStateIdentifier struct {
BlockHash *string `json:"BlockHash,omitempty"`
BlockHeight *uint64 `json:"BlockHeight,omitempty"`
StateRoot *string `json:"StateRootHash,omitempty"`
}

type ParamBlockIdentifier struct {
BlockIdentifier *BlockIdentifier `json:"block_identifier"`
}
Expand Down Expand Up @@ -154,10 +161,41 @@ type SpeculativeExecParams struct {

type PurseIdentifier struct {
MainPurseUnderPublicKey *keypair.PublicKey `json:"main_purse_under_public_key,omitempty"`
MainPurseUnderAccountHash *string `json:"main_purse_under_account_hash,omitempty"`
PurseUref *string `json:"purse_uref,omitempty"`
MainPurseUnderAccountHash *key.AccountHash `json:"main_purse_under_account_hash,omitempty"`
MainPurseUnderEntityAddr *key.EntityAddr `json:"main_purse_under_entity_addr,omitempty"`
PurseUref *key.URef `json:"purse_uref,omitempty"`
}

func NewPurseIdentifierFromPublicKey(pubKey keypair.PublicKey) PurseIdentifier {
return PurseIdentifier{
MainPurseUnderPublicKey: &pubKey,
}
}

func NewPurseIdentifierFromAccountHash(accountHash key.AccountHash) PurseIdentifier {
return PurseIdentifier{
MainPurseUnderAccountHash: &accountHash,
}
}

func NewPurseIdentifierFromEntityAddr(entityAddr key.EntityAddr) PurseIdentifier {
return PurseIdentifier{
MainPurseUnderEntityAddr: &entityAddr,
}
}

func NewPurseIdentifierFromUref(uref key.URef) PurseIdentifier {
return PurseIdentifier{
PurseUref: &uref,
}
}

type QueryBalanceRequest struct {
PurseIdentifier PurseIdentifier `json:"purse_identifier"`
PurseIdentifier PurseIdentifier `json:"purse_identifier"`
StateIdentifier *GlobalStateIdentifier `json:"state_identifier,omitempty"`
}

type QueryBalanceDetailsRequest struct {
PurseIdentifier PurseIdentifier `json:"purse_identifier"`
StateIdentifier *GlobalStateIdentifier `json:"state_identifier,omitempty"`
}
21 changes: 21 additions & 0 deletions rpc/response.go
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,27 @@ func (b QueryBalanceResult) GetRawJSON() json.RawMessage {
return b.rawJSON
}

type QueryBalanceDetailsResult struct {
APIVersion string `json:"api_version"`
TotalBalance clvalue.UInt512 `json:"total_balance"`
AvailableBalance clvalue.UInt512 `json:"available_balance"`
TotalBalanceProof string `json:"total_balance_proof"`
Holds []BalanceHoldWithProof `json:"holds"`

rawJSON json.RawMessage
}

func (b QueryBalanceDetailsResult) GetRawJSON() json.RawMessage {
return b.rawJSON
}

// BalanceHoldWithProof The block time at which the hold was created.
type BalanceHoldWithProof struct {
//Time types.BlockTime `json:"time"`
Amount clvalue.UInt512 `json:"amount"`
Proof string `json:"proof"`
}

type InfoGetChainspecResult struct {
ApiVersion string `json:"api_version"`
ChainspecBytes struct {
Expand Down
102 changes: 99 additions & 3 deletions rpc/rpc_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ func (c *client) GetDictionaryItemByIdentifier(ctx context.Context, stateRootHas
return result, nil
}

func (c *client) GetAccountBalance(ctx context.Context, stateRootHash *string, purseURef string) (StateGetBalanceResult, error) {
func (c *client) GetBalance(ctx context.Context, purseURef string, stateRootHash *string) (StateGetBalanceResult, error) {
if stateRootHash == nil {
latestHashResult, err := c.GetStateRootHashLatest(ctx)
if err != nil {
Expand Down Expand Up @@ -502,10 +502,10 @@ func (c *client) PutDeploy(ctx context.Context, deploy types.Deploy) (PutDeployR
return result, nil
}

func (c *client) QueryBalance(ctx context.Context, identifier PurseIdentifier) (QueryBalanceResult, error) {
func (c *client) QueryLatestBalance(ctx context.Context, identifier PurseIdentifier) (QueryBalanceResult, error) {
var result QueryBalanceResult

resp, err := c.processRequest(ctx, MethodQueryBalance, QueryBalanceRequest{identifier}, &result)
resp, err := c.processRequest(ctx, MethodQueryBalance, QueryBalanceRequest{PurseIdentifier: identifier}, &result)
if err != nil {
return QueryBalanceResult{}, err
}
Expand All @@ -514,6 +514,102 @@ func (c *client) QueryBalance(ctx context.Context, identifier PurseIdentifier) (
return result, nil
}

func (c *client) QueryBalanceByBlockHeight(ctx context.Context, purseIdentifier PurseIdentifier, height uint64) (QueryBalanceResult, error) {
var result QueryBalanceResult

resp, err := c.processRequest(ctx, MethodQueryBalance, QueryBalanceRequest{PurseIdentifier: purseIdentifier, StateIdentifier: &GlobalStateIdentifier{
BlockHeight: &height,
}}, &result)
if err != nil {
return QueryBalanceResult{}, err
}

result.rawJSON = resp.Result
return result, nil
}

func (c *client) QueryBalanceByBlockHash(ctx context.Context, purseIdentifier PurseIdentifier, blockHash string) (QueryBalanceResult, error) {
var result QueryBalanceResult

resp, err := c.processRequest(ctx, MethodQueryBalance, QueryBalanceRequest{PurseIdentifier: purseIdentifier, StateIdentifier: &GlobalStateIdentifier{
BlockHash: &blockHash,
}}, &result)
if err != nil {
return QueryBalanceResult{}, err
}

result.rawJSON = resp.Result
return result, nil
}

func (c *client) QueryBalanceByStateRootHash(ctx context.Context, purseIdentifier PurseIdentifier, stateRootHash string) (QueryBalanceResult, error) {
var result QueryBalanceResult

resp, err := c.processRequest(ctx, MethodQueryBalance, QueryBalanceRequest{PurseIdentifier: purseIdentifier, StateIdentifier: &GlobalStateIdentifier{
StateRoot: &stateRootHash,
}}, &result)
if err != nil {
return QueryBalanceResult{}, err
}

result.rawJSON = resp.Result
return result, nil
}

func (c *client) QueryLatestBalanceDetails(ctx context.Context, purseIdentifier PurseIdentifier) (QueryBalanceDetailsResult, error) {
var result QueryBalanceDetailsResult

resp, err := c.processRequest(ctx, MethodQueryBalanceDetails, QueryBalanceDetailsRequest{PurseIdentifier: purseIdentifier}, &result)
if err != nil {
return QueryBalanceDetailsResult{}, err
}

result.rawJSON = resp.Result
return result, nil
}

func (c *client) QueryBalanceDetailsByStateRootHash(ctx context.Context, purseIdentifier PurseIdentifier, stateRootHash string) (QueryBalanceDetailsResult, error) {
var result QueryBalanceDetailsResult

resp, err := c.processRequest(ctx, MethodQueryBalanceDetails, QueryBalanceDetailsRequest{purseIdentifier, &GlobalStateIdentifier{
StateRoot: &stateRootHash,
}}, &result)
if err != nil {
return QueryBalanceDetailsResult{}, err
}

result.rawJSON = resp.Result
return result, nil
}

func (c *client) QueryBalanceDetailsByBlockHeight(ctx context.Context, purseIdentifier PurseIdentifier, height uint64) (QueryBalanceDetailsResult, error) {
var result QueryBalanceDetailsResult

resp, err := c.processRequest(ctx, MethodQueryBalanceDetails, QueryBalanceDetailsRequest{purseIdentifier, &GlobalStateIdentifier{
BlockHeight: &height,
}}, &result)
if err != nil {
return QueryBalanceDetailsResult{}, err
}

result.rawJSON = resp.Result
return result, nil
}

func (c *client) QueryBalanceDetailsByBlockHash(ctx context.Context, purseIdentifier PurseIdentifier, blockHash string) (QueryBalanceDetailsResult, error) {
var result QueryBalanceDetailsResult

resp, err := c.processRequest(ctx, MethodQueryBalanceDetails, QueryBalanceDetailsRequest{purseIdentifier, &GlobalStateIdentifier{
BlockHash: &blockHash,
}}, &result)
if err != nil {
return QueryBalanceDetailsResult{}, err
}

result.rawJSON = resp.Result
return result, nil
}

func (c *client) GetChainspec(ctx context.Context) (InfoGetChainspecResult, error) {
var result InfoGetChainspecResult

Expand Down
43 changes: 43 additions & 0 deletions tests/rpc/integration/rpc_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

"github.com/make-software/casper-go-sdk/casper"
"github.com/make-software/casper-go-sdk/rpc"
"github.com/make-software/casper-go-sdk/types/key"
)

func GetRpcClient() rpc.Client {
Expand All @@ -40,6 +41,48 @@ func Test_DefaultClient_GetAccountInfo_ByAccountKey(t *testing.T) {
assert.Equal(t, "account-hash-bf06bdb1616050cea5862333d1f4787718f1011c95574ba92378419eefeeee59", res.Account.AccountHash.ToPrefixedString())
}

func Test_DefaultClient_QueryBalanceDetails(t *testing.T) {
pubKey, err := casper.NewPublicKey("0111bc2070a9af0f26f94b8549bffa5643ead0bc68eba3b1833039cfa2a9a8205d")
require.NoError(t, err)

ctx := context.Background()

res, err := GetRpcClient().GetBlockLatest(ctx)
require.NoError(t, err)

accountHash := pubKey.AccountHash()
assertResponse := func(result rpc.QueryBalanceDetailsResult, err error) {
require.NoError(t, err)
assert.NotEmpty(t, result.AvailableBalance)
assert.NotEmpty(t, result.TotalBalance)
assert.NotEmpty(t, result.TotalBalanceProof)
assert.Empty(t, result.Holds)
}

// latest call
assertResponse(GetRpcClient().QueryLatestBalanceDetails(ctx, rpc.NewPurseIdentifierFromAccountHash(accountHash)))

// by BlockHeight and MainPurseUnderAccountHash
assertResponse(GetRpcClient().QueryBalanceDetailsByBlockHeight(ctx,
rpc.NewPurseIdentifierFromPublicKey(pubKey),
res.Block.Height,
))

// by BlockHash and MainPurseUnderEntityAddr
assertResponse(GetRpcClient().QueryBalanceDetailsByBlockHash(ctx,
rpc.NewPurseIdentifierFromAccountHash(accountHash),
res.Block.Hash.ToHex(),
))

// by StateRootHash and MainPurseUnderEntityAddr
assertResponse(GetRpcClient().QueryBalanceDetailsByStateRootHash(ctx,
rpc.NewPurseIdentifierFromEntityAddr(key.EntityAddr{
Account: &accountHash.Hash,
}),
res.Block.StateRootHash.ToHex(),
))
}

func Test_DefaultClient_QueryStateByStateHash(t *testing.T) {
accountKey := "account-hash-bf06bdb1616050cea5862333d1f4787718f1011c95574ba92378419eefeeee59"
res, err := GetRpcClient().QueryGlobalStateByStateHash(context.Background(), nil, accountKey, nil)
Expand Down
10 changes: 5 additions & 5 deletions tests/rpc/rpc_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,10 +209,10 @@ func Test_DefaultClient_GetStateBalance(t *testing.T) {
defer server.Close()
client := casper.NewRPCClient(casper.NewRPCHandler(server.URL, http.DefaultClient))
hash := "fb9c42717769d72442ff17a5ff1574b4bc1c83aedf5992b14e4d071423f86240"
result, err := client.GetAccountBalance(
result, err := client.GetBalance(
context.Background(),
&hash,
"uref-7b12008bb757ee32caefb3f7a1f77d9f659ee7a4e21ad4950c4e0294000492eb-007",
&hash,
)
require.NoError(t, err)
assert.Equal(t, "93000000000", result.BalanceValue.String())
Expand All @@ -222,10 +222,10 @@ func Test_DefaultClient_GetStateBalance_WithEmptyStateRootHash(t *testing.T) {
server := SetupServer(t, "../data/rpc_response/get_account_balance.json")
defer server.Close()
client := casper.NewRPCClient(casper.NewRPCHandler(server.URL, http.DefaultClient))
result, err := client.GetAccountBalance(
result, err := client.GetBalance(
context.Background(),
nil,
"uref-7b12008bb757ee32caefb3f7a1f77d9f659ee7a4e21ad4950c4e0294000492eb-007",
nil,
)
require.NoError(t, err)
assert.NotEmpty(t, result.BalanceValue)
Expand Down Expand Up @@ -453,7 +453,7 @@ func Test_DefaultClient_QueryBalance_byPublicKey(t *testing.T) {
pubKey, err := casper.NewPublicKey("0115394d1f395a87dfed4ab62bbfbc91b573bbb2bffb2c8ebb9c240c51d95bcc4d")
require.NoError(t, err)
client := casper.NewRPCClient(casper.NewRPCHandler(server.URL, http.DefaultClient))
result, err := client.QueryBalance(context.Background(), rpc.PurseIdentifier{
result, err := client.QueryLatestBalance(context.Background(), rpc.PurseIdentifier{
MainPurseUnderPublicKey: &pubKey,
})
require.NoError(t, err)
Expand Down
2 changes: 1 addition & 1 deletion types/key/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func NewAccountHash(source string) (AccountHash, error) {
return AccountHash{}, err
}

return AccountHash{Hash: hexBytes, originPrefix: originPrefix}, err
return AccountHash{Hash: hexBytes, originPrefix: originPrefix}, nil
}

func (h *AccountHash) UnmarshalJSON(data []byte) error {
Expand Down
2 changes: 1 addition & 1 deletion types/key/balance_hold_addr.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ func (h *BalanceHoldAddr) UnmarshalJSON(data []byte) error {
}

func (h BalanceHoldAddr) MarshalJSON() ([]byte, error) {
return []byte(h.ToPrefixedString()), nil
return json.Marshal(h.ToPrefixedString())
}

func (h BalanceHoldAddr) ToPrefixedString() string {
Expand Down
2 changes: 1 addition & 1 deletion types/key/bid_addr.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ func (h *BidAddr) UnmarshalJSON(data []byte) error {
}

func (h BidAddr) MarshalJSON() ([]byte, error) {
return []byte(h.ToPrefixedString()), nil
return json.Marshal(h.ToPrefixedString())
}

func (h BidAddr) ToPrefixedString() string {
Expand Down
2 changes: 1 addition & 1 deletion types/key/block_global_addr.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func (h *BlockGlobalAddr) UnmarshalJSON(data []byte) error {
}

func (h BlockGlobalAddr) MarshalJSON() ([]byte, error) {
return []byte(h.ToPrefixedString()), nil
return json.Marshal(h.ToPrefixedString())
}

func (h BlockGlobalAddr) ToPrefixedString() string {
Expand Down
2 changes: 1 addition & 1 deletion types/key/byte_code.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func (h ByteCode) IsEmpty() bool {
}

func (h ByteCode) MarshalJSON() ([]byte, error) {
return []byte(h.ToPrefixedString()), nil
return json.Marshal(h.ToPrefixedString())
}

func (h ByteCode) ToPrefixedString() string {
Expand Down
Loading