Skip to content

Commit

Permalink
grant_installation (#50)
Browse files Browse the repository at this point in the history
  • Loading branch information
tsachiherman authored Jan 29, 2024
1 parent dc93376 commit d5fc1cd
Show file tree
Hide file tree
Showing 5 changed files with 256 additions and 6 deletions.
21 changes: 21 additions & 0 deletions gateway-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,24 @@ pub struct Message {
/// Signature of S
pub s: Vec<u8>,
}

/// GrantInstallationResult represents the result of a grant installation operation in the DID registry.
///
/// This struct encapsulates the outcome of an attempt to grant an installation,
/// providing details about the operation's status, a descriptive message, and the
/// transaction identifier associated with the blockchain transaction.
///
/// # Fields
/// * `status` - A `String` indicating the outcome status of the operation. Typically, this
/// would be values like "Success" or "Failure".
/// * `message` - A `String` providing more detailed information about the operation. This
/// can be a success message, error description, or any other relevant information.
/// * `transaction` - A `String` representing the unique identifier of the transaction on the
/// blockchain. This can be used to track the transaction in a blockchain explorer.
///
#[derive(Serialize, Deserialize, Clone)]
pub struct GrantInstallationResult {
pub status: String,
pub message: String,
pub transaction: String,
}
52 changes: 47 additions & 5 deletions registry/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ pub mod error;

use std::str::FromStr;

use error::ContactOperationError;
use ethers::types::{H160, U256};
use ethers::{core::types::Signature, providers::Middleware, types::Address};
use gateway_types::GrantInstallationResult;
use lib_didethresolver::{
did_registry::DIDRegistry,
types::{Attribute, XmtpAttribute},
};

use error::ContactOperationError;

pub struct ContactOperations<Middleware> {
registry: DIDRegistry<Middleware>,
}
Expand All @@ -23,16 +24,57 @@ where
Self { registry }
}

fn resolve_did_address(&self, did: String) -> Result<H160, ContactOperationError<M>> {
// for now, we will just assume the DID is a valid ethereum wallet address
// TODO: Parse or resolve the actual DID
let address = Address::from_str(&did)?;
Ok(address)
}

pub async fn grant_installation(
&self,
did: String,
name: XmtpAttribute,
value: Vec<u8>,
signature: Signature,
validity: U256,
) -> Result<GrantInstallationResult, ContactOperationError<M>> {
let address = self.resolve_did_address(did)?;
let attribute: [u8; 32] = Attribute::from(name).into();
log::debug!(
"setting attribute {:#?}",
String::from_utf8_lossy(&attribute)
);

let transaction_receipt = self
.registry
.set_attribute_signed(
address,
signature.v.try_into()?,
signature.r.into(),
signature.s.into(),
attribute,
value.into(),
validity,
)
.send()
.await?
.await?;
Ok(GrantInstallationResult {
status: "completed".to_string(),
message: "Installation request complete.".to_string(),
transaction: transaction_receipt.unwrap().transaction_hash.to_string(),
})
}

pub async fn revoke_installation(
&self,
did: String,
name: XmtpAttribute,
value: Vec<u8>,
signature: Signature,
) -> Result<(), ContactOperationError<M>> {
// for now, we will just assume the DID is a valid ethereum wallet address
// TODO: Parse or resolve the actual DID
let address = Address::from_str(&did)?;
let address = self.resolve_did_address(did)?;
let attribute: [u8; 32] = Attribute::from(name).into();
log::debug!(
"Revoking attribute {:#?}",
Expand Down
98 changes: 98 additions & 0 deletions xps-gateway/src/rpc/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use ethers::core::types::Signature;
use ethers::prelude::*;
use jsonrpsee::{proc_macros::rpc, types::ErrorObjectOwned};

use gateway_types::GrantInstallationResult;
use gateway_types::Message;
use lib_didethresolver::types::XmtpAttribute;

Expand All @@ -14,6 +15,103 @@ pub trait Xps {
#[method(name = "sendMessage")]
async fn send_message(&self, _message: Message) -> Result<(), ErrorObjectOwned>;

/// # Documentation for JSON RPC Endpoint: `grantInstallation`
///
/// ## Overview
///
/// The `grantInstallation` method is used to register an installation on the network and associate the installation with a concrete identity.
///
/// ## JSON RPC Endpoint Specification
///
/// ### Method Name
/// `grantInstallation`
///
/// ### Request Parameters
/// did: string
/// name: String,
/// value: String,
/// signature: Signature,
///
/// ### Request Format
/// ```json
/// {
/// "jsonrpc": "2.0",
/// "method": "status",
/// "id": 1
/// }
/// ```
/// - `jsonrpc`: Specifies the version of the JSON RPC protocol being used. Always "2.0".
/// - `method`: The name of the method being called. Here it is "grantInstallation".
/// - `id`: A unique identifier established by the client that must be number or string. Used for correlating the response with the request.
/// ### Response Format
/// The response will typically include the result of the operation or an error if the operation was unsuccessful.
/// #### Success Response
/// ```json
/// {
/// "jsonrpc": "2.0",
/// "result": "OK",
/// "id": 1
/// }
/// ```
///
/// - `result`: Contains data related to the success of the operation. The nature of this data can vary based on the implementation.
///
/// #### Error Response
/// ```json
/// {
/// "jsonrpc": "2.0",
/// "error": {
/// "code": <error_code>,
/// "message": "<error_message>"
/// },
/// "id": 1
/// }
/// ```
///
/// - `error`: An object containing details about the error.
/// - `code`: A numeric error code.
/// - `message`: A human-readable string describing the error.
///
/// ### Example Usage
///
/// #### Request
/// ```json
/// {
/// "jsonrpc": "2.0",
/// "method": "status",
/// "id": 42
/// }
/// ```
///
/// #### Response
/// ```json
/// {
/// "jsonrpc": "2.0",
/// "result": "OK",
/// "id": 42
/// }
/// ```
///
/// ### Command Line Example
/// ```bash
/// $ $ curl -H "Content-Type: application/json" -d '{"id":7000, "jsonrpc":"2.0", "method":"xps_status"}' http:///localhost:34695
/// {"jsonrpc":"2.0","result":"OK","id":7000}
/// ```
///
/// ### Notes
/// - The system should have proper error handling to deal with invalid requests, unauthorized access, and other potential issues.
#[method(name = "grantInstallation")]
async fn grant_installation(
&self,
did: String,
name: XmtpAttribute,
value: Vec<u8>,
signature: Signature,
) -> Result<GrantInstallationResult, ErrorObjectOwned>;

/// # Documentation for JSON RPC Endpoint: `revoke_installation`
///
/// ## JSON RPC Endpoint Specification
Expand Down
25 changes: 25 additions & 0 deletions xps-gateway/src/rpc/methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ use jsonrpsee::types::error::ErrorCode;
use async_trait::async_trait;
use ethers::prelude::*;
use ethers::{core::types::Signature, providers::Middleware};
use gateway_types::GrantInstallationResult;
use jsonrpsee::types::ErrorObjectOwned;
use lib_didethresolver::types::XmtpAttribute;
use rand::{rngs::StdRng, SeedableRng};
use std::sync::Arc;
use thiserror::Error;

use gateway_types::Message;
Expand All @@ -20,13 +22,15 @@ use registry::{error::ContactOperationError, ContactOperations};
pub struct XpsMethods<P: Middleware + 'static> {
contact_operations: ContactOperations<GatewaySigner<P>>,
pub wallet: LocalWallet,
pub signer: Arc<GatewaySigner<P>>,
}

impl<P: Middleware> XpsMethods<P> {
pub fn new(context: &GatewayContext<P>) -> Self {
Self {
contact_operations: ContactOperations::new(context.registry.clone()),
wallet: LocalWallet::new(&mut StdRng::from_entropy()),
signer: context.signer.clone(),
}
}
}
Expand All @@ -44,6 +48,27 @@ impl<P: Middleware + 'static> XpsServer for XpsMethods<P> {
Ok("OK".to_string())
}

async fn grant_installation(
&self,
did: String,
name: XmtpAttribute,
value: Vec<u8>,
signature: Signature,
) -> Result<GrantInstallationResult, ErrorObjectOwned> {
log::debug!("xps_grantInstallation called");
let block_number = self.signer.get_block_number().await.unwrap();
let validity_period: U64 = U64::from(60 * 60 * 24 * 365 / 5); // number of round in one year, assuming 5-second round.
let validity = block_number + validity_period;

let result = self
.contact_operations
.grant_installation(did, name, value, signature, U256::from(validity.as_u64()))
.await
.map_err(RpcError::from)?;

Ok(result)
}

async fn revoke_installation(
&self,
did: String,
Expand Down
66 changes: 65 additions & 1 deletion xps-gateway/tests/integration_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ use lib_didethresolver::{
};
use xps_gateway::rpc::XpsClient;

use ethers::types::{Address, U256};
use ethers::middleware::Middleware;
use ethers::types::{Address, U256, U64};
use gateway_types::Message;

use integration_util::*;
Expand Down Expand Up @@ -51,6 +52,69 @@ async fn test_wallet_address() -> Result<(), Error> {
.await
}

#[tokio::test]
async fn test_grant_installation() -> Result<(), Error> {
with_xps_client(None, |client, context, resolver, anvil| async move {
let wallet: LocalWallet = anvil.keys()[3].clone().into();
let me = get_user(&anvil, 3).await;
let name = *b"xmtp/installation/hex ";
let value = b"02b97c30de767f084ce3080168ee293053ba33b235d7116a3263d29f1450936b71";

let attribute = XmtpAttribute {
purpose: XmtpKeyPurpose::Installation,
encoding: KeyEncoding::Hex,
};

let block_number = context.signer.get_block_number().await.unwrap();
let validity_period: U64 = U64::from(60 * 60 * 24 * 365 / 5); // number of round in one year, assuming 5-second round.
let validity = block_number + validity_period;

let signature = wallet
.sign_attribute(
&context.registry,
name,
value.to_vec(),
U256::from(validity.as_u64()),
)
.await?;

client
.grant_installation(
format!("0x{}", hex::encode(me.address())),
attribute,
value.to_vec(),
signature,
)
.await?;

let doc = resolver
.resolve_did(me.address(), None)
.await
.unwrap()
.document;

assert_eq!(doc.verification_method.len(), 2);
assert_eq!(
doc.verification_method[0].id,
DidUrl::parse(format!(
"did:ethr:0x{}#controller",
hex::encode(me.address())
))
.unwrap()
);
assert_eq!(
doc.verification_method[1].id,
DidUrl::parse(format!(
"did:ethr:0x{}?meta=installation#xmtp-0",
hex::encode(me.address())
))
.unwrap()
);
Ok(())
})
.await
}

#[tokio::test]
async fn test_revoke_installation() -> Result<(), Error> {
with_xps_client(None, |client, context, resolver, anvil| async move {
Expand Down

0 comments on commit d5fc1cd

Please sign in to comment.