Module 0xb::committee
- Struct
BlocklistValidatorEvent
- Struct
BridgeCommittee
- Struct
CommitteeUpdateEvent
- Struct
CommitteeMemberUrlUpdateEvent
- Struct
CommitteeMember
- Struct
CommitteeMemberRegistration
- Constants
- Function
verify_signatures
- Function
create
- Function
register
- Function
try_create_next_committee
- Function
execute_blocklist
- Function
committee_members
- Function
update_node_url
- Function
check_uniqueness_bridge_keys
use 0x1::option;
use 0x1::vector;
use 0x2::ecdsa_k1;
use 0x2::event;
use 0x2::tx_context;
use 0x2::vec_map;
use 0x2::vec_set;
use 0x3::sui_system;
use 0xb::crypto;
use 0xb::message;
Struct BlocklistValidatorEvent
struct BlocklistValidatorEvent has copy, drop
Struct BridgeCommittee
struct BridgeCommittee has store
Click to open
Fields
- members: vec_map::VecMap<vector<u8>, committee::CommitteeMember>
- member_registrations: vec_map::VecMap<address, committee::CommitteeMemberRegistration>
- last_committee_update_epoch: u64
Struct CommitteeUpdateEvent
struct CommitteeUpdateEvent has copy, drop
Click to open
Fields
- members: vec_map::VecMap<vector<u8>, committee::CommitteeMember>
- stake_participation_percentage: u64
Struct CommitteeMemberUrlUpdateEvent
struct CommitteeMemberUrlUpdateEvent has copy, drop
Struct CommitteeMember
struct CommitteeMember has copy, drop, store
Click to open
Fields
- sui_address: address
- The Sui Address of the validator
- bridge_pubkey_bytes: vector<u8>
- The public key bytes of the bridge key
- voting_power: u64
- Voting power, values are voting power in the scale of 10000.
- http_rest_url: vector<u8>
- The HTTP REST URL the member's node listens to it looks like b'https://127.0.0.1:9191'
- blocklisted: bool
- If this member is blocklisted
Struct CommitteeMemberRegistration
struct CommitteeMemberRegistration has copy, drop, store
Click to open
Constants
const ENotSystemAddress: u64 = 3;
const EInvalidSignature: u64 = 2;
const ECDSA_COMPRESSED_PUBKEY_LENGTH: u64 = 33;
const ECommitteeAlreadyInitiated: u64 = 7;
const EDuplicatePubkey: u64 = 8;
const EDuplicatedSignature: u64 = 1;
const EInvalidPubkeyLength: u64 = 6;
const ESenderIsNotInBridgeCommittee: u64 = 9;
const ESenderNotActiveValidator: u64 = 5;
const ESignatureBelowThreshold: u64 = 0;
const EValidatorBlocklistContainsUnknownKey: u64 = 4;
const SUI_MESSAGE_PREFIX: vector<u8> = [83, 85, 73, 95, 66, 82, 73, 68, 71, 69, 95, 77, 69, 83, 83, 65, 71, 69];
Function verify_signatures
public fun verify_signatures(self: &committee::BridgeCommittee, message: message::BridgeMessage, signatures: vector<vector<u8>>)
Click to open
Implementation
public fun verify_signatures(
self: &BridgeCommittee,
message: BridgeMessage,
signatures: vector<vector<u8>>,
) {
let (mut i, signature_counts) = (0, vector::length(&signatures));
let mut seen_pub_key = vec_set::empty<vector<u8>>();
let required_voting_power = message.required_voting_power();
// add prefix to the message bytes
let mut message_bytes = SUI_MESSAGE_PREFIX;
message_bytes.append(message.serialize_message());
let mut threshold = 0;
while (i < signature_counts) {
let pubkey = ecdsa_k1::secp256k1_ecrecover(&signatures[i], &message_bytes, 0);
// check duplicate
// and make sure pub key is part of the committee
assert!(!seen_pub_key.contains(&pubkey), EDuplicatedSignature);
assert!(self.members.contains(&pubkey), EInvalidSignature);
// get committee signature weight and check pubkey is part of the committee
let member = &self.members[&pubkey];
if (!member.blocklisted) {
threshold = threshold + member.voting_power;
};
seen_pub_key.insert(pubkey);
i = i + 1;
};
assert!(threshold >= required_voting_power, ESignatureBelowThreshold);
}
Function create
public(friend) fun create(ctx: &tx_context::TxContext): committee::BridgeCommittee
Click to open
Implementation
public(package) fun create(ctx: &TxContext): BridgeCommittee {
assert!(tx_context::sender(ctx) == @0x0, ENotSystemAddress);
BridgeCommittee {
members: vec_map::empty(),
member_registrations: vec_map::empty(),
last_committee_update_epoch: 0,
}
}
Function register
public(friend) fun register(self: &mut committee::BridgeCommittee, system_state: &mut sui_system::SuiSystemState, bridge_pubkey_bytes: vector<u8>, http_rest_url: vector<u8>, ctx: &tx_context::TxContext)
Click to open
Implementation
public(package) fun register(
self: &mut BridgeCommittee,
system_state: &mut SuiSystemState,
bridge_pubkey_bytes: vector<u8>,
http_rest_url: vector<u8>,
ctx: &TxContext
) {
// We disallow registration after committee initiated in v1
assert!(self.members.is_empty(), ECommitteeAlreadyInitiated);
// Ensure pubkey is valid
assert!(bridge_pubkey_bytes.length() == ECDSA_COMPRESSED_PUBKEY_LENGTH, EInvalidPubkeyLength);
// sender must be the same sender that created the validator object, this is to prevent DDoS from non-validator actor.
let sender = ctx.sender();
let validators = system_state.active_validator_addresses();
assert!(validators.contains(&sender), ESenderNotActiveValidator);
// Sender is active validator, record the registration
// In case validator need to update the info
let registration = if (self.member_registrations.contains(&sender)) {
let registration = &mut self.member_registrations[&sender];
registration.http_rest_url = http_rest_url;
registration.bridge_pubkey_bytes = bridge_pubkey_bytes;
*registration
} else {
let registration = CommitteeMemberRegistration {
sui_address: sender,
bridge_pubkey_bytes,
http_rest_url,
};
self.member_registrations.insert(sender, registration);
registration
};
// check uniqueness of the bridge pubkey.
// `try_create_next_committee` will abort if bridge_pubkey_bytes are not unique and
// that will fail the end of epoch transaction (possibly "forever", well, we
// need to deploy proper validator changes to stop end of epoch from failing).
check_uniqueness_bridge_keys(self, bridge_pubkey_bytes);
emit(registration)
}
Function try_create_next_committee
public(friend) fun try_create_next_committee(self: &mut committee::BridgeCommittee, active_validator_voting_power: vec_map::VecMap<address, u64>, min_stake_participation_percentage: u64, ctx: &tx_context::TxContext)
Click to open
Implementation
public(package) fun try_create_next_committee(
self: &mut BridgeCommittee,
active_validator_voting_power: VecMap<address, u64>,
min_stake_participation_percentage: u64,
ctx: &TxContext
) {
let mut i = 0;
let mut new_members = vec_map::empty();
let mut stake_participation_percentage = 0;
while (i < self.member_registrations.size()) {
// retrieve registration
let (_, registration) = self.member_registrations.get_entry_by_idx(i);
// Find validator stake amount from system state
// Process registration if it's active validator
let voting_power = active_validator_voting_power.try_get(®istration.sui_address);
if (voting_power.is_some()) {
let voting_power = voting_power.destroy_some();
stake_participation_percentage = stake_participation_percentage + voting_power;
let member = CommitteeMember {
sui_address: registration.sui_address,
bridge_pubkey_bytes: registration.bridge_pubkey_bytes,
voting_power: (voting_power as u64),
http_rest_url: registration.http_rest_url,
blocklisted: false,
};
new_members.insert(registration.bridge_pubkey_bytes, member)
};
i = i + 1;
};
// Make sure the new committee represent enough stakes, percentage are accurate to 2DP
if (stake_participation_percentage >= min_stake_participation_percentage) {
// Clear registrations
self.member_registrations = vec_map::empty();
// Store new committee info
self.members = new_members;
self.last_committee_update_epoch = ctx.epoch();
emit(CommitteeUpdateEvent {
members: new_members,
stake_participation_percentage
})
}
}
Function execute_blocklist
public(friend) fun execute_blocklist(self: &mut committee::BridgeCommittee, blocklist: message::Blocklist)
Click to open
Implementation
public(package) fun execute_blocklist(self: &mut BridgeCommittee, blocklist: Blocklist) {
let blocklisted = blocklist.blocklist_type() != 1;
let eth_addresses = blocklist.blocklist_validator_addresses();
let list_len = eth_addresses.length();
let mut list_idx = 0;
let mut member_idx = 0;
let mut pub_keys = vector[];
while (list_idx < list_len) {
let target_address = ð_addresses[list_idx];
let mut found = false;
while (member_idx < self.members.size()) {
let (pub_key, member) = self.members.get_entry_by_idx_mut(member_idx);
let eth_address = crypto::ecdsa_pub_key_to_eth_address(pub_key);
if (*target_address == eth_address) {
member.blocklisted = blocklisted;
pub_keys.push_back(*pub_key);
found = true;
member_idx = 0;
break
};
member_idx = member_idx + 1;
};
assert!(found, EValidatorBlocklistContainsUnknownKey);
list_idx = list_idx + 1;
};
emit(BlocklistValidatorEvent {
blocklisted,
public_keys: pub_keys,
})
}
Function committee_members
public(friend) fun committee_members(self: &committee::BridgeCommittee): &vec_map::VecMap<vector<u8>, committee::CommitteeMember>
Click to open
Implementation
public(package) fun committee_members(
self: &BridgeCommittee,
): &VecMap<vector<u8>, CommitteeMember> {
&self.members
}
Function update_node_url
public(friend) fun update_node_url(self: &mut committee::BridgeCommittee, new_url: vector<u8>, ctx: &tx_context::TxContext)
Click to open
Implementation
public(package) fun update_node_url(self: &mut BridgeCommittee, new_url: vector<u8>, ctx: &TxContext) {
let mut idx = 0;
while (idx < self.members.size()) {
let (_, member) = self.members.get_entry_by_idx_mut(idx);
if (member.sui_address == ctx.sender()) {
member.http_rest_url = new_url;
emit (CommitteeMemberUrlUpdateEvent {
member: member.bridge_pubkey_bytes,
new_url
});
return
};
idx = idx + 1;
};
abort ESenderIsNotInBridgeCommittee
}
Function check_uniqueness_bridge_keys
fun check_uniqueness_bridge_keys(self: &committee::BridgeCommittee, bridge_pubkey_bytes: vector<u8>)
Click to open
Implementation
fun check_uniqueness_bridge_keys(self: &BridgeCommittee, bridge_pubkey_bytes: vector<u8>) {
let mut count = self.member_registrations.size();
// bridge_pubkey_bytes must be found once and once only
let mut bridge_key_found = false;
while (count > 0) {
count = count - 1;
let (_, registration) = self.member_registrations.get_entry_by_idx(count);
if (registration.bridge_pubkey_bytes == bridge_pubkey_bytes) {
assert!(!bridge_key_found, EDuplicatePubkey);
bridge_key_found = true; // bridge_pubkey_bytes found, we must not have another one
}
};
}