pallas-txbuilder: thread voting_procedures through StagingTransaction → Conway build
Fills the third of pallas-txbuilder's Conway TODOs (after auxiliary_data + certificates): - StagingTransaction gains `voting_procedures: Option<Vec<u8>>` (opaque CBOR — same Eq-derive workaround as auxiliary_data + certificates) - Builder methods .voting_procedures(cbor_bytes) + .clear_voting_procedures() - conway::build_conway_raw decodes via VotingProcedures::decode_fragment and assigns to TransactionBody.voting_procedures VotingProcedures is itself a NonEmptyKeyValuePairs<Voter, NonEmptyKeyValuePairs<GovActionId, VotingProcedure>> map, so callers pre-assemble the full map and encode once. Tests: - voting_procedures_plumb_through_to_tx_body — encode DRepKey vote-Yes on a synthetic GovActionId, build, decode, assert round-trip - no_voting_procedures_means_none — negative path
This commit is contained in:
parent
310b0fe562
commit
507fd9da15
3 changed files with 120 additions and 3 deletions
|
|
@ -57,7 +57,32 @@ Tests added:
|
|||
asserts the cert round-trips byte-for-byte.
|
||||
- `no_certificates_means_none` — negative path.
|
||||
|
||||
### 3. `pallas-addresses` — `pub fn new` on `StakeAddress`
|
||||
### 3. `pallas-txbuilder` — `voting_procedures` field on `StagingTransaction`
|
||||
|
||||
Upstream had `voting_procedures: None, // TODO` in the conway builder.
|
||||
Without this, **DRep / SPO / committee voting on Conway governance
|
||||
actions can't ride a tx built with pallas-txbuilder.**
|
||||
|
||||
Added 2026-05-06:
|
||||
- `pub voting_procedures: Option<Vec<u8>>` field (opaque CBOR for the
|
||||
same `Eq`-derive reason as `auxiliary_data`).
|
||||
- Builder methods `.voting_procedures(cbor_bytes)` and
|
||||
`.clear_voting_procedures()`.
|
||||
- `conway::build_conway_raw` decodes via `VotingProcedures::decode_fragment`
|
||||
and assigns to `TransactionBody.voting_procedures`.
|
||||
|
||||
The `VotingProcedures` type is itself a single map
|
||||
(`NonEmptyKeyValuePairs<Voter, NonEmptyKeyValuePairs<GovActionId,
|
||||
VotingProcedure>>`), so callers pre-assemble the full map and encode
|
||||
once. No staged accumulator needed.
|
||||
|
||||
Tests added:
|
||||
- `voting_procedures_plumb_through_to_tx_body` — encodes a
|
||||
single DRepKey-vote-Yes on a synthetic GovActionId, builds, decodes,
|
||||
asserts the vote round-trips byte-for-byte.
|
||||
- `no_voting_procedures_means_none` — negative path.
|
||||
|
||||
### 4. `pallas-addresses` — `pub fn new` on `StakeAddress`
|
||||
|
||||
Upstream defined `pub struct StakeAddress(Network, StakePayload)` with
|
||||
**unexported tuple fields** and no `new()` constructor — so external
|
||||
|
|
@ -103,6 +128,7 @@ per change) for upstream review ergonomics:
|
|||
|
||||
- `pallas-txbuilder: thread auxiliary_data through StagingTransaction → Conway build`
|
||||
- `pallas-txbuilder: thread certificates through StagingTransaction → Conway build`
|
||||
- `pallas-txbuilder: thread voting_procedures through StagingTransaction → Conway build`
|
||||
- `pallas-addresses: pub fn new on StakeAddress`
|
||||
|
||||
## Change discipline going forward
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use pallas_primitives::{
|
|||
AuxiliaryData, Certificate, DatumOption, ExUnits as PallasExUnits, NativeScript, NetworkId,
|
||||
NonZeroInt, PlutusData, PlutusScript, PostAlonzoTransactionOutput,
|
||||
PseudoScript as PallasScript, PseudoTransactionOutput, Redeemer, RedeemerTag,
|
||||
TransactionBody, TransactionInput, Tx, Value, WitnessSet,
|
||||
TransactionBody, TransactionInput, Tx, Value, VotingProcedures, WitnessSet,
|
||||
},
|
||||
Fragment, NonEmptyKeyValuePairs, NonEmptySet, PositiveCoin,
|
||||
};
|
||||
|
|
@ -261,7 +261,14 @@ impl BuildConway for StagingTransaction {
|
|||
collateral_return,
|
||||
reference_inputs,
|
||||
total_collateral: None, // TODO
|
||||
voting_procedures: None, // TODO
|
||||
voting_procedures: self
|
||||
.voting_procedures
|
||||
.as_deref()
|
||||
.map(|bytes| {
|
||||
VotingProcedures::decode_fragment(bytes)
|
||||
.map_err(|_| TxBuilderError::CorruptedTxBytes)
|
||||
})
|
||||
.transpose()?,
|
||||
proposal_procedures: None, // TODO
|
||||
treasury_value: None, // TODO
|
||||
donation: None, // TODO
|
||||
|
|
@ -494,6 +501,67 @@ mod tests {
|
|||
assert!(body.transaction_body.certificates.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn voting_procedures_plumb_through_to_tx_body() {
|
||||
use pallas_primitives::conway::{
|
||||
GovActionId, Vote, Voter, VotingProcedure, VotingProcedures,
|
||||
};
|
||||
use pallas_codec::utils::Nullable;
|
||||
use pallas_crypto::hash::Hash;
|
||||
|
||||
// Construct a single-voter, single-action vote.
|
||||
let voter_hash = Hash::<28>::from([7u8; 28]);
|
||||
let voter = Voter::DRepKey(voter_hash);
|
||||
let action = GovActionId {
|
||||
transaction_id: Hash::<32>::from([9u8; 32]),
|
||||
action_index: 0,
|
||||
};
|
||||
let procedure = VotingProcedure {
|
||||
vote: Vote::Yes,
|
||||
anchor: Nullable::Null,
|
||||
};
|
||||
|
||||
let inner = NonEmptyKeyValuePairs::Def(vec![(action, procedure)]);
|
||||
let outer: VotingProcedures = NonEmptyKeyValuePairs::Def(vec![(voter, inner)]);
|
||||
let vp_bytes = minicbor::to_vec(&outer).expect("encode voting procedures");
|
||||
|
||||
let tx = StagingTransaction::new()
|
||||
.voting_procedures(vp_bytes)
|
||||
.network_id(0)
|
||||
.fee(180_000)
|
||||
.build_conway_raw()
|
||||
.expect("build_conway_raw");
|
||||
|
||||
let body = pallas_primitives::conway::Tx::decode_fragment(tx.tx_bytes.as_ref())
|
||||
.expect("decode tx_bytes");
|
||||
let vp = body
|
||||
.transaction_body
|
||||
.voting_procedures
|
||||
.expect("voting_procedures field populated");
|
||||
let outer_vec = vp.to_vec();
|
||||
assert_eq!(outer_vec.len(), 1);
|
||||
let (got_voter, inner_kv) = &outer_vec[0];
|
||||
assert!(matches!(got_voter, Voter::DRepKey(h) if h.as_ref() == &[7u8; 28]));
|
||||
let inner_vec = inner_kv.to_vec();
|
||||
assert_eq!(inner_vec.len(), 1);
|
||||
let (got_action, got_proc) = &inner_vec[0];
|
||||
assert_eq!(got_action.transaction_id.as_ref(), &[9u8; 32]);
|
||||
assert_eq!(got_action.action_index, 0);
|
||||
assert!(matches!(got_proc.vote, Vote::Yes));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_voting_procedures_means_none() {
|
||||
let tx = StagingTransaction::new()
|
||||
.network_id(0)
|
||||
.fee(180_000)
|
||||
.build_conway_raw()
|
||||
.expect("build_conway_raw");
|
||||
let body = pallas_primitives::conway::Tx::decode_fragment(tx.tx_bytes.as_ref())
|
||||
.expect("decode tx_bytes");
|
||||
assert!(body.transaction_body.voting_procedures.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_auxiliary_data_means_no_hash() {
|
||||
let tx = StagingTransaction::new()
|
||||
|
|
|
|||
|
|
@ -50,6 +50,12 @@ pub struct StagingTransaction {
|
|||
/// plumbs them into `TransactionBody.certificates`. Used for
|
||||
/// stake registration/delegation, pool registration, DRep ops, etc.
|
||||
pub certificates: Option<Vec<Vec<u8>>>,
|
||||
/// CBOR-encoded `pallas_primitives::conway::VotingProcedures`. A single
|
||||
/// blob (the type is `NonEmptyKeyValuePairs<Voter,
|
||||
/// NonEmptyKeyValuePairs<GovActionId, VotingProcedure>>`, so callers
|
||||
/// pre-assemble the full map and encode it once). `conway::build_conway_raw`
|
||||
/// decodes on the way out into `TransactionBody.voting_procedures`.
|
||||
pub voting_procedures: Option<Vec<u8>>,
|
||||
// pub withdrawals: TODO
|
||||
// pub updates: TODO
|
||||
// pub phase_2_valid: TODO
|
||||
|
|
@ -420,6 +426,23 @@ impl StagingTransaction {
|
|||
self.certificates = None;
|
||||
self
|
||||
}
|
||||
|
||||
/// Attach pre-encoded `voting_procedures` (CBOR bytes of a
|
||||
/// `conway::VotingProcedures`). Callers assemble the full
|
||||
/// `NonEmptyKeyValuePairs<Voter, NonEmptyKeyValuePairs<GovActionId,
|
||||
/// VotingProcedure>>` map and pass the encoded bytes here.
|
||||
///
|
||||
/// Used for DRep / SPO / committee voting on Conway governance actions.
|
||||
pub fn voting_procedures(mut self, cbor_bytes: Vec<u8>) -> Self {
|
||||
self.voting_procedures = Some(cbor_bytes);
|
||||
self
|
||||
}
|
||||
|
||||
/// Drop any voting procedures previously attached.
|
||||
pub fn clear_voting_procedures(mut self) -> Self {
|
||||
self.voting_procedures = None;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Don't want our wrapper types in fields public
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue