Skip to content

Commit

Permalink
add timestamp to ethr builder (#48)
Browse files Browse the repository at this point in the history
* add timestamp to ethr builder

* improve coverage

* add `get_query_value` fn to did_url

* clippy

* use AsRef<str>

* 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
  • Loading branch information
insipx authored Feb 15, 2024
1 parent c7f7452 commit bb2c7d6
Show file tree
Hide file tree
Showing 12 changed files with 310 additions and 135 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ tokio-test = "0.4"
futures = "0.3"
ctor = "0.2.5"
surf = "2.3"
regex = "1.10"

[features]
default = []
Expand Down
7 changes: 7 additions & 0 deletions lib/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use ethers::{
contract::ContractError,
providers::{Middleware, ProviderError},
signers::WalletError,
types::U64,
};
use jsonrpsee::types::ErrorObjectOwned;
use thiserror::Error;
Expand All @@ -16,6 +17,12 @@ pub enum ResolverError<M: Middleware> {
ContractError(#[from] ContractError<M>),
#[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)
Expand Down
62 changes: 56 additions & 6 deletions lib/src/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,32 @@ impl<M> From<DIDRegistry<M>> for Resolver<M> {
}
}

/// 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<M: Middleware>(
meta: &LogMeta,
signer: impl Middleware,
) -> Result<Self, ResolverError<M>> {
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<M: Middleware + 'static> Resolver<M> {
/// Instantiate a new did:ethr resolver
pub async fn new(middleware: M, registry: Address) -> Result<Self, ResolverError<M>> {
Expand Down Expand Up @@ -83,21 +109,22 @@ impl<M: Middleware + 'static> Resolver<M> {
Ok(history)
}

fn dispatch_event(
async fn dispatch_event(
&self,
doc: &mut EthrBuilder,
address: H160,
event: DIDRegistryEvents,
meta: LogMeta,
) {
) -> Result<(), ResolverError<M>> {
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);
Expand All @@ -116,6 +143,7 @@ impl<M: Middleware + 'static> Resolver<M> {
address, meta.block_number, meta.log_index, e,
);
};
Ok(())
}

async fn wrap_did_resolution(
Expand Down Expand Up @@ -150,7 +178,8 @@ impl<M: Middleware + 'static> Resolver<M> {
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;
Expand All @@ -162,7 +191,7 @@ impl<M: Middleware + 'static> Resolver<M> {
}
} 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;
Expand Down Expand Up @@ -216,6 +245,8 @@ impl<M: Middleware + 'static> Resolver<M> {

#[cfg(test)]
mod tests {
use ethers::{prelude::Provider, providers::MockProvider, types::TxHash};

use super::*;

#[test]
Expand All @@ -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::<TxHash>::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::<Provider<MockProvider>>(&meta, Arc::new(provider))
.await
.unwrap();
assert_eq!(context.block_timestamp, 0);
}
}
7 changes: 5 additions & 2 deletions lib/src/rpc/methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ impl<M: Middleware + 'static> DidRegistryServer for DidRegistryMethods<M> {
) -> Result<DidResolutionResult, ErrorObjectOwned> {
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()));

Expand All @@ -43,9 +45,10 @@ impl<M: Middleware + 'static> DidRegistryServer for DidRegistryMethods<M> {
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?)
}
}

Expand Down
2 changes: 1 addition & 1 deletion lib/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ impl From<Attribute> for [u8; 32] {

// internal function to fill a [u8; 32] with bytes.
// anything over 32 bytes will be cutoff.
fn string_to_bytes32<S: AsRef<str>>(s: S) -> [u8; 32] {
pub fn string_to_bytes32<S: AsRef<str>>(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);
Expand Down
19 changes: 19 additions & 0 deletions lib/src/types/did_url.rs
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,15 @@ impl DidUrl {
minion
}

/// get a query value based on its `key`
pub fn get_query_value<S: AsRef<str>>(&self, key: S) -> Option<String> {
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
Expand Down Expand Up @@ -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);
}
}
Loading

0 comments on commit bb2c7d6

Please sign in to comment.