use std::str::FromStr;
use prost::Message;
use crate::{
error::{BuilderError, SubmitError},
fabric::{
common::Payload,
gateway::{SubmitRequest, gateway_client::GatewayClient},
msp::SerializedIdentity,
protos::{ChaincodeAction, ChaincodeActionPayload, ProposalResponsePayload, Transaction},
},
signer::Signer,
transaction::{
PreparedTransaction, TransaktionBuilder, generate_nonce, generate_transaction_id,
},
};
pub struct Client {
identity: SerializedIdentity,
signer: Signer,
tonic_connection: TonicConnection,
}
struct TonicConnection {
tls_config: tonic::transport::ClientTlsConfig,
host: tonic::transport::Uri,
channel: Option<tonic::transport::Channel>,
}
impl Client {
pub async fn connect(&mut self) -> Result<(), tonic::transport::Error> {
self.tonic_connection.channel = Some(
tonic::transport::Channel::builder(self.tonic_connection.host.clone())
.tls_config(self.tonic_connection.tls_config.clone())
.expect("Invald TLS config")
.connect()
.await?,
);
Ok(())
}
pub fn get_transaction_builder(&self) -> TransaktionBuilder {
TransaktionBuilder {
identity: self.identity.clone(),
signer: self.signer.clone(),
channel_name: None,
chaincode_id: None,
contract_id: None,
function_name: None,
function_args: vec![],
proposal: None,
header: None,
nonce: None,
transaction_id: None,
}
}
pub async fn submit_transaction(
&self,
prepared_transaction: PreparedTransaction,
) -> Result<Vec<u8>, SubmitError> {
if self.tonic_connection.channel.is_none() {
return Err(SubmitError::NotConnected);
}
let mut gateway_client = GatewayClient::new(
self.tonic_connection
.channel
.as_ref()
.expect("Expected value is none.")
.clone(),
);
let response = gateway_client
.endorse(prepared_transaction.endorse_request)
.await;
match response {
Ok(response) => {
match response.into_inner().prepared_transaction {
Some(mut envelope) => {
let mut result = vec![];
if let Ok(payload) = Payload::decode(envelope.payload.as_slice())
&& let Ok(transaction) = Transaction::decode(payload.data.as_slice())
{
let mut payload_found = false;
for action in transaction.actions {
if let Ok(action) =
ChaincodeActionPayload::decode(action.payload.as_slice())
&& let Some(action) = action.action
&& let Ok(payload) = ProposalResponsePayload::decode(
action.proposal_response_payload.as_slice(),
)
&& let Ok(action) =
ChaincodeAction::decode(payload.extension.as_slice())
&& let Some(response) = action.response
{
result = response.payload;
payload_found = true;
}
}
if !payload_found{
panic!("Couldn't find payload in response: {}",String::from_utf8_lossy(envelope.payload.as_slice()))
}
}
let nonce = generate_nonce();
envelope.signature = self.signer.sign_message(&envelope.payload);
let transaction_id = generate_transaction_id(
&nonce,
self.identity.encode_to_vec().as_slice(),
);
let submit_request = SubmitRequest {
transaction_id: transaction_id.clone(),
channel_id: prepared_transaction.channel_name.clone(),
prepared_transaction: Some(envelope),
};
match gateway_client.submit(submit_request).await {
Ok(_) => Ok(result),
Err(err) => Err(SubmitError::NodeError(
String::from_utf8_lossy(err.details()).into_owned(),
)),
}
}
None => Err(SubmitError::EmptyRespone),
}
}
Err(err) => Err(SubmitError::NodeError(
String::from_utf8_lossy(err.details()).into_owned(),
)),
}
}
}
#[derive(Default)]
pub struct ClientBuilder {
identity: Option<SerializedIdentity>,
tls: Option<Vec<u8>>,
signer: Option<Signer>,
scheme: Option<String>,
authority: Option<String>,
}
impl ClientBuilder {
pub fn new() -> ClientBuilder {
ClientBuilder::default()
}
pub fn with_identity(
mut self,
identity: SerializedIdentity,
) -> Result<ClientBuilder, BuilderError> {
self.identity = Some(identity);
Ok(self)
}
pub fn with_signer(mut self, signer: Signer) -> Result<ClientBuilder, BuilderError> {
self.signer = Some(signer);
Ok(self)
}
pub fn with_scheme(mut self, scheme: impl Into<String>) -> Result<ClientBuilder, BuilderError> {
let scheme = scheme.into().trim().to_string();
if scheme.is_empty() {
return Err(BuilderError::InvalidParameter(
"scheme cannot be empty".into(),
));
}
self.scheme = Some(scheme);
Ok(self)
}
pub fn with_tls(mut self, bytes: impl Into<Vec<u8>>) -> Result<ClientBuilder, BuilderError> {
self.tls = Some(bytes.into());
Ok(self)
}
pub fn with_authority(
mut self,
authority: impl Into<String>,
) -> Result<ClientBuilder, BuilderError> {
let authority = authority.into().trim().to_string();
if authority.is_empty() {
return Err(BuilderError::InvalidParameter(
"authority cannot be empty".into(),
));
}
self.authority = Some(authority);
Ok(self)
}
pub fn build(self) -> Result<Client, BuilderError> {
let identity = match self.identity {
Some(identity) => identity,
None => return Err(BuilderError::MissingParameter("identity".into())),
};
let signer = match self.signer {
Some(signer) => signer,
None => return Err(BuilderError::MissingParameter("signer".into())),
};
let tls = match self.tls {
Some(tls) => tls,
None => return Err(BuilderError::MissingParameter("tls".into())),
};
let tls_config = tonic::transport::ClientTlsConfig::new()
.ca_certificate(tonic::transport::Certificate::from_pem(tls.as_slice()));
let scheme = match self.scheme {
Some(scheme) => scheme,
None => "https".to_string(),
};
let authority = match self.authority {
Some(authority) => authority,
None => "localhost:7051".to_string(),
};
let scheme =
tonic::codegen::http::uri::Scheme::from_str(scheme.as_str()).expect("Invalid scheme");
let uri_builder = tonic::transport::Uri::builder()
.scheme(scheme)
.authority(authority)
.path_and_query("/");
let uri = match uri_builder.build() {
Ok(uri) => uri,
Err(err) => return Err(BuilderError::InvalidParameter(err.to_string())),
};
let tonic_connection = TonicConnection {
tls_config,
host: uri,
channel: None,
};
Ok(Client {
identity,
signer,
tonic_connection,
})
}
}