Skip to content

Commit

Permalink
feat: localstatequery UTxOsByAddress and UTxOsByTxIn support
Browse files Browse the repository at this point in the history
This adds support for the UTxOs by Address and UTxOs by TxIn queries.

Fixes #315
  • Loading branch information
agaffney committed Mar 21, 2024
1 parent c5a7fdf commit 568a616
Show file tree
Hide file tree
Showing 3 changed files with 226 additions and 11 deletions.
79 changes: 79 additions & 0 deletions cmd/gouroboros/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,16 @@
package main

import (
"encoding/hex"
"encoding/json"
"flag"
"fmt"
"os"
"strconv"
"strings"

ouroboros "github.com/blinklabs-io/gouroboros"
"github.com/blinklabs-io/gouroboros/ledger"
"github.com/blinklabs-io/gouroboros/protocol/localstatequery"
)

Expand Down Expand Up @@ -165,6 +170,80 @@ func testQuery(f *globalFlags) {
os.Exit(1)
}
fmt.Printf("genesis-config: %#v\n", *genesisConfig)
case "utxos-by-address":
var tmpAddrs []ledger.Address
if len(queryFlags.flagset.Args()) <= 1 {
fmt.Println("No addresses specified")
os.Exit(1)
}
for _, addr := range queryFlags.flagset.Args()[1:] {
tmpAddr, err := ledger.NewAddress(addr)
if err != nil {
fmt.Printf("Invalid address %q: %s", addr, err)
os.Exit(1)
}
tmpAddrs = append(tmpAddrs, tmpAddr)
}
utxos, err := o.LocalStateQuery().Client.GetUTxOByAddress(tmpAddrs)
if err != nil {
fmt.Printf("ERROR: failure querying UTxOs by address: %s\n", err)
os.Exit(1)
}
for utxoId, utxo := range utxos.Results {
fmt.Println("---")
fmt.Printf("UTxO ID: %s#%d\n", utxoId.Hash.String(), utxoId.Idx)
fmt.Printf("Amount: %d\n", utxo.OutputAmount.Amount)
if utxo.OutputAmount.Assets != nil {
assetsJson, err := json.Marshal(utxo.OutputAmount.Assets)
if err != nil {
fmt.Printf("ERROR: failed to marshal asset JSON: %s\n", err)
os.Exit(1)
}
fmt.Printf("Assets: %s\n", assetsJson)
}
}
case "utxos-by-txin":
var tmpTxIns []ledger.TransactionInput
if len(queryFlags.flagset.Args()) <= 1 {
fmt.Println("No UTxO IDs specified")
os.Exit(1)
}
for _, txIn := range queryFlags.flagset.Args()[1:] {
txInParts := strings.SplitN(txIn, `#`, 2)
if len(txInParts) != 2 {
fmt.Printf("Invalid UTxO ID %q", txIn)
os.Exit(1)
}
txIdHex, err := hex.DecodeString(txInParts[0])
if err != nil {
fmt.Printf("Invalid UTxO ID %q: %s", err)

Check failure on line 219 in cmd/gouroboros/query.go

View workflow job for this annotation

GitHub Actions / run-tests

fmt.Printf format %s reads arg #2, but call has 1 arg

Check failure on line 219 in cmd/gouroboros/query.go

View workflow job for this annotation

GitHub Actions / lint

printf: fmt.Printf format %s reads arg #2, but call has 1 arg (govet)
os.Exit(1)
}
txOutputIdx, err := strconv.ParseUint(txInParts[1], 10, 32)

Check failure on line 222 in cmd/gouroboros/query.go

View workflow job for this annotation

GitHub Actions / lint

ineffectual assignment to err (ineffassign)
tmpTxIn := ledger.ShelleyTransactionInput{
TxId: ledger.Blake2b256(txIdHex),
OutputIndex: uint32(txOutputIdx),
}
tmpTxIns = append(tmpTxIns, tmpTxIn)
}
utxos, err := o.LocalStateQuery().Client.GetUTxOByTxIn(tmpTxIns)
if err != nil {
fmt.Printf("ERROR: failure querying UTxOs by TxIn: %s\n", err)
os.Exit(1)
}
for utxoId, utxo := range utxos.Results {
fmt.Println("---")
fmt.Printf("UTxO ID: %s#%d\n", utxoId.Hash.String(), utxoId.Idx)
fmt.Printf("Amount: %d\n", utxo.OutputAmount.Amount)
if utxo.OutputAmount.Assets != nil {
assetsJson, err := json.Marshal(utxo.OutputAmount.Assets)
if err != nil {
fmt.Printf("ERROR: failed to marshal asset JSON: %s\n", err)
os.Exit(1)
}
fmt.Printf("Assets: %s\n", assetsJson)
}
}
default:
fmt.Printf("ERROR: unknown query: %s\n", queryFlags.flagset.Args()[0])
os.Exit(1)
Expand Down
24 changes: 15 additions & 9 deletions protocol/localstatequery/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,9 @@ func (c *Client) GetEpochNo() (int, error) {
}

// TODO
/*
query [2 #6.258([*[0 int]]) int is the stake the user intends to delegate, the array must be sorted
*/
func (c *Client) GetNonMyopicMemberRewards() (*NonMyopicMemberRewardsResult, error) {
c.busyMutex.Lock()
defer c.busyMutex.Unlock()
Expand Down Expand Up @@ -355,7 +358,6 @@ func (c *Client) GetCurrentProtocolParams() (CurrentProtocolParamsResult, error)
}
}

// TODO
func (c *Client) GetProposedProtocolParamsUpdates() (*ProposedProtocolParamsUpdatesResult, error) {
c.busyMutex.Lock()
defer c.busyMutex.Unlock()
Expand Down Expand Up @@ -394,9 +396,8 @@ func (c *Client) GetStakeDistribution() (*StakeDistributionResult, error) {
return &result, nil
}

// TODO
func (c *Client) GetUTxOByAddress(
addrs []interface{},
addrs []ledger.Address,
) (*UTxOByAddressResult, error) {
c.busyMutex.Lock()
defer c.busyMutex.Unlock()
Expand All @@ -407,6 +408,7 @@ func (c *Client) GetUTxOByAddress(
query := buildShelleyQuery(
currentEra,
QueryTypeShelleyUtxoByAddress,
addrs,
)
var result UTxOByAddressResult
if err := c.runQuery(query, &result); err != nil {
Expand All @@ -415,7 +417,6 @@ func (c *Client) GetUTxOByAddress(
return &result, nil
}

// TODO
func (c *Client) GetUTxOWhole() (*UTxOWholeResult, error) {
c.busyMutex.Lock()
defer c.busyMutex.Unlock()
Expand Down Expand Up @@ -454,6 +455,9 @@ func (c *Client) DebugEpochState() (*DebugEpochStateResult, error) {
}

// TODO
/*
query [10 #6.258([ *rwdr ])]
*/
func (c *Client) GetFilteredDelegationsAndRewardAccounts(
creds []interface{},
) (*FilteredDelegationsAndRewardAccountsResult, error) {
Expand All @@ -466,6 +470,7 @@ func (c *Client) GetFilteredDelegationsAndRewardAccounts(
query := buildShelleyQuery(
currentEra,
QueryTypeShelleyFilteredDelegationAndRewardAccounts,
// TODO: add params
)
var result FilteredDelegationsAndRewardAccountsResult
if err := c.runQuery(query, &result); err != nil {
Expand All @@ -474,7 +479,6 @@ func (c *Client) GetFilteredDelegationsAndRewardAccounts(
return &result, nil
}

// TODO
func (c *Client) GetGenesisConfig() (*GenesisConfigResult, error) {
c.busyMutex.Lock()
defer c.busyMutex.Unlock()
Expand Down Expand Up @@ -531,7 +535,6 @@ func (c *Client) DebugChainDepState() (*DebugChainDepStateResult, error) {
return &result, nil
}

// TODO
func (c *Client) GetRewardProvenance() (*RewardProvenanceResult, error) {
c.busyMutex.Lock()
defer c.busyMutex.Unlock()
Expand All @@ -550,8 +553,7 @@ func (c *Client) GetRewardProvenance() (*RewardProvenanceResult, error) {
return &result, nil
}

// TODO
func (c *Client) GetUTxOByTxIn(txins []interface{}) (*UTxOByTxInResult, error) {
func (c *Client) GetUTxOByTxIn(txIns []ledger.TransactionInput) (*UTxOByTxInResult, error) {
c.busyMutex.Lock()
defer c.busyMutex.Unlock()
currentEra, err := c.getCurrentEra()
Expand All @@ -561,6 +563,7 @@ func (c *Client) GetUTxOByTxIn(txins []interface{}) (*UTxOByTxInResult, error) {
query := buildShelleyQuery(
currentEra,
QueryTypeShelleyUtxoByTxin,
txIns,
)
var result UTxOByTxInResult
if err := c.runQuery(query, &result); err != nil {
Expand All @@ -569,7 +572,6 @@ func (c *Client) GetUTxOByTxIn(txins []interface{}) (*UTxOByTxInResult, error) {
return &result, nil
}

// TODO
func (c *Client) GetStakePools() (*StakePoolsResult, error) {
c.busyMutex.Lock()
defer c.busyMutex.Unlock()
Expand All @@ -589,6 +591,9 @@ func (c *Client) GetStakePools() (*StakePoolsResult, error) {
}

// TODO
/*
query [17 #6.258([*poolid])
*/
func (c *Client) GetStakePoolParams(
poolIds []interface{},
) (*StakePoolParamsResult, error) {
Expand All @@ -601,6 +606,7 @@ func (c *Client) GetStakePoolParams(
query := buildShelleyQuery(
currentEra,
QueryTypeShelleyStakePoolParams,
// TODO: add params
)
var result StakePoolParamsResult
if err := c.runQuery(query, &result); err != nil {
Expand Down
134 changes: 132 additions & 2 deletions protocol/localstatequery/queries.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
package localstatequery

import (
"fmt"

"github.com/blinklabs-io/gouroboros/cbor"
"github.com/blinklabs-io/gouroboros/ledger"
)
Expand Down Expand Up @@ -141,6 +143,10 @@ type eraHistoryResultParams struct {
}

// TODO
/*
result [{ *[0 int] => non_myopic_rewards }] for each stake display reward
non_myopic_rewards { *poolid => int } int is the amount of lovelaces each pool would reward
*/
type NonMyopicMemberRewardsResult interface{}

type CurrentProtocolParamsResult interface {
Expand All @@ -149,10 +155,68 @@ type CurrentProtocolParamsResult interface {

// TODO
type ProposedProtocolParamsUpdatesResult interface{}

// TODO
/*
result [{ *poolid => [[num den] vrf-hash]}] num/den is the quotient representing the stake fractions
*/
type StakeDistributionResult interface{}
type UTxOByAddressResult interface{}

type UTxOByAddressResult struct {
cbor.StructAsArray
Results map[UtxoId]ledger.BabbageTransactionOutput
}

type UtxoId struct {
cbor.StructAsArray
Hash ledger.Blake2b256
Idx int
DatumHash ledger.Blake2b256
}

func (u *UtxoId) UnmarshalCBOR(data []byte) error {
listLen, err := cbor.ListLength(data)
if err != nil {
return err
}
switch listLen {
case 2:
var tmpData struct {
cbor.StructAsArray
Hash ledger.Blake2b256
Idx int
}
if _, err := cbor.Decode(data, &tmpData); err != nil {
return err
}
u.Hash = tmpData.Hash
u.Idx = tmpData.Idx
case 3:
return cbor.DecodeGeneric(data, u)
default:
return fmt.Errorf("invalid list length: %d", listLen)
}
return nil
}

// TODO
/*
result [{* utxo => value }]
*/
type UTxOWholeResult interface{}

// TODO
type DebugEpochStateResult interface{}

// TODO
/*
rwdr [flag bytestring] bytestring is the keyhash of the staking vkey
flag 0/1 0=keyhash 1=scripthash
result [[ delegation rewards] ]
delegation { * rwdr => poolid } poolid is a bytestring
rewards { * rwdr => int }
It seems to be a requirement to sort the reward addresses on the query. Scripthash addresses come first, then within a group the bytestring being a network order integer sort ascending.
*/
type FilteredDelegationsAndRewardAccountsResult interface{}

type GenesisConfigResult struct {
Expand Down Expand Up @@ -199,12 +263,78 @@ type GenesisConfigResult struct {

// TODO
type DebugNewEpochStateResult interface{}

// TODO
type DebugChainDepStateResult interface{}

// TODO
/*
result [ *Element ] Expanded in order on the next rows.
Element CDDL Comment
epochLength
poolMints { *poolid => block-count }
maxLovelaceSupply
NA
NA
NA
?circulatingsupply?
total-blocks
?decentralization? [num den]
?available block entries
success-rate [num den]
NA
NA ??treasuryCut
activeStakeGo
nil
nil
*/
type RewardProvenanceResult interface{}
type UTxOByTxInResult interface{}

type UTxOByTxInResult struct {
cbor.StructAsArray
Results map[UtxoId]ledger.BabbageTransactionOutput
}

// TODO
/*
result [#6.258([ *poolid ])]
*/
type StakePoolsResult interface{}

// TODO
/*
result [{ *poolid => [ *pool_param ] }]
pool_param CDDL Comment
operator keyhash
vrf_keyhash keyhash
pledge coin
margin #6.30([num den])
reward_account
pool_owners set<addr_keyhash>
relays [ *relay ]
pool_metadata pool_metadata/null
relay CDDL Comment
single_host_addr [0 port/null ipv4/null ipv6/null]
single_host_name [1 port/null dns_name] An A or AAAA DNS
multi_host_name [2 dns_name] A SRV DNS record
Type CDDL Comment
port uint .le 65535
ipv4 bytes .size 4
ipv6 bytes .size 16
dns_name tstr .size (0..64)
pool_metadata [url metadata_hash]
url tstr .size (0..64)
*/
type StakePoolParamsResult interface{}

// TODO
type RewardInfoPoolsResult interface{}

// TODO
type PoolStateResult interface{}

// TODO
type StakeSnapshotsResult interface{}

// TODO
type PoolDistrResult interface{}

0 comments on commit 568a616

Please sign in to comment.