Skip to content

Commit

Permalink
feat: localstatequery stake-related queries (#557)
Browse files Browse the repository at this point in the history
This adds support for the StakeDistribution, StakePools, and
StakePoolParams queries.

Fixes #553
  • Loading branch information
agaffney authored Mar 22, 2024
1 parent b57bfc5 commit 0745027
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 41 deletions.
27 changes: 27 additions & 0 deletions cmd/gouroboros/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,13 +163,40 @@ func testQuery(f *globalFlags) {
os.Exit(1)
}
fmt.Printf("stake-distribution: %#v\n", *stakeDistribution)
case "stake-pools":
stakePools, err := o.LocalStateQuery().Client.GetStakePools()
if err != nil {
fmt.Printf("ERROR: failure querying stake pools: %s\n", err)
os.Exit(1)
}
fmt.Printf("stake-pools: %#v\n", *stakePools)
case "genesis-config":
genesisConfig, err := o.LocalStateQuery().Client.GetGenesisConfig()
if err != nil {
fmt.Printf("ERROR: failure querying genesis config: %s\n", err)
os.Exit(1)
}
fmt.Printf("genesis-config: %#v\n", *genesisConfig)
case "pool-params":
var tmpPools []ledger.PoolId
if len(queryFlags.flagset.Args()) <= 1 {
fmt.Println("No pools specified")
os.Exit(1)
}
for _, pool := range queryFlags.flagset.Args()[1:] {
tmpPoolId, err := ledger.NewPoolIdFromBech32(pool)
if err != nil {
fmt.Printf("Invalid bech32 pool ID %q: %s", pool, err)
os.Exit(1)
}
tmpPools = append(tmpPools, tmpPoolId)
}
poolParams, err := o.LocalStateQuery().Client.GetStakePoolParams(tmpPools)
if err != nil {
fmt.Printf("ERROR: failure querying stake pool params: %s\n", err)
os.Exit(1)
}
fmt.Printf("pool-params: %#v\n", *poolParams)
case "utxos-by-address":
var tmpAddrs []ledger.Address
if len(queryFlags.flagset.Args()) <= 1 {
Expand Down
34 changes: 34 additions & 0 deletions ledger/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,40 @@ func (a AssetFingerprint) String() string {
return encoded
}

type PoolId [28]byte

func NewPoolIdFromBech32(poolId string) (PoolId, error) {
var p PoolId
_, data, err := bech32.DecodeNoLimit(poolId)
if err != nil {
return p, err
}
decoded, err := bech32.ConvertBits(data, 5, 8, false)
if err != nil {
return p, err
}
if len(decoded) != len(p) {
return p, fmt.Errorf("invalid pool ID length: %d", len(decoded))
}
p = PoolId(decoded)
return p, err
}

func (p PoolId) String() string {
// Convert data to base32 and encode as bech32
convData, err := bech32.ConvertBits(p[:], 8, 5, true)
if err != nil {
panic(
fmt.Sprintf("unexpected error converting data to base32: %s", err),
)
}
encoded, err := bech32.Encode("pool", convData)
if err != nil {
panic(fmt.Sprintf("unexpected error encoding data as bech32: %s", err))
}
return encoded
}

const (
AddressHeaderTypeMask = 0xF0
AddressHeaderNetworkMask = 0x0F
Expand Down
11 changes: 5 additions & 6 deletions protocol/localstatequery/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -590,12 +590,8 @@ func (c *Client) GetStakePools() (*StakePoolsResult, error) {
return &result, nil
}

// TODO
/*
query [17 #6.258([*poolid])
*/
func (c *Client) GetStakePoolParams(
poolIds []interface{},
poolIds []ledger.PoolId,
) (*StakePoolParamsResult, error) {
c.busyMutex.Lock()
defer c.busyMutex.Unlock()
Expand All @@ -606,7 +602,10 @@ func (c *Client) GetStakePoolParams(
query := buildShelleyQuery(
currentEra,
QueryTypeShelleyStakePoolParams,
// TODO: add params
cbor.Tag{
Number: cbor.CborTagSet,
Content: poolIds,
},
)
var result StakePoolParamsResult
if err := c.runQuery(query, &result); err != nil {
Expand Down
124 changes: 89 additions & 35 deletions protocol/localstatequery/queries.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package localstatequery

import (
"fmt"
"net"

"github.com/blinklabs-io/gouroboros/cbor"
"github.com/blinklabs-io/gouroboros/ledger"
Expand Down Expand Up @@ -156,11 +157,14 @@ 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 StakeDistributionResult struct {
cbor.StructAsArray
Results map[ledger.PoolId]struct {
cbor.StructAsArray
StakeFraction *cbor.Rat
VrfHash ledger.Blake2b256
}
}

type UTxOByAddressResult struct {
cbor.StructAsArray
Expand Down Expand Up @@ -295,37 +299,87 @@ type UTxOByTxInResult struct {
Results map[UtxoId]ledger.BabbageTransactionOutput
}

// TODO
/*
result [#6.258([ *poolid ])]
*/
type StakePoolsResult interface{}
type StakePoolsResult struct {
cbor.StructAsArray
Results []ledger.PoolId
}

// 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{}
type StakePoolParamsResult struct {
cbor.StructAsArray
Results map[ledger.PoolId]struct {
cbor.StructAsArray
Operator ledger.Blake2b224
VrfKeyHash ledger.Blake2b256
Pledge uint
FixedCost uint
Margin *cbor.Rat
RewardAccount ledger.Address
PoolOwners []ledger.Blake2b224
Relays []StakePoolParamsResultRelay
PoolMetadata *struct {
cbor.StructAsArray
Url string
MetadataHash ledger.Blake2b256
}
}
}

type StakePoolParamsResultRelay struct {
Type int
Port *uint
Ipv4 *net.IP
Ipv6 *net.IP
Hostname *string
}

func (s *StakePoolParamsResultRelay) UnmarshalCBOR(data []byte) error {
tmpId, err := cbor.DecodeIdFromList(data)
if err != nil {
return err
}
s.Type = tmpId
switch tmpId {
case 0:
var tmpData struct {
cbor.StructAsArray
Type uint
Port *uint
Ipv4 *net.IP
Ipv6 *net.IP
}
if _, err := cbor.Decode(data, &tmpData); err != nil {
return err
}
s.Port = tmpData.Port
s.Ipv4 = tmpData.Ipv4
s.Ipv6 = tmpData.Ipv6
case 1:
var tmpData struct {
cbor.StructAsArray
Type uint
Port *uint
Hostname *string
}
if _, err := cbor.Decode(data, &tmpData); err != nil {
return err
}
s.Port = tmpData.Port
s.Hostname = tmpData.Hostname
case 2:
var tmpData struct {
cbor.StructAsArray
Type uint
Hostname *string
}
if _, err := cbor.Decode(data, &tmpData); err != nil {
return err
}
s.Hostname = tmpData.Hostname
default:
return fmt.Errorf("invalid relay type: %d", tmpId)
}
return nil
}

// TODO
type RewardInfoPoolsResult interface{}
Expand Down

0 comments on commit 0745027

Please sign in to comment.