Skip to content

Commit

Permalink
Add nonce JSON-RPC endpoint (#72)
Browse files Browse the repository at this point in the history
  • Loading branch information
insipx authored Feb 22, 2024
1 parent 65634b5 commit e04ae49
Show file tree
Hide file tree
Showing 9 changed files with 286 additions and 13 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

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

172 changes: 166 additions & 6 deletions lib-xps/src/rpc/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -517,14 +517,90 @@ pub trait Xps {
#[method(name = "status")]
async fn status(&self) -> Result<String, ErrorObjectOwned>;

/// ### Documentation for JSON RPC Endpoint: `xps_walletAddress`
/// ---
/// #### Endpoint Name: `xps_walletAddress`
/// #### Description:
/// The `xps_walletAddress` endpoint retrieves the current Wallet Address of the internal wallet managed by the server. This endpoint is essential for applications that need to display or monitor the wallet, especially in the context of cryptocurrency transactions or account management.
/// #### Request:
/// - **Method:** `POST`
/// - **URL:** `/rpc/v1/walletAddress`
/// - **Headers:**
/// - `Content-Type: application/json`
/// - **Body:**
/// - **JSON Object:**
/// - `jsonrpc`: `"2.0"`
/// - `method`: `"xps_walletAddress"`
/// - `params`: Array (optional parameters as required)
/// - `id`: Request identifier (integer or string)
/// **Example Request Body:**
/// ```json
/// {
/// "jsonrpc": "2.0",
/// "method": "xps_walletAddress",
/// "params": [],
/// "id": 1
/// }
/// ```
/// #### Response:
/// - **Success Status Code:** `200 OK`
/// - **Error Status Codes:**
/// - `400 Bad Request` - Invalid request format or parameters.
/// - `500 Internal Server Error` - Server or wallet-related error.
/// **Success Response Body:**
/// ```json
/// {
/// "jsonrpc": "2.0",
/// "result": "0xce90a7949bb78892f159f428d0dc23a8e3584d75",
/// "id": 1
/// }
/// ```
/// **Error Response Body:**
/// ```json
/// {
/// "jsonrpc": "2.0",
/// "error": {
/// "code": -32602,
/// "message": "Invalid parameters"
/// },
/// "id": 1
/// }
/// ```
/// #### Error Handling:
/// - **Invalid Parameters:** Check if the request body is properly formatted and includes valid parameters.
/// - **Wallet or Server Errors:** Ensure that the server and wallet are operational. Consult server logs for detailed error information.
/// #### Security Considerations:
/// - **Authentication and Authorization:** Implement robust authentication and authorization checks to ensure only authorized users can access wallet balance information.
/// - **Secure Communication:** Utilize HTTPS to encrypt data in transit and prevent eavesdropping.
/// #### Usage Example:
/// ```javascript
/// const requestBody = {
/// jsonrpc: "2.0",
/// method: "xps_walletAddress",
/// params: [],
/// id: 1
/// };
/// fetch('https://server.example.com/rpc/v1/walletAddress', {
/// method: 'POST',
/// headers: {
/// 'Content-Type': 'application/json'
/// },
/// body: JSON.stringify(requestBody)
/// })
/// .then(response => response.json())
/// .then(data => console.log('Wallet Balance:', data.result))
/// .catch(error => console.error('Error:', error));
/// ```
/// </div>
/// ```
#[method(name = "walletAddress")]
async fn wallet_address(&self) -> Result<Address, ErrorObjectOwned>;

/// ### Documentation for JSON RPC Endpoint: `balance`
/// ### Documentation for JSON RPC Endpoint: `xps_balance`
/// ---
/// #### Endpoint Name: `balance`
/// #### Endpoint Name: `xps_balance`
/// #### Description:
/// The `balance` endpoint retrieves the current balance of the internal wallet managed by the server. This endpoint is essential for applications that need to display or monitor the wallet's balance, especially in the context of cryptocurrency transactions or account management.
/// The `xps_balance` endpoint retrieves the current balance of the internal wallet managed by the server. This endpoint is essential for applications that need to display or monitor the wallet's balance, especially in the context of cryptocurrency transactions or account management.
/// #### Request:
/// - **Method:** `POST`
/// - **URL:** `/rpc/v1/balance`
Expand All @@ -533,14 +609,14 @@ pub trait Xps {
/// - **Body:**
/// - **JSON Object:**
/// - `jsonrpc`: `"2.0"`
/// - `method`: `"balance"`
/// - `method`: `"xps_balance"`
/// - `params`: Array (optional parameters as required)
/// - `id`: Request identifier (integer or string)
/// **Example Request Body:**
/// ```json
/// {
/// "jsonrpc": "2.0",
/// "method": "balance",
/// "method": "xps_balance",
/// "params": [],
/// "id": 1
/// }
Expand Down Expand Up @@ -582,7 +658,7 @@ pub trait Xps {
/// ```javascript
/// const requestBody = {
/// jsonrpc: "2.0",
/// method: "balance",
/// method: "xps_balance",
/// params: [],
/// id: 1
/// };
Expand All @@ -601,4 +677,88 @@ pub trait Xps {
/// ```
#[method(name = "balance")]
async fn balance(&self) -> Result<WalletBalance, ErrorObjectOwned>;

/// ### Documentation for JSON RPC Endpoint: `xps_nonce`
/// ---
/// #### Endpoint Name: `nonce`
/// #### Description:
/// The `xps_nonce` endpoint retrieves the nonce for `address` in the [`DIDRegistry`]. This is
/// needed for signing payloads for DID Registry meta transactions.

/// #### Request:
/// - **Method:** `POST`
/// - **URL:** `/rpc/v1/nonce`
/// - **Headers:**
/// - `Content-Type: application/json`
/// - **Body:**
/// - **JSON Object:**
/// - `jsonrpc`: `"2.0"`
/// - `method`: `"xps_nonce"`
/// - `params`: Array (optional parameters as required)
/// - `id`: Request identifier (integer or string)
/// **Example Request Body:**
/// ```json
/// {
/// "jsonrpc": "2.0",
/// "method": "xps_nonce",
/// "params": ["0xce90a7949bb78892f159f428d0dc23a8e3584d75"],
/// "id": 1
/// }
/// ```
/// #### Response:
/// - **Success Status Code:** `200 OK`
/// - **Error Status Codes:**
/// - `400 Bad Request` - Invalid request format or parameters.
/// - `500 Internal Server Error` - Server or wallet-related error.
/// **Success Response Body:**
/// ```json
/// {
/// "jsonrpc": "2.0",
/// "result": {
/// "nonce": 0
/// },
/// "id": 1
/// }
/// ```
/// **Error Response Body:**
/// ```json
/// {
/// "jsonrpc": "2.0",
/// "error": {
/// "code": -32602,
/// "message": "Invalid parameters"
/// },
/// "id": 1
/// }
/// ```
/// #### Error Handling:
/// - **Invalid Parameters:** Check if the request body is properly formatted and includes valid parameters.
/// - **Wallet or Server Errors:** Ensure that the server and wallet are operational. Consult server logs for detailed error information.
/// #### Security Considerations:
/// - **Authentication and Authorization:** Implement robust authentication and authorization checks to ensure only authorized users can access wallet balance information.
/// - **Secure Communication:** Utilize HTTPS to encrypt data in transit and prevent eavesdropping.
/// #### Usage Example:
/// ```javascript
/// const requestBody = {
/// jsonrpc: "2.0",
/// method: "xps_nonce",
/// params: ["0xce90a7949bb78892f159f428d0dc23a8e3584d75"],
/// id: 1
/// };
/// fetch('https://server.example.com/rpc/v1/nonce', {
/// method: 'POST',
/// headers: {
/// 'Content-Type': 'application/json'
/// },
/// body: JSON.stringify(requestBody)
/// })
/// .then(response => response.json())
/// .then(data => console.log('Wallet Balance:', data.result))
/// .catch(error => console.error('Error:', error));
/// ```
/// </div>
/// ```

#[method(name = "nonce")]
async fn nonce(&self, did: String) -> Result<U256, ErrorObjectOwned>;
}
10 changes: 10 additions & 0 deletions lib-xps/src/rpc/methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,16 @@ impl<P: Middleware + 'static> XpsServer for XpsMethods<P> {
.map_err(RpcError::from)?;
Ok(result)
}

async fn nonce(&self, did: String) -> Result<U256, ErrorObjectOwned> {
log::debug!("xps_nonce called");
let result = self
.contact_operations
.nonce(did)
.await
.map_err(RpcError::from)?;
Ok(result)
}
}

/// Error types for DID Registry JSON-RPC
Expand Down
1 change: 0 additions & 1 deletion lib-xps/src/util.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
//! Internal Utility functions for use in crate

#[cfg(test)]
use std::sync::Once;
use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Registry};
Expand Down
13 changes: 13 additions & 0 deletions lib-xps/tests/integration_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -582,3 +582,16 @@ async fn test_did_deactivation() -> Result<(), Error> {
})
.await
}

#[tokio::test]
async fn test_nonce() -> Result<(), Error> {
with_xps_client(None, None, |client, _, _, anvil| async move {
let me: LocalWallet = anvil.keys()[3].clone().into();

let nonce = client.nonce(hex::encode(me.address())).await?;
assert_eq!(U256::from(0), nonce);

Ok(())
})
.await
}
4 changes: 4 additions & 0 deletions registry/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,7 @@ xps-types.workspace = true
lib-didethresolver.workspace = true
rustc-hex.workspace = true
thiserror.workspace = true

[dev-dependencies]
tracing-subscriber.workspace = true
ctor.workspace = true
2 changes: 2 additions & 0 deletions registry/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,6 @@ pub enum ContactOperationError<M: Middleware> {
DIDDeactivated,
#[error("Type failed to convert")]
Type(#[from] lib_didethresolver::error::TypeError),
#[error("Error parsing hex bytes: {0}")]
Bytes(#[from] ethers::types::ParseBytesError),
}
76 changes: 70 additions & 6 deletions registry/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
pub mod error;
#[cfg(test)]
mod test;

use std::str::FromStr;

use error::ContactOperationError;
use ethers::types::{H160, U256};
use ethers::{core::types::Signature, providers::Middleware, types::Address};
use lib_didethresolver::types::VerificationMethodProperties;
use lib_didethresolver::Resolver;
use lib_didethresolver::{did_registry::DIDRegistry, types::XmtpAttribute};
use ethers::{
providers::Middleware,
types::{Address, Bytes, Signature, H160, U256},
};
use lib_didethresolver::{
did_registry::DIDRegistry,
types::{VerificationMethodProperties, XmtpAttribute},
Resolver,
};
use xps_types::{GrantInstallationResult, KeyPackageResult, Status};

pub struct ContactOperations<Middleware> {
Expand All @@ -29,7 +35,8 @@ where
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)?;

let address = Address::from_slice(Bytes::from_str(did.as_ref())?.to_vec().as_slice());
Ok(address)
}

Expand Down Expand Up @@ -165,4 +172,61 @@ where

Ok(())
}

/// get the nonce for a given address from [`DIDRegistry`]
pub async fn nonce(&self, did: String) -> Result<U256, ContactOperationError<M>> {
let address = self.resolve_did_address(did)?;
let nonce = self.registry.nonce(address).await?;
Ok(nonce)
}
}

#[cfg(test)]
mod tests {
use super::*;
use ethers::{
abi::AbiEncode,
providers::{MockProvider, Provider},
};
use lib_didethresolver::did_registry::NonceReturn;

impl ContactOperations<Provider<MockProvider>> {
pub fn mocked() -> (Self, MockProvider) {
let (mock_provider, mock) = Provider::mocked();
let registry = DIDRegistry::new(H160::zero(), mock_provider.into());

(ContactOperations::new(registry), mock)
}
}

#[test]
fn test_resolve_address_from_hexstr() {
let addr = "0x0000000000000000000000000000000000000000";
let (ops, _) = ContactOperations::mocked();
assert_eq!(
ops.resolve_did_address(addr.to_string()).unwrap(),
H160::zero()
);

let addr = "0000000000000000000000000000000000000000";
assert_eq!(
ops.resolve_did_address(addr.to_string()).unwrap(),
H160::zero()
);
}

#[tokio::test]
async fn test_nonce() {
let (ops, mock) = ContactOperations::mocked();

mock.push::<String, String>(NonceReturn(U256::from(212)).encode_hex())
.unwrap();

let nonce = ops
.nonce("0x1111111111111111111111111111111111111111".to_string())
.await
.unwrap();

assert_eq!(nonce, U256::from(212));
}
}
19 changes: 19 additions & 0 deletions registry/src/test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//! Internal Utility functions for use in crate
use std::sync::Once;
use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Registry};

static INIT: Once = Once::new();

#[ctor::ctor]
fn __init_test_logging() {
INIT.call_once(|| {
let fmt = fmt::layer().compact();
Registry::default().with(env()).with(fmt).init()
})
}

/// Try to get the logging environment from the `RUST_LOG` environment variable.
/// If it is not set, use the default of `info`.
fn env() -> EnvFilter {
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"))
}

0 comments on commit e04ae49

Please sign in to comment.