-
Notifications
You must be signed in to change notification settings - Fork 185
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
- Loading branch information
Showing
13 changed files
with
797 additions
and
16 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
[package] | ||
name = "subnet-resolver" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
|
||
[dependencies] | ||
ethabi = "18.0.0" | ||
jsonrpsee = { version = "0.20.1", features = ["http-client", "macros"] } | ||
|
||
libp2p-identity = { workspace = true, features = ["peerid"] } | ||
serde_json = { workspace = true } | ||
thiserror = { workspace = true } | ||
tokio = { workspace = true } | ||
hex = "0.4.3" | ||
eyre = { workspace = true } | ||
serde = { workspace = true } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
use libp2p_identity::ParseError; | ||
use thiserror::Error; | ||
|
||
#[derive(Debug, Error)] | ||
pub enum ChainDataError { | ||
#[error("empty data, nothing to parse")] | ||
Empty, | ||
#[error("missing token for field '{0}'")] | ||
MissingParsedToken(&'static str), | ||
#[error("invalid token for field '{0}'")] | ||
InvalidParsedToken(&'static str), | ||
#[error("data is not a valid hex: '{0}'")] | ||
DecodeHex(#[source] hex::FromHexError), | ||
#[error(transparent)] | ||
EthError(#[from] ethabi::Error), | ||
} | ||
|
||
#[derive(Error, Debug)] | ||
pub enum ResolveSubnetError { | ||
#[error("error encoding function: '{0}'")] | ||
EncodeFunction(#[from] ethabi::Error), | ||
#[error("error sending jsonrpc request: '{0}'")] | ||
RpcError(#[from] jsonrpsee::core::error::Error), | ||
#[error(transparent)] | ||
ChainData(#[from] ChainDataError), | ||
#[error("getPATs response is empty")] | ||
Empty, | ||
#[error("'{1}' from getPATs is not a valid PeerId")] | ||
InvalidPeerId(#[source] ParseError, &'static str), | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
#![feature(try_blocks)] | ||
mod error; | ||
mod resolve; | ||
mod utils; | ||
|
||
pub use resolve::{resolve_subnet, SubnetResolveResult, Worker}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
use crate::error::{ChainDataError, ResolveSubnetError}; | ||
use crate::utils::{decode_hex, next_opt, parse_peer_id}; | ||
use ethabi::ParamType::{Address, Array, FixedBytes, Tuple, Uint}; | ||
use ethabi::{Function, ParamType, StateMutability, Token}; | ||
use jsonrpsee::core::client::ClientT; | ||
use jsonrpsee::http_client::HttpClientBuilder; | ||
use jsonrpsee::rpc_params; | ||
|
||
use serde::{Deserialize, Serialize}; | ||
use serde_json::json; | ||
use tokio::runtime::Handle; | ||
|
||
/// Parse data from chain. Accepts data with and without "0x" prefix. | ||
pub fn parse_chain_data(data: &str) -> Result<Vec<Token>, ChainDataError> { | ||
if data.is_empty() { | ||
return Err(ChainDataError::Empty); | ||
} | ||
let data = decode_hex(data).map_err(ChainDataError::DecodeHex)?; | ||
let signature: ParamType = Array(Box::new(Tuple(vec![ | ||
// bytes32 id | ||
FixedBytes(32), | ||
// uint256 index | ||
Uint(256), | ||
// bytes32 peerId | ||
FixedBytes(32), | ||
// bytes32 workerId | ||
FixedBytes(32), | ||
// address owner | ||
Address, | ||
// uint256 collateral | ||
Uint(256), | ||
// uint256 created | ||
Uint(256), | ||
]))); | ||
Ok(ethabi::decode(&[signature], &data)?) | ||
} | ||
|
||
#[derive(Serialize, Deserialize, Clone, Debug)] | ||
pub struct Worker { | ||
pub pat_id: String, | ||
pub host_id: String, | ||
pub worker_id: Vec<String>, | ||
} | ||
#[derive(Serialize, Deserialize, Clone, Debug)] | ||
pub struct SubnetResolveResult { | ||
pub success: bool, | ||
pub workers: Vec<Worker>, | ||
pub error: Vec<String>, | ||
} | ||
|
||
fn decode_pats(data: String) -> Result<Vec<Worker>, ResolveSubnetError> { | ||
let tokens = parse_chain_data(&data)?; | ||
let tokens = tokens.into_iter().next().ok_or(ResolveSubnetError::Empty)?; | ||
let tokens = tokens | ||
.into_array() | ||
.ok_or(ChainDataError::InvalidParsedToken("response"))?; | ||
let mut result = vec![]; | ||
for token in tokens { | ||
let tuple = token | ||
.into_tuple() | ||
.ok_or(ChainDataError::InvalidParsedToken("tuple"))?; | ||
let mut tuple = tuple.into_iter(); | ||
|
||
let pat_id = next_opt(&mut tuple, "pat_id", Token::into_fixed_bytes)?; | ||
let pat_id = hex::encode(pat_id); | ||
|
||
// skip 'index' field | ||
let mut tuple = tuple.skip(1); | ||
|
||
let peer_id = next_opt(&mut tuple, "compute_peer_id", Token::into_fixed_bytes)?; | ||
let peer_id = parse_peer_id(peer_id) | ||
.map_err(|e| ResolveSubnetError::InvalidPeerId(e, "compute_peer_id"))?; | ||
let worker_id = next_opt(&mut tuple, "compute_worker_id", Token::into_fixed_bytes)?; | ||
// if all bytes are 0, then worker_id is considered empty | ||
let all_zeros = worker_id.iter().all(|b| *b == 0); | ||
let worker_id = if all_zeros { | ||
vec![] | ||
} else { | ||
let worker_id = parse_peer_id(worker_id) | ||
.map_err(|e| ResolveSubnetError::InvalidPeerId(e, "worker_id"))?; | ||
vec![worker_id.to_string()] | ||
}; | ||
|
||
let pat = Worker { | ||
pat_id: format!("0x{}", pat_id), | ||
host_id: peer_id.to_string(), | ||
worker_id, | ||
}; | ||
result.push(pat); | ||
} | ||
|
||
Ok(result) | ||
} | ||
|
||
pub fn resolve_subnet(deal_id: String, api_endpoint: &str) -> SubnetResolveResult { | ||
let res: Result<_, ResolveSubnetError> = try { | ||
// Description of the `getPATs` function from the `chain.workers` smart contract on chain | ||
#[allow(deprecated)] | ||
let input = Function { | ||
name: String::from("getPATs"), | ||
inputs: vec![], | ||
outputs: vec![], | ||
constant: None, | ||
state_mutability: StateMutability::View, | ||
} | ||
.encode_input(&[])?; | ||
let input = format!("0x{}", hex::encode(input)); | ||
let client = HttpClientBuilder::default().build(api_endpoint)?; | ||
let params = rpc_params![json!({ "data": input, "to": deal_id }), json!("latest")]; | ||
let response: Result<String, _> = tokio::task::block_in_place(move || { | ||
Handle::current().block_on(async move { client.request("eth_call", params).await }) | ||
}); | ||
|
||
let pats = response?; | ||
|
||
decode_pats(pats)? | ||
}; | ||
|
||
match res { | ||
Ok(workers) => SubnetResolveResult { | ||
success: true, | ||
workers, | ||
error: vec![], | ||
}, | ||
Err(err) => SubnetResolveResult { | ||
success: false, | ||
workers: vec![], | ||
error: vec![format!("{}", err)], | ||
}, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
use crate::error::ChainDataError; | ||
use ethabi::Token; | ||
use libp2p_identity::{ParseError, PeerId}; | ||
|
||
/// Static prefix of the PeerId. Protobuf encoding + multihash::identity + length and so on. | ||
pub(crate) const PEER_ID_PREFIX: &[u8] = &[0, 36, 8, 1, 18, 32]; | ||
|
||
pub(crate) fn parse_peer_id(bytes: Vec<u8>) -> Result<PeerId, ParseError> { | ||
let peer_id = [PEER_ID_PREFIX, &bytes].concat(); | ||
|
||
PeerId::from_bytes(&peer_id) | ||
} | ||
|
||
pub(crate) fn decode_hex(h: &str) -> Result<Vec<u8>, hex::FromHexError> { | ||
let h = h.trim_start_matches("0x"); | ||
hex::decode(h) | ||
} | ||
|
||
pub(crate) fn next_opt<T>( | ||
data_tokens: &mut impl Iterator<Item = Token>, | ||
name: &'static str, | ||
f: impl Fn(Token) -> Option<T>, | ||
) -> Result<T, ChainDataError> { | ||
let next = data_tokens | ||
.next() | ||
.ok_or(ChainDataError::MissingParsedToken(name))?; | ||
let parsed = f(next).ok_or(ChainDataError::InvalidParsedToken(name))?; | ||
|
||
Ok(parsed) | ||
} |
Oops, something went wrong.