Skip to content

Commit 6fdbfdd

Browse files
conorschcronokirby
andauthored
feat(pcli): support lqt votes by subaccount (#5244)
## Describe your changes Previously the `pcli tx lqt-vote` command only inspected subaccount 0 when searching for delegation notes to use in voting. If a user stores their delegations in a different subaccounts, it wasn't possible to vote with them. Here we allow a user to opt in to voting in the LQT via a specific subaccount, provided as `--source n`, the same CLI used for `pcli tx send`. ## Issue ticket number and link n/a ## Testing and review Visual review of the diff should be sufficient, as the logic is straightforward. ## Checklist before requesting a review - [x] I have added guiding text to explain how a reviewer should test these changes. - [x] If this code contains consensus-breaking changes, I have added the "consensus-breaking" label. Otherwise, I declare my belief that there are not consensus-breaking changes, for the following reason: > only changes client-side tooling, no changes to app consensus logic --------- Signed-off-by: Lúcás Meier <cronokirby@gmail.com> Co-authored-by: Lúcás Meier <lucas@cronokirby.com>
1 parent 762286a commit 6fdbfdd

File tree

2 files changed

+28
-12
lines changed

2 files changed

+28
-12
lines changed

crates/bin/pcli/src/command/tx/lqt_vote.rs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use anyhow::anyhow;
22
use penumbra_sdk_asset::asset::REGISTRY;
33
use penumbra_sdk_fee::{FeeTier, GasPrices};
4-
use penumbra_sdk_keys::Address;
4+
use penumbra_sdk_keys::{keys::AddressIndex, Address};
55
use penumbra_sdk_proto::core::component::sct::v1::{
66
query_service_client::QueryServiceClient as SctQueryServiceClient, EpochByHeightRequest,
77
};
@@ -38,6 +38,9 @@ pub struct LqtVoteCmd {
3838
/// This can also be an integer, indicating an ephemeral address of a sub-account.
3939
#[clap(short, long)]
4040
rewards_recipient: Option<String>,
41+
/// Only consider delegations within the specified subaccount.
42+
#[clap(long, default_value = "0", display_order = 300)]
43+
source: u32,
4144
/// The selected fee tier.
4245
#[clap(short, long, default_value_t)]
4346
fee_tier: FeeTier,
@@ -79,7 +82,17 @@ impl LqtVoteCmd {
7982
let vote_denom = vote_meta.base_denom();
8083

8184
let epoch = fetch_epoch(app).await?;
82-
let voting_notes = app.view().lqt_voting_notes(epoch.index, None).await?;
85+
let voting_notes = app
86+
.view()
87+
.lqt_voting_notes(epoch.index, Some(AddressIndex::new(self.source)))
88+
.await?;
89+
90+
if voting_notes.is_empty() {
91+
anyhow::bail!(
92+
"no voting notes found in subaccount {}, cannot cast LQT vote",
93+
self.source
94+
);
95+
}
8396

8497
let mut planner = Planner::new(OsRng);
8598

@@ -106,11 +119,13 @@ impl LqtVoteCmd {
106119
let change_addr = app
107120
.config
108121
.full_viewing_key
109-
.ephemeral_address(OsRng, Default::default())
122+
.ephemeral_address(OsRng, AddressIndex::new(self.source))
110123
.0;
111124
planner.change_address(change_addr.clone());
112125

113-
let plan = planner.plan(app.view(), Default::default()).await?;
126+
let plan = planner
127+
.plan(app.view(), AddressIndex::new(self.source))
128+
.await?;
114129
app.build_and_submit_transaction(plan).await?;
115130

116131
Ok(())

crates/view/src/storage.rs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -988,17 +988,15 @@ impl Storage {
988988
.await?
989989
}
990990

991+
/// Return all notes that are eligible for voting at `votable_at_height`.
992+
/// If an `address_index` is provided, only notes from within that subaccount
993+
/// will be returned; otherwise, all voting notes across all subaccounts will
994+
/// returned. If you want just the main account, then set `Some(0)` in the caller.
991995
pub async fn notes_for_voting(
992996
&self,
993997
address_index: Option<penumbra_sdk_keys::keys::AddressIndex>,
994998
votable_at_height: u64,
995999
) -> anyhow::Result<Vec<(SpendableNoteRecord, IdentityKey)>> {
996-
// If set, only return notes with the specified address index.
997-
// crypto.AddressIndex address_index = 3;
998-
let address_clause = address_index
999-
.map(|d| format!("x'{}'", hex::encode(d.to_bytes())))
1000-
.unwrap_or_else(|| "address_index".to_string());
1001-
10021000
let pool = self.pool.clone();
10031001

10041002
spawn_blocking(move || {
@@ -1021,8 +1019,7 @@ impl Storage {
10211019
FROM
10221020
notes JOIN spendable_notes ON notes.note_commitment = spendable_notes.note_commitment
10231021
WHERE
1024-
spendable_notes.address_index IS {address_clause}
1025-
AND notes.asset_id IN (
1022+
notes.asset_id IN (
10261023
SELECT asset_id FROM assets WHERE denom LIKE '_delegation\\_%' ESCAPE '\\'
10271024
)
10281025
AND ((spendable_notes.height_spent IS NULL) OR (spendable_notes.height_spent > {votable_at_height}))
@@ -1036,6 +1033,10 @@ impl Storage {
10361033
// do it this way; if it becomes slow, we can do it better
10371034
let mut results = Vec::new();
10381035
for record in spendable_note_records {
1036+
// Skip notes that don't match the account index, if declared.
1037+
if matches!(address_index, Some(a) if a.account != record.address_index.account) {
1038+
continue;
1039+
}
10391040
let asset_id = record.note.asset_id().to_bytes().to_vec();
10401041
let denom: String = dbtx.query_row_and_then(
10411042
"SELECT denom FROM assets WHERE asset_id = ?1",

0 commit comments

Comments
 (0)