Skip to content

Commit 1fa463b

Browse files
authored
implement basic bitmap for validator overlap (#46)
* implement basic bitmap for validator overlap * fmt * cut cycle count by ~20% * fmt * remove redundant code * maybe this is a better name
1 parent c1bf204 commit 1fa463b

File tree

6 files changed

+119
-16
lines changed

6 files changed

+119
-16
lines changed

contracts/artifacts/Blobstream0.json

+64-14
Large diffs are not rendered by default.

contracts/src/Blobstream0.sol

+9
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ contract Blobstream0 is IDAOracle, Ownable2Step {
4040
/// NOTE: Matches existing Blobstream contract, for ease of integration.
4141
event HeadUpdate(uint64 blockNumber, bytes32 headerHash);
4242

43+
/// @notice Validator bitmap of the intersection of validators that signed off on both the
44+
/// trusted block and the new header. This event is emitted to allow for slashing equivocations.
45+
/// NOTE: This event matches existing Blobstream contracts, for ease of integration.
46+
/// @param trustedBlock The trusted block of the block range.
47+
/// @param targetBlock The target block of the block range.
48+
/// @param validatorBitmap The validator bitmap for the block range.
49+
event ValidatorBitmapEquivocation(uint64 trustedBlock, uint64 targetBlock, uint256 validatorBitmap);
50+
4351
/// @notice Target height for next batch was below the current height.
4452
error InvalidTargetHeight();
4553

@@ -113,6 +121,7 @@ contract Blobstream0 is IDAOracle, Ownable2Step {
113121
verifier.verify(_seal, imageId, sha256(_commitBytes));
114122

115123
emit DataCommitmentStored(proofNonce, latestHeight, commit.newHeight, commit.merkleRoot);
124+
emit ValidatorBitmapEquivocation(latestHeight, commit.newHeight, commit.validatorBitmap);
116125

117126
// Update latest block in state
118127
latestHeight = commit.newHeight;

contracts/src/ImageID.sol

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,5 @@ pragma solidity ^0.8.20;
2020

2121
library ImageID {
2222
bytes32 public constant LIGHT_CLIENT_GUEST_ID =
23-
bytes32(0xce4bdb306e686a0d2a412c0ddb392d1dd3b07842a28c828519dc44052daf5319);
23+
bytes32(0x7c4d4662f341e9147ab7b60c4dedce832d331f65df62ee43b09abdff5d8283e3);
2424
}

contracts/src/RangeCommitment.sol

+1
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@ struct RangeCommitment {
2424
uint64 newHeight;
2525
bytes32 newHeaderHash;
2626
bytes32 merkleRoot;
27+
uint256 validatorBitmap;
2728
}

light-client-guest/guest/src/main.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
use alloy_sol_types::SolValue;
1616
use blobstream0_primitives::proto::{TrustedLightBlock, UntrustedLightBlock};
17-
use blobstream0_primitives::RangeCommitment;
17+
use blobstream0_primitives::{generate_bitmap, RangeCommitment};
1818
use blobstream0_primitives::{build_merkle_root, expect_block_hash, light_client_verify};
1919
use risc0_zkvm::guest::env;
2020
use tendermint_light_client_verifier::{types::Header, Verdict};
@@ -47,6 +47,10 @@ fn main() {
4747
// Assert all bytes have been read, as a sanity check.
4848
assert!(cursor.is_empty());
4949

50+
51+
// Generate validator bitmap of intersection of trusted and untrusted block signatures.
52+
let validator_bitmap = generate_bitmap(&trusted_block, &untrusted_block);
53+
5054
// Build merkle root, while also verifying hash links between all blocks.
5155
let merkle_root = build_merkle_root(&trusted_block, &interval_headers, &untrusted_block);
5256

@@ -64,6 +68,7 @@ fn main() {
6468
newHeight: untrusted_block.signed_header.header.height.value(),
6569
newHeaderHash: expect_block_hash(untrusted_block.signed_header.header()).into(),
6670
merkleRoot: merkle_root.into(),
71+
validatorBitmap: validator_bitmap.into(),
6772
};
6873
env::commit_slice(commit.abi_encode().as_slice());
6974
}

primitives/src/lib.rs

+38
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@ use alloy_primitives::U256;
1717
use alloy_sol_types::SolValue;
1818
use proto::{TrustedLightBlock, UntrustedLightBlock};
1919
use sha2::Sha256;
20+
use std::collections::BTreeSet;
2021
use std::iter;
2122
use std::time::Duration;
23+
use tendermint::account::Id;
24+
use tendermint::block::signed_header::SignedHeader;
2225
use tendermint::merkle::simple_hash_from_byte_vectors;
2326
use tendermint::Hash;
2427
use tendermint_light_client_verifier::types::{Header, TrustThreshold};
@@ -142,6 +145,41 @@ pub fn light_client_verify(
142145
)
143146
}
144147

148+
fn commitment_signature_addresses(signed_header: &SignedHeader) -> BTreeSet<Id> {
149+
signed_header
150+
.commit
151+
.signatures
152+
.iter()
153+
.filter_map(|sig| sig.is_commit().then(|| sig.validator_address().unwrap()))
154+
.collect()
155+
}
156+
157+
/// Generates a bitmap of the validators that have signed both headers. The ordering is based on the
158+
/// trusted block's validator set ordering.
159+
pub fn generate_bitmap(
160+
trusted_block: &TrustedLightBlock,
161+
untrusted_block: &UntrustedLightBlock,
162+
) -> U256 {
163+
// Create sets of validator addresses that have signed each block
164+
let trusted_validators = commitment_signature_addresses(&trusted_block.signed_header);
165+
let untrusted_validators = commitment_signature_addresses(&untrusted_block.signed_header);
166+
167+
// Construct the validator bitmap.
168+
trusted_block
169+
.next_validators
170+
.validators()
171+
.iter()
172+
.enumerate()
173+
.fold(U256::ZERO, |mut bitmap, (i, validator)| {
174+
if trusted_validators.contains(&validator.address)
175+
&& untrusted_validators.contains(&validator.address)
176+
{
177+
bitmap.set_bit(i, true);
178+
}
179+
bitmap
180+
})
181+
}
182+
145183
/// Convenience function to pull the block hash data, assuming a Sha256 hash.
146184
pub fn expect_block_hash(block: &Header) -> [u8; 32] {
147185
let Hash::Sha256(hash) = block.hash() else {

0 commit comments

Comments
 (0)