Skip to content

Commit 8c6fcf5

Browse files
authoredDec 12, 2024
Merge pull request #8 from opentensor/spiigot/fix-reveal-round-exact-epoch
fix reveal_round calculation edge cases
2 parents 0e87d13 + d8bfff8 commit 8c6fcf5

File tree

3 files changed

+231
-25
lines changed

3 files changed

+231
-25
lines changed
 

‎CHANGELOG.MD

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Changelog
2+
3+
## v0.1.0a1 /2024-12-03
4+
5+
## What's Changed
6+
* feat/roman/without-tlock-network-client by @roman-opentensor in https://github.com/opentensor/bittensor-commit-reveal/pull/1
7+
* improve reveal round calculation by @JohnReedV in https://github.com/opentensor/bittensor-commit-reveal/pull/2
8+
* Release/add workflow by @ibraheem-opentensor in https://github.com/opentensor/bittensor-commit-reveal/pull/3
9+
* Updates the readme file by @ibraheem-opentensor in https://github.com/opentensor/bittensor-commit-reveal/pull/5
10+
11+
## New Contributors
12+
* @roman-opentensor made their first contribution in https://github.com/opentensor/bittensor-commit-reveal/pull/1
13+
* @JohnReedV made their first contribution in https://github.com/opentensor/bittensor-commit-reveal/pull/2
14+
* @ibraheem-opentensor made their first contribution in https://github.com/opentensor/bittensor-commit-reveal/pull/3
15+
16+
**Full Changelog**: https://github.com/opentensor/bittensor-commit-reveal/commits/v0.1.0a1

‎src/lib.rs

+18-25
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,14 @@ async fn generate_commit(
3333
block_time: u64,
3434
) -> Result<(Vec<u8>, u64), (std::io::Error, String)> {
3535
// Steps comes from here https://github.com/opentensor/subtensor/pull/982/files#diff-7261bf1c7f19fc66a74c1c644ec2b4b277a341609710132fb9cd5f622350a6f5R120-R131
36-
// 1 Instantiate payload
36+
// Instantiate payload
3737
let payload = WeightsTlockPayload {
3838
uids,
3939
values,
4040
version_key,
4141
};
42-
43-
// 2 Serialize payload
4442
let serialized_payload = payload.encode();
4543

46-
// Calculate reveal_round
47-
// all of 3 variables are constants for drand quicknet
4844
let period = 3;
4945
let genesis_time = 1692803367;
5046
let public_key = "83cf0f2896adee7eb8b5f01fcad3912212c437e0073e911fb90022d3e760183c8c4b450b6a0a6c3ac6a5776a2d1064510d1fec758c921cc22b0e17e63aaf4bcb5ed66304de9cf809bd274ca73bab4af5a6e9c76a4bc09e76eae8991ef5ece45a";
@@ -54,31 +50,29 @@ async fn generate_commit(
5450
.unwrap()
5551
.as_secs();
5652

57-
// Compute the current epoch index
5853
let tempo_plus_one = tempo + 1;
5954
let netuid_plus_one = (netuid as u64) + 1;
6055
let block_with_offset = current_block + netuid_plus_one;
6156
let current_epoch = block_with_offset / tempo_plus_one;
6257

63-
// Compute the reveal epoch
64-
let reveal_epoch = current_epoch + subnet_reveal_period_epochs;
65-
66-
// Compute the block number when the reveal epoch starts
67-
let reveal_block_number = reveal_epoch * tempo_plus_one - netuid_plus_one;
68-
69-
// Compute the number of blocks until the reveal epoch
70-
let blocks_until_reveal = reveal_block_number.saturating_sub(current_block);
71-
72-
// Compute the time until the reveal in seconds
73-
let time_until_reveal = blocks_until_reveal * block_time;
58+
// Calculate reveal epoch and ensure enough time for SUBTENSOR_PULSE_DELAY pulses
59+
let mut reveal_epoch = current_epoch + subnet_reveal_period_epochs;
60+
let mut reveal_block_number = reveal_epoch * tempo_plus_one - netuid_plus_one;
61+
let mut blocks_until_reveal = reveal_block_number.saturating_sub(current_block);
62+
let mut time_until_reveal = blocks_until_reveal * block_time;
63+
64+
// Ensure at least SUBTENSOR_PULSE_DELAY * period seconds lead time
65+
while time_until_reveal < SUBTENSOR_PULSE_DELAY * period {
66+
reveal_epoch += 1;
67+
reveal_block_number = reveal_epoch * tempo_plus_one - netuid_plus_one;
68+
blocks_until_reveal = reveal_block_number.saturating_sub(current_block);
69+
time_until_reveal = blocks_until_reveal * block_time;
70+
}
7471

75-
// Compute the reveal time in seconds since UNIX_EPOCH
7672
let reveal_time = now + time_until_reveal;
77-
78-
// Compute the reveal round, ensuring we round up
7973
let reveal_round = ((reveal_time - genesis_time + period - 1) / period) - SUBTENSOR_PULSE_DELAY;
8074

81-
// 3. Deserialize the public key
75+
// Deserialize public key
8276
let pub_key_bytes = hex::decode(public_key).map_err(|e| {
8377
(
8478
std::io::Error::new(std::io::ErrorKind::InvalidData, format!("{:?}", e)),
@@ -94,15 +88,15 @@ async fn generate_commit(
9488
)
9589
})?;
9690

97-
// 4 Create identity
91+
// Create identity from reveal_round
9892
let message = {
9993
let mut hasher = sha2::Sha256::new();
10094
hasher.update(reveal_round.to_be_bytes());
10195
hasher.finalize().to_vec()
10296
};
10397
let identity = Identity::new(b"", vec![message]);
10498

105-
// 5. Encryption via tle with t-lock under the hood
99+
// Encrypt payload
106100
let esk = [2; 32];
107101
let ct = tle::<TinyBLS381, AESGCMStreamCipherProvider, OsRng>(
108102
pub_key,
@@ -118,7 +112,7 @@ async fn generate_commit(
118112
)
119113
})?;
120114

121-
// 6. Compress ct
115+
// Compress ciphertext
122116
let mut ct_bytes: Vec<u8> = Vec::new();
123117
ct.serialize_compressed(&mut ct_bytes).map_err(|e| {
124118
(
@@ -127,7 +121,6 @@ async fn generate_commit(
127121
)
128122
})?;
129123

130-
// 7. Return result
131124
Ok((ct_bytes, reveal_round))
132125
}
133126

‎src/tests/test_commit_reveal.py

+197
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
import pytest
2+
import time
3+
from bittensor_commit_reveal import get_encrypted_commit
4+
5+
SUBTENSOR_PULSE_DELAY = 24
6+
PERIOD = 3 # Drand period in seconds
7+
GENESIS_TIME = 1692803367
8+
9+
10+
def test_get_encrypted_commits():
11+
uids = [1, 2]
12+
weights = [11, 22]
13+
version_key = 50
14+
tempo = 100
15+
current_block = 1000
16+
netuid = 1
17+
reveal_period = 2
18+
block_time = 12
19+
20+
start_time = int(time.time())
21+
ct_pybytes, reveal_round = get_encrypted_commit(
22+
uids,
23+
weights,
24+
version_key,
25+
tempo,
26+
current_block,
27+
netuid,
28+
reveal_period,
29+
block_time,
30+
)
31+
32+
# Basic checks
33+
assert (
34+
ct_pybytes is not None and len(ct_pybytes) > 0
35+
), "Ciphertext should not be empty"
36+
assert reveal_round > 0, "Reveal round should be positive"
37+
38+
expected_reveal_round, _, _ = compute_expected_reveal_round(
39+
start_time, tempo, current_block, netuid, reveal_period, block_time
40+
)
41+
42+
# The reveal_round should be close to what we predict
43+
assert (
44+
abs(reveal_round - expected_reveal_round) <= 1
45+
), f"Reveal round {reveal_round} not close to expected {expected_reveal_round}"
46+
47+
48+
def test_generate_commit_success():
49+
uids = [1, 2, 3]
50+
values = [10, 20, 30]
51+
version_key = 42
52+
tempo = 50
53+
current_block = 500
54+
netuid = 100
55+
subnet_reveal_period_epochs = 2
56+
block_time = 12
57+
58+
start_time = int(time.time())
59+
ct_pybytes, reveal_round = get_encrypted_commit(
60+
uids,
61+
values,
62+
version_key,
63+
tempo,
64+
current_block,
65+
netuid,
66+
subnet_reveal_period_epochs,
67+
block_time,
68+
)
69+
70+
assert (
71+
ct_pybytes is not None and len(ct_pybytes) > 0
72+
), "Ciphertext should not be empty"
73+
assert reveal_round > 0, "Reveal round should be positive"
74+
75+
expected_reveal_round, expected_reveal_time, time_until_reveal = (
76+
compute_expected_reveal_round(
77+
start_time,
78+
tempo,
79+
current_block,
80+
netuid,
81+
subnet_reveal_period_epochs,
82+
block_time,
83+
)
84+
)
85+
86+
assert (
87+
abs(reveal_round - expected_reveal_round) <= 1
88+
), f"Reveal round {reveal_round} differs from expected {expected_reveal_round}"
89+
90+
required_lead_time = SUBTENSOR_PULSE_DELAY * PERIOD
91+
computed_reveal_time = (
92+
GENESIS_TIME + (reveal_round + SUBTENSOR_PULSE_DELAY) * PERIOD
93+
)
94+
assert computed_reveal_time - start_time >= required_lead_time, (
95+
"Not enough lead time before reveal. "
96+
f"computed_reveal_time={computed_reveal_time}, start_time={start_time}, required={required_lead_time}"
97+
)
98+
99+
assert (
100+
time_until_reveal >= SUBTENSOR_PULSE_DELAY * PERIOD
101+
), f"time_until_reveal {time_until_reveal} is less than required {SUBTENSOR_PULSE_DELAY * PERIOD}"
102+
103+
104+
@pytest.mark.asyncio
105+
async def test_generate_commit_various_tempos():
106+
NETUID = 1
107+
CURRENT_BLOCK = 100_000
108+
SUBNET_REVEAL_PERIOD_EPOCHS = 1
109+
BLOCK_TIME = 6
110+
TEMPOS = [10, 50, 100, 250, 360, 500, 750, 1000]
111+
112+
uids = [0]
113+
values = [100]
114+
version_key = 1
115+
116+
for tempo in TEMPOS:
117+
start_time = int(time.time())
118+
119+
ct_pybytes, reveal_round = get_encrypted_commit(
120+
uids,
121+
values,
122+
version_key,
123+
tempo,
124+
CURRENT_BLOCK,
125+
NETUID,
126+
SUBNET_REVEAL_PERIOD_EPOCHS,
127+
BLOCK_TIME,
128+
)
129+
130+
assert len(ct_pybytes) > 0, f"Ciphertext is empty for tempo {tempo}"
131+
assert reveal_round > 0, f"Reveal round is zero or negative for tempo {tempo}"
132+
133+
expected_reveal_round, _, time_until_reveal = compute_expected_reveal_round(
134+
start_time,
135+
tempo,
136+
CURRENT_BLOCK,
137+
NETUID,
138+
SUBNET_REVEAL_PERIOD_EPOCHS,
139+
BLOCK_TIME,
140+
)
141+
142+
assert (
143+
abs(reveal_round - expected_reveal_round) <= 1
144+
), f"Tempo {tempo}: reveal_round {reveal_round} not close to expected {expected_reveal_round}"
145+
146+
computed_reveal_time = (
147+
GENESIS_TIME + (reveal_round + SUBTENSOR_PULSE_DELAY) * PERIOD
148+
)
149+
required_lead_time = SUBTENSOR_PULSE_DELAY * PERIOD
150+
151+
assert computed_reveal_time - start_time >= required_lead_time, (
152+
f"Tempo {tempo}: Not enough lead time: reveal_time={computed_reveal_time}, "
153+
f"start_time={start_time}, required={required_lead_time}"
154+
)
155+
156+
assert (
157+
time_until_reveal >= SUBTENSOR_PULSE_DELAY * PERIOD
158+
), f"Tempo {tempo}: time_until_reveal {time_until_reveal} is less than required {SUBTENSOR_PULSE_DELAY * PERIOD}"
159+
160+
161+
def compute_expected_reveal_round(
162+
now: int,
163+
tempo: int,
164+
current_block: int,
165+
netuid: int,
166+
subnet_reveal_period_epochs: int,
167+
block_time: int,
168+
):
169+
tempo_plus_one = tempo + 1
170+
netuid_plus_one = netuid + 1
171+
block_with_offset = current_block + netuid_plus_one
172+
current_epoch = block_with_offset // tempo_plus_one
173+
174+
# Initial guess for reveal_epoch
175+
reveal_epoch = current_epoch + subnet_reveal_period_epochs
176+
reveal_block_number = reveal_epoch * tempo_plus_one - netuid_plus_one
177+
178+
# Compute blocks_until_reveal, ensure non-negative
179+
blocks_until_reveal = reveal_block_number - current_block
180+
if blocks_until_reveal < 0:
181+
blocks_until_reveal = 0
182+
time_until_reveal = blocks_until_reveal * block_time
183+
184+
# Adjust until we have enough lead time (at least SUBTENSOR_PULSE_DELAY pulses * period seconds)
185+
while time_until_reveal < SUBTENSOR_PULSE_DELAY * PERIOD:
186+
reveal_epoch += 1
187+
reveal_block_number = reveal_epoch * tempo_plus_one - netuid_plus_one
188+
blocks_until_reveal = reveal_block_number - current_block
189+
if blocks_until_reveal < 0:
190+
blocks_until_reveal = 0
191+
time_until_reveal = blocks_until_reveal * block_time
192+
193+
reveal_time = now + time_until_reveal
194+
reveal_round = (
195+
(reveal_time - GENESIS_TIME + PERIOD - 1) // PERIOD
196+
) - SUBTENSOR_PULSE_DELAY
197+
return reveal_round, reveal_time, time_until_reveal

0 commit comments

Comments
 (0)