Skip to content

Commit

Permalink
Merge pull request #10 from opentensor/release/0.1.0
Browse files Browse the repository at this point in the history
Release/0.1.0
  • Loading branch information
ibraheem-opentensor authored Dec 12, 2024
2 parents 85d3c12 + b0b7592 commit ac0ce8e
Show file tree
Hide file tree
Showing 5 changed files with 284 additions and 26 deletions.
60 changes: 60 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
version: 2.1

orbs:
python: circleci/python@2.1.1

jobs:
ruff:
resource_class: small
parameters:
python-version:
type: string
docker:
- image: cimg/python:<< parameters.python-version >>
steps:
- checkout
- run:
name: Install Ruff
command: pip install ruff
- run:
name: Run Ruff
command: ruff check .

build-and-test:
resource_class: medium
parallelism: 2
parameters:
python-version:
type: string
docker:
- image: cimg/python:<< parameters.python-version >>
steps:
- checkout
- run:
name: Set Up Virtual Environment
command: |
curl https://sh.rustup.rs -sSf | sh -s -- -y
. "$HOME/.cargo/env"
python -m venv .venv
. .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install '.[dev]'
- run:
name: Run Tests
command: |
. .venv/bin/activate
pytest tests/
- store_test_results:
path: test-results
- store_artifacts:
path: test-results

workflows:
test-and-lint:
jobs:
- ruff:
python-version: "3.9.13"
- build-and-test:
matrix:
parameters:
python-version: ["3.9", "3.10", "3.11", "3.12"]
8 changes: 8 additions & 0 deletions CHANGELOG.MD
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## v0.1.0 /2024-12-12

## What's Changed
* fix reveal_round calculation edge cases by @JohnReedV in https://github.com/opentensor/bittensor-commit-reveal/pull/8
* Adds circleci test config by @ibraheem-opentensor in https://github.com/opentensor/bittensor-commit-reveal/pull/9

**Full Changelog**: https://github.com/opentensor/bittensor-commit-reveal/compare/v0.1.0a1...v0.1.0

## v0.1.0a1 /2024-12-03

## What's Changed
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "bittensor-commit-reveal"
version = "0.1.0a1"
version = "0.1.0"
description = ""
readme = "README.md"
license = {file = "LICENSE"}
Expand Down
43 changes: 18 additions & 25 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,14 @@ async fn generate_commit(
block_time: u64,
) -> Result<(Vec<u8>, u64), (std::io::Error, String)> {
// Steps comes from here https://github.com/opentensor/subtensor/pull/982/files#diff-7261bf1c7f19fc66a74c1c644ec2b4b277a341609710132fb9cd5f622350a6f5R120-R131
// 1 Instantiate payload
// Instantiate payload
let payload = WeightsTlockPayload {
uids,
values,
version_key,
};

// 2 Serialize payload
let serialized_payload = payload.encode();

// Calculate reveal_round
// all of 3 variables are constants for drand quicknet
let period = 3;
let genesis_time = 1692803367;
let public_key = "83cf0f2896adee7eb8b5f01fcad3912212c437e0073e911fb90022d3e760183c8c4b450b6a0a6c3ac6a5776a2d1064510d1fec758c921cc22b0e17e63aaf4bcb5ed66304de9cf809bd274ca73bab4af5a6e9c76a4bc09e76eae8991ef5ece45a";
Expand All @@ -54,31 +50,29 @@ async fn generate_commit(
.unwrap()
.as_secs();

// Compute the current epoch index
let tempo_plus_one = tempo + 1;
let netuid_plus_one = (netuid as u64) + 1;
let block_with_offset = current_block + netuid_plus_one;
let current_epoch = block_with_offset / tempo_plus_one;

// Compute the reveal epoch
let reveal_epoch = current_epoch + subnet_reveal_period_epochs;

// Compute the block number when the reveal epoch starts
let reveal_block_number = reveal_epoch * tempo_plus_one - netuid_plus_one;

// Compute the number of blocks until the reveal epoch
let blocks_until_reveal = reveal_block_number.saturating_sub(current_block);

// Compute the time until the reveal in seconds
let time_until_reveal = blocks_until_reveal * block_time;
// Calculate reveal epoch and ensure enough time for SUBTENSOR_PULSE_DELAY pulses
let mut reveal_epoch = current_epoch + subnet_reveal_period_epochs;
let mut reveal_block_number = reveal_epoch * tempo_plus_one - netuid_plus_one;
let mut blocks_until_reveal = reveal_block_number.saturating_sub(current_block);
let mut time_until_reveal = blocks_until_reveal * block_time;

// Ensure at least SUBTENSOR_PULSE_DELAY * period seconds lead time
while time_until_reveal < SUBTENSOR_PULSE_DELAY * period {
reveal_epoch += 1;
reveal_block_number = reveal_epoch * tempo_plus_one - netuid_plus_one;
blocks_until_reveal = reveal_block_number.saturating_sub(current_block);
time_until_reveal = blocks_until_reveal * block_time;
}

// Compute the reveal time in seconds since UNIX_EPOCH
let reveal_time = now + time_until_reveal;

// Compute the reveal round, ensuring we round up
let reveal_round = ((reveal_time - genesis_time + period - 1) / period) - SUBTENSOR_PULSE_DELAY;

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

// 4 Create identity
// Create identity from reveal_round
let message = {
let mut hasher = sha2::Sha256::new();
hasher.update(reveal_round.to_be_bytes());
hasher.finalize().to_vec()
};
let identity = Identity::new(b"", vec![message]);

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

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

// 7. Return result
Ok((ct_bytes, reveal_round))
}

Expand Down
197 changes: 197 additions & 0 deletions src/tests/test_commit_reveal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
import pytest
import time
from bittensor_commit_reveal import get_encrypted_commit

SUBTENSOR_PULSE_DELAY = 24
PERIOD = 3 # Drand period in seconds
GENESIS_TIME = 1692803367


def test_get_encrypted_commits():
uids = [1, 2]
weights = [11, 22]
version_key = 50
tempo = 100
current_block = 1000
netuid = 1
reveal_period = 2
block_time = 12

start_time = int(time.time())
ct_pybytes, reveal_round = get_encrypted_commit(
uids,
weights,
version_key,
tempo,
current_block,
netuid,
reveal_period,
block_time,
)

# Basic checks
assert (
ct_pybytes is not None and len(ct_pybytes) > 0
), "Ciphertext should not be empty"
assert reveal_round > 0, "Reveal round should be positive"

expected_reveal_round, _, _ = compute_expected_reveal_round(
start_time, tempo, current_block, netuid, reveal_period, block_time
)

# The reveal_round should be close to what we predict
assert (
abs(reveal_round - expected_reveal_round) <= 1
), f"Reveal round {reveal_round} not close to expected {expected_reveal_round}"


def test_generate_commit_success():
uids = [1, 2, 3]
values = [10, 20, 30]
version_key = 42
tempo = 50
current_block = 500
netuid = 100
subnet_reveal_period_epochs = 2
block_time = 12

start_time = int(time.time())
ct_pybytes, reveal_round = get_encrypted_commit(
uids,
values,
version_key,
tempo,
current_block,
netuid,
subnet_reveal_period_epochs,
block_time,
)

assert (
ct_pybytes is not None and len(ct_pybytes) > 0
), "Ciphertext should not be empty"
assert reveal_round > 0, "Reveal round should be positive"

expected_reveal_round, expected_reveal_time, time_until_reveal = (
compute_expected_reveal_round(
start_time,
tempo,
current_block,
netuid,
subnet_reveal_period_epochs,
block_time,
)
)

assert (
abs(reveal_round - expected_reveal_round) <= 1
), f"Reveal round {reveal_round} differs from expected {expected_reveal_round}"

required_lead_time = SUBTENSOR_PULSE_DELAY * PERIOD
computed_reveal_time = (
GENESIS_TIME + (reveal_round + SUBTENSOR_PULSE_DELAY) * PERIOD
)
assert computed_reveal_time - start_time >= required_lead_time, (
"Not enough lead time before reveal. "
f"computed_reveal_time={computed_reveal_time}, start_time={start_time}, required={required_lead_time}"
)

assert (
time_until_reveal >= SUBTENSOR_PULSE_DELAY * PERIOD
), f"time_until_reveal {time_until_reveal} is less than required {SUBTENSOR_PULSE_DELAY * PERIOD}"


@pytest.mark.asyncio
async def test_generate_commit_various_tempos():
NETUID = 1
CURRENT_BLOCK = 100_000
SUBNET_REVEAL_PERIOD_EPOCHS = 1
BLOCK_TIME = 6
TEMPOS = [10, 50, 100, 250, 360, 500, 750, 1000]

uids = [0]
values = [100]
version_key = 1

for tempo in TEMPOS:
start_time = int(time.time())

ct_pybytes, reveal_round = get_encrypted_commit(
uids,
values,
version_key,
tempo,
CURRENT_BLOCK,
NETUID,
SUBNET_REVEAL_PERIOD_EPOCHS,
BLOCK_TIME,
)

assert len(ct_pybytes) > 0, f"Ciphertext is empty for tempo {tempo}"
assert reveal_round > 0, f"Reveal round is zero or negative for tempo {tempo}"

expected_reveal_round, _, time_until_reveal = compute_expected_reveal_round(
start_time,
tempo,
CURRENT_BLOCK,
NETUID,
SUBNET_REVEAL_PERIOD_EPOCHS,
BLOCK_TIME,
)

assert (
abs(reveal_round - expected_reveal_round) <= 1
), f"Tempo {tempo}: reveal_round {reveal_round} not close to expected {expected_reveal_round}"

computed_reveal_time = (
GENESIS_TIME + (reveal_round + SUBTENSOR_PULSE_DELAY) * PERIOD
)
required_lead_time = SUBTENSOR_PULSE_DELAY * PERIOD

assert computed_reveal_time - start_time >= required_lead_time, (
f"Tempo {tempo}: Not enough lead time: reveal_time={computed_reveal_time}, "
f"start_time={start_time}, required={required_lead_time}"
)

assert (
time_until_reveal >= SUBTENSOR_PULSE_DELAY * PERIOD
), f"Tempo {tempo}: time_until_reveal {time_until_reveal} is less than required {SUBTENSOR_PULSE_DELAY * PERIOD}"


def compute_expected_reveal_round(
now: int,
tempo: int,
current_block: int,
netuid: int,
subnet_reveal_period_epochs: int,
block_time: int,
):
tempo_plus_one = tempo + 1
netuid_plus_one = netuid + 1
block_with_offset = current_block + netuid_plus_one
current_epoch = block_with_offset // tempo_plus_one

# Initial guess for reveal_epoch
reveal_epoch = current_epoch + subnet_reveal_period_epochs
reveal_block_number = reveal_epoch * tempo_plus_one - netuid_plus_one

# Compute blocks_until_reveal, ensure non-negative
blocks_until_reveal = reveal_block_number - current_block
if blocks_until_reveal < 0:
blocks_until_reveal = 0
time_until_reveal = blocks_until_reveal * block_time

# Adjust until we have enough lead time (at least SUBTENSOR_PULSE_DELAY pulses * period seconds)
while time_until_reveal < SUBTENSOR_PULSE_DELAY * PERIOD:
reveal_epoch += 1
reveal_block_number = reveal_epoch * tempo_plus_one - netuid_plus_one
blocks_until_reveal = reveal_block_number - current_block
if blocks_until_reveal < 0:
blocks_until_reveal = 0
time_until_reveal = blocks_until_reveal * block_time

reveal_time = now + time_until_reveal
reveal_round = (
(reveal_time - GENESIS_TIME + PERIOD - 1) // PERIOD
) - SUBTENSOR_PULSE_DELAY
return reveal_round, reveal_time, time_until_reveal

0 comments on commit ac0ce8e

Please sign in to comment.