Skip to content

Commit 5993509

Browse files
chrisguineyclaude
andauthored
docs: governance query examples and documentation (blinklabs-io#1512) (blinklabs-io#1813)
Add usage examples and reference documentation for the Conway governance (CIP-1694) local-state queries. - Add drep-state, committee-state, proposals, and vote-delegatees subcommands to the examples/state-query CLI, alongside the existing local-state queries. Usage examples live under examples/ rather than in test files so they are easy to find without reading the test suite. - Expand doc comments on all governance query methods (GetConstitution, GetGovState, GetDRepState, GetDRepStakeDistr, GetCommitteeMembersState, GetFilteredVoteDelegatees, GetSPOStakeDistr, GetProposals, GetRatifyState) to cover when to use each, the response structure, and the Conway-era requirement. Signed-off-by: Chris Guiney <chris@guiney.net> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent d42ca84 commit 5993509

2 files changed

Lines changed: 261 additions & 20 deletions

File tree

examples/state-query/main.go

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,12 @@ func main() {
155155
fmt.Println(" utxos-by-address <address> [address...]")
156156
fmt.Println(" utxos-by-txin <txid#idx> [txid#idx...]")
157157
fmt.Println(" utxo-whole-result [limit] (WARNING: May timeout on large networks)")
158+
fmt.Println()
159+
fmt.Println("Governance query types (Conway era / CIP-1694):")
160+
fmt.Println(" drep-state")
161+
fmt.Println(" committee-state")
162+
fmt.Println(" proposals")
163+
fmt.Println(" vote-delegatees <stake-key-hash> [stake-key-hash...]")
158164
os.Exit(1)
159165
}
160166
queryType := os.Args[1]
@@ -453,6 +459,121 @@ func main() {
453459
if limit >= 0 && count < total {
454460
fmt.Printf("\n(Showing %d of %d total UTxOs. Use 'utxo-whole-result <limit>' to specify limit)\n", count, total)
455461
}
462+
case "drep-state":
463+
// Conway governance query (CIP-1694). Passing nil returns the state
464+
// of every registered DRep; pass DRep credentials to filter.
465+
drepState, err := o.LocalStateQuery().Client.GetDRepState(nil)
466+
if err != nil {
467+
panic(fmt.Errorf("failure querying DRep state: %w", err))
468+
}
469+
fmt.Printf("drep-state:\n")
470+
for cred, entry := range *drepState {
471+
fmt.Println("---")
472+
fmt.Printf(" DRep credential: %x\n", cred.Bytes)
473+
fmt.Printf(" Expiry epoch: %d\n", entry.Expiry)
474+
fmt.Printf(" Deposit: %d\n", entry.Deposit)
475+
if entry.Anchor != nil {
476+
fmt.Printf(" Metadata URL: %s\n", entry.Anchor.Url)
477+
}
478+
}
479+
case "committee-state":
480+
// Conway governance query (CIP-1694). Passing nil for all three
481+
// filters returns every constitutional committee member.
482+
committeeState, err := o.LocalStateQuery().
483+
Client.GetCommitteeMembersState(nil, nil, nil)
484+
if err != nil {
485+
panic(
486+
fmt.Errorf("failure querying committee members state: %w", err),
487+
)
488+
}
489+
fmt.Printf("committee-state:\n")
490+
fmt.Printf(" Epoch: %d\n", committeeState.Epoch)
491+
if committeeState.Threshold != nil {
492+
fmt.Printf(" Threshold: %s\n", committeeState.Threshold.String())
493+
}
494+
// Members are keyed by cold credential; each reports its hot-credential
495+
// authorization status and whether its term is active/expired/unknown.
496+
for cold, member := range committeeState.Members {
497+
fmt.Println("---")
498+
fmt.Printf(" Member cold credential: %x\n", cold.Bytes)
499+
fmt.Printf(" Member status: %d\n", member.Status)
500+
fmt.Printf(" Hot credential status: %d\n", member.HotCredStatus.Status)
501+
if member.Expiry != nil {
502+
fmt.Printf(" Term expires epoch: %d\n", *member.Expiry)
503+
}
504+
}
505+
case "proposals":
506+
// Conway governance query (CIP-1694). Returns all active governance
507+
// proposals along with the votes cast on each so far.
508+
proposals, err := o.LocalStateQuery().Client.GetProposals()
509+
if err != nil {
510+
panic(fmt.Errorf("failure querying proposals: %w", err))
511+
}
512+
fmt.Printf("proposals:\n")
513+
for _, proposal := range *proposals {
514+
fmt.Println("---")
515+
fmt.Printf(
516+
" Action ID: %x#%d\n",
517+
proposal.Id.TransactionId,
518+
proposal.Id.GovActionIdx,
519+
)
520+
fmt.Printf(" Proposed in epoch: %d\n", proposal.ProposedIn)
521+
fmt.Printf(" Expires after epoch: %d\n", proposal.ExpiresAfter)
522+
fmt.Printf(
523+
" Votes: %d committee, %d DRep, %d SPO\n",
524+
len(proposal.CommitteeVotes),
525+
len(proposal.DRepVotes),
526+
len(proposal.SPOVotes),
527+
)
528+
}
529+
case "vote-delegatees":
530+
// Conway governance query (CIP-1694). Optionally filter by stake
531+
// credential key hashes (hex); with no arguments, returns the vote
532+
// delegation for every stake credential.
533+
creds := make([]lcommon.Credential, 0, len(os.Args[2:]))
534+
for _, arg := range os.Args[2:] {
535+
hashBytes, err := hex.DecodeString(arg)
536+
if err != nil {
537+
fmt.Printf(
538+
"ERROR: Invalid stake credential hash %q: %s\n",
539+
arg,
540+
err,
541+
)
542+
os.Exit(1)
543+
}
544+
if len(hashBytes) != 28 {
545+
fmt.Printf(
546+
"ERROR: Invalid stake credential hash %q: expected 28 bytes, got %d\n",
547+
arg,
548+
len(hashBytes),
549+
)
550+
os.Exit(1)
551+
}
552+
creds = append(creds, lcommon.Credential{
553+
CredType: lcommon.CredentialTypeAddrKeyHash,
554+
Credential: lcommon.NewBlake2b224(hashBytes),
555+
})
556+
}
557+
delegatees, err := o.LocalStateQuery().
558+
Client.GetFilteredVoteDelegatees(creds)
559+
if err != nil {
560+
panic(fmt.Errorf("failure querying vote delegatees: %w", err))
561+
}
562+
fmt.Printf("vote-delegatees:\n")
563+
// Each stake credential maps to the DRep it delegates to. A DRep type
564+
// of Abstain or NoConfidence carries no credential bytes.
565+
for stakeCred, drep := range *delegatees {
566+
fmt.Println("---")
567+
fmt.Printf(" Stake credential: %x\n", stakeCred.Bytes)
568+
switch drep.Type {
569+
case lcommon.DrepTypeAddrKeyHash, lcommon.DrepTypeScriptHash:
570+
fmt.Printf(" Delegated to DRep: %x\n", drep.Credential)
571+
case lcommon.DrepTypeAbstain:
572+
fmt.Printf(" Delegated to: abstain\n")
573+
case lcommon.DrepTypeNoConfidence:
574+
fmt.Printf(" Delegated to: no-confidence\n")
575+
}
576+
}
456577
default:
457578
fmt.Printf("ERROR: unknown query: %s\n", queryType)
458579
fmt.Println()
@@ -469,6 +590,12 @@ func main() {
469590
fmt.Println(" utxos-by-address <address> [address...]")
470591
fmt.Println(" utxos-by-txin <txid#idx> [txid#idx...]")
471592
fmt.Println(" utxo-whole-result [limit] (WARNING: May timeout on large networks)")
593+
fmt.Println()
594+
fmt.Println("Governance query types (Conway era / CIP-1694):")
595+
fmt.Println(" drep-state")
596+
fmt.Println(" committee-state")
597+
fmt.Println(" proposals")
598+
fmt.Println(" vote-delegatees <stake-key-hash> [stake-key-hash...]")
472599
os.Exit(1)
473600
}
474601
}

protocol/localstatequery/client.go

Lines changed: 134 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -891,8 +891,18 @@ func (c *Client) GetPoolDistr(poolIds []any) (*PoolDistrResult, error) {
891891
return &result, nil
892892
}
893893

894-
// GetConstitution returns the current constitution (Conway era)
895-
// The constitution contains an anchor URL/hash and an optional guardrails script hash
894+
// GetConstitution returns the current on-chain constitution (CIP-1694).
895+
//
896+
// Use this to read the constitution that governance actions are checked
897+
// against: its metadata anchor (URL and hash of the constitution document)
898+
// and the optional guardrails script hash that constrains protocol-parameter
899+
// and treasury-withdrawal actions.
900+
//
901+
// Response: a [ConstitutionResult] with the anchor and optional guardrails
902+
// script hash (nil when no guardrails script is set).
903+
//
904+
// Era: requires the Conway era or later. Returns an error if the acquired
905+
// ledger state is on an earlier era.
896906
func (c *Client) GetConstitution() (*ConstitutionResult, error) {
897907
c.Protocol.Logger().
898908
Debug("calling GetConstitution()",
@@ -924,8 +934,21 @@ func (c *Client) GetConstitution() (*ConstitutionResult, error) {
924934
return &result, nil
925935
}
926936

927-
// GetGovState returns the full governance state (Conway era)
928-
// This includes proposals, committee, constitution, and protocol parameters
937+
// GetGovState returns the full governance state (CIP-1694).
938+
//
939+
// Use this as the one-shot snapshot of everything governance-related: the
940+
// active proposals with their voting state, the constitutional committee, the
941+
// constitution, the current/previous/scheduled protocol parameters, and the
942+
// DRep pulsing state. Prefer the narrower queries ([Client.GetProposals],
943+
// [Client.GetDRepState], [Client.GetCommitteeMembersState]) when you only need
944+
// one piece, as this query returns and decodes considerably more data.
945+
//
946+
// Response: a [GovStateResult]. Several fields are returned as raw CBOR
947+
// because their shape is era-specific; the Committee field is a StrictMaybe
948+
// and can be decoded with [GovStateResult.DecodeCommittee].
949+
//
950+
// Era: requires the Conway era or later. Returns an error if the acquired
951+
// ledger state is on an earlier era.
929952
func (c *Client) GetGovState() (*GovStateResult, error) {
930953
c.Protocol.Logger().
931954
Debug("calling GetGovState()",
@@ -957,8 +980,19 @@ func (c *Client) GetGovState() (*GovStateResult, error) {
957980
return &result, nil
958981
}
959982

960-
// GetDRepState returns the state of specified DReps (Conway era)
961-
// If credentials is nil or empty, returns state for all DReps
983+
// GetDRepState returns the registration state of delegate representatives
984+
// (DReps) (CIP-1694).
985+
//
986+
// Use this to inspect registered DReps: when each one's activity period
987+
// expires, the deposit it locked at registration, and its optional metadata
988+
// anchor. Pass a list of DRep credentials to filter the response, or nil/empty
989+
// to return the state of every registered DRep.
990+
//
991+
// Response: a [DRepStateResult], a map keyed by each DRep's credential with a
992+
// [DRepStateEntry] value (expiry epoch, deposit, optional anchor).
993+
//
994+
// Era: requires the Conway era or later. Returns an error if the acquired
995+
// ledger state is on an earlier era.
962996
func (c *Client) GetDRepState(
963997
credentials []lcommon.Credential,
964998
) (*DRepStateResult, error) {
@@ -1001,8 +1035,20 @@ func (c *Client) GetDRepState(
10011035
return &result, nil
10021036
}
10031037

1004-
// GetDRepStakeDistr returns the stake distribution for specified DReps (Conway era)
1005-
// If dreps is nil or empty, returns distribution for all DReps
1038+
// GetDRepStakeDistr returns the stake distribution across DReps (CIP-1694).
1039+
//
1040+
// Use this to read the voting power (total delegated stake, in lovelace) of
1041+
// DReps, for example to weigh how a proposal's DRep votes translate into
1042+
// stake. Pass a list of DReps to filter the response, or nil/empty for the
1043+
// full distribution. Note the [lcommon.Drep] type also covers the predefined
1044+
// Abstain and NoConfidence options, not only credential-backed DReps.
1045+
//
1046+
// Response: a [DRepStakeDistrResult] containing the raw CBOR map of DReps to
1047+
// stake amounts. It is returned undecoded because its key encoding is
1048+
// era-specific; decode it with [cbor.Decode] against an era-appropriate type.
1049+
//
1050+
// Era: requires the Conway era or later. Returns an error if the acquired
1051+
// ledger state is on an earlier era.
10061052
func (c *Client) GetDRepStakeDistr(
10071053
dreps []lcommon.Drep,
10081054
) (*DRepStakeDistrResult, error) {
@@ -1045,9 +1091,27 @@ func (c *Client) GetDRepStakeDistr(
10451091
return &result, nil
10461092
}
10471093

1048-
// GetCommitteeMembersState returns the state of committee members (Conway era)
1049-
// The filter parameters allow querying by cold credentials, hot credentials, or member status
1050-
// Pass nil/empty to query without that filter
1094+
// GetCommitteeMembersState returns the state of the constitutional committee
1095+
// (CIP-1694).
1096+
//
1097+
// Use this to inspect committee members and their standing: each member's
1098+
// hot-credential authorization status (not-authorized, authorized, or
1099+
// resigned), its term status (active, expired, or unrecognized), its term
1100+
// expiry epoch, and any change scheduled for the next epoch boundary, along
1101+
// with the committee voting threshold and the current epoch.
1102+
//
1103+
// The three filters narrow the response: by member cold credentials, by hot
1104+
// credentials, and by member status (see [MemberStatus]). Pass nil/empty for a
1105+
// filter to leave it unconstrained; passing nil for all three returns every
1106+
// member. The committee uses a hot/cold key setup, so a member is identified
1107+
// by its cold credential and votes with its authorized hot credential.
1108+
//
1109+
// Response: a [CommitteeMembersStateResult] mapping each member's cold
1110+
// credential to a [CommitteeMemberState], plus the [cbor.Rat] threshold and
1111+
// epoch.
1112+
//
1113+
// Era: requires the Conway era or later. Returns an error if the acquired
1114+
// ledger state is on an earlier era.
10511115
func (c *Client) GetCommitteeMembersState(
10521116
coldCreds []lcommon.Credential,
10531117
hotCreds []lcommon.Credential,
@@ -1108,8 +1172,21 @@ func (c *Client) GetCommitteeMembersState(
11081172
return &result, nil
11091173
}
11101174

1111-
// GetFilteredVoteDelegatees returns the DRep delegations for specified stake credentials (Conway era)
1112-
// If credentials is nil or empty, returns delegations for all credentials
1175+
// GetFilteredVoteDelegatees returns the DRep that each stake credential has
1176+
// delegated its vote to (CIP-1694).
1177+
//
1178+
// Use this to look up where stake credentials have delegated their voting
1179+
// rights. Per CIP-1694 a vote-delegation certificate maps a stake credential
1180+
// to a DRep credential, so each result is a single [lcommon.Drep]; that DRep
1181+
// may be a credential-backed DRep or one of the predefined Abstain or
1182+
// NoConfidence options. Pass a list of stake credentials to filter the
1183+
// response, or nil/empty to return delegations for all credentials.
1184+
//
1185+
// Response: a [FilteredVoteDelegateesResult], a map keyed by stake credential
1186+
// with the delegated [lcommon.Drep] as the value.
1187+
//
1188+
// Era: requires the Conway era or later. Returns an error if the acquired
1189+
// ledger state is on an earlier era.
11131190
func (c *Client) GetFilteredVoteDelegatees(
11141191
credentials []lcommon.Credential,
11151192
) (*FilteredVoteDelegateesResult, error) {
@@ -1152,8 +1229,20 @@ func (c *Client) GetFilteredVoteDelegatees(
11521229
return &result, nil
11531230
}
11541231

1155-
// GetSPOStakeDistr returns the SPO stake distribution for governance voting (Conway era)
1156-
// If poolIds is nil or empty, returns distribution for all pools
1232+
// GetSPOStakeDistr returns the stake-pool-operator (SPO) stake distribution
1233+
// used for governance voting (CIP-1694).
1234+
//
1235+
// Use this to read each pool's governance voting power (the Lovelace delegated
1236+
// to it), for example to weigh how SPO votes on a proposal translate into
1237+
// stake. This is the governance-voting view of pool stake, distinct from the
1238+
// block-production stake distribution returned by [Client.GetStakeDistribution].
1239+
// Pass a list of pool IDs to filter the response, or nil/empty for all pools.
1240+
//
1241+
// Response: an [SPOStakeDistrResult] mapping each pool ID to its voting power
1242+
// in Lovelace.
1243+
//
1244+
// Era: requires the Conway era or later. Returns an error if the acquired
1245+
// ledger state is on an earlier era.
11571246
func (c *Client) GetSPOStakeDistr(
11581247
poolIds []ledger.PoolId,
11591248
) (*SPOStakeDistrResult, error) {
@@ -1196,9 +1285,20 @@ func (c *Client) GetSPOStakeDistr(
11961285
return &result, nil
11971286
}
11981287

1199-
// GetProposals returns all active governance proposals (Conway era)
1200-
// Each proposal includes its governance action ID, votes, proposal procedure,
1201-
// and the epoch range during which it is active
1288+
// GetProposals returns all active governance proposals (CIP-1694).
1289+
//
1290+
// Use this to enumerate governance actions that are currently open for voting
1291+
// and to inspect their tally. Each entry carries the governance action ID (the
1292+
// creating transaction hash plus the action's index within that transaction),
1293+
// the votes cast so far by the constitutional committee, DReps, and SPOs, the
1294+
// raw proposal procedure, and the epoch window during which the action is live
1295+
// (proposed-in epoch through expires-after epoch).
1296+
//
1297+
// Response: a [ProposalsResult], a slice of [GovActionState], one per active
1298+
// proposal.
1299+
//
1300+
// Era: requires the Conway era or later. Returns an error if the acquired
1301+
// ledger state is on an earlier era.
12021302
func (c *Client) GetProposals() (*ProposalsResult, error) {
12031303
c.Protocol.Logger().
12041304
Debug("calling GetProposals()",
@@ -1230,8 +1330,22 @@ func (c *Client) GetProposals() (*ProposalsResult, error) {
12301330
return &result, nil
12311331
}
12321332

1233-
// GetRatifyState returns the current ratification state (Conway era)
1234-
// This includes the enact state, enacted proposals, expired proposal IDs, and delayed flag
1333+
// GetRatifyState returns the current governance ratification state (CIP-1694).
1334+
//
1335+
// Governance actions are checked for ratification on the epoch boundary and
1336+
// enacted on the following boundary. Use this to see the outcome of that
1337+
// process for the current epoch: the enact state (the committee, constitution,
1338+
// protocol parameters, treasury, withdrawals, and previous governance action
1339+
// IDs that will take effect), the actions that were ratified/enacted, the IDs
1340+
// of actions that expired, and a delayed flag indicating that enactment of
1341+
// further actions is held back this epoch (for example behind a hard fork).
1342+
//
1343+
// Response: a [RatifyStateResult] with the [EnactState], the enacted
1344+
// [GovActionState] list, the expired [lcommon.GovActionId] list, and the
1345+
// delayed flag.
1346+
//
1347+
// Era: requires the Conway era or later. Returns an error if the acquired
1348+
// ledger state is on an earlier era.
12351349
func (c *Client) GetRatifyState() (*RatifyStateResult, error) {
12361350
c.Protocol.Logger().
12371351
Debug("calling GetRatifyState()",

0 commit comments

Comments
 (0)