Skip to main content

Module 0xb::committee

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::iota_system; use 0xb::crypto; use 0xb::message;

Struct BlocklistValidatorEvent

struct BlocklistValidatorEvent has copy, drop

Fields
blocklisted: bool
public_keys: vector<vector<u8>>

Struct BridgeCommittee

struct BridgeCommittee has store

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

Fields
members: vec_map::VecMap<vector<u8>, committee::CommitteeMember>
stake_participation_percentage: u64

Struct CommitteeMemberUrlUpdateEvent

struct CommitteeMemberUrlUpdateEvent has copy, drop

Fields
member: vector<u8>
new_url: vector<u8>

Struct CommitteeMember

struct CommitteeMember has copy, drop, store

Fields
iota_address: address

The Iota 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

Fields
iota_address: address

The Iota Address of the validator

bridge_pubkey_bytes: vector<u8>

The public key bytes of the bridge key

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'

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 IOTA_MESSAGE_PREFIX: vector<u8> = [73, 79, 84, 65, 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>>)

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 = IOTA_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

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 iota_system::IotaSystemState, bridge_pubkey_bytes: vector<u8>, http_rest_url: vector<u8>, ctx: &tx_context::TxContext)

Implementation

public(package) fun register( self: &mut BridgeCommittee, system_state: &mut IotaSystemState, 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 { iota_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)

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(&registration.iota_address); if (voting_power.is_some()) { let voting_power = voting_power.destroy_some(); stake_participation_percentage = stake_participation_percentage + voting_power;

let member = CommitteeMember { iota_address: registration.iota_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)

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 = &eth_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>

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)

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.iota_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>)

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 } }; }