Skip to content

Commit 2c4d4e2

Browse files
committed
Create BLS accumulator benchmartks with UTxO
1 parent dcee4ac commit 2c4d4e2

File tree

3 files changed

+284
-0
lines changed

3 files changed

+284
-0
lines changed

hydra-tx/bench/accumulator/Main.hs

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
{-# LANGUAGE DuplicateRecordFields #-}
2+
3+
-- | Benchmark suite for the BLS accumulator implementation.
4+
--
5+
-- This suite measures the performance of accumulator operations with realistic
6+
-- UTxO sets to understand the performance implications of using accumulators
7+
-- for snapshot signing and partial fanout.
8+
module Main where
9+
10+
import Hydra.Prelude
11+
12+
import Cardano.Api.UTxO qualified as UTxO
13+
import Codec.Serialise (serialise)
14+
import Criterion.Main (bench, bgroup, defaultMain, nf, nfIO, whnf, whnfIO)
15+
import Hydra.Cardano.Api (Tx, UTxO)
16+
import Hydra.Tx.Accumulator (
17+
buildFromUTxO,
18+
createMembershipProof,
19+
createMembershipProofFromUTxO,
20+
defaultCRS,
21+
generateCRS,
22+
getAccumulatorHash,
23+
unHydraAccumulator,
24+
)
25+
import Hydra.Tx.IsTx (IsTx (..))
26+
import Test.Hydra.Tx.Gen (genUTxOAdaOnlyOfSize)
27+
import Test.QuickCheck (generate)
28+
29+
main :: IO ()
30+
main = do
31+
putTextLn "=== Accumulator Benchmark Suite ==="
32+
putTextLn "Generating test data..."
33+
34+
-- Generate UTxO sets of various sizes
35+
utxo10 <- generateUTxO 10
36+
utxo50 <- generateUTxO 50
37+
utxo100 <- generateUTxO 100
38+
utxo500 <- generateUTxO 500
39+
utxo1000 <- generateUTxO 1000
40+
utxo5000 <- generateUTxO 5000
41+
utxo10000 <- generateUTxO 10000
42+
43+
putTextLn $ "Generated UTxO sets: 10, 50, 100, 500, 1000, 5000, 10000"
44+
45+
-- Pre-build accumulators for membership proof tests
46+
let acc10 = buildFromUTxO @Tx utxo10
47+
acc50 = buildFromUTxO @Tx utxo50
48+
acc100 = buildFromUTxO @Tx utxo100
49+
acc500 = buildFromUTxO @Tx utxo500
50+
acc1000 = buildFromUTxO @Tx utxo1000
51+
acc5000 = buildFromUTxO @Tx utxo5000
52+
acc10000 = buildFromUTxO @Tx utxo10000
53+
54+
putTextLn "Pre-built accumulators"
55+
56+
-- Generate subsets for membership proofs
57+
-- Testing realistic scenarios: proving 10-20% of UTxOs
58+
subset5_from50 <- generateSubset utxo50 5
59+
subset10_from100 <- generateSubset utxo100 10
60+
subset50_from500 <- generateSubset utxo500 50
61+
subset100_from1000 <- generateSubset utxo1000 100
62+
subset500_from5000 <- generateSubset utxo5000 500
63+
subset1000_from10000 <- generateSubset utxo10000 1000
64+
65+
putTextLn "Generated subsets for membership proofs"
66+
67+
-- Extract individual elements for low-level proof testing
68+
let elements10 = toPairList @Tx utxo10
69+
elements100 = toPairList @Tx utxo100
70+
serialized10 = utxoToElement @Tx <$> elements10
71+
serialized100 = utxoToElement @Tx <$> elements100
72+
73+
putTextLn "Starting benchmarks..."
74+
putTextLn ""
75+
76+
defaultMain
77+
[ bgroup
78+
"1. Build Accumulator from UTxO"
79+
[ bench "10 UTxOs" $ whnf (buildFromUTxO @Tx) utxo10
80+
, bench "50 UTxOs" $ whnf (buildFromUTxO @Tx) utxo50
81+
, bench "100 UTxOs" $ whnf (buildFromUTxO @Tx) utxo100
82+
, bench "500 UTxOs" $ whnf (buildFromUTxO @Tx) utxo500
83+
, bench "1000 UTxOs" $ whnf (buildFromUTxO @Tx) utxo1000
84+
, bench "5000 UTxOs" $ whnf (buildFromUTxO @Tx) utxo5000
85+
, bench "10000 UTxOs" $ whnf (buildFromUTxO @Tx) utxo10000
86+
]
87+
, bgroup
88+
"2. UTxO to Elements Conversion"
89+
[ bench "Extract 10 TxOuts" $ whnf (toPairList @Tx) utxo10
90+
, bench "Extract 100 TxOuts" $ whnf (toPairList @Tx) utxo100
91+
, bench "Extract 1000 TxOuts" $ whnf (toPairList @Tx) utxo1000
92+
, bench "Serialize 10 TxOuts" $ whnf (fmap (utxoToElement @Tx)) elements10
93+
, bench "Serialize 100 TxOuts" $ whnf (fmap (utxoToElement @Tx)) elements100
94+
]
95+
, bgroup
96+
"3. Create Membership Proofs"
97+
[ bench "5 from 50" $ whnfIO $ createMembershipProofFromUTxO @Tx subset5_from50 acc50 defaultCRS
98+
, bench "10 from 100" $ whnfIO $ createMembershipProofFromUTxO @Tx subset10_from100 acc100 defaultCRS
99+
, bench "50 from 500" $ whnfIO $ createMembershipProofFromUTxO @Tx subset50_from500 acc500 defaultCRS
100+
, bench "100 from 1000" $ whnfIO $ createMembershipProofFromUTxO @Tx subset100_from1000 acc1000 defaultCRS
101+
, bench "500 from 5000" $ whnfIO $ createMembershipProofFromUTxO @Tx subset500_from5000 acc5000 defaultCRS
102+
, bench "1000 from 10000" $ whnfIO $ createMembershipProofFromUTxO @Tx subset1000_from10000 acc10000 defaultCRS
103+
]
104+
, bgroup
105+
"4. Create Membership Proofs (Low-level)"
106+
[ bench "5 elements from 10" $ whnfIO $ createMembershipProof (take 5 serialized10) acc10 defaultCRS
107+
, bench "10 elements from 100" $ whnfIO $ createMembershipProof (take 10 serialized100) acc100 defaultCRS
108+
, bench "50 elements from 100" $ whnfIO $ createMembershipProof (take 50 serialized100) acc100 defaultCRS
109+
]
110+
, bgroup
111+
"5. Accumulator Hashing"
112+
[ bench "Hash 10 UTxOs" $ nf getAccumulatorHash acc10
113+
, bench "Hash 100 UTxOs" $ nf getAccumulatorHash acc100
114+
, bench "Hash 1000 UTxOs" $ nf getAccumulatorHash acc1000
115+
, bench "Hash 10000 UTxOs" $ nf getAccumulatorHash acc10000
116+
]
117+
, bgroup
118+
"6. Accumulator Serialization"
119+
[ bench "Serialize accumulator (10)" $ nf (serialise . unHydraAccumulator) acc10
120+
, bench "Serialize accumulator (100)" $ nf (serialise . unHydraAccumulator) acc100
121+
, bench "Serialize accumulator (1000)" $ nf (serialise . unHydraAccumulator) acc1000
122+
, bench "Serialize accumulator (10000)" $ nf (serialise . unHydraAccumulator) acc10000
123+
]
124+
, bgroup
125+
"7. CRS Generation"
126+
[ bench "CRS size 10" $ whnf generateCRS 10
127+
, bench "CRS size 100" $ whnf generateCRS 100
128+
, bench "CRS size 1000" $ whnf generateCRS 1000
129+
, bench "CRS size 5000" $ whnf generateCRS 5000
130+
, bench "CRS size 10000" $ whnf generateCRS 10000
131+
]
132+
, bgroup
133+
"8. End-to-End Snapshot Simulation"
134+
[ bench "Full cycle: 100 UTxOs" $ nfIO (fullSnapshotCycle utxo100)
135+
, bench "Full cycle: 1000 UTxOs" $ nfIO (fullSnapshotCycle utxo1000)
136+
, bench "Partial fanout: 100 from 1000" $ nfIO (partialFanoutCycle utxo1000 subset100_from1000)
137+
]
138+
]
139+
140+
-- | Generate a UTxO set of specified size with realistic transaction outputs.
141+
generateUTxO :: Int -> IO UTxO
142+
generateUTxO n = generate $ genUTxOAdaOnlyOfSize n
143+
144+
-- | Generate a subset of a given UTxO.
145+
-- This simulates selecting UTxOs for partial fanout.
146+
generateSubset :: UTxO -> Int -> IO UTxO
147+
generateSubset utxo n = do
148+
let allPairs = UTxO.toList utxo
149+
if n >= length allPairs
150+
then pure utxo
151+
else do
152+
let subsetPairs = take n allPairs
153+
pure $ UTxO.fromList subsetPairs
154+
155+
-- | Simulate the full snapshot creation cycle:
156+
-- 1. Build accumulator from UTxO
157+
-- 2. Hash the accumulator
158+
-- 3. Serialize for signing
159+
fullSnapshotCycle :: UTxO -> IO ByteString
160+
fullSnapshotCycle utxo = do
161+
let accumulator = buildFromUTxO @Tx utxo
162+
hash = getAccumulatorHash accumulator
163+
pure hash
164+
165+
-- | Simulate a partial fanout operation:
166+
-- 1. Build accumulator from full UTxO
167+
-- 2. Create membership proof for subset
168+
-- 3. Return the proof
169+
partialFanoutCycle :: UTxO -> UTxO -> IO Text
170+
partialFanoutCycle fullUtxo subsetUtxo = do
171+
let accumulator = buildFromUTxO @Tx fullUtxo
172+
createMembershipProofFromUTxO @Tx subsetUtxo accumulator defaultCRS
173+
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# Accumulator Benchmark Suite
2+
3+
This benchmark suite measures the performance characteristics of the BLS accumulator implementation for the Hydra protocol, specifically for snapshot signing.
4+
5+
## Running the Benchmarks
6+
7+
### Basic Run
8+
9+
```bash
10+
cabal bench accumulator-bench
11+
```
12+
13+
### With Memory Profiling
14+
15+
```bash
16+
cabal bench accumulator-bench --benchmark-options '+RTS -T'
17+
```
18+
19+
This will show peak memory allocation in the output.
20+
21+
### Generate JSON Report
22+
23+
```bash
24+
cabal bench accumulator-bench --benchmark-options '--json results.json'
25+
```
26+
27+
Then analyze with Python (results.json is a single-line array):
28+
```bash
29+
python3 << 'EOF'
30+
import json
31+
with open('results.json') as f:
32+
data = json.load(f)
33+
for item in data[2]: # results are in data[2]
34+
if isinstance(item, dict) and 'Build Accumulator' in item.get('reportName', ''):
35+
print(f"{item['reportName']}: {item['reportAnalysis']['anMean']['estPoint']*1000:.2f} ms")
36+
EOF
37+
```
38+
39+
40+
### Detailed Output
41+
42+
```bash
43+
cabal bench accumulator-bench --benchmark-options '-v'
44+
```
45+
46+
## Benchmark Groups
47+
48+
### 1. Build Accumulator from UTxO
49+
Measures the time to build an accumulator from different UTxO sizes (10 to 10,000).
50+
51+
**Key Metric**: Should be < 100ms for 1000 UTxOs to be viable for snapshot signing.
52+
53+
### 2. UTxO to Elements Conversion
54+
Measures the overhead of extracting and serializing TxOuts for accumulator elements.
55+
56+
### 3. Create Membership Proofs
57+
Measures the time to create cryptographic proofs that a subset of UTxOs exists in the full set.
58+
59+
**Key Metric**: Critical for partial fanout performance.
60+
61+
### 4. Create Membership Proofs (Low-level)
62+
Direct measurement of proof generation from pre-serialized elements.
63+
64+
### 5. Accumulator Hashing
65+
Measures the time to compute the hash of an accumulator for snapshot signing.
66+
67+
### 6. Accumulator Serialization
68+
Measures serialization overhead and size.
69+
70+
### 7. CRS Generation
71+
Measures the one-time cost of generating the Common Reference String.
72+
73+
**Note**: This is typically done once at startup, not in the critical path.
74+
75+
### 8. End-to-End Snapshot Simulation
76+
Simulates complete workflows:
77+
- Full snapshot cycle (build + hash + serialize)
78+
- Partial fanout cycle (build + prove)
79+
80+
## Advanced Usage
81+
82+
### Profile Specific Sizes
83+
84+
Focus on realistic production sizes:
85+
```bash
86+
cabal bench accumulator-bench --benchmark-options '-m pattern "1000"'
87+
```
88+
89+
### Generate Plots
90+
91+
Using criterion's built-in plotting (requires gnuplot):
92+
```bash
93+
cabal bench accumulator-bench --benchmark-options '--output benchmark.html'
94+
```

hydra-tx/hydra-tx.cabal

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,23 @@ test-suite tests
213213
build-tool-depends: hspec-discover:hspec-discover
214214
ghc-options: -threaded -rtsopts
215215

216+
benchmark accumulator-bench
217+
import: project-config
218+
hs-source-dirs: bench/accumulator
219+
main-is: Main.hs
220+
type: exitcode-stdio-1.0
221+
build-depends:
222+
, cardano-api
223+
, criterion
224+
, hydra-cardano-api
225+
, hydra-prelude
226+
, hydra-tx
227+
, hydra-tx:testlib
228+
, QuickCheck
229+
, serialise
230+
231+
ghc-options: -threaded -rtsopts -with-rtsopts=-N
232+
216233
executable hydra-tx
217234
import: project-config
218235
hs-source-dirs: exe

0 commit comments

Comments
 (0)