From bb2c7d66028af5b5e3b04ede80bfafaffed6b921 Mon Sep 17 00:00:00 2001 From: Andrew Plaza Date: Thu, 15 Feb 2024 18:28:34 -0500 Subject: [PATCH] add timestamp to ethr builder (#48) * add timestamp to ethr builder * improve coverage * add `get_query_value` fn to did_url * clippy * use AsRef * timestamp -> block_timestamp, always return error * store block timestamp in seconds * fix bugs w.r.t byte conversions * remove constant use std::time::Duration * fmt --- Cargo.lock | 1 + lib/Cargo.toml | 1 + lib/src/error.rs | 7 ++ lib/src/resolver.rs | 62 ++++++++++-- lib/src/rpc/methods.rs | 7 +- lib/src/types.rs | 2 +- lib/src/types/did_url.rs | 19 ++++ lib/src/types/ethr.rs | 154 ++++++++++++++++++++++-------- lib/src/types/xmtp.rs | 27 +++++- lib/tests/integration_test.rs | 110 ++++++++------------- lib/tests/integration_util/mod.rs | 39 +++++++- resolver/src/argenv.rs | 16 ++-- 12 files changed, 310 insertions(+), 135 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4f0d63b..2eef657 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2580,6 +2580,7 @@ dependencies = [ "peg", "percent-encoding", "rand 0.8.5", + "regex", "rustc-hex", "serde", "serde_json", diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 16af6c1..f3bd624 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -50,6 +50,7 @@ tokio-test = "0.4" futures = "0.3" ctor = "0.2.5" surf = "2.3" +regex = "1.10" [features] default = [] diff --git a/lib/src/error.rs b/lib/src/error.rs index f48af7a..02d2a50 100644 --- a/lib/src/error.rs +++ b/lib/src/error.rs @@ -3,6 +3,7 @@ use ethers::{ contract::ContractError, providers::{Middleware, ProviderError}, signers::WalletError, + types::U64, }; use jsonrpsee::types::ErrorObjectOwned; use thiserror::Error; @@ -16,6 +17,12 @@ pub enum ResolverError { ContractError(#[from] ContractError), #[error("{0}")] Middleware(String), + #[error("Block {0} containing Registry event not found")] + MissingBlock(U64), + #[error(transparent)] + Time(#[from] ethers::core::types::TimeError), + #[error("Block {0} timestamp out of range")] + TimestampOutOfRange(U64), } /// Errors originating from the parsing of a did url identifier, [`Did`](crate::types::DidUrl) diff --git a/lib/src/resolver.rs b/lib/src/resolver.rs index 7559ac6..aff92cf 100644 --- a/lib/src/resolver.rs +++ b/lib/src/resolver.rs @@ -28,6 +28,32 @@ impl From> for Resolver { } } +/// Extra context passed to the document builder from the [`Resolver`] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct EventContext { + /// the timestamp in seconds in which the block from the document was built. + pub block_timestamp: u64, +} + +impl EventContext { + pub async fn new( + meta: &LogMeta, + signer: impl Middleware, + ) -> Result> { + let block = signer + .get_block(meta.block_number) + .await + .map_err(|e| ResolverError::Middleware(e.to_string()))?; + + let block_timestamp: u64 = block + .ok_or(ResolverError::MissingBlock(meta.block_number))? + .timestamp + .as_u64(); + + Ok(Self { block_timestamp }) + } +} + impl Resolver { /// Instantiate a new did:ethr resolver pub async fn new(middleware: M, registry: Address) -> Result> { @@ -83,21 +109,22 @@ impl Resolver { Ok(history) } - fn dispatch_event( + async fn dispatch_event( &self, doc: &mut EthrBuilder, address: H160, event: DIDRegistryEvents, meta: LogMeta, - ) { + ) -> Result<(), ResolverError> { + let context = EventContext::new(&meta, self.signer()).await?; let res = match event { DIDRegistryEvents::DiddelegateChangedFilter(delegate_changed) => { log::trace!("Delegate Changed {:?}", delegate_changed); - doc.delegate_event(delegate_changed) + doc.delegate_event(delegate_changed, &context) } DIDRegistryEvents::DidattributeChangedFilter(attribute_event) => { log::trace!("Attribute Changed {:?}", attribute_event); - doc.attribute_event(attribute_event) + doc.attribute_event(attribute_event, &context) } DIDRegistryEvents::DidownerChangedFilter(owner_changed) => { log::trace!("Owner Changed {:?}", owner_changed); @@ -116,6 +143,7 @@ impl Resolver { address, meta.block_number, meta.log_index, e, ); }; + Ok(()) } async fn wrap_did_resolution( @@ -150,7 +178,8 @@ impl Resolver { if version_id.unwrap_or_default() > U64::zero() { if meta.block_number <= version_id.unwrap_or_default() { // 1. delegate events - Resolver::dispatch_event(self, &mut base_document, address, event, meta); + Resolver::dispatch_event(self, &mut base_document, address, event, meta) + .await?; // 2. set latest version if current_version_id < block_number { current_version_id = block_number; @@ -162,7 +191,7 @@ impl Resolver { } } else { // 1. delegate events - Resolver::dispatch_event(self, &mut base_document, address, event, meta); + Resolver::dispatch_event(self, &mut base_document, address, event, meta).await?; // 2. set latest version if current_version_id < block_number { current_version_id = block_number; @@ -216,6 +245,8 @@ impl Resolver { #[cfg(test)] mod tests { + use ethers::{prelude::Provider, providers::MockProvider, types::TxHash}; + use super::*; #[test] @@ -225,4 +256,23 @@ mod tests { let resolver = Resolver::from(registry); assert_eq!(resolver.registry.address(), Address::zero()); } + + #[tokio::test] + async fn test_context_constructor() { + let (provider, mock) = Provider::mocked(); + mock.push(Block::::default()).unwrap(); + + let meta = LogMeta { + address: Address::zero(), + block_hash: H256::zero(), + block_number: U64::zero(), + log_index: U256::zero(), + transaction_hash: H256::zero(), + transaction_index: U64::zero(), + }; + let context = EventContext::new::>(&meta, Arc::new(provider)) + .await + .unwrap(); + assert_eq!(context.block_timestamp, 0); + } } diff --git a/lib/src/rpc/methods.rs b/lib/src/rpc/methods.rs index a9449c4..ea2a727 100644 --- a/lib/src/rpc/methods.rs +++ b/lib/src/rpc/methods.rs @@ -34,6 +34,8 @@ impl DidRegistryServer for DidRegistryMethods { ) -> Result { log::debug!("did_resolveDid called"); + log::trace!("Resolving for key {}", &address); + // parse the version_id let parsed_version_id = version_id.map(|str| U64::from(u64::from_str(&str).unwrap())); @@ -43,9 +45,10 @@ impl DidRegistryServer for DidRegistryMethods { H160::from_str(&address).map_err(RpcError::from)?, parsed_version_id, ) - .await?; + .await; + log::debug!("Resolution Result {:?}", resolution_result); - Ok(resolution_result) + Ok(resolution_result?) } } diff --git a/lib/src/types.rs b/lib/src/types.rs index 531b217..33561ac 100644 --- a/lib/src/types.rs +++ b/lib/src/types.rs @@ -272,7 +272,7 @@ impl From for [u8; 32] { // internal function to fill a [u8; 32] with bytes. // anything over 32 bytes will be cutoff. -fn string_to_bytes32>(s: S) -> [u8; 32] { +pub fn string_to_bytes32>(s: S) -> [u8; 32] { let s = s.as_ref(); let mut attr_bytes: [u8; 32] = [b' '; 32]; let length = std::cmp::min(s.as_bytes().len(), 32); diff --git a/lib/src/types/did_url.rs b/lib/src/types/did_url.rs index fdf50e6..e33179f 100644 --- a/lib/src/types/did_url.rs +++ b/lib/src/types/did_url.rs @@ -300,6 +300,15 @@ impl DidUrl { minion } + /// get a query value based on its `key` + pub fn get_query_value>(&self, key: S) -> Option { + self.query.as_ref().and_then(|q| { + q.iter() + .find(|(k, _)| k == key.as_ref()) + .map(|(_, v)| v.to_string()) + }) + } + /// Immutable copy constructor to add a query parameter to the DID URL. /// # Examples /// ```rust @@ -745,4 +754,14 @@ mod tests { assert_eq!(Network::Sepolia.to_string(), "sepolia"); assert_eq!(Network::Other(0x1a1).to_string(), "417"); } + + #[test] + fn test_get_query_value() { + let did_url = DidUrl::parse("did:ethr:mainnet:0x0000000000000000000000000000000000000000?meta=hi&username=&password=hunter2").unwrap(); + + assert_eq!(did_url.get_query_value("meta"), Some("hi".into())); + assert_eq!(did_url.get_query_value("username"), Some("".into())); + assert_eq!(did_url.get_query_value("password"), Some("hunter2".into())); + assert_eq!(did_url.get_query_value("does_not_exist"), None); + } } diff --git a/lib/src/types/ethr.rs b/lib/src/types/ethr.rs index 09634fd..c01b3e2 100644 --- a/lib/src/types/ethr.rs +++ b/lib/src/types/ethr.rs @@ -16,8 +16,11 @@ use super::{ }; use crate::{ error::EthrBuilderError, - resolver::did_registry::{ - DidattributeChangedFilter, DiddelegateChangedFilter, DidownerChangedFilter, + resolver::{ + did_registry::{ + DidattributeChangedFilter, DiddelegateChangedFilter, DidownerChangedFilter, + }, + EventContext, }, types::{self, NULL_ADDRESS}, }; @@ -78,7 +81,7 @@ pub struct EthrBuilder { /// Map of keys to their index in the document /// _*NOTE*_: this is used to ensure the order of keys is maintained, but indexes of the same /// number are expected. (EX: a delegate and service both with index 0) - pub(super) keys: HashMap, + pub(super) keys: HashMap, } impl Default for EthrBuilder { @@ -139,6 +142,7 @@ impl EthrBuilder { Ok(()) } + /// check whether the document has been deactivated pub fn is_deactivated(&mut self) -> bool { self.is_deactivated } @@ -152,6 +156,7 @@ impl EthrBuilder { pub fn delegate_event( &mut self, event: DiddelegateChangedFilter, + context: &EventContext, ) -> Result<(), EthrBuilderError> { let delegate_type = String::from_utf8_lossy(&event.delegate_type); let key_purpose = types::parse_delegate(&delegate_type)?; @@ -167,7 +172,8 @@ impl EthrBuilder { return Ok(()); } - self.keys.insert(key, self.delegate_count); + self.keys + .insert(key, (self.delegate_count, context.clone())); self.delegate_count += 1; Ok(()) @@ -180,6 +186,7 @@ impl EthrBuilder { pub fn attribute_event( &mut self, event: DidattributeChangedFilter, + context: &EventContext, ) -> Result<(), EthrBuilderError> { let name = event.name_string_lossy(); let attribute = types::parse_attribute(&name).unwrap_or(Attribute::Other(name.to_string())); @@ -208,19 +215,20 @@ impl EthrBuilder { match attribute { Attribute::PublicKey(_) => { if event.is_valid(&self.now) { - self.keys.insert(key, self.delegate_count); + self.keys + .insert(key, (self.delegate_count, context.clone())); } self.delegate_count += 1; } Attribute::Service(_) => { if event.is_valid(&self.now) { - self.keys.insert(key, self.service_count); + self.keys.insert(key, (self.service_count, context.clone())); } self.service_count += 1; } Attribute::Xmtp(_) => { if event.is_valid(&self.now) { - self.keys.insert(key, self.xmtp_count); + self.keys.insert(key, (self.xmtp_count, context.clone())); } self.xmtp_count += 1; } @@ -313,8 +321,13 @@ impl EthrBuilder { value: V, encoding: KeyEncoding, ) -> Result, EthrBuilderError> { - let value = hex::decode(value.as_ref())?; - Ok(match encoding { + log::debug!( + "decoding attribute value {:?} with encoding: {}", + value.as_ref(), + encoding + ); + + let enc = match encoding { KeyEncoding::Hex => Some(VerificationMethodProperties::PublicKeyHex { public_key_hex: hex::encode(value), }), @@ -324,7 +337,9 @@ impl EthrBuilder { KeyEncoding::Base58 => Some(VerificationMethodProperties::PublicKeyBase58 { public_key_base58: bs58::encode(value).into_string(), }), - }) + }; + log::debug!("Encoded {:?}", enc); + Ok(enc) } /// Adds a delegate to the document @@ -413,10 +428,13 @@ impl EthrBuilder { } fn build_keys(&mut self) -> Result<(), EthrBuilderError> { - let mut keys = self.keys.drain().collect::>(); - keys.sort_by_key(|k| k.1); + let mut keys = self + .keys + .drain() + .collect::>(); + keys.sort_by_key(|(_, (index, _))| *index); - for (key, index) in keys { + for (key, (index, context)) in keys { match key { Key::Attribute { value, attribute, .. @@ -427,7 +445,7 @@ impl EthrBuilder { Attribute::Service(service) => { self.service(index, value, service)?; } - Attribute::Xmtp(xmtp) => self.xmtp_key(index, value, xmtp)?, + Attribute::Xmtp(xmtp) => self.xmtp_key(index, value, xmtp, &context)?, Attribute::Other(_) => (), }, Key::Delegate { delegate, purpose } => { @@ -445,6 +463,12 @@ pub(crate) mod tests { use super::*; use crate::types::test::address; + impl EventContext { + pub fn mock(block_timestamp: u64) -> Self { + Self { block_timestamp } + } + } + //TODO: dids are case-sensitive w.r.t their addresses. One did should equal the other, no //matter the case of the address (other than blockchain_account_id b/c of EIP55) pub fn base_attr_changed( @@ -466,14 +490,19 @@ pub(crate) mod tests { let event = DidattributeChangedFilter { name: *b"did/pub/Secp256k1/veriKey/hex ", - value: b"02b97c30de767f084ce3080168ee293053ba33b235d7116a3263d29f1450936b71".into(), + value: hex::decode( + "02b97c30de767f084ce3080168ee293053ba33b235d7116a3263d29f1450936b71", + ) + .unwrap() + .into(), ..base_attr_changed(identity, None) }; let mut builder = EthrBuilder::default(); builder.account_address(&identity).unwrap(); builder.now(U256::zero()); - builder.attribute_event(event).unwrap(); + let context = EventContext::mock(0); + builder.attribute_event(event, &context).unwrap(); let doc = builder.build().unwrap(); assert_eq!( doc.verification_method[1], @@ -496,14 +525,18 @@ pub(crate) mod tests { let identity = address("0x7e575682a8e450e33eb0493f9972821ae333cd7f"); let event = DidattributeChangedFilter { name: *b"did/pub/Ed25519/veriKey/base58 ", - value: b"b97c30de767f084ce3080168ee293053ba33b235d7116a3263d29f1450936b71".into(), + value: hex::decode("b97c30de767f084ce3080168ee293053ba33b235d7116a3263d29f1450936b71") + .unwrap() + .into(), ..base_attr_changed(identity, None) }; let mut builder = EthrBuilder::default(); builder.account_address(&identity).unwrap(); builder.now(U256::zero()); - builder.attribute_event(event).unwrap(); + builder + .attribute_event(event, &EventContext::mock(0)) + .unwrap(); let doc = builder.build().unwrap(); assert_eq!( doc.verification_method[1], @@ -525,14 +558,16 @@ pub(crate) mod tests { let identity = address("0x7e575682a8e450e33eb0493f9972821ae333cd7f"); let event = DidattributeChangedFilter { name: *b"did/pub/X25519/enc/base64 ", - value: b"302a300506032b656e032100118557777ffb078774371a52b00fed75561dcf975e61c47553e664a617661052".into(), + value: hex::decode("302a300506032b656e032100118557777ffb078774371a52b00fed75561dcf975e61c47553e664a617661052").unwrap().into(), ..base_attr_changed(identity, None) }; let mut builder = EthrBuilder::default(); builder.account_address(&identity).unwrap(); builder.now(U256::zero()); - builder.attribute_event(event).unwrap(); + builder + .attribute_event(event, &EventContext::mock(0)) + .unwrap(); let doc = builder.build().unwrap(); assert_eq!( doc.verification_method[1], @@ -562,7 +597,9 @@ pub(crate) mod tests { let mut builder = EthrBuilder::default(); builder.account_address(&identity).unwrap(); builder.now(U256::zero()); - builder.attribute_event(event).unwrap(); + builder + .attribute_event(event, &EventContext::mock(0)) + .unwrap(); let doc = builder.build().unwrap(); assert_eq!( doc.service, @@ -582,17 +619,17 @@ pub(crate) mod tests { let events = vec![ DidattributeChangedFilter { name: *b"did/pub/Secp256k1/veriKey/hex ", - value: b"02b97c30de767f084ce3080168ee293053ba33b235d7116a3263d29f1450936b71".into(), + value: hex::decode("02b97c30de767f084ce3080168ee293053ba33b235d7116a3263d29f1450936b71").unwrap().into(), ..base_attr_changed(identity, None) }, DidattributeChangedFilter { name: *b"did/pub/Secp256k1/sigAuth/base58", - value: b"b97c30de767f084ce3080168ee293053ba33b235d7116a3263d29f1450936b71".into(), + value: hex::decode("b97c30de767f084ce3080168ee293053ba33b235d7116a3263d29f1450936b71").unwrap().into(), ..base_attr_changed(identity, None) }, DidattributeChangedFilter { name: *b"did/pub/X25519/enc/base64 ", - value: b"302a300506032b656e032100118557777ffb078774371a52b00fed75561dcf975e61c47553e664a617661052".into(), + value: hex::decode("302a300506032b656e032100118557777ffb078774371a52b00fed75561dcf975e61c47553e664a617661052").unwrap().into(), ..base_attr_changed(identity, None) }, DidattributeChangedFilter { @@ -612,7 +649,9 @@ pub(crate) mod tests { builder.now(U256::zero()); for event in events { - builder.attribute_event(event).unwrap(); + builder + .attribute_event(event, &EventContext::mock(0)) + .unwrap(); } let doc = builder.build().unwrap(); @@ -638,17 +677,17 @@ pub(crate) mod tests { let events = vec![ DidattributeChangedFilter { name: *b"did/pub/Secp256k1/veriKey/hex ", - value: b"02b97c30de767f084ce3080168ee293053ba33b235d7116a3263d29f1450936b71".into(), + value: hex::decode("02b97c30de767f084ce3080168ee293053ba33b235d7116a3263d29f1450936b71").unwrap().into(), ..base_attr_changed(identity, None) }, DidattributeChangedFilter { name: *b"did/pub/Secp256k1/sigAuth/base58", - value: b"b97c30de767f084ce3080168ee293053ba33b235d7116a3263d29f1450936b71".into(), + value: hex::decode("b97c30de767f084ce3080168ee293053ba33b235d7116a3263d29f1450936b71").unwrap().into(), ..base_attr_changed(identity, Some(10)) }, DidattributeChangedFilter { name: *b"did/pub/X25519/enc/base64 ", - value: b"302a300506032b656e032100118557777ffb078774371a52b00fed75561dcf975e61c47553e664a617661052".into(), + value: hex::decode("302a300506032b656e032100118557777ffb078774371a52b00fed75561dcf975e61c47553e664a617661052").unwrap().into(), ..base_attr_changed(identity, None) }, DidattributeChangedFilter { @@ -667,7 +706,9 @@ pub(crate) mod tests { builder.account_address(&identity).unwrap(); builder.now(U256::from(100)); for event in events { - builder.attribute_event(event).unwrap(); + builder + .attribute_event(event, &EventContext::mock(0)) + .unwrap(); } let doc = builder.build().unwrap(); @@ -726,7 +767,9 @@ pub(crate) mod tests { builder.account_address(&identity).unwrap(); builder.now(U256::zero()); for event in events { - builder.delegate_event(event).unwrap(); + builder + .delegate_event(event, &EventContext::mock(0)) + .unwrap(); } let doc = builder.build().unwrap(); @@ -802,7 +845,9 @@ pub(crate) mod tests { builder.account_address(&identity).unwrap(); builder.now(U256::zero()); for event in &events { - builder.delegate_event(event.clone()).unwrap(); + builder + .delegate_event(event.clone(), &EventContext::mock(0)) + .unwrap(); } // both events are valid @@ -812,7 +857,9 @@ pub(crate) mod tests { builder.account_address(&identity).unwrap(); builder.now(U256::from(75)); for event in &events { - builder.delegate_event(event.clone()).unwrap(); + builder + .delegate_event(event.clone(), &EventContext::mock(0)) + .unwrap(); } // only one event is valid assert_eq!(builder.keys.len(), 1); @@ -821,7 +868,9 @@ pub(crate) mod tests { builder.account_address(&identity).unwrap(); builder.now(U256::from(125)); for event in &events { - builder.delegate_event(event.clone()).unwrap(); + builder + .delegate_event(event.clone(), &EventContext::mock(0)) + .unwrap(); } // no events valid @@ -834,12 +883,20 @@ pub(crate) mod tests { let attributes = vec![ DidattributeChangedFilter { name: *b"did/pub/Secp256k1/veriKey/hex ", - value: b"02b97c30de767f084ce3080168ee293053ba33b235d7116a3263d29f1450936b71".into(), + value: hex::decode( + "02b97c30de767f084ce3080168ee293053ba33b235d7116a3263d29f1450936b71", + ) + .unwrap() + .into(), ..base_attr_changed(identity, None) }, DidattributeChangedFilter { name: *b"did/pub/Secp256k1/sigAuth/base58", - value: b"b97c30de767f084ce3080168ee293053ba33b235d7116a3263d29f1450936b71".into(), + value: hex::decode( + "b97c30de767f084ce3080168ee293053ba33b235d7116a3263d29f1450936b71", + ) + .unwrap() + .into(), ..base_attr_changed(identity, None) }, ]; @@ -864,10 +921,19 @@ pub(crate) mod tests { builder.account_address(&identity).unwrap(); builder.now(U256::zero()); - builder.attribute_event(attributes[0].clone()).unwrap(); - builder.delegate_event(delegates[0].clone()).unwrap(); - builder.attribute_event(attributes[1].clone()).unwrap(); - builder.delegate_event(delegates[1].clone()).unwrap(); + let context = EventContext::mock(0); + builder + .attribute_event(attributes[0].clone(), &context) + .unwrap(); + builder + .delegate_event(delegates[0].clone(), &context) + .unwrap(); + builder + .attribute_event(attributes[1].clone(), &context) + .unwrap(); + builder + .delegate_event(delegates[1].clone(), &context) + .unwrap(); let doc = builder.build().unwrap(); @@ -932,7 +998,11 @@ pub(crate) mod tests { let identity = address("0x7e575682a8e450e33eb0493f9972821ae333cd7f"); let event = DidattributeChangedFilter { name: *b"test/random/attribute99999999 ", - value: b"02b97c30de767f084ce3080168ee293053ba33b235d7116a3263d29f1450936b71".into(), + value: hex::decode( + "02b97c30de767f084ce3080168ee293053ba33b235d7116a3263d29f1450936b71", + ) + .unwrap() + .into(), ..base_attr_changed(identity, None) }; @@ -940,7 +1010,9 @@ pub(crate) mod tests { builder.account_address(&identity).unwrap(); builder.now(U256::zero()); - builder.attribute_event(event).unwrap(); + builder + .attribute_event(event, &EventContext::mock(0)) + .unwrap(); let doc = builder.build().unwrap(); diff --git a/lib/src/types/xmtp.rs b/lib/src/types/xmtp.rs index fae6399..5fd2f8e 100644 --- a/lib/src/types/xmtp.rs +++ b/lib/src/types/xmtp.rs @@ -17,8 +17,9 @@ use super::{string_to_bytes32, Attribute, EthrBuilder, KeyEncoding, KeyType, VerificationMethod}; use crate::error::EthrBuilderError; -use std::fmt; +use std::{fmt, time::Duration}; +use crate::resolver::EventContext; use serde::{Deserialize, Serialize}; /// The XMTP Attribute Type @@ -83,12 +84,18 @@ impl EthrBuilder { index: usize, value: V, key: XmtpAttribute, + context: &EventContext, ) -> Result<(), EthrBuilderError> { log::debug!("index: {}", index); + + let timestamp_ns = Duration::from_secs(context.block_timestamp).as_nanos(); + let did_url = self .id .with_fragment(Some(&format!("xmtp-{}", index))) - .with_query("meta", Some(&key.purpose.to_string())); + .with_query("meta", Some(&key.purpose.to_string())) + .with_query("timestamp", Some(×tamp_ns.to_string())); + let method = VerificationMethod { id: did_url, controller: self.id.clone(), @@ -131,7 +138,9 @@ mod test { builder.now(U256::zero()); for attr in attributes { - builder.attribute_event(attr).unwrap() + builder + .attribute_event(attr, &EventContext::mock(10_000)) + .unwrap() } let doc = builder.build().unwrap(); @@ -139,7 +148,13 @@ mod test { assert_eq!(doc.verification_method[1].id.fragment().unwrap(), "xmtp-0"); assert_eq!( doc.verification_method[1].id.query().unwrap(), - vec![("meta".to_string(), "installation".to_string())] + vec![ + ("meta".to_string(), "installation".to_string()), + ( + "timestamp".to_string(), + Duration::from_secs(10000).as_nanos().to_string() + ) + ] ); assert_eq!(doc.verification_method.len(), 2); @@ -160,7 +175,9 @@ mod test { builder.now(U256::from(100)); for attr in attributes { - builder.attribute_event(attr).unwrap() + builder + .attribute_event(attr, &EventContext::mock(10_000)) + .unwrap() } assert_eq!(builder.xmtp_count, 1); diff --git a/lib/tests/integration_test.rs b/lib/tests/integration_test.rs index 62c0586..89cd97f 100644 --- a/lib/tests/integration_test.rs +++ b/lib/tests/integration_test.rs @@ -7,7 +7,8 @@ use ethers::{ signers::{LocalWallet, Signer as _}, types::{Address, U256}, }; -use integration_util::{validate_document, with_client}; +use integration_util::{revoke_attribute, set_attribute, validate_document, with_client}; +use regex::Regex; #[cfg(test)] mod it { @@ -24,21 +25,8 @@ mod it { pub async fn test_attributes() -> Result<()> { with_client(None, |client, registry, signer, _| async move { let me = signer.address(); - let did = registry.set_attribute( - me, - *b"did/pub/Secp256k1/veriKey/hex ", - b"02b97c30de767f084ce3080168ee293053ba33b235d7116a3263d29f1450936b71".into(), - U256::from(604_800), - ); - did.send().await?.await?; - - let did = registry.set_attribute( - me, - *b"did/pub/Ed25519/veriKey/base64 ", - b"302a300506032b656e032100118557777ffb078774371a52b00fed75561dcf975e61c47553e664a617661052".into(), - U256::from(604_800), - ); - did.send().await?.await?; + set_attribute(®istry, me, "did/pub/Secp256k1/veriKey/hex", "02b97c30de767f084ce3080168ee293053ba33b235d7116a3263d29f1450936b71", 604_800).await?; + set_attribute(®istry, me, "did/pub/Ed25519/veriKey/base64", "302a300506032b656e032100118557777ffb078774371a52b00fed75561dcf975e61c47553e664a617661052", 604_800).await?; let resolution_response = client.resolve_did(hex::encode(me), None).await?; validate_document(&resolution_response.document).await; @@ -57,8 +45,7 @@ mod it { assert_eq!( resolution_response.document.verification_method[1].verification_properties, Some(VerificationMethodProperties::PublicKeyHex { - public_key_hex: - "02b97c30de767f084ce3080168ee293053ba33b235d7116a3263d29f1450936b71".to_string() + public_key_hex: "02b97c30de767f084ce3080168ee293053ba33b235d7116a3263d29f1450936b71".to_string() }) ); @@ -81,9 +68,8 @@ mod it { .to_string() }) ); - assert_eq!( - resolution_response.metadata.clone().deactivated, - false + assert!( + !resolution_response.metadata.clone().deactivated ); assert_eq!( resolution_response.metadata.clone().version_id, @@ -103,28 +89,14 @@ mod it { pub async fn test_attributes_versions() -> Result<()> { with_client(None, |client, registry, signer, _| async move { let me = signer.address(); - let did = registry.set_attribute( - me, - *b"did/pub/Secp256k1/veriKey/hex ", - b"02b97c30de767f084ce3080168ee293053ba33b235d7116a3263d29f1450936b71".into(), - U256::from(604_800), - ); - did.send().await?.await?; - - let did = registry.set_attribute( - me, - *b"did/pub/Ed25519/veriKey/base64 ", - b"302a300506032b656e032100118557777ffb078774371a52b00fed75561dcf975e61c47553e664a617661052".into(), - U256::from(604_800), - ); - did.send().await?.await?; + set_attribute(®istry, me,"did/pub/Secp256k1/veriKey/hex", "02b97c30de767f084ce3080168ee293053ba33b235d7116a3263d29f1450936b71", 604_800).await?; + set_attribute(®istry, me, "did/pub/Ed25519/veriKey/base64", "302a300506032b656e032100118557777ffb078774371a52b00fed75561dcf975e61c47553e664a617661052", 604_800).await?; let resolution_response = client.resolve_did(hex::encode(me), Some::("2".to_string())).await?; validate_document(&resolution_response.document).await; - assert_eq!( - resolution_response.metadata.clone().deactivated, - false + assert!( + !resolution_response.metadata.clone().deactivated ); assert_eq!( resolution_response.metadata.clone().version_id, @@ -226,20 +198,22 @@ mod it { pub async fn test_attribute_revocation() -> Result<()> { with_client(None, |client, registry, signer, _| async move { let me = signer.address(); - let did = registry.set_attribute( - me, - *b"did/pub/Secp256k1/veriKey/hex ", - b"02b97c30de767f084ce3080168ee293053ba33b235d7116a3263d29f1450936b71".into(), - U256::from(604_800), - ); - did.send().await?.await?; - let did = registry.revoke_attribute( + set_attribute( + ®istry, me, - *b"did/pub/Secp256k1/veriKey/hex ", - b"02b97c30de767f084ce3080168ee293053ba33b235d7116a3263d29f1450936b71".into(), - ); - did.send().await?.await?; + "did/pub/Secp256k1/veriKey/hex", + "02b97c30de767f084ce3080168ee293053ba33b235d7116a3263d29f1450936b71", + 604_800, + ) + .await?; + revoke_attribute( + ®istry, + me, + "did/pub/Secp256k1/veriKey/hex", + "02b97c30de767f084ce3080168ee293053ba33b235d7116a3263d29f1450936b71", + ) + .await?; let document = client.resolve_did(hex::encode(me), None).await?.document; validate_document(&document).await; @@ -323,31 +297,31 @@ mod it { pub async fn test_xmtp_revocation() -> Result<()> { with_client(None, |client, registry, signer, _| async move { let me = signer.address(); - let attribute_name = *b"xmtp/installation/hex "; - let did = registry.set_attribute( + let attribute_name = "xmtp/installation/hex "; + set_attribute( + ®istry, me, attribute_name, - b"02b97c30de767f084ce3080168ee293053ba33b235d7116a3263d29f1450936b71".into(), - U256::from(604_800), - ); - did.send().await?.await?; + "02b97c30de767f084ce3080168ee293053ba33b235d7116a3263d29f1450936b71", + 604_800, + ) + .await?; let document = client.resolve_did(hex::encode(me), None).await?.document; - assert_eq!( - document.verification_method[1].id, - DidUrl::parse(format!( - "did:ethr:0x{}?meta=installation#xmtp-0", - hex::encode(me) - )) - .unwrap() + let regexr = format!( + r"did:ethr:mainnet:0x{}\?meta=installation×tamp=\d+#xmtp-0", + hex::encode(me) ); + let test = Regex::new(®exr).unwrap(); + assert!(test.is_match(&document.verification_method[1].id.to_string())); - let did = registry.revoke_attribute( + revoke_attribute( + ®istry, me, attribute_name, - b"02b97c30de767f084ce3080168ee293053ba33b235d7116a3263d29f1450936b71".into(), - ); - did.send().await?.await?; + "02b97c30de767f084ce3080168ee293053ba33b235d7116a3263d29f1450936b71", + ) + .await?; let document = client.resolve_did(hex::encode(me), None).await?.document; validate_document(&document).await; diff --git a/lib/tests/integration_util/mod.rs b/lib/tests/integration_util/mod.rs index 64a9c31..92b8e59 100644 --- a/lib/tests/integration_util/mod.rs +++ b/lib/tests/integration_util/mod.rs @@ -1,5 +1,7 @@ //! Shared setup code for integration tests use ethers::providers::Middleware; +use ethers::types::{Bytes, H160}; +use hex::FromHex; use std::sync::{Arc, Once}; use std::{future::Future, time::Duration}; use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Registry}; @@ -11,7 +13,7 @@ use ethers::{ middleware::SignerMiddleware, providers::{Provider, Ws}, signers::{LocalWallet, Signer as _}, - types::Address, + types::{Address, U256}, }; use futures::future::FutureExt; use jsonrpsee::{ @@ -19,8 +21,8 @@ use jsonrpsee::{ ws_client::{WsClient, WsClientBuilder}, }; use lib_didethresolver::{ - did_registry::DIDRegistry, rpc::DidRegistryMethods, types::DidDocument, DidRegistryServer, - Resolver, + did_registry::DIDRegistry, rpc::DidRegistryMethods, types::string_to_bytes32, + types::DidDocument, DidRegistryServer, Resolver, }; use serde::{Deserialize, Serialize}; use tokio::time::timeout as timeout_tokio; @@ -109,7 +111,7 @@ where async fn deploy_to_anvil(anvil: &AnvilInstance) -> Address { let wallet: LocalWallet = anvil.keys()[0].clone().into(); - let client = client(&anvil, wallet).await; + let client = client(anvil, wallet).await; let registry = DIDRegistry::deploy(client.clone(), ()) .unwrap() @@ -164,3 +166,32 @@ pub async fn validate_document(document: &DidDocument) { } assert!(response.valid); } + +pub async fn set_attribute( + registry: &DIDRegistry, + did: H160, + key: &str, + value: &str, + validity: u64, +) -> Result<()> { + let attribute = registry.set_attribute( + did, + string_to_bytes32(key), + Bytes::from_hex(value)?, + U256::from(validity), + ); + + attribute.send().await?.await?; + Ok(()) +} + +pub async fn revoke_attribute( + registry: &DIDRegistry, + did: H160, + key: &str, + value: &str, +) -> Result<()> { + let attribute = registry.revoke_attribute(did, string_to_bytes32(key), Bytes::from_hex(value)?); + attribute.send().await?.await?; + Ok(()) +} diff --git a/resolver/src/argenv.rs b/resolver/src/argenv.rs index f812bb7..59f4e42 100644 --- a/resolver/src/argenv.rs +++ b/resolver/src/argenv.rs @@ -53,12 +53,12 @@ mod tests { #[test] fn test_parse_host_arg() -> anyhow::Result<()> { let env = setup(); - let args = Args::parse_from(&["didethresolver", "-s", "host.xyz"]); + let args = Args::parse_from(["didethresolver", "-s", "host.xyz"]); assert_eq!(args.host, "host.xyz"); assert_eq!(args.port, DEFAULT_PORT); assert_eq!(args.rpc_url, DEFAULT_RPC_URL); assert_eq!(args.did_registry, DID_ETH_REGISTRY); - let args2 = Args::parse_from(&["didethresolver", "--host", "h.xyz"]); + let args2 = Args::parse_from(["didethresolver", "--host", "h.xyz"]); assert_eq!(args2.host, "h.xyz"); putback(env) } @@ -66,12 +66,12 @@ mod tests { #[test] fn test_parse_port_arg() -> anyhow::Result<()> { let env = setup(); - let args = Args::parse_from(&["didethresolver", "-p", "1234"]); + let args = Args::parse_from(["didethresolver", "-p", "1234"]); assert_eq!(args.host, DEFAULT_HOST); assert_eq!(args.port, 1234); assert_eq!(args.rpc_url, DEFAULT_RPC_URL); assert_eq!(args.did_registry, DID_ETH_REGISTRY); - let args2 = Args::parse_from(&["didethresolver", "--port", "4321"]); + let args2 = Args::parse_from(["didethresolver", "--port", "4321"]); assert_eq!(args2.host, DEFAULT_HOST); assert_eq!(args2.port, 4321); putback(env) @@ -80,12 +80,12 @@ mod tests { #[test] fn test_parse_rpc_url_arg() -> anyhow::Result<()> { let env = setup(); - let args = Args::parse_from(&["didethresolver", "-r", "http://rpc.xyz"]); + let args = Args::parse_from(["didethresolver", "-r", "http://rpc.xyz"]); assert_eq!(args.host, DEFAULT_HOST); assert_eq!(args.port, DEFAULT_PORT); assert_eq!(args.rpc_url, "http://rpc.xyz"); assert_eq!(args.did_registry, DID_ETH_REGISTRY); - let args2 = Args::parse_from(&["didethresolver", "--rpc-url", "http://rpc2.xyz"]); + let args2 = Args::parse_from(["didethresolver", "--rpc-url", "http://rpc2.xyz"]); assert_eq!(args2.host, DEFAULT_HOST); assert_eq!(args2.port, DEFAULT_PORT); assert_eq!(args2.rpc_url, "http://rpc2.xyz"); @@ -95,12 +95,12 @@ mod tests { #[test] fn test_parse_did_registry_arg() -> anyhow::Result<()> { let env = setup(); - let args = Args::parse_from(&["didethresolver", "-d", "0x1234567890"]); + let args = Args::parse_from(["didethresolver", "-d", "0x1234567890"]); assert_eq!(args.host, DEFAULT_HOST); assert_eq!(args.port, DEFAULT_PORT); assert_eq!(args.rpc_url, DEFAULT_RPC_URL); assert_eq!(args.did_registry, "0x1234567890"); - let args2 = Args::parse_from(&[ + let args2 = Args::parse_from([ "didethresolver", "--did-registry", "0x0987654321",