Skip to content

Commit 2182806

Browse files
committed
Implement verify verifier set handler for stacks.
1 parent b2dda21 commit 2182806

File tree

12 files changed

+513
-3
lines changed

12 files changed

+513
-3
lines changed

ampd/src/config.rs

+6
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,12 @@ mod tests {
362362
),
363363
http_url: Url::from_str("http://127.0.0.1").unwrap(),
364364
},
365+
HandlerConfig::StacksVerifierSetVerifier {
366+
cosmwasm_contract: TMAddress::from(
367+
AccountId::new("axelar", &[0u8; 32]).unwrap(),
368+
),
369+
http_url: Url::from_str("http://127.0.0.1").unwrap(),
370+
},
365371
],
366372
..Config::default()
367373
}

ampd/src/handlers/config.rs

+26
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ pub enum Config {
6767
cosmwasm_contract: TMAddress,
6868
http_url: Url,
6969
},
70+
StacksVerifierSetVerifier {
71+
cosmwasm_contract: TMAddress,
72+
http_url: Url,
73+
},
7074
}
7175

7276
fn validate_evm_verifier_set_verifier_configs<'de, D>(configs: &[Config]) -> Result<(), D::Error>
@@ -168,6 +172,11 @@ where
168172
Config::StacksMsgVerifier,
169173
"Stacks message verifier"
170174
)?;
175+
ensure_unique_config!(
176+
&configs,
177+
Config::StacksVerifierSetVerifier,
178+
"Stacks verifier set verifier"
179+
)?;
171180

172181
Ok(configs)
173182
}
@@ -331,5 +340,22 @@ mod tests {
331340
Err(e) if e.to_string().contains("only one Stacks message verifier config is allowed")
332341
)
333342
);
343+
344+
let configs = vec![
345+
Config::StacksVerifierSetVerifier {
346+
cosmwasm_contract: TMAddress::random(PREFIX),
347+
http_url: "http://localhost:8080/".parse().unwrap(),
348+
},
349+
Config::StacksVerifierSetVerifier {
350+
cosmwasm_contract: TMAddress::random(PREFIX),
351+
http_url: "http://localhost:8080/".parse().unwrap(),
352+
},
353+
];
354+
355+
assert!(
356+
matches!(deserialize_handler_configs(to_value(configs).unwrap()),
357+
Err(e) if e.to_string().contains("only one Stacks verifier set verifier config is allowed")
358+
)
359+
);
334360
}
335361
}

ampd/src/handlers/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ pub mod multisig;
66
pub mod mvx_verify_msg;
77
pub mod mvx_verify_verifier_set;
88
pub mod stacks_verify_msg;
9+
pub mod stacks_verify_verifier_set;
910
pub(crate) mod stellar_verify_msg;
1011
pub(crate) mod stellar_verify_verifier_set;
1112
pub mod sui_verify_msg;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
use std::convert::TryInto;
2+
3+
use async_trait::async_trait;
4+
use axelar_wasm_std::voting::{PollId, Vote};
5+
use cosmrs::cosmwasm::MsgExecuteContract;
6+
use cosmrs::tx::Msg;
7+
use cosmrs::Any;
8+
use error_stack::ResultExt;
9+
use events::Error::EventTypeMismatch;
10+
use events::Event;
11+
use events_derive::try_from;
12+
use multisig::verifier_set::VerifierSet;
13+
use serde::Deserialize;
14+
use tokio::sync::watch::Receiver;
15+
use tracing::{info, info_span};
16+
use valuable::Valuable;
17+
use voting_verifier::msg::ExecuteMsg;
18+
19+
use crate::event_processor::EventHandler;
20+
use crate::handlers::errors::Error;
21+
use crate::stacks::http_client::Client;
22+
use crate::stacks::verifier::verify_verifier_set;
23+
use crate::types::{Hash, TMAddress};
24+
25+
#[derive(Deserialize, Debug)]
26+
pub struct VerifierSetConfirmation {
27+
pub tx_id: Hash,
28+
pub event_index: u32,
29+
pub verifier_set: VerifierSet,
30+
}
31+
32+
#[derive(Deserialize, Debug)]
33+
#[try_from("wasm-verifier_set_poll_started")]
34+
struct PollStartedEvent {
35+
poll_id: PollId,
36+
source_gateway_address: String,
37+
verifier_set: VerifierSetConfirmation,
38+
participants: Vec<TMAddress>,
39+
expires_at: u64,
40+
}
41+
42+
pub struct Handler {
43+
verifier: TMAddress,
44+
voting_verifier_contract: TMAddress,
45+
http_client: Client,
46+
latest_block_height: Receiver<u64>,
47+
}
48+
49+
impl Handler {
50+
pub fn new(
51+
verifier: TMAddress,
52+
voting_verifier_contract: TMAddress,
53+
http_client: Client,
54+
latest_block_height: Receiver<u64>,
55+
) -> Self {
56+
Self {
57+
verifier,
58+
voting_verifier_contract,
59+
http_client,
60+
latest_block_height,
61+
}
62+
}
63+
64+
fn vote_msg(&self, poll_id: PollId, vote: Vote) -> MsgExecuteContract {
65+
MsgExecuteContract {
66+
sender: self.verifier.as_ref().clone(),
67+
contract: self.voting_verifier_contract.as_ref().clone(),
68+
msg: serde_json::to_vec(&ExecuteMsg::Vote {
69+
poll_id,
70+
votes: vec![vote],
71+
})
72+
.expect("vote msg should serialize"),
73+
funds: vec![],
74+
}
75+
}
76+
}
77+
78+
#[async_trait]
79+
impl EventHandler for Handler {
80+
type Err = Error;
81+
82+
async fn handle(&self, event: &Event) -> error_stack::Result<Vec<Any>, Error> {
83+
if !event.is_from_contract(self.voting_verifier_contract.as_ref()) {
84+
return Ok(vec![]);
85+
}
86+
87+
let PollStartedEvent {
88+
poll_id,
89+
source_gateway_address,
90+
verifier_set,
91+
participants,
92+
expires_at,
93+
..
94+
} = match event.try_into() as error_stack::Result<_, _> {
95+
Err(report) if matches!(report.current_context(), EventTypeMismatch(_)) => {
96+
return Ok(vec![]);
97+
}
98+
event => event.change_context(Error::DeserializeEvent)?,
99+
};
100+
101+
if !participants.contains(&self.verifier) {
102+
return Ok(vec![]);
103+
}
104+
105+
let latest_block_height = *self.latest_block_height.borrow();
106+
if latest_block_height >= expires_at {
107+
info!(poll_id = poll_id.to_string(), "skipping expired poll");
108+
return Ok(vec![]);
109+
}
110+
111+
let transaction = self
112+
.http_client
113+
.get_valid_transaction(&verifier_set.tx_id)
114+
.await;
115+
116+
let vote = info_span!(
117+
"verify a new verifier set for Stacks",
118+
poll_id = poll_id.to_string(),
119+
id = format!("{}_{}", verifier_set.tx_id, verifier_set.event_index)
120+
)
121+
.in_scope(|| {
122+
info!("ready to verify a new worker set in poll");
123+
124+
let vote = transaction.map_or(Vote::NotFound, |transaction| {
125+
verify_verifier_set(&source_gateway_address, &transaction, verifier_set)
126+
});
127+
info!(
128+
vote = vote.as_value(),
129+
"ready to vote for a new worker set in poll"
130+
);
131+
132+
vote
133+
});
134+
135+
Ok(vec![self
136+
.vote_msg(poll_id, vote)
137+
.into_any()
138+
.expect("vote msg should serialize")])
139+
}
140+
}
141+
142+
#[cfg(test)]
143+
mod tests {
144+
use std::collections::HashMap;
145+
use std::convert::TryInto;
146+
147+
use cosmrs::cosmwasm::MsgExecuteContract;
148+
use cosmrs::tx::Msg;
149+
use cosmwasm_std;
150+
use error_stack::Result;
151+
use multisig::key::KeyType;
152+
use multisig::test::common::{build_verifier_set, ecdsa_test_data};
153+
use tokio::sync::watch;
154+
use tokio::test as async_test;
155+
use voting_verifier::events::{
156+
PollMetadata, PollStarted, TxEventConfirmation, VerifierSetConfirmation,
157+
};
158+
159+
use super::PollStartedEvent;
160+
use crate::event_processor::EventHandler;
161+
use crate::handlers::tests::into_structured_event;
162+
use crate::stacks::http_client::Client;
163+
use crate::types::{EVMAddress, Hash, TMAddress};
164+
use crate::PREFIX;
165+
166+
#[test]
167+
fn should_deserialize_verifier_set_poll_started_event() {
168+
let event: Result<PollStartedEvent, events::Error> = into_structured_event(
169+
verifier_set_poll_started_event(participants(5, None), 100),
170+
&TMAddress::random(PREFIX),
171+
)
172+
.try_into();
173+
174+
assert!(event.is_ok());
175+
176+
let event = event.unwrap();
177+
178+
assert!(event.poll_id == 100u64.into());
179+
assert!(
180+
event.source_gateway_address
181+
== "SP2N959SER36FZ5QT1CX9BR63W3E8X35WQCMBYYWC.axelar-gateway"
182+
);
183+
184+
let verifier_set = event.verifier_set;
185+
186+
assert!(
187+
verifier_set.tx_id
188+
== "0xee0049faf8dde5507418140ed72bd64f73cc001b08de98e0c16a3a8d9f2c38cf"
189+
.parse()
190+
.unwrap()
191+
);
192+
assert!(verifier_set.event_index == 1u32);
193+
assert!(verifier_set.verifier_set.signers.len() == 3);
194+
assert_eq!(verifier_set.verifier_set.threshold, Uint128::from(2u128));
195+
196+
let mut signers = verifier_set.verifier_set.signers.values();
197+
let signer1 = signers.next().unwrap();
198+
let signer2 = signers.next().unwrap();
199+
200+
assert_eq!(signer1.pub_key.as_ref(), HexBinary::from_hex(
201+
"45e67eaf446e6c26eb3a2b55b64339ecf3a4d1d03180bee20eb5afdd23fa644f",
202+
)
203+
.unwrap().as_ref());
204+
assert_eq!(signer1.weight, Uint128::from(1u128));
205+
206+
assert_eq!(signer2.pub_key.as_ref(), HexBinary::from_hex(
207+
"dd9822c7fa239dda9913ebee813ecbe69e35d88ff651548d5cc42c033a8a667b",
208+
)
209+
.unwrap().as_ref());
210+
assert_eq!(signer2.weight, Uint128::from(1u128));
211+
}
212+
213+
fn verifier_set_poll_started_event(
214+
participants: Vec<TMAddress>,
215+
expires_at: u64,
216+
) -> PollStarted {
217+
PollStarted::VerifierSet {
218+
metadata: PollMetadata {
219+
poll_id: "100".parse().unwrap(),
220+
source_chain: "multiversx".parse().unwrap(),
221+
source_gateway_address: "SP2N959SER36FZ5QT1CX9BR63W3E8X35WQCMBYYWC.axelar-gateway"
222+
.parse()
223+
.unwrap(),
224+
confirmation_height: 15,
225+
expires_at,
226+
participants: participants
227+
.into_iter()
228+
.map(|addr| cosmwasm_std::Addr::unchecked(addr.to_string()))
229+
.collect(),
230+
},
231+
verifier_set: VerifierSetConfirmation {
232+
tx_id: "0xee0049faf8dde5507418140ed72bd64f73cc001b08de98e0c16a3a8d9f2c38cf"
233+
.parse()
234+
.unwrap(),
235+
event_index: 1,
236+
verifier_set: build_verifier_set(KeyType::Ecdsa, &ecdsa_test_data::signers()),
237+
},
238+
}
239+
}
240+
241+
fn participants(n: u8, worker: Option<TMAddress>) -> Vec<TMAddress> {
242+
(0..n)
243+
.map(|_| TMAddress::random(PREFIX))
244+
.chain(worker)
245+
.collect()
246+
}
247+
}

ampd/src/lib.rs

+13
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,19 @@ where
403403
),
404404
event_processor_config.clone(),
405405
),
406+
handlers::config::Config::StacksVerifierSetVerifier {
407+
cosmwasm_contract,
408+
http_url,
409+
} => self.create_handler_task(
410+
"stacks-verifier-set-verifier",
411+
handlers::stacks_verify_verifier_set::Handler::new(
412+
verifier.clone(),
413+
cosmwasm_contract,
414+
Client::new_http(http_url.to_string().trim_end_matches('/').into()),
415+
self.block_height_monitor.latest_block_height(),
416+
),
417+
event_processor_config.clone(),
418+
),
406419
};
407420
self.event_processor = self.event_processor.add_task(task);
408421
}

ampd/src/stacks/error.rs

+4
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,8 @@ use thiserror::Error;
44
pub enum Error {
55
#[error("required property is empty")]
66
PropertyEmpty,
7+
#[error("invalid encoding")]
8+
InvalidEncoding,
9+
#[error("provided key is not ecdsa")]
10+
NotEcdsaKey,
711
}

ampd/src/stacks/http_client.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ impl Client {
8383
.collect()
8484
}
8585

86-
async fn get_valid_transaction(&self, tx_hash: &Hash) -> Option<Transaction> {
86+
pub async fn get_valid_transaction(&self, tx_hash: &Hash) -> Option<Transaction> {
8787
self.get_transaction(tx_hash.to_string().as_str())
8888
.await
8989
.ok()

0 commit comments

Comments
 (0)