Skip to content

Commit e80cf55

Browse files
authored
Merge pull request #235 from input-output-hk/prc/rewards-fix-2
Rewards update 2
2 parents 4607754 + a247f7f commit e80cf55

32 files changed

+105864
-514
lines changed

Cargo.lock

Lines changed: 36 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

common/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ async-trait = "0.1"
1818
bech32 = "0.11"
1919
bigdecimal = "0.4.8"
2020
bitmask-enum = "2.2"
21-
blake2 = "0.10"
2221
bs58 = "0.5"
2322
chrono = { workspace = true }
2423
gcd = "2.3"
@@ -37,6 +36,8 @@ num-traits = "0.2"
3736
imbl = { workspace = true }
3837
dashmap = { workspace = true }
3938
rayon = "1.11.0"
39+
cryptoxide = "0.5.1"
40+
blake2 = "0.10.6"
4041

4142
[lib]
4243
crate-type = ["rlib"]

common/src/address.rs

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -329,10 +329,7 @@ impl Address {
329329
#[cfg(test)]
330330
mod tests {
331331
use super::*;
332-
use blake2::{
333-
digest::{Update, VariableOutput},
334-
Blake2bVar,
335-
};
332+
use crate::crypto::keyhash_224;
336333

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

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

366360
// pubkey is the raw key - we need the Blake2B hash
367-
let mut hasher = Blake2bVar::new(28).unwrap();
368-
hasher.update(&pubkey);
369-
let mut hash = vec![0u8; 28];
370-
hasher.finalize_variable(&mut hash).unwrap();
361+
let hash = keyhash_224(&pubkey);
371362
assert_eq!(28, hash.len());
372363
hash
373364
}

common/src/crypto.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
//! Common cryptography helper functions for Acropolis
22
33
use crate::types::KeyHash;
4-
use blake2::{digest::consts::U32, Blake2b, Digest};
4+
use cryptoxide::hashing::blake2b::Blake2b;
55

66
/// Get a Blake2b-256 hash of a key
7-
pub fn keyhash(key: &[u8]) -> KeyHash {
8-
let mut hasher = Blake2b::<U32>::new();
9-
hasher.update(key);
10-
hasher.finalize().to_vec()
7+
pub fn keyhash_256(key: &[u8]) -> KeyHash {
8+
let mut context = Blake2b::<256>::new();
9+
context.update_mut(&key);
10+
context.finalize().to_vec()
11+
}
12+
13+
/// Get a Blake2b-224 hash of a key
14+
pub fn keyhash_224(key: &[u8]) -> KeyHash {
15+
let mut context = Blake2b::<224>::new();
16+
context.update_mut(&key);
17+
context.finalize().to_vec()
1118
}

common/src/ledger_state.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ pub struct SPOState {
3333
#[n(0)]
3434
pub pools: BTreeMap<KeyHash, PoolRegistration>,
3535
#[n(1)]
36+
pub updates: BTreeMap<KeyHash, PoolRegistration>,
37+
#[n(2)]
3638
pub retiring: BTreeMap<KeyHash, u64>,
3739
}
3840

common/src/messages.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -179,9 +179,8 @@ pub struct EpochActivityMessage {
179179
/// Total fees in this epoch
180180
pub total_fees: u64,
181181

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

186185
/// Nonce
187186
pub nonce: Option<NonceHash>,

common/src/queries/epochs.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ pub enum EpochsStateQuery {
1111
GetPreviousEpochs { epoch_number: u64 },
1212
GetEpochStakeDistribution { epoch_number: u64 },
1313
GetEpochStakeDistributionByPool { epoch_number: u64 },
14-
GetLatestEpochBlocksMintedByPool { vrf_key_hash: KeyHash },
14+
GetLatestEpochBlocksMintedByPool { spo_id: KeyHash },
1515
}
1616

1717
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]

modules/accounts_state/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,17 @@ anyhow = { workspace = true }
1717
bigdecimal = "0.4.8"
1818
chrono = { workspace = true }
1919
config = { workspace = true }
20+
dashmap = { workspace = true }
2021
hex = { workspace = true }
2122
imbl = { workspace = true }
2223
serde = { workspace = true }
2324
serde_json = { workspace = true }
2425
serde_with = { workspace = true }
2526
tokio = { workspace = true }
2627
tracing = { workspace = true }
28+
rayon = "1.10.0"
29+
csv = "1.3.1"
30+
itertools = "0.14.0"
2731

2832
[lib]
2933
path = "src/accounts_state.rs"

modules/accounts_state/NOTES.md

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ ORDER BY pool_id_hex;
8080

8181
## Specific SPO test
8282

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

8585
```sql
8686
SELECT
@@ -94,24 +94,45 @@ AND encode(ph.hash_raw, 'hex') LIKE '30c6319d1f680%'
9494
GROUP BY ph.hash_raw;
9595
```
9696

97+
Note: pool_id for this SPO is 93
98+
9799
| pool_id_hex | member_rewards | leader_rewards |
98100
|----------------------------------------------------------|----------------|----------------|
99-
| 30c6319d1f680470c8d2d48f8d44fd2848fa9b8cd6ac944d4dfc0c54 | 33869550293 | 2164196243 |
101+
| 30c6319d1f680470c8d2d48f8d44fd2848fa9b8cd6ac944d4dfc0c54 | 32024424770 | 2067130351 |
100102

101103
Total 34091555121
102104

103105
We have
104106

105107
```
106-
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
108+
2025-08-26T10:49:39.003335Z INFO acropolis_module_accounts_state::rewards: Pool 30c6319d1f680470c8d2d48f8d44fd2848fa9b8cd6ac944d4dfc0c54 blocks=0 pool_stake=44180895641393 relative_pool_stake=0.001392062719472796345022132114111547444335115561171699064775592918376184270
109+
138741760710696148952284469 relative_blocks=0 pool_performance=1 optimum_rewards=34091555158 pool_rewards=34091555158
107110
```
108111

109-
Optimum rewards: 34113076193
112+
Optimum rewards: 34091555158
113+
114+
Difference: We are too high by 37 LL compared to DBSync - suspect rounding of individual payments
115+
We match the maxP from the Haskell node:
116+
117+
```
118+
**** 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****
119+
```
110120

111-
Difference: We are too high by 21521072, or 0.06%
121+
## ADA pots data from DBSync
112122

113-
Input into this in epoch 212 is:
123+
First 10 epochs in ada_pots:
114124

115125
```
116-
Calculating rewards: epoch=212 total_supply=31737719158318701 stake_rewards=31854784667376
126+
id | slot_no | epoch_no | treasury | reserves | rewards | utxo | deposits_stake | fees | block_id | deposits_drep | deposits_proposal
127+
-----+-----------+----------+------------------+-------------------+-----------------+-------------------+----------------+--------------+----------+---------------+-------------------
128+
1 | 4924800 | 209 | 8332813711755 | 13286160713028443 | 593536826186446 | 31111517964861148 | 441012000000 | 10670212208 | 4512244 | 0 | 0
129+
2 | 5356800 | 210 | 16306644182013 | 13278197552770393 | 277915861250199 | 31427038405450971 | 533870000000 | 7666346424 | 4533814 | 0 | 0
130+
3 | 5788800 | 211 | 24275595982960 | 13270236767315870 | 164918966125973 | 31539966264042924 | 594636000000 | 7770532273 | 4555361 | 0 | 0
131+
4 | 6220800 | 212 | 32239292149804 | 13262280841681299 | 147882943225525 | 31556964153057144 | 626252000000 | 6517886228 | 4576676 | 0 | 0
132+
5 | 6652800 | 213 | 40198464232058 | 13247093198353459 | 133110645284460 | 31578940375911744 | 651738000000 | 5578218279 | 4597956 | 0 | 0
133+
6 | 7084800 | 214 | 48148335794725 | 13230232787944838 | 121337581585558 | 31599599756081623 | 674438000000 | 7100593256 | 4619398 | 0 | 0
134+
7 | 7516800 | 215 | 55876297807656 | 13212986170770203 | 117660526059600 | 31612774463528795 | 695040000000 | 7501833746 | 4640850 | 0 | 0
135+
8 | 7948807 | 216 | 63707722011028 | 13195031638588164 | 122159720478561 | 31618386634872973 | 706174000000 | 8110049274 | 4662422 | 0 | 0
136+
9 | 8380800 | 217 | 71629614335572 | 13176528835451373 | 127730158329564 | 31623386398075064 | 719058000000 | 5935808427 | 4683639 | 0 | 0
137+
10 | 8812800 | 218 | 79429791062499 | 13157936081322000 | 134680552513121 | 31627219255406326 | 729244000000 | 5075696054 | 4704367 | 0 | 0
117138
```

modules/accounts_state/README.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# AccountsState module
2+
3+
This is the module which does the majority of the work in calculating monetary change
4+
(reserves, treasury) and rewards
5+
6+
## Notes on verification
7+
8+
The module has an inbuilt 'Verifier' which can compare against CSV files dumped from
9+
DBSync.
10+
11+
### Pots verification
12+
13+
Verifying the 'pots' values (reserves, treasury, deposits) is a good overall marker of
14+
successful calculation since everything (including rewards) feeds into it.
15+
16+
To create a pots verification file, export the `ada_pots` table as CSV
17+
from Postgres on a DBSync database:
18+
19+
```sql
20+
\COPY (
21+
SELECT epoch_no AS epoch, reserves, treasury, deposits_stake AS deposits
22+
FROM ada_pots
23+
ORDER BY epoch_no
24+
) TO 'pots.mainnet.csv' WITH CSV HEADER
25+
```
26+
27+
Then configure this as (e.g.)
28+
29+
```toml
30+
[module.accounts-state]
31+
verify-pots-file = "../../modules/accounts_state/test-data/pots.mainnet.csv"
32+
```
33+
34+
This is the default, since the pots file is small. It will be updated periodically.
35+
36+
### Rewards verification
37+
38+
The verifier can also compare the rewards paid to members (delegators) and leader (pool)
39+
against a capture from the DBSync `rewards` table. We name the files for the epoch *earned*,
40+
which is one less than when we calculate it.
41+
42+
To create a rewards CSV file in Postgres on a DBSync database:
43+
44+
```sql
45+
\COPY (
46+
select encode(ph.hash_raw, 'hex') as spo, encode(a.hash_raw, 'hex') as address,
47+
r.type, r.amount
48+
from reward r
49+
join pool_hash ph on r.pool_id = ph.id
50+
join stake_address a on a.id = r.addr_id
51+
where r.earned_epoch=211 and r.amount > 0
52+
) to 'rewards.mainnet.211.csv' with csv header
53+
```
54+
55+
To configure verification, provide a path template which takes the epoch number:
56+
57+
```toml
58+
[module.accounts-state]
59+
verify-rewards-files = "../../modules/accounts_state/test-data/rewards.mainnet.{}.csv"
60+
```
61+
62+
The verifier will only verify epochs where this file exists.

0 commit comments

Comments
 (0)