Skip to content

Commit 3621f2c

Browse files
feat: sparse pub rand generation (#110)
## Description <!-- Brief description of changes --> ## Checklist - [x] I have updated the [docs/SPEC.md](https://github.com/babylonlabs-io/rollup-bsn-contracts/blob/main/docs/SPEC.md) file if this change affects the specification - [x] I have updated the schema by running `cargo gen-schema` --------- Co-authored-by: Runchao Han <[email protected]>
1 parent b303f3e commit 3621f2c

14 files changed

+380
-103
lines changed

CHANGELOG.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
3939

4040
### State and API breaking
4141

42-
* [#82](https://github.com/babylonlabs-io/rollup-bsn-contracts/pull/108) core:
43-
add allow-list to instantiate arg
42+
* [#110](https://github.com/babylonlabs-io/rollup-bsn-contracts/pull/110) feat: sparse pub rand generation
4443
* [#97](https://github.com/babylonlabs-io/rollup-bsn-contracts/pull/97) feat:
45-
versioning of FP allowlist
44+
* [#82](https://github.com/babylonlabs-io/rollup-bsn-contracts/pull/108) core: add allow-list to instantiate arg
4645
* [#100](https://github.com/babylonlabs-io/rollup-bsn-contracts/pull/100) chore:
4746
add query to fetch highest voted height
4847

contracts/finality/schema/finality.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -837,6 +837,7 @@
837837
"required": [
838838
"babylon_epoch",
839839
"commitment",
840+
"interval",
840841
"num_pub_rand",
841842
"start_height"
842843
],
@@ -856,6 +857,12 @@
856857
"minimum": 0.0
857858
}
858859
},
860+
"interval": {
861+
"description": "The interval of the commitment between each two consecutive pub rand values The pub rand values will be in height `start_height`, `start_height + interval`, `start_height + 2 * interval`, ...",
862+
"type": "integer",
863+
"format": "uint64",
864+
"minimum": 0.0
865+
},
859866
"num_pub_rand": {
860867
"description": "The amount of committed public randomness",
861868
"type": "integer",
@@ -901,6 +908,7 @@
901908
"required": [
902909
"babylon_epoch",
903910
"commitment",
911+
"interval",
904912
"num_pub_rand",
905913
"start_height"
906914
],
@@ -920,6 +928,12 @@
920928
"minimum": 0.0
921929
}
922930
},
931+
"interval": {
932+
"description": "The interval of the commitment between each two consecutive pub rand values The pub rand values will be in height `start_height`, `start_height + interval`, `start_height + 2 * interval`, ...",
933+
"type": "integer",
934+
"format": "uint64",
935+
"minimum": 0.0
936+
},
923937
"num_pub_rand": {
924938
"description": "The amount of committed public randomness",
925939
"type": "integer",
@@ -951,6 +965,7 @@
951965
"required": [
952966
"babylon_epoch",
953967
"commitment",
968+
"interval",
954969
"num_pub_rand",
955970
"start_height"
956971
],
@@ -970,6 +985,12 @@
970985
"minimum": 0.0
971986
}
972987
},
988+
"interval": {
989+
"description": "The interval of the commitment between each two consecutive pub rand values The pub rand values will be in height `start_height`, `start_height + interval`, `start_height + 2 * interval`, ...",
990+
"type": "integer",
991+
"format": "uint64",
992+
"minimum": 0.0
993+
},
973994
"num_pub_rand": {
974995
"description": "The amount of committed public randomness",
975996
"type": "integer",
@@ -1005,6 +1026,7 @@
10051026
"required": [
10061027
"babylon_epoch",
10071028
"commitment",
1029+
"interval",
10081030
"num_pub_rand",
10091031
"start_height"
10101032
],
@@ -1024,6 +1046,12 @@
10241046
"minimum": 0.0
10251047
}
10261048
},
1049+
"interval": {
1050+
"description": "The interval of the commitment between each two consecutive pub rand values The pub rand values will be in height `start_height`, `start_height + interval`, `start_height + 2 * interval`, ...",
1051+
"type": "integer",
1052+
"format": "uint64",
1053+
"minimum": 0.0
1054+
},
10271055
"num_pub_rand": {
10281056
"description": "The amount of committed public randomness",
10291057
"type": "integer",

contracts/finality/schema/raw/response_to_first_pub_rand_commit.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"required": [
1717
"babylon_epoch",
1818
"commitment",
19+
"interval",
1920
"num_pub_rand",
2021
"start_height"
2122
],
@@ -35,6 +36,12 @@
3536
"minimum": 0.0
3637
}
3738
},
39+
"interval": {
40+
"description": "The interval of the commitment between each two consecutive pub rand values The pub rand values will be in height `start_height`, `start_height + interval`, `start_height + 2 * interval`, ...",
41+
"type": "integer",
42+
"format": "uint64",
43+
"minimum": 0.0
44+
},
3845
"num_pub_rand": {
3946
"description": "The amount of committed public randomness",
4047
"type": "integer",

contracts/finality/schema/raw/response_to_last_pub_rand_commit.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"required": [
1717
"babylon_epoch",
1818
"commitment",
19+
"interval",
1920
"num_pub_rand",
2021
"start_height"
2122
],
@@ -35,6 +36,12 @@
3536
"minimum": 0.0
3637
}
3738
},
39+
"interval": {
40+
"description": "The interval of the commitment between each two consecutive pub rand values The pub rand values will be in height `start_height`, `start_height + interval`, `start_height + 2 * interval`, ...",
41+
"type": "integer",
42+
"format": "uint64",
43+
"minimum": 0.0
44+
},
3845
"num_pub_rand": {
3946
"description": "The amount of committed public randomness",
4047
"type": "integer",

contracts/finality/schema/raw/response_to_list_pub_rand_commit.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"required": [
1313
"babylon_epoch",
1414
"commitment",
15+
"interval",
1516
"num_pub_rand",
1617
"start_height"
1718
],
@@ -31,6 +32,12 @@
3132
"minimum": 0.0
3233
}
3334
},
35+
"interval": {
36+
"description": "The interval of the commitment between each two consecutive pub rand values The pub rand values will be in height `start_height`, `start_height + interval`, `start_height + 2 * interval`, ...",
37+
"type": "integer",
38+
"format": "uint64",
39+
"minimum": 0.0
40+
},
3441
"num_pub_rand": {
3542
"description": "The amount of committed public randomness",
3643
"type": "integer",

contracts/finality/schema/raw/response_to_pub_rand_commit_for_height.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"required": [
1717
"babylon_epoch",
1818
"commitment",
19+
"interval",
1920
"num_pub_rand",
2021
"start_height"
2122
],
@@ -35,6 +36,12 @@
3536
"minimum": 0.0
3637
}
3738
},
39+
"interval": {
40+
"description": "The interval of the commitment between each two consecutive pub rand values The pub rand values will be in height `start_height`, `start_height + interval`, `start_height + 2 * interval`, ...",
41+
"type": "integer",
42+
"format": "uint64",
43+
"minimum": 0.0
44+
},
3845
"num_pub_rand": {
3946
"description": "The amount of committed public randomness",
4047
"type": "integer",

contracts/finality/src/error.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,8 @@ pub enum ContractError {
9191
DuplicatedFinalitySig(String, u64),
9292
#[error("Too few public randomness values: given {given}, required minimum {required}")]
9393
TooFewPubRand { given: u64, required: u64 },
94-
#[error("Block height overflow: start height {start_height} is equal or higher than (start height + num pub rand) {end_height}")]
95-
OverflowInBlockHeight { start_height: u64, end_height: u64 },
94+
#[error("Block height overflow detected for start height {start_height}")]
95+
OverflowInBlockHeight { start_height: u64 },
9696
#[error("Invalid commitment length: expected {expected} bytes, got {actual}")]
9797
InvalidCommitmentLength { expected: usize, actual: usize },
9898
#[error("Invalid signature length: expected {expected} bytes, got {actual}")]

contracts/finality/src/exec/finality.rs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,10 @@ fn verify_finality_signature(
137137
signing_context: &str,
138138
signature: &[u8],
139139
) -> Result<(), ContractError> {
140-
let proof_height = pr_commit.start_height + proof.index;
140+
// For sparse generation: proof_height = start_height + index * interval
141+
// For consecutive generation (interval=1): proof_height = start_height + index * 1 = start_height + index
142+
let proof_height = pr_commit.start_height + proof.index * pr_commit.interval;
143+
141144
if block_height != proof_height {
142145
return Err(ContractError::InvalidFinalitySigHeight(
143146
proof_height,
@@ -289,6 +292,7 @@ pub(crate) mod tests {
289292
let pr_commit = PubRandCommit {
290293
start_height: pr_commit.start_height,
291294
num_pub_rand: pr_commit.num_pub_rand,
295+
interval: 1, // TODO: test with interval > 1
292296
babylon_epoch: current_epoch,
293297
commitment: pr_commit.commitment,
294298
};
@@ -298,9 +302,31 @@ pub(crate) mod tests {
298302
// This needs mock data from babylon_test_utils
299303
// https://github.com/babylonlabs-io/rollup-bsn-contracts/issues/66
300304
let context = "";
305+
// For test, we need to create a mock storage with proper config
306+
let mut deps = mock_deps_babylon();
307+
308+
// Set up a default config for testing (interval = 1 for consecutive generation)
309+
let config = crate::state::config::Config {
310+
bsn_id: "test-bsn".to_string(),
311+
min_pub_rand: 1,
312+
rate_limiting: crate::state::config::RateLimitingConfig {
313+
max_msgs_per_interval: 100,
314+
block_interval: 10,
315+
},
316+
bsn_activation_height: 0,
317+
// Consecutive generation for this test
318+
// TODO: test with interval > 1
319+
finality_signature_interval: 1,
320+
};
321+
crate::state::config::set_config(deps.as_mut().storage, &config).unwrap();
322+
323+
// Calculate block height using the same logic as verify_finality_signature
324+
let block_height = pr_commit.start_height
325+
+ proof.index.unsigned_abs() * config.finality_signature_interval;
326+
301327
let res = verify_finality_signature(
302328
&hex::decode(&pk_hex).unwrap(),
303-
pr_commit.start_height + proof.index.unsigned_abs(),
329+
block_height,
304330
&pub_rand_value,
305331
// we need to add a typecast below because the provided proof is of type
306332
// tendermint_proto::crypto::Proof, whereas the fn expects babylon_merkle::proof

contracts/finality/src/exec/public_randomness.rs

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::error::ContractError;
33
use crate::msg::BabylonMsg;
44
use crate::state::allowlist::ensure_fp_in_allowlist;
55
use crate::state::config::get_config;
6-
use crate::state::public_randomness::{insert_pub_rand_commit, PubRandCommit};
6+
use crate::state::public_randomness::{compute_end_height, insert_pub_rand_commit, PubRandCommit};
77
use crate::state::rate_limiting::check_rate_limit_and_accumulate;
88
use crate::utils::{get_fp_rand_commit_context_v0, query_finality_provider};
99
use babylon_bindings::BabylonQuery;
@@ -44,7 +44,13 @@ pub fn handle_public_randomness_commit(
4444
let config = get_config(deps.storage)?;
4545

4646
// Validate the commitment parameters
47-
validate_pub_rand_commit(start_height, num_pub_rand, commitment, config.min_pub_rand)?;
47+
validate_pub_rand_commit(
48+
start_height,
49+
num_pub_rand,
50+
commitment,
51+
config.min_pub_rand,
52+
config.finality_signature_interval,
53+
)?;
4854

4955
let fp_btc_pk = hex::decode(fp_btc_pk_hex)?;
5056

@@ -79,6 +85,7 @@ pub fn handle_public_randomness_commit(
7985
PubRandCommit {
8086
start_height,
8187
num_pub_rand,
88+
interval: config.finality_signature_interval,
8289
babylon_epoch: current_epoch,
8390
commitment: commitment.to_vec(),
8491
},
@@ -97,6 +104,7 @@ pub fn validate_pub_rand_commit(
97104
num_pub_rand: u64,
98105
commitment: &[u8],
99106
min_pub_rand: u64,
107+
finality_signature_interval: u64,
100108
) -> Result<(), ContractError> {
101109
// Check if commitment is exactly 32 bytes
102110
if commitment.len() != COMMITMENT_LENGTH_BYTES {
@@ -106,15 +114,9 @@ pub fn validate_pub_rand_commit(
106114
});
107115
}
108116

109-
// Check for overflow when doing (StartHeight + NumPubRand)
117+
// Check for overflow when computing the end height
110118
// To avoid public randomness reset
111-
let end_height = start_height.saturating_add(num_pub_rand);
112-
if start_height >= end_height {
113-
return Err(ContractError::OverflowInBlockHeight {
114-
start_height,
115-
end_height,
116-
});
117-
}
119+
compute_end_height(start_height, num_pub_rand, finality_signature_interval)?;
118120

119121
// Validate minimum public randomness requirement
120122
if num_pub_rand < min_pub_rand {
@@ -412,8 +414,8 @@ pub(crate) mod tests {
412414
fn test_overflow_protection_fails() {
413415
let mut deps = mock_deps_babylon();
414416

415-
// Configure the contract with random min_pub_rand
416-
let instantiate_msg = new_init_msg(get_random_u64());
417+
// Configure the contract with small min_pub_rand to avoid TooFewPubRand error
418+
let instantiate_msg = new_init_msg(1);
417419

418420
let info = message_info(&deps.api.addr_make(CREATOR), &[]);
419421
instantiate(deps.as_mut(), mock_env(), info, instantiate_msg).unwrap();
@@ -428,17 +430,16 @@ pub(crate) mod tests {
428430
deps.as_mut(),
429431
&mock_env(),
430432
&get_random_fp_pk_hex(),
431-
u64::MAX, // This will cause overflow when added to num_pub_rand
432-
1,
433+
u64::MAX - 10, // This will cause overflow when we add (num_pub_rand-1)*interval
434+
100, // Large enough to pass min_pub_rand but cause overflow
433435
&get_random_block_hash(),
434436
&random_signature,
435437
);
436438

437439
assert_eq!(
438440
result.unwrap_err(),
439441
ContractError::OverflowInBlockHeight {
440-
start_height: u64::MAX,
441-
end_height: u64::MAX // saturating_add results in MAX
442+
start_height: u64::MAX - 10
442443
}
443444
);
444445
}

0 commit comments

Comments
 (0)