Skip to content

Commit edf3e05

Browse files
committed
feat: keep track of committees hot keys
Signed-off-by: jeluard <[email protected]>
1 parent c32e7da commit edf3e05

File tree

9 files changed

+160
-17
lines changed

9 files changed

+160
-17
lines changed

crates/amaru-ledger/src/state/transaction.rs

+32-12
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use amaru_kernel::{
2424
Slot, StakeCredential, TransactionInput, TransactionOutput, STAKE_CREDENTIAL_DEPOSIT,
2525
};
2626
use std::{
27-
collections::{BTreeMap, BTreeSet},
27+
collections::{BTreeMap, BTreeSet, HashMap},
2828
vec,
2929
};
3030
use tracing::{trace, trace_span, Span};
@@ -84,6 +84,7 @@ pub fn apply(
8484
&mut state.pools,
8585
&mut state.accounts,
8686
&mut state.dreps,
87+
&mut state.committee,
8788
certificate,
8889
CertificatePointer {
8990
slot: absolute_slot,
@@ -211,6 +212,7 @@ fn apply_certificate(
211212
pools: &mut DiffEpochReg<PoolId, PoolParams>,
212213
accounts: &mut DiffBind<StakeCredential, PoolId, (DRep, CertificatePointer), Lovelace>,
213214
dreps: &mut DiffBind<StakeCredential, Anchor, Empty, (Lovelace, CertificatePointer)>,
215+
committee: &mut HashMap<StakeCredential, StakeCredential>,
214216
certificate: Certificate,
215217
pointer: CertificatePointer,
216218
) {
@@ -264,29 +266,41 @@ fn apply_certificate(
264266
}
265267
Certificate::StakeVoteDeleg(credential, pool, drep) => {
266268
let drep_deleg = Certificate::VoteDeleg(credential.clone(), drep);
267-
apply_certificate(parent, pools, accounts, dreps, drep_deleg, pointer);
269+
apply_certificate(
270+
parent, pools, accounts, dreps, committee, drep_deleg, pointer,
271+
);
268272
let pool_deleg = Certificate::StakeDelegation(credential, pool);
269-
apply_certificate(parent, pools, accounts, dreps, pool_deleg, pointer);
273+
apply_certificate(
274+
parent, pools, accounts, dreps, committee, pool_deleg, pointer,
275+
);
270276
}
271277
Certificate::StakeRegDeleg(credential, pool, coin) => {
272278
let reg = Certificate::Reg(credential.clone(), coin);
273-
apply_certificate(parent, pools, accounts, dreps, reg, pointer);
279+
apply_certificate(parent, pools, accounts, dreps, committee, reg, pointer);
274280
let pool_deleg = Certificate::StakeDelegation(credential, pool);
275-
apply_certificate(parent, pools, accounts, dreps, pool_deleg, pointer);
281+
apply_certificate(
282+
parent, pools, accounts, dreps, committee, pool_deleg, pointer,
283+
);
276284
}
277285
Certificate::StakeVoteRegDeleg(credential, pool, drep, coin) => {
278286
let reg = Certificate::Reg(credential.clone(), coin);
279-
apply_certificate(parent, pools, accounts, dreps, reg, pointer);
287+
apply_certificate(parent, pools, accounts, dreps, committee, reg, pointer);
280288
let pool_deleg = Certificate::StakeDelegation(credential.clone(), pool);
281-
apply_certificate(parent, pools, accounts, dreps, pool_deleg, pointer);
289+
apply_certificate(
290+
parent, pools, accounts, dreps, committee, pool_deleg, pointer,
291+
);
282292
let drep_deleg = Certificate::VoteDeleg(credential, drep);
283-
apply_certificate(parent, pools, accounts, dreps, drep_deleg, pointer);
293+
apply_certificate(
294+
parent, pools, accounts, dreps, committee, drep_deleg, pointer,
295+
);
284296
}
285297
Certificate::VoteRegDeleg(credential, drep, coin) => {
286298
let reg = Certificate::Reg(credential.clone(), coin);
287-
apply_certificate(parent, pools, accounts, dreps, reg, pointer);
299+
apply_certificate(parent, pools, accounts, dreps, committee, reg, pointer);
288300
let drep_deleg = Certificate::VoteDeleg(credential, drep);
289-
apply_certificate(parent, pools, accounts, dreps, drep_deleg, pointer);
301+
apply_certificate(
302+
parent, pools, accounts, dreps, committee, drep_deleg, pointer,
303+
);
290304
}
291305
Certificate::RegDRepCert(drep, deposit, anchor) => {
292306
trace!(target: EVENT_TARGET, parent: parent, drep = ?drep, deposit = ?deposit, anchor = ?anchor, "certificate.drep.registration");
@@ -308,7 +322,13 @@ fn apply_certificate(
308322
.bind_right(credential, Some((drep, pointer)))
309323
.unwrap();
310324
}
311-
// FIXME: Process other types of certificates
312-
Certificate::AuthCommitteeHot { .. } | Certificate::ResignCommitteeCold { .. } => {}
325+
Certificate::AuthCommitteeHot(cold_credential, hot_credential) => {
326+
trace!(name: "committee.hot_key", target: EVENT_TARGET, parent: parent, cold_credential = ?cold_credential, hot_credential = ?hot_credential);
327+
committee.insert(cold_credential, hot_credential);
328+
}
329+
Certificate::ResignCommitteeCold(cold_credential, anchor) => {
330+
trace!(name: "committee.hot_key", target: EVENT_TARGET, parent: parent, cold_credential = ?cold_credential, anchor = ?anchor);
331+
committee.remove(&cold_credential);
332+
}
313333
}
314334
}

crates/amaru-ledger/src/state/volatile_db.rs

+9-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ use amaru_kernel::{
2222
epoch_from_slot, Anchor, CertificatePointer, DRep, Epoch, Lovelace, Point, PoolId, PoolParams,
2323
StakeCredential, TransactionInput, TransactionOutput,
2424
};
25-
use std::collections::{BTreeSet, VecDeque};
25+
use std::{
26+
collections::{BTreeSet, HashMap, VecDeque},
27+
iter,
28+
};
2629

2730
// VolatileDB
2831
// ----------------------------------------------------------------------------
@@ -141,6 +144,7 @@ pub struct VolatileState {
141144
pub pools: DiffEpochReg<PoolId, PoolParams>,
142145
pub accounts: DiffBind<StakeCredential, PoolId, (DRep, CertificatePointer), Lovelace>,
143146
pub dreps: DiffBind<StakeCredential, Anchor, Empty, (Lovelace, CertificatePointer)>,
147+
pub committee: HashMap<StakeCredential, StakeCredential>,
144148
pub withdrawals: BTreeSet<StakeCredential>,
145149
pub voting_dreps: BTreeSet<StakeCredential>,
146150
pub fees: Lovelace,
@@ -188,12 +192,14 @@ impl AnchoredVolatileState {
188192
impl Iterator<Item = pools::Value>,
189193
impl Iterator<Item = (accounts::Key, accounts::Value)>,
190194
impl Iterator<Item = (dreps::Key, dreps::Value)>,
195+
impl Iterator<Item = (committees::Key, committees::Value)>,
191196
>,
192197
store::Columns<
193198
impl Iterator<Item = utxo::Key>,
194199
impl Iterator<Item = (pools::Key, Epoch)>,
195200
impl Iterator<Item = accounts::Key>,
196201
impl Iterator<Item = dreps::Key>,
202+
impl Iterator<Item = committees::Key>,
197203
>,
198204
> {
199205
let slot = self.anchor.0.slot_or_default();
@@ -244,12 +250,14 @@ impl AnchoredVolatileState {
244250
(credential, (anchor, deposit, epoch))
245251
},
246252
),
253+
committees: self.state.committee.into_iter(),
247254
},
248255
remove: store::Columns {
249256
utxo: self.state.utxo.consumed.into_iter(),
250257
pools: self.state.pools.unregistered.into_iter(),
251258
accounts: self.state.accounts.unregistered.into_iter(),
252259
dreps: self.state.dreps.unregistered.into_iter(),
260+
committees: iter::empty(),
253261
},
254262
}
255263
}

crates/amaru-ledger/src/store.rs

+7-3
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,14 @@ pub trait Store: Snapshot + Send + Sync {
107107
impl Iterator<Item = pools::Value>,
108108
impl Iterator<Item = (accounts::Key, accounts::Value)>,
109109
impl Iterator<Item = (dreps::Key, dreps::Value)>,
110+
impl Iterator<Item = (committees::Key, committees::Value)>,
110111
>,
111112
remove: Columns<
112113
impl Iterator<Item = utxo::Key>,
113114
impl Iterator<Item = (pools::Key, Epoch)>,
114115
impl Iterator<Item = accounts::Key>,
115116
impl Iterator<Item = dreps::Key>,
117+
impl Iterator<Item = committees::Key>,
116118
>,
117119
withdrawals: impl Iterator<Item = accounts::Key>,
118120
voting_dreps: BTreeSet<StakeCredential>,
@@ -167,22 +169,24 @@ pub trait Store: Snapshot + Send + Sync {
167169

168170
/// A summary of all database columns, in a single struct. This can be derived to provide updates
169171
/// operations on multiple columns in a single db-transaction.
170-
pub struct Columns<U, P, A, D> {
172+
pub struct Columns<U, P, A, D, CO> {
171173
pub utxo: U,
172174
pub pools: P,
173175
pub accounts: A,
174176
pub dreps: D,
177+
pub committees: CO,
175178
}
176179

177-
impl<U, P, A, D> Default
178-
for Columns<iter::Empty<U>, iter::Empty<P>, iter::Empty<A>, iter::Empty<D>>
180+
impl<U, P, A, D, CO> Default
181+
for Columns<iter::Empty<U>, iter::Empty<P>, iter::Empty<A>, iter::Empty<D>, iter::Empty<CO>>
179182
{
180183
fn default() -> Self {
181184
Self {
182185
utxo: iter::empty(),
183186
pools: iter::empty(),
184187
accounts: iter::empty(),
185188
dreps: iter::empty(),
189+
committees: iter::empty(),
186190
}
187191
}
188192
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Copyright 2025 PRAGMA
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
use amaru_kernel::{cbor, StakeCredential};
16+
use iter_borrow::IterBorrow;
17+
18+
pub const EVENT_TARGET: &str = "amaru::ledger::store::committees";
19+
20+
/// Iterator used to browse rows from the Accounts column. Meant to be referenced using qualified imports.
21+
pub type Iter<'a, 'b> = IterBorrow<'a, 'b, Key, Option<Row>>;
22+
23+
pub type Value = StakeCredential;
24+
25+
pub type Key = StakeCredential;
26+
27+
#[derive(Debug, Clone, PartialEq)]
28+
pub struct Row {
29+
pub hot_credential: StakeCredential,
30+
}
31+
32+
impl Row {
33+
#[allow(clippy::panic)]
34+
pub fn unsafe_decode(bytes: Vec<u8>) -> Self {
35+
cbor::decode(&bytes).unwrap_or_else(|e| {
36+
panic!(
37+
"unable to decode account from CBOR ({}): {e:?}",
38+
hex::encode(&bytes)
39+
)
40+
})
41+
}
42+
}
43+
44+
impl<C> cbor::encode::Encode<C> for Row {
45+
fn encode<W: cbor::encode::Write>(
46+
&self,
47+
e: &mut cbor::Encoder<W>,
48+
ctx: &mut C,
49+
) -> Result<(), cbor::encode::Error<W::Error>> {
50+
e.array(1)?;
51+
e.encode_with(self.hot_credential.clone(), ctx)?;
52+
Ok(())
53+
}
54+
}
55+
56+
impl<'a, C> cbor::decode::Decode<'a, C> for Row {
57+
fn decode(d: &mut cbor::Decoder<'a>, ctx: &mut C) -> Result<Self, cbor::decode::Error> {
58+
d.array()?;
59+
Ok(Row {
60+
hot_credential: d.decode_with(ctx)?,
61+
})
62+
}
63+
}

crates/amaru-ledger/src/store/columns/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
// limitations under the License.
1414

1515
pub mod accounts;
16+
pub mod committees;
1617
pub mod dreps;
1718
pub mod pools;
1819
pub mod pots;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright 2025 PRAGMA
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
use crate::rocksdb::common::{as_key, as_value, PREFIX_LEN};
16+
use amaru_ledger::store::StoreError;
17+
use rocksdb::Transaction;
18+
19+
use amaru_ledger::store::columns::committees::{Key, Row, Value};
20+
21+
/// Name prefixed used for storing delegations entries. UTF-8 encoding for "comm"
22+
pub const PREFIX: [u8; PREFIX_LEN] = [0x43, 0x4F, 0x4D, 0x4D];
23+
24+
/// Register a new DRep.
25+
pub fn add<DB>(
26+
db: &Transaction<'_, DB>,
27+
rows: impl Iterator<Item = (Key, Value)>,
28+
) -> Result<(), StoreError> {
29+
for (credential, hot_credential) in rows {
30+
let key = as_key(&PREFIX, &credential);
31+
// Always override if a mapping already exists.
32+
let row = Row { hot_credential };
33+
db.put(key, as_value(row))
34+
.map_err(|err| StoreError::Internal(err.into()))?;
35+
}
36+
37+
Ok(())
38+
}

crates/amaru-stores/src/rocksdb/columns/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
// limitations under the License.
1414

1515
pub mod accounts;
16+
pub mod committees;
1617
pub mod dreps;
1718
pub mod pools;
1819
pub mod pots;

crates/amaru-stores/src/rocksdb/mod.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -285,12 +285,14 @@ impl Store for RocksDB {
285285
impl Iterator<Item = scolumns::pools::Value>,
286286
impl Iterator<Item = (scolumns::accounts::Key, scolumns::accounts::Value)>,
287287
impl Iterator<Item = (scolumns::dreps::Key, scolumns::dreps::Value)>,
288+
impl Iterator<Item = (scolumns::committees::Key, scolumns::committees::Value)>,
288289
>,
289290
remove: Columns<
290291
impl Iterator<Item = scolumns::utxo::Key>,
291292
impl Iterator<Item = (scolumns::pools::Key, Epoch)>,
292293
impl Iterator<Item = scolumns::accounts::Key>,
293294
impl Iterator<Item = scolumns::dreps::Key>,
295+
impl Iterator<Item = scolumns::committees::Key>,
294296
>,
295297
withdrawals: impl Iterator<Item = scolumns::accounts::Key>,
296298
voting_dreps: BTreeSet<StakeCredential>,
@@ -331,8 +333,9 @@ impl Store for RocksDB {
331333

332334
utxo::add(&batch, add.utxo)?;
333335
pools::add(&batch, add.pools)?;
334-
335336
accounts::add(&batch, add.accounts)?;
337+
committees::add(&batch, add.committees)?;
338+
336339
accounts::reset(&batch, withdrawals)?;
337340

338341
dreps::add(&batch, add.dreps)?;

crates/amaru/src/bin/amaru/cmd/import_ledger_state.rs

+5
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,7 @@ fn import_utxo(
369369
pools: iter::empty(),
370370
accounts: iter::empty(),
371371
dreps: iter::empty(),
372+
committees: iter::empty(),
372373
},
373374
Default::default(),
374375
iter::empty(),
@@ -420,6 +421,7 @@ fn import_dreps(
420421
),
421422
)
422423
}),
424+
committees: iter::empty(),
423425
},
424426
Default::default(),
425427
iter::empty(),
@@ -476,12 +478,14 @@ fn import_stake_pools(
476478
}),
477479
accounts: iter::empty(),
478480
dreps: iter::empty(),
481+
committees: iter::empty(),
479482
},
480483
store::Columns {
481484
pools: state.unregistered.into_iter(),
482485
utxo: iter::empty(),
483486
accounts: iter::empty(),
484487
dreps: iter::empty(),
488+
committees: iter::empty(),
485489
},
486490
iter::empty(),
487491
BTreeSet::new(),
@@ -577,6 +581,7 @@ fn import_accounts(
577581
pools: iter::empty(),
578582
accounts: chunk,
579583
dreps: iter::empty(),
584+
committees: iter::empty(),
580585
},
581586
Default::default(),
582587
iter::empty(),

0 commit comments

Comments
 (0)