Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
e67f25c
Tests for stake_rewards output in monetary.rs
sandtreader Aug 25, 2025
1405c34
Add another layer of snapshot queue (latest), track blocks in snapshots
sandtreader Aug 26, 2025
a8ac6ff
Fix pool margin calculation and log total
sandtreader Aug 26, 2025
7100ac8
Fix margin calculation in delegator rewards
sandtreader Aug 26, 2025
06ceb75
cargo fmt
sandtreader Aug 27, 2025
5606c7f
Don't pay member rewards to pool owners' addresses
sandtreader Aug 27, 2025
eb9b24b
Log number of pools (leaders) paid
sandtreader Aug 27, 2025
6823510
Reorder shapshot and monetary change
sandtreader Aug 27, 2025
abf756e
Move state out of rewards module
alexwoods Aug 27, 2025
19f9ee3
Tidy blocks_produced calc
sandtreader Aug 28, 2025
13baa3c
Add optional pots verifier from DBSync CSV
sandtreader Sep 2, 2025
0c2f004
New logging of SPO registration/pledge changes
sandtreader Sep 2, 2025
3c13eb5
cargo fmt
sandtreader Sep 2, 2025
4b53f60
Don't apply SPO updates until next epoch
alexwoods Sep 3, 2025
96f9790
Log SPO cost/margin updates as well
sandtreader Sep 3, 2025
52553b0
Check for SPO reward account being registered
sandtreader Sep 4, 2025
5560578
Intermediate state in optimising SPO reward account registrations
sandtreader Sep 4, 2025
1449622
Fix timing of SPO reward account capture
sandtreader Sep 4, 2025
5978039
Refactor Verifier ready for additions
sandtreader Sep 4, 2025
e074739
Refactor Verifier ready for additions
sandtreader Sep 4, 2025
9b46a93
Add rewards verification (WIP)
sandtreader Sep 4, 2025
1989644
Implement verification of individual SPO rewards
alexwoods Sep 5, 2025
d1c7c4c
Stake calculations include rewards!
sandtreader Sep 8, 2025
37953c9
Strip 'e1' header from SPO reward account before adding to rewards
sandtreader Sep 8, 2025
6dcb47a
Fix and tidy rewards verifier
sandtreader Sep 9, 2025
49bd57b
Don't pay reward to SPO's reward account
sandtreader Sep 9, 2025
a1a8e76
Fix counting and logging of verify errors
sandtreader Sep 9, 2025
36af309
Don't truncate to_delegators in reward calc
sandtreader Sep 9, 2025
600bfce
Further reduce reward logging -> debug
sandtreader Sep 9, 2025
a69f75c
Fix non-OBFT block counts
sandtreader Sep 9, 2025
652797f
Use previous protocol parameters in monetary updates
sandtreader Sep 10, 2025
ce2e73a
Delay pool retirement until after counting previous epoch SPO blocks
sandtreader Sep 10, 2025
0cd2360
Fix reward verifier - use compare with 'rtype' in merge
sandtreader Sep 10, 2025
1a36e2f
Check for addresses deregistered during previous epoch
sandtreader Sep 11, 2025
4979f4d
Implement Shelley node bug with shared reward accounts
sandtreader Sep 11, 2025
c73477e
Calculate activeStake and use in pool performance calc
sandtreader Sep 11, 2025
d9e4aea
Use SPO-produced total blocks in performance calc
sandtreader Sep 15, 2025
1c595cf
Fix deposit timing, stakeless SPOs, epoch number logging
sandtreader Sep 16, 2025
092c9f1
Delay starting rewards until 4*k*20 slots into epoch
sandtreader Sep 16, 2025
19aabdf
Reference block counts by SPO ID throughout
sandtreader Sep 17, 2025
6013d03
Record chain of registration/deregistrations through snapshots
sandtreader Sep 17, 2025
0dcd934
Track late registrations as well (WIP)
sandtreader Sep 18, 2025
fed087a
Remove unused imports and update Cargo.lock
sandtreader Oct 7, 2025
a2a32b8
Tidy unused imports, unnecessary Clone
sandtreader Oct 8, 2025
3f922e5
cargo fmt
sandtreader Oct 8, 2025
2a926d6
Merge branch 'main' into prc/rewards-fix-2
sandtreader Oct 13, 2025
f351d2c
cargo fmt
sandtreader Oct 13, 2025
3cdcdad
Merge branch 'main' into prc/rewards-fix-2
sandtreader Oct 13, 2025
a873152
Downgrade SPO, rewards debug
sandtreader Oct 14, 2025
5f0424f
Merge branch 'main' into prc/rewards-fix-2
sandtreader Oct 14, 2025
a247f7f
Fix review comment - duplicated sync check
sandtreader Oct 14, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 36 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ async-trait = "0.1"
bech32 = "0.11"
bigdecimal = "0.4.8"
bitmask-enum = "2.2"
blake2 = "0.10"
bs58 = "0.5"
chrono = { workspace = true }
gcd = "2.3"
Expand All @@ -37,6 +36,8 @@ num-traits = "0.2"
imbl = { workspace = true }
dashmap = { workspace = true }
rayon = "1.11.0"
cryptoxide = "0.5.1"
blake2 = "0.10.6"

[lib]
crate-type = ["rlib"]
Expand Down
15 changes: 3 additions & 12 deletions common/src/address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -329,10 +329,7 @@ impl Address {
#[cfg(test)]
mod tests {
use super::*;
use blake2::{
digest::{Update, VariableOutput},
Blake2bVar,
};
use crate::crypto::keyhash_224;

#[test]
fn byron_address() {
Expand All @@ -351,10 +348,7 @@ mod tests {
let (_, pubkey) = bech32::decode(payment_key).expect("Invalid Bech32 string");

// pubkey is the raw key - we need the Blake2B hash
let mut hasher = Blake2bVar::new(28).unwrap();
hasher.update(&pubkey);
let mut hash = vec![0u8; 28];
hasher.finalize_variable(&mut hash).unwrap();
let hash = keyhash_224(&pubkey);
assert_eq!(28, hash.len());
hash
}
Expand All @@ -364,10 +358,7 @@ mod tests {
let (_, pubkey) = bech32::decode(stake_key).expect("Invalid Bech32 string");

// pubkey is the raw key - we need the Blake2B hash
let mut hasher = Blake2bVar::new(28).unwrap();
hasher.update(&pubkey);
let mut hash = vec![0u8; 28];
hasher.finalize_variable(&mut hash).unwrap();
let hash = keyhash_224(&pubkey);
assert_eq!(28, hash.len());
hash
}
Expand Down
17 changes: 12 additions & 5 deletions common/src/crypto.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
//! Common cryptography helper functions for Acropolis

use crate::types::KeyHash;
use blake2::{digest::consts::U32, Blake2b, Digest};
use cryptoxide::hashing::blake2b::Blake2b;

/// Get a Blake2b-256 hash of a key
pub fn keyhash(key: &[u8]) -> KeyHash {
let mut hasher = Blake2b::<U32>::new();
hasher.update(key);
hasher.finalize().to_vec()
pub fn keyhash_256(key: &[u8]) -> KeyHash {
let mut context = Blake2b::<256>::new();
context.update_mut(&key);
context.finalize().to_vec()
}

/// Get a Blake2b-224 hash of a key
pub fn keyhash_224(key: &[u8]) -> KeyHash {
let mut context = Blake2b::<224>::new();
context.update_mut(&key);
context.finalize().to_vec()
}
2 changes: 2 additions & 0 deletions common/src/ledger_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ pub struct SPOState {
#[n(0)]
pub pools: BTreeMap<KeyHash, PoolRegistration>,
#[n(1)]
pub updates: BTreeMap<KeyHash, PoolRegistration>,
#[n(2)]
pub retiring: BTreeMap<KeyHash, u64>,
}

Expand Down
5 changes: 2 additions & 3 deletions common/src/messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,9 +179,8 @@ pub struct EpochActivityMessage {
/// Total fees in this epoch
pub total_fees: u64,

/// List of all VRF vkey hashes used on blocks (SPO indicator) and
/// number of blocks produced
pub vrf_vkey_hashes: Vec<(KeyHash, usize)>,
/// Map of SPO IDs to blocks produced
pub spo_blocks: Vec<(KeyHash, usize)>,

/// Nonce
pub nonce: Option<NonceHash>,
Expand Down
2 changes: 1 addition & 1 deletion common/src/queries/epochs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pub enum EpochsStateQuery {
GetPreviousEpochs { epoch_number: u64 },
GetEpochStakeDistribution { epoch_number: u64 },
GetEpochStakeDistributionByPool { epoch_number: u64 },
GetLatestEpochBlocksMintedByPool { vrf_key_hash: KeyHash },
GetLatestEpochBlocksMintedByPool { spo_id: KeyHash },
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
Expand Down
4 changes: 4 additions & 0 deletions modules/accounts_state/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,17 @@ anyhow = { workspace = true }
bigdecimal = "0.4.8"
chrono = { workspace = true }
config = { workspace = true }
dashmap = { workspace = true }
hex = { workspace = true }
imbl = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
serde_with = { workspace = true }
tokio = { workspace = true }
tracing = { workspace = true }
rayon = "1.10.0"
csv = "1.3.1"
itertools = "0.14.0"

[lib]
path = "src/accounts_state.rs"
35 changes: 28 additions & 7 deletions modules/accounts_state/NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ ORDER BY pool_id_hex;

## Specific SPO test

Epoch 212 (spendable in 213), SPO 30c6319d1f680..., rewards actually given out:
Epoch 211 (spendable in 213), SPO 30c6319d1f680..., rewards actually given out:

```sql
SELECT
Expand All @@ -94,24 +94,45 @@ AND encode(ph.hash_raw, 'hex') LIKE '30c6319d1f680%'
GROUP BY ph.hash_raw;
```

Note: pool_id for this SPO is 93

| pool_id_hex | member_rewards | leader_rewards |
|----------------------------------------------------------|----------------|----------------|
| 30c6319d1f680470c8d2d48f8d44fd2848fa9b8cd6ac944d4dfc0c54 | 33869550293 | 2164196243 |
| 30c6319d1f680470c8d2d48f8d44fd2848fa9b8cd6ac944d4dfc0c54 | 32024424770 | 2067130351 |

Total 34091555121

We have

```
2025-08-21T13:59:50.578627Z INFO acropolis_module_accounts_state::rewards: Pool 30c6319d1f680470c8d2d48f8d44fd2848fa9b8cd6ac944d4dfc0c54 blocks=1 pool_stake=44180895641393 relative_pool_stake=0.001392062719472796345022132114111547444335115561171699064775592918376184270138741760710696148952284469 relative_blocks=0.0005022601707684580612757408337518834756403817177297840281265695630336514314414866901054746358613761929 pool_performance=1 optimum_rewards=34113076193 pool_rewards=34113076193
2025-08-26T10:49:39.003335Z INFO acropolis_module_accounts_state::rewards: Pool 30c6319d1f680470c8d2d48f8d44fd2848fa9b8cd6ac944d4dfc0c54 blocks=0 pool_stake=44180895641393 relative_pool_stake=0.001392062719472796345022132114111547444335115561171699064775592918376184270
138741760710696148952284469 relative_blocks=0 pool_performance=1 optimum_rewards=34091555158 pool_rewards=34091555158
```

Optimum rewards: 34113076193
Optimum rewards: 34091555158

Difference: We are too high by 37 LL compared to DBSync - suspect rounding of individual payments
We match the maxP from the Haskell node:

```
**** Calculating PoolRewardInfo: epoch=0, rewardInfo=PoolRewardInfo {poolRelativeStake = StakeShare (44180895641393 % 31737719158318701), poolPot = Coin 34091555158, poolPs = PoolParams {ppId = KeyHash {unKeyHash = "30c6319d1f680470c8d2d48f8d44fd2848fa9b8cd6ac944d4dfc0c54"}, ppVrf = VRFVerKeyHash {unVRFVerKeyHash = "f2b08e8ec5fe945b41ece1c254e25843e35e574dd43535cbf244524019f704e9"}, ppPledge = Coin 50000000000, ppCost = Coin 340000000, ppMargin = 1 % 20, ppRewardAccount = RewardAccount {raNetwork = Mainnet, raCredential = KeyHashObj (KeyHash {unKeyHash = "8a10720c17ce32b75f489ed13fb706dac51c6006b7fee1a687f36620"})}, ppOwners = fromList [KeyHash {unKeyHash = "8a10720c17ce32b75f489ed13fb706dac51c6006b7fee1a687f36620"}], ppRelays = StrictSeq {fromStrict = fromList [SingleHostName (SJust (Port {portToWord16 = 3001})) (DnsName {dnsToText = "europe1-relay.jpn-sp.net"})]}, ppMetadata = SJust (PoolMetadata {pmUrl = Url {urlToText = "https://tokyostaker.com/metadata/jp3.json"}, pmHash = "\201\246\183K\128\&1 \EOT*\f\194\GS>B\168\136j\239\241\&4\189\230\175\SI4\163\160P\206\162\163]"})}, poolBlocks = 1, poolLeaderReward = LeaderOnlyReward {lRewardPool = KeyHash {unKeyHash = "30c6319d1f680470c8d2d48f8d44fd2848fa9b8cd6ac944d4dfc0c54"}, lRewardAmount = Coin 2067130351}}, activeStake=Coin 10177811974822904, totalStake=Coin 31737719158318701, pledgeRelative=50000000000 % 31737719158318701, sigmaA=44180895641393 % 10177811974822904, maxP=34091555158, appPerf=1 % 1, R=Coin 31834688329017****
```

Difference: We are too high by 21521072, or 0.06%
## ADA pots data from DBSync

Input into this in epoch 212 is:
First 10 epochs in ada_pots:

```
Calculating rewards: epoch=212 total_supply=31737719158318701 stake_rewards=31854784667376
id | slot_no | epoch_no | treasury | reserves | rewards | utxo | deposits_stake | fees | block_id | deposits_drep | deposits_proposal
-----+-----------+----------+------------------+-------------------+-----------------+-------------------+----------------+--------------+----------+---------------+-------------------
1 | 4924800 | 209 | 8332813711755 | 13286160713028443 | 593536826186446 | 31111517964861148 | 441012000000 | 10670212208 | 4512244 | 0 | 0
2 | 5356800 | 210 | 16306644182013 | 13278197552770393 | 277915861250199 | 31427038405450971 | 533870000000 | 7666346424 | 4533814 | 0 | 0
3 | 5788800 | 211 | 24275595982960 | 13270236767315870 | 164918966125973 | 31539966264042924 | 594636000000 | 7770532273 | 4555361 | 0 | 0
4 | 6220800 | 212 | 32239292149804 | 13262280841681299 | 147882943225525 | 31556964153057144 | 626252000000 | 6517886228 | 4576676 | 0 | 0
5 | 6652800 | 213 | 40198464232058 | 13247093198353459 | 133110645284460 | 31578940375911744 | 651738000000 | 5578218279 | 4597956 | 0 | 0
6 | 7084800 | 214 | 48148335794725 | 13230232787944838 | 121337581585558 | 31599599756081623 | 674438000000 | 7100593256 | 4619398 | 0 | 0
7 | 7516800 | 215 | 55876297807656 | 13212986170770203 | 117660526059600 | 31612774463528795 | 695040000000 | 7501833746 | 4640850 | 0 | 0
8 | 7948807 | 216 | 63707722011028 | 13195031638588164 | 122159720478561 | 31618386634872973 | 706174000000 | 8110049274 | 4662422 | 0 | 0
9 | 8380800 | 217 | 71629614335572 | 13176528835451373 | 127730158329564 | 31623386398075064 | 719058000000 | 5935808427 | 4683639 | 0 | 0
10 | 8812800 | 218 | 79429791062499 | 13157936081322000 | 134680552513121 | 31627219255406326 | 729244000000 | 5075696054 | 4704367 | 0 | 0
```
62 changes: 62 additions & 0 deletions modules/accounts_state/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# AccountsState module

This is the module which does the majority of the work in calculating monetary change
(reserves, treasury) and rewards

## Notes on verification

The module has an inbuilt 'Verifier' which can compare against CSV files dumped from
DBSync.

### Pots verification

Verifying the 'pots' values (reserves, treasury, deposits) is a good overall marker of
successful calculation since everything (including rewards) feeds into it.

To create a pots verification file, export the `ada_pots` table as CSV
from Postgres on a DBSync database:

```sql
\COPY (
SELECT epoch_no AS epoch, reserves, treasury, deposits_stake AS deposits
FROM ada_pots
ORDER BY epoch_no
) TO 'pots.mainnet.csv' WITH CSV HEADER
```

Then configure this as (e.g.)

```toml
[module.accounts-state]
verify-pots-file = "../../modules/accounts_state/test-data/pots.mainnet.csv"
```

This is the default, since the pots file is small. It will be updated periodically.

### Rewards verification

The verifier can also compare the rewards paid to members (delegators) and leader (pool)
against a capture from the DBSync `rewards` table. We name the files for the epoch *earned*,
which is one less than when we calculate it.

To create a rewards CSV file in Postgres on a DBSync database:

```sql
\COPY (
select encode(ph.hash_raw, 'hex') as spo, encode(a.hash_raw, 'hex') as address,
r.type, r.amount
from reward r
join pool_hash ph on r.pool_id = ph.id
join stake_address a on a.id = r.addr_id
where r.earned_epoch=211 and r.amount > 0
) to 'rewards.mainnet.211.csv' with csv header
```

To configure verification, provide a path template which takes the epoch number:

```toml
[module.accounts-state]
verify-rewards-files = "../../modules/accounts_state/test-data/rewards.mainnet.{}.csv"
```

The verifier will only verify epochs where this file exists.
Loading