Skip to content

Commit 298d967

Browse files
committed
feat(interface): typed vote and admin methods on MpcContractHandle
Adds vote_add_domains, vote_new_parameters, vote_cancel_keygen, vote_cancel_resharing, update_participant_url, register_backup_service, start_node_migration, register_foreign_chain_support, and the borsh vote_update_foreign_chain_providers (30 Tgas), sharing a zero-deposit call helper. The catalog gains one section per method. e2e: every remaining cluster.rs call site routes through the handle; the generic call_from_all_nodes_concurrently helper is inlined into its only caller (start_add_domains); register_backup_service and start_node_migration take typed BackupServiceInfo/DestinationNodeInfo (migration tests construct the DTOs they already asserted against); near_signer_public_key_str becomes the typed near_signer_public_key.
1 parent 16d73bc commit 298d967

8 files changed

Lines changed: 393 additions & 145 deletions

File tree

crates/e2e-tests/src/cluster.rs

Lines changed: 66 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@ use near_mpc_contract_interface::{
1212
client::MpcContractHandle,
1313
method_names,
1414
types::{
15-
AccountId as ContractAccountId, Attestation, AuthScheme, CKDAppPublicKey, ChainEntry,
16-
ChainRouting, DomainConfig, DomainId, DomainPurpose, Ed25519PublicKey, EpochId,
17-
ForeignChain, GovernanceThreshold, GovernanceThresholdParameters, MockAttestation,
18-
ParticipantId, ParticipantInfo, Participants, Payload, ProposeUpdateArgs,
19-
ProposedGovernanceThresholdParameters, Protocol, ProtocolContractState, ProviderConfig,
20-
ProviderId, ReconstructionThreshold, SignRequestArgs,
15+
AccountId as ContractAccountId, Attestation, AuthScheme, BackupServiceInfo,
16+
CKDAppPublicKey, ChainEntry, ChainRouting, DestinationNodeInfo, DomainConfig, DomainId,
17+
DomainPurpose, Ed25519PublicKey, EpochId, ForeignChain, GovernanceThreshold,
18+
GovernanceThresholdParameters, MockAttestation, ParticipantId, ParticipantInfo,
19+
Participants, Payload, ProposeUpdateArgs, ProposedGovernanceThresholdParameters, Protocol,
20+
ProtocolContractState, ProviderConfig, ProviderId, ReconstructionThreshold,
21+
SignRequestArgs,
2122
},
2223
};
2324
use rand::SeedableRng;
@@ -48,7 +49,6 @@ pub fn cluster_poll_retry() -> ConstantBuilder {
4849
)
4950
}
5051

51-
const NODE_MANAGEMENT_DEPOSIT: near_kit::NearToken = near_kit::NearToken::from_yoctonear(1);
5252
// The contract's default `key_event_timeout_blocks = 30` is ~18 s on
5353
// mainnet (~600 ms blocks). The e2e sandbox runs ~8 blocks/s, so the
5454
// same 30 collapses to ~3.7 s — too tight for the resharing
@@ -57,7 +57,6 @@ const NODE_MANAGEMENT_DEPOSIT: near_kit::NearToken = near_kit::NearToken::from_y
5757
// load. Override to 240 blocks (~30 s in sandbox) as a comfortable
5858
// budget over mainnet's effective headroom.
5959
const KEY_EVENT_TIMEOUT_BLOCKS: u64 = 240;
60-
const VOTE_FOREIGN_CHAIN_GAS: near_kit::Gas = near_kit::Gas::from_tgas(30);
6160
const CONTRACT_DEPLOY_TIMEOUT: Duration = Duration::from_secs(15);
6261
const PROPOSER_NODE_INDEX: usize = 0;
6362

@@ -520,9 +519,34 @@ impl MpcCluster {
520519
/// `Initializing` state. Does NOT wait for key generation to complete —
521520
/// use `add_domains_and_wait` for the full flow.
522521
pub async fn start_add_domains(&self, domains: Vec<DomainConfig>) -> anyhow::Result<()> {
523-
let args = json!({ "domains": &domains });
524-
self.call_from_all_nodes_concurrently(method_names::VOTE_ADD_DOMAINS, args)
525-
.await?;
522+
let handles: Vec<_> = self
523+
.nodes
524+
.iter()
525+
.zip(self.node_keys.iter())
526+
.enumerate()
527+
.filter(|(_, (node, _))| matches!(node, MpcNodeState::Running(_)))
528+
.map(|(i, (node, key))| {
529+
let client = self
530+
.blockchain
531+
.client_for(node.account_id().as_ref(), key)?;
532+
Ok((
533+
i,
534+
node.account_id().clone(),
535+
self.contract.handle_for(client),
536+
))
537+
})
538+
.collect::<anyhow::Result<Vec<_>>>()?;
539+
540+
let votes = handles.iter().map(|(i, account, contract_handle)| {
541+
let domains = domains.clone();
542+
async move {
543+
contract_handle
544+
.vote_add_domains(domains)
545+
.await
546+
.with_context(|| format!("node {i} ({account}) failed to vote_add_domains"))
547+
}
548+
});
549+
futures::future::try_join_all(votes).await?;
526550

527551
self.wait_for_state(
528552
|s| matches!(s, ProtocolContractState::Initializing(_)),
@@ -539,13 +563,9 @@ impl MpcCluster {
539563
node_index: usize,
540564
next_domain_id: u64,
541565
) -> anyhow::Result<near_kit::FinalExecutionOutcome> {
542-
let client = self.operator_client_for(node_index)?;
543566
self.contract
544-
.call_from(
545-
&client,
546-
method_names::VOTE_CANCEL_KEYGEN,
547-
json!({ "next_domain_id": next_domain_id }),
548-
)
567+
.handle_for(self.operator_client_for(node_index)?)
568+
.vote_cancel_keygen(next_domain_id)
549569
.await
550570
.with_context(|| format!("node {node_index} failed to send cancel keygen vote"))
551571
}
@@ -582,8 +602,8 @@ impl MpcCluster {
582602
};
583603

584604
tracing::info!(?prospective_epoch_id, new_threshold, "voting for resharing");
585-
let args = json!({ "prospective_epoch_id": prospective_epoch_id, "proposal": proposal });
586-
self.vote_resharing(current_participants, args).await?;
605+
self.vote_resharing(current_participants, prospective_epoch_id, proposal)
606+
.await?;
587607

588608
self.wait_for_state(
589609
|s| matches!(s, ProtocolContractState::Resharing(_)),
@@ -641,7 +661,8 @@ impl MpcCluster {
641661
async fn vote_resharing(
642662
&self,
643663
current_participants: &Participants,
644-
args: serde_json::Value,
664+
prospective_epoch_id: EpochId,
665+
proposal: ProposedGovernanceThresholdParameters,
645666
) -> anyhow::Result<()> {
646667
let current_accounts: std::collections::HashSet<_> = current_participants
647668
.participants
@@ -664,10 +685,10 @@ impl MpcCluster {
664685
}
665686

666687
for i in participants_first.iter().chain(candidates_second.iter()) {
667-
let client = self.operator_client_for(*i)?;
668688
let outcome = self
669689
.contract
670-
.call_from(&client, method_names::VOTE_NEW_PARAMETERS, args.clone())
690+
.handle_for(self.operator_client_for(*i)?)
691+
.vote_new_parameters(prospective_epoch_id, proposal.clone())
671692
.await
672693
.with_context(|| format!("node {i} failed to send resharing vote"))?;
673694
if !outcome.is_success() {
@@ -689,9 +710,9 @@ impl MpcCluster {
689710
&self,
690711
node_index: usize,
691712
) -> anyhow::Result<near_kit::FinalExecutionOutcome> {
692-
let client = self.operator_client_for(node_index)?;
693713
self.contract
694-
.call_from(&client, method_names::VOTE_CANCEL_RESHARING, json!({}))
714+
.handle_for(self.operator_client_for(node_index)?)
715+
.vote_cancel_resharing()
695716
.await
696717
.with_context(|| format!("node {node_index} failed to send cancel resharing vote"))
697718
}
@@ -758,40 +779,6 @@ impl MpcCluster {
758779
Ok(())
759780
}
760781

761-
async fn call_from_all_nodes_concurrently(
762-
&self,
763-
method: &str,
764-
args: serde_json::Value,
765-
) -> anyhow::Result<()> {
766-
let clients: Vec<_> = self
767-
.nodes
768-
.iter()
769-
.zip(self.node_keys.iter())
770-
.enumerate()
771-
.filter(|(_, (node, _))| matches!(node, MpcNodeState::Running(_)))
772-
.map(|(i, (node, key))| {
773-
let client = self
774-
.blockchain
775-
.client_for(node.account_id().as_ref(), key)?;
776-
Ok((i, node.account_id().clone(), client))
777-
})
778-
.collect::<anyhow::Result<Vec<_>>>()?;
779-
780-
let futures = clients.iter().map(|(i, account, client)| {
781-
let args = args.clone();
782-
let method = method.to_string();
783-
async move {
784-
self.contract
785-
.call_from(client, &method, args)
786-
.await
787-
.with_context(|| format!("node {i} ({account}) failed to call {method}"))
788-
}
789-
});
790-
791-
futures::future::try_join_all(futures).await?;
792-
Ok(())
793-
}
794-
795782
pub fn client_for(&self, account_id: &AccountId) -> anyhow::Result<NearKitCaller> {
796783
let key = self
797784
.user_accounts
@@ -871,17 +858,13 @@ impl MpcCluster {
871858
pub async fn register_backup_service(
872859
&self,
873860
node_index: usize,
874-
backup_service_info: serde_json::Value,
861+
backup_service_info: BackupServiceInfo,
875862
) -> anyhow::Result<near_kit::FinalExecutionOutcome> {
876-
let client = self.operator_client_for(node_index)?;
877863
self.contract
878-
.call_from_deposit(
879-
&client,
880-
method_names::REGISTER_BACKUP_SERVICE,
881-
json!({ "backup_service_info": backup_service_info }),
882-
NODE_MANAGEMENT_DEPOSIT,
883-
)
864+
.handle_for(self.operator_client_for(node_index)?)
865+
.register_backup_service(backup_service_info)
884866
.await
867+
.context("failed to register backup service")
885868
}
886869
/// View the foreign chains the contract accepts requests for.
887870
pub async fn view_foreign_chains_supported_by_contract(
@@ -907,19 +890,11 @@ impl MpcCluster {
907890
node_index: usize,
908891
foreign_chain_support: &near_mpc_contract_interface::types::SupportedForeignChains,
909892
) -> anyhow::Result<near_kit::FinalExecutionOutcome> {
910-
let node = &self.nodes[node_index];
911-
let client = self
912-
.blockchain
913-
.client_for(node.account_id().as_ref(), &self.operator_keys[node_index])?;
914893
self.contract
915-
.call_from(
916-
&client,
917-
method_names::REGISTER_FOREIGN_CHAIN_SUPPORT,
918-
json!({
919-
"foreign_chain_support": serde_json::to_value(foreign_chain_support)?,
920-
}),
921-
)
894+
.handle_for(self.operator_client_for(node_index)?)
895+
.register_foreign_chain_support(foreign_chain_support.clone())
922896
.await
897+
.context("failed to register foreign chain support")
923898
}
924899

925900
pub async fn view_available_foreign_chains(
@@ -997,13 +972,8 @@ impl MpcCluster {
997972
.operator_client_for(idx)
998973
.with_context(|| format!("whitelist_foreign_chains: node {idx}"))?;
999974
self.contract
1000-
.call_from_borsh_with_deposit(
1001-
&client,
1002-
method_names::VOTE_UPDATE_FOREIGN_CHAIN_PROVIDERS,
1003-
batch.clone(),
1004-
VOTE_FOREIGN_CHAIN_GAS,
1005-
near_kit::NearToken::from_yoctonear(0),
1006-
)
975+
.handle_for(client)
976+
.vote_update_foreign_chain_providers(batch.clone())
1007977
.await
1008978
.with_context(|| {
1009979
format!("vote_update_foreign_chain_providers from node {idx} failed")
@@ -1016,17 +986,13 @@ impl MpcCluster {
1016986
pub async fn start_node_migration(
1017987
&self,
1018988
node_index: usize,
1019-
destination_node_info: serde_json::Value,
989+
destination_node_info: DestinationNodeInfo,
1020990
) -> anyhow::Result<near_kit::FinalExecutionOutcome> {
1021-
let client = self.operator_client_for(node_index)?;
1022991
self.contract
1023-
.call_from_deposit(
1024-
&client,
1025-
method_names::START_NODE_MIGRATION,
1026-
json!({ "destination_node_info": destination_node_info }),
1027-
NODE_MANAGEMENT_DEPOSIT,
1028-
)
992+
.handle_for(self.operator_client_for(node_index)?)
993+
.start_node_migration(destination_node_info)
1029994
.await
995+
.context("failed to start node migration")
1030996
}
1031997

1032998
/// Update the registered URL of a specific node, called from that node's own operator account.
@@ -1035,15 +1001,11 @@ impl MpcCluster {
10351001
node_index: usize,
10361002
url: String,
10371003
) -> anyhow::Result<near_kit::FinalExecutionOutcome> {
1038-
let client = self.operator_client_for(node_index)?;
10391004
self.contract
1040-
.call_from_deposit(
1041-
&client,
1042-
method_names::UPDATE_PARTICIPANT_URL,
1043-
json!({ "url": url }),
1044-
NODE_MANAGEMENT_DEPOSIT,
1045-
)
1005+
.handle_for(self.operator_client_for(node_index)?)
1006+
.update_participant_url(url)
10461007
.await
1008+
.context("failed to update participant url")
10471009
}
10481010

10491011
/// Send a verify_foreign_transaction request from the default user account.
@@ -1191,10 +1153,10 @@ impl MpcNodeState {
11911153
}
11921154
}
11931155

1194-
pub fn near_signer_public_key_str(&self) -> String {
1156+
pub fn near_signer_public_key(&self) -> Ed25519PublicKey {
11951157
match self {
1196-
MpcNodeState::Running(n) => n.setup().near_signer_public_key_str(),
1197-
MpcNodeState::Stopped(s) => s.near_signer_public_key_str(),
1158+
MpcNodeState::Running(n) => n.setup().near_signer_public_key(),
1159+
MpcNodeState::Stopped(s) => s.near_signer_public_key(),
11981160
}
11991161
}
12001162
}
@@ -1335,13 +1297,13 @@ async fn add_initial_domains(
13351297
domains: &[DomainConfig],
13361298
) -> anyhow::Result<()> {
13371299
tracing::info!(count = domains.len(), "adding domains");
1338-
let args = json!({ "domains": domains });
13391300

13401301
for &i in participant_indices {
13411302
let account = format!("node{i}.{SANDBOX_ROOT_ACCOUNT}");
13421303
let client = blockchain.client_for(&account, &operator_keys[i])?;
13431304
contract
1344-
.call_from(&client, method_names::VOTE_ADD_DOMAINS, args.clone())
1305+
.handle_for(client)
1306+
.vote_add_domains(domains.to_vec())
13451307
.await
13461308
.with_context(|| format!("node {i} failed to vote add domains"))?;
13471309
}

crates/e2e-tests/src/mpc_node.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -283,11 +283,8 @@ impl MpcNodeSetup {
283283
&self.near_signer_key
284284
}
285285

286-
/// The NEAR signer public key formatted as `"ed25519:<base58>"`.
287-
pub fn near_signer_public_key_str(&self) -> String {
288-
String::from(&Ed25519PublicKey::from(
289-
&self.near_signer_key.verifying_key(),
290-
))
286+
pub fn near_signer_public_key(&self) -> Ed25519PublicKey {
287+
Ed25519PublicKey::from(&self.near_signer_key.verifying_key())
291288
}
292289

293290
/// Path to the mpc-node binary.

crates/e2e-tests/tests/migration_endpoint.rs

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ async fn migration_endpoint__should_track_migration_state() {
4040
};
4141
let web_addr = node.web_address();
4242
let account_id = node_state.account_id().to_string();
43-
let p2p_public_key = node_state.p2p_public_key_str();
4443

4544
// Given: the migration state carried over from prior iterations
4645
// (empty on the first iteration).
@@ -53,19 +52,18 @@ async fn migration_endpoint__should_track_migration_state() {
5352
.expect("endpoint state mismatch");
5453

5554
// When: register a bogus backup service for this node.
56-
let backup_service_info = serde_json::json!({ "public_key": p2p_public_key });
55+
let backup_info = BackupServiceInfo {
56+
public_key: node_state.p2p_public_key(),
57+
};
5758
let outcome = cluster
58-
.register_backup_service(i, backup_service_info)
59+
.register_backup_service(i, backup_info.clone())
5960
.await
6061
.expect("failed to register backup service");
6162
assert!(
6263
outcome.is_success(),
6364
"register_backup_service failed: {:?}",
6465
outcome.failure_message()
6566
);
66-
let backup_info = BackupServiceInfo {
67-
public_key: node_state.p2p_public_key(),
68-
};
6967
expected_migrations.insert(account_id.clone(), (Some(backup_info.clone()), None));
7068

7169
// Then: contract and endpoint both expose the backup registration.
@@ -77,13 +75,13 @@ async fn migration_endpoint__should_track_migration_state() {
7775
.expect("endpoint state mismatch after backup registration");
7876

7977
// When: start node migration with a bogus destination.
80-
let destination_node_info = serde_json::json!({
81-
"signer_account_pk": p2p_public_key,
82-
"destination_node_info": {
83-
"url": "http://bogus:1234",
84-
"tls_public_key": p2p_public_key,
78+
let destination_node_info = DestinationNodeInfo {
79+
signer_account_pk: node_state.p2p_public_key(),
80+
destination_node_info: ParticipantInfo {
81+
url: "http://bogus:1234".to_string(),
82+
tls_public_key: node_state.p2p_public_key(),
8583
},
86-
});
84+
};
8785
let outcome = cluster
8886
.start_node_migration(i, destination_node_info)
8987
.await

0 commit comments

Comments
 (0)