Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: multi signature production #790

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions chain-signatures/node/src/mesh/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,18 +127,10 @@ impl Pool {
.await;
}
}
tracing::debug!(
"Pool.establish_participants set participants to {:?}",
self.connections.read().await.clone().keys_vec()
);
}

async fn set_participants(&self, participants: &Participants) {
*self.connections.write().await = participants.clone();
tracing::debug!(
"Pool set participants to {:?}",
self.connections.read().await.keys_vec()
);
}

async fn set_potential_participants(&self, participants: &Participants) {
Expand Down
10 changes: 6 additions & 4 deletions chain-signatures/node/src/mesh/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,17 @@ impl Mesh {
.establish_participants(contract_state)
.await;
self.ping().await;

tracing::debug!(
?self.active_participants,
?self.active_potential_participants,
"mesh participants",
);
}

/// Ping the active participants such that we can see who is alive.
pub async fn ping(&mut self) {
self.active_participants = self.connections.ping().await;
tracing::debug!(
"Mesh.ping set active participants to {:?}",
self.active_participants.keys_vec()
);
self.active_potential_participants = self.connections.ping_potential().await;
}
}
6 changes: 0 additions & 6 deletions chain-signatures/node/src/protocol/cryptography.rs
Original file line number Diff line number Diff line change
Expand Up @@ -358,12 +358,6 @@ impl CryptographicProtocol for RunningState {
) -> Result<NodeState, CryptographicError> {
let protocol_cfg = &ctx.cfg().protocol;
let active = ctx.mesh().active_participants();
tracing::debug!(
"RunningState.progress active participants: {:?} potential participants: {:?} me: {:?}",
active.keys_vec(),
ctx.mesh().potential_participants().await.keys_vec(),
ctx.me().await
);
if active.len() < self.threshold {
tracing::info!(
active = ?active.keys_vec(),
Expand Down
24 changes: 6 additions & 18 deletions chain-signatures/node/src/protocol/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,15 +284,10 @@ impl MpcSignProtocol {
};

let crypto_time = Instant::now();
tracing::debug!("State progress. Node state: {}", state);
let mut state = match state.progress(&mut self).await {
Ok(state) => {
tracing::debug!("Progress ok. New state: {}", state);
state
}
Ok(state) => state,
Err(err) => {
tracing::debug!("Progress error. State not changed");
tracing::info!("protocol unable to progress: {err:?}");
tracing::warn!("protocol unable to progress: {err:?}");
tokio::time::sleep(Duration::from_millis(100)).await;
continue;
}
Expand All @@ -303,19 +298,14 @@ impl MpcSignProtocol {

let consensus_time = Instant::now();
if let Some(contract_state) = contract_state {
tracing::debug!(
"State advance. Node state: {}, contract state: {:?}",
state,
contract_state
);
let from_state = format!("{state}");
state = match state.advance(&mut self, contract_state).await {
Ok(state) => {
tracing::debug!("Advance ok. New node state: {}", state);
tracing::debug!("advance ok: {from_state} => {state}");
state
}
Err(err) => {
tracing::debug!("Advance error. State not changed");
tracing::info!("protocol unable to advance: {err:?}");
tracing::warn!("protocol unable to advance: {err:?}");
tokio::time::sleep(Duration::from_millis(100)).await;
continue;
}
Expand All @@ -327,9 +317,7 @@ impl MpcSignProtocol {

let message_time = Instant::now();
if let Err(err) = state.handle(&self, &mut queue).await {
tracing::info!("protocol unable to handle messages: {err:?}");
tokio::time::sleep(Duration::from_millis(100)).await;
continue;
tracing::warn!("protocol unable to handle messages: {err:?}");
}
crate::metrics::PROTOCOL_LATENCY_ITER_MESSAGE
.with_label_values(&[my_account_id.as_str()])
Expand Down
6 changes: 4 additions & 2 deletions chain-signatures/node/src/protocol/presignature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ impl PresignatureGenerator {

pub fn poke(&mut self) -> Result<Action<PresignOutput<Secp256k1>>, ProtocolError> {
if self.timestamp.elapsed() > self.timeout {
tracing::info!(
let id = hash_as_id(self.triple0, self.triple1);
tracing::warn!(
presignature_id = id,
self.triple0,
self.triple1,
self.mine,
Expand Down Expand Up @@ -392,8 +394,8 @@ impl PresignatureManager {
}

pub fn take_mine(&mut self) -> Option<Presignature> {
tracing::info!(mine = ?self.mine, "my presignatures");
let my_presignature_id = self.mine.pop_front()?;
tracing::debug!(my_presignature_id, "take presignature of mine");
// SAFETY: This unwrap is safe because taking mine will always succeed since it is only
// present when generation completes where the determination of ownership is made.
Some(self.take(my_presignature_id).unwrap())
Expand Down
16 changes: 2 additions & 14 deletions integration-tests/chain-signatures/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use crate::containers::LocalStack;
use anyhow::Context as _;
use bollard::exec::{CreateExecOptions, StartExecResults};
use futures::StreamExt;
use mpc_contract::config::{PresignatureConfig, ProtocolConfig, TripleConfig};
use mpc_contract::config::ProtocolConfig;
use mpc_contract::primitives::CandidateInfo;
use mpc_node::gcp::GcpService;
use mpc_node::storage;
Expand All @@ -38,19 +38,7 @@ impl Default for MultichainConfig {
Self {
nodes: 3,
threshold: 2,
protocol: ProtocolConfig {
triple: TripleConfig {
min_triples: 8,
max_triples: 80,
..Default::default()
},
presignature: PresignatureConfig {
min_presignatures: 2,
max_presignatures: 20,
..Default::default()
},
..Default::default()
},
protocol: Default::default(),
}
}
}
Expand Down
44 changes: 34 additions & 10 deletions integration-tests/chain-signatures/tests/actions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,19 +38,27 @@ use k256::{
};
use serde_json::json;

pub async fn request_sign(
pub async fn new_account(
ctx: &MultichainTestContext<'_>,
) -> anyhow::Result<([u8; 32], [u8; 32], Account, CryptoHash)> {
) -> anyhow::Result<(Account, InMemorySigner)> {
let worker = &ctx.nodes.ctx().worker;
let account = worker.dev_create_account().await?;
let payload: [u8; 32] = rand::thread_rng().gen();
let payload_hashed = web3::signing::keccak256(&payload);

let signer = InMemorySigner {
account_id: account.id().clone(),
public_key: account.secret_key().public_key().to_string().parse()?,
secret_key: account.secret_key().to_string().parse()?,
};

Ok((account, signer))
}

pub async fn request_sign(
ctx: &MultichainTestContext<'_>,
signer: &InMemorySigner,
) -> anyhow::Result<([u8; 32], [u8; 32], CryptoHash)> {
let payload: [u8; 32] = rand::thread_rng().gen();
let payload_hashed = web3::signing::keccak256(&payload);

let (nonce, block_hash, _) = ctx
.rpc_client
.fetch_nonce(&signer.account_id, &signer.public_key)
Expand Down Expand Up @@ -79,11 +87,24 @@ pub async fn request_sign(
deposit: 1,
}))],
}
.sign(&signer),
.sign(signer),
})
.await?;
tokio::time::sleep(Duration::from_secs(1)).await;
Ok((payload, payload_hashed, account, tx_hash))
Ok((payload, payload_hashed, tx_hash))
}

pub async fn request_sign_multiple(
ctx: &MultichainTestContext<'_>,
signer: &InMemorySigner,
amount: usize,
) -> anyhow::Result<Vec<([u8; 32], CryptoHash)>> {
let mut results = Vec::with_capacity(amount);
for _ in 0..amount {
let (_payload, payload_hashed, tx_hash) = request_sign(ctx, signer).await?;
results.push((payload_hashed, tx_hash));
}
Ok(results)
}

pub async fn assert_signature(
Expand All @@ -104,7 +125,8 @@ pub async fn single_signature_rogue_responder(
ctx: &MultichainTestContext<'_>,
state: &RunningContractState,
) -> anyhow::Result<()> {
let (_, payload_hash, account, tx_hash) = request_sign(ctx).await?;
let (account, signer) = new_account(ctx).await?;
let (_, payload_hash, tx_hash) = request_sign(ctx, &signer).await?;

// We have to use seperate transactions because one could fail.
// This leads to a potential race condition where this transaction could get sent after the signature completes, but I think that's unlikely
Expand Down Expand Up @@ -135,7 +157,8 @@ pub async fn single_signature_production(
ctx: &MultichainTestContext<'_>,
state: &RunningContractState,
) -> anyhow::Result<()> {
let (_, payload_hash, account, tx_hash) = request_sign(ctx).await?;
let (account, signer) = new_account(ctx).await?;
let (_, payload_hash, tx_hash) = request_sign(ctx, &signer).await?;
let signature = wait_for::signature_responded(ctx, tx_hash).await?;

let mut mpc_pk_bytes = vec![0x04];
Expand Down Expand Up @@ -276,7 +299,8 @@ pub async fn single_payload_signature_production(
ctx: &MultichainTestContext<'_>,
state: &RunningContractState,
) -> anyhow::Result<()> {
let (payload, payload_hash, account, tx_hash) = request_sign(ctx).await?;
let (account, signer) = new_account(ctx).await?;
let (payload, payload_hash, tx_hash) = request_sign(ctx, &signer).await?;
let first_tx_result = wait_for::signature_responded(ctx, tx_hash).await;
let signature = match first_tx_result {
Ok(sig) => sig,
Expand Down
55 changes: 55 additions & 0 deletions integration-tests/chain-signatures/tests/actions/wait_for.rs
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,61 @@ pub async fn signature_responded(
}
}

pub async fn signatures_responded(
ctx: &MultichainTestContext<'_>,
tx_hashs: &[CryptoHash],
) -> Result<Vec<FullSignature<Secp256k1>>, WaitForError> {
let is_tx_ready = |tx_hash: CryptoHash| async move {
let outcome_view = match ctx
.jsonrpc_client
.call(RpcTransactionStatusRequest {
transaction_info: TransactionInfo::TransactionId {
tx_hash,
sender_account_id: ctx.nodes.ctx().mpc_contract.id().clone(),
},
wait_until: near_primitives::views::TxExecutionStatus::Final,
})
.await
{
Err(error) => return Err(WaitForError::JsonRpc(format!("{error:?}"))),
Ok(outcome_view) => outcome_view,
};

let Some(outcome) = outcome_view.final_execution_outcome else {
return Err(WaitForError::Signature(SignatureError::NotYetAvailable));
};

let outcome = outcome.into_outcome();

let FinalExecutionStatus::SuccessValue(payload) = outcome.status else {
return Ok(Outcome::Failed(format!("{:?}", outcome.status)));
};

let result: SignatureResponse = match serde_json::from_slice(&payload) {
Err(error) => return Err(WaitForError::SerdeJson(format!("{error:?}"))),
Ok(response) => response,
};
Ok(Outcome::Signature(cait_sith::FullSignature::<Secp256k1> {
big_r: result.big_r.affine_point,
s: result.s.scalar,
}))
};
let mut result = Vec::new();
for _ in 0..8 {
for hash in tx_hashs {
match is_tx_ready(*hash).await {
Ok(Outcome::Signature(signature)) => result.push(signature),
Ok(Outcome::Failed(err)) => {
return Err(WaitForError::Signature(SignatureError::Failed(err)))
}
Err(_) => continue,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may result in one result pushed more than once to result, I suggest use a Map and skip checking already in Map hashs.

}
}
tokio::time::sleep(Duration::from_secs(20)).await;
}
Ok(result)
}

pub async fn signature_payload_responded(
ctx: &MultichainTestContext<'_>,
account: Account,
Expand Down
39 changes: 38 additions & 1 deletion integration-tests/chain-signatures/tests/cases/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use mpc_node::kdf::into_eth_sig;
use mpc_node::test_utils;
use mpc_node::types::LatestBlockHeight;
use mpc_node::util::NearPublicKeyExt;
use rand::{Rng, SeedableRng};
use test_log::test;

pub mod nightly;
Expand Down Expand Up @@ -99,6 +100,41 @@ async fn test_signature_basic() -> anyhow::Result<()> {
.await
}

#[test(tokio::test)]
async fn test_signature_multiple_requests() -> anyhow::Result<()> {
let mut rng = rand::rngs::StdRng::from_seed([0; 32]);

with_multichain_nodes(MultichainConfig::default(), |ctx| {
Box::pin(async move {
let state_0 = wait_for::running_mpc(&ctx, Some(0)).await?;
assert_eq!(state_0.participants.len(), 3);

for i in 0..10 {
let random_secs: u32 = rng.gen_range(1, 40);
tokio::time::sleep(std::time::Duration::from_secs(random_secs as u64)).await;
let (_account, signer) = actions::new_account(&ctx).await.unwrap();

let sig_amount = rng.gen_range(1, 3);
tracing::info!(i, sig_amount, "Producing signature");

let (_payload_hashes, tx_hashes): (Vec<_>, Vec<_>) =
actions::request_sign_multiple(&ctx, &signer, sig_amount)
.await?
.into_iter()
.unzip();

match wait_for::signatures_responded(&ctx, &tx_hashes).await {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what I see in signatures_responded() fn there is no guarantee that it will return all signatures. Some may be in progress. Should we check the length here? Or make sure we have N signatures in the function?

Ok(_signatures) => tracing::info!(?tx_hashes, "got signatures"),
Err(err) => tracing::error!("unable to produce signatures in time: {err:?}"),
}
}

Ok(())
})
})
.await
}

#[test(tokio::test)]
async fn test_signature_offline_node() -> anyhow::Result<()> {
with_multichain_nodes(MultichainConfig::default(), |mut ctx| {
Expand Down Expand Up @@ -147,7 +183,8 @@ async fn test_key_derivation() -> anyhow::Result<()> {

for _ in 0..3 {
let mpc_pk: k256::AffinePoint = state_0.public_key.clone().into_affine_point();
let (_, payload_hashed, account, tx_hash) = actions::request_sign(&ctx).await?;
let (account, signer) = actions::new_account(&ctx).await?;
let (_, payload_hashed, tx_hash) = actions::request_sign(&ctx, &signer).await?;
let sig = wait_for::signature_responded(&ctx, tx_hash).await?;

let hd_path = "test";
Expand Down
Loading
Loading