use prost::Message;
use crate::{
error::LifecycleError,
fabric::{
common::{Envelope, Payload},
gateway::CommitStatusResponse,
lifecycle::{
ApproveChaincodeDefinitionForMyOrgArgs, CheckCommitReadinessArgs,
CheckCommitReadinessResult, CommitChaincodeDefinitionArgs,
GetInstalledChaincodePackageArgs, GetInstalledChaincodePackageResult,
InstallChaincodeArgs, InstallChaincodeResult, QueryApprovedChaincodeDefinitionArgs,
QueryApprovedChaincodeDefinitionResult, QueryApprovedChaincodeDefinitionsArgs,
QueryApprovedChaincodeDefinitionsResult, QueryChaincodeDefinitionArgs,
QueryChaincodeDefinitionResult, QueryChaincodeDefinitionsArgs,
QueryChaincodeDefinitionsResult, QueryInstalledChaincodeArgs,
QueryInstalledChaincodeResult, QueryInstalledChaincodesArgs,
QueryInstalledChaincodesResult,
},
protos::{
ChaincodeActionPayload, ChaincodeEndorsedAction, ProposalResponse, SignedProposal,
Transaction, TransactionAction,
},
},
gateway::client::Client,
};
const LIFECYCLE_CHAINCODE: &str = "_lifecycle";
pub struct LifecycleClient<'a> {
client: &'a Client,
}
impl<'a> LifecycleClient<'a> {
pub fn new(client: &'a Client) -> Self {
Self { client }
}
pub async fn install_chaincode(
&self,
package: Vec<u8>,
) -> Result<InstallChaincodeResult, LifecycleError> {
let args = InstallChaincodeArgs {
chaincode_install_package: package,
};
let signed_proposal =
self.build_lifecycle_proposal("", "InstallChaincode", args.encode_to_vec())?;
let proposal_response = self
.client
.process_proposal(signed_proposal)
.await
.map_err(LifecycleError::from)?;
let response = proposal_response
.response
.ok_or(LifecycleError::EmptyResponse)?;
if response.status != 200 {
return Err(LifecycleError::NodeError(response.message));
}
InstallChaincodeResult::decode(response.payload.as_slice())
.map_err(|_| LifecycleError::DecodeError("Failed to decode InstallChaincodeResult"))
}
pub async fn query_installed_chaincodes(
&self,
) -> Result<QueryInstalledChaincodesResult, LifecycleError> {
let args = QueryInstalledChaincodesArgs {};
let result_bytes = self
.evaluate_lifecycle("", "QueryInstalledChaincodes", args.encode_to_vec())
.await?;
QueryInstalledChaincodesResult::decode(result_bytes.as_slice()).map_err(|_| {
LifecycleError::DecodeError("Failed to decode QueryInstalledChaincodesResult")
})
}
pub async fn query_installed_chaincode(
&self,
package_id: impl Into<String>,
) -> Result<QueryInstalledChaincodeResult, LifecycleError> {
let args = QueryInstalledChaincodeArgs {
package_id: package_id.into(),
};
let result_bytes = self
.evaluate_lifecycle("", "QueryInstalledChaincode", args.encode_to_vec())
.await?;
QueryInstalledChaincodeResult::decode(result_bytes.as_slice()).map_err(|_| {
LifecycleError::DecodeError("Failed to decode QueryInstalledChaincodeResult")
})
}
pub async fn get_installed_chaincode_package(
&self,
package_id: impl Into<String>,
) -> Result<Vec<u8>, LifecycleError> {
let args = GetInstalledChaincodePackageArgs {
package_id: package_id.into(),
};
let result_bytes = self
.evaluate_lifecycle("", "GetInstalledChaincodePackage", args.encode_to_vec())
.await?;
let result = GetInstalledChaincodePackageResult::decode(result_bytes.as_slice())
.map_err(|_| {
LifecycleError::DecodeError(
"Failed to decode GetInstalledChaincodePackageResult",
)
})?;
Ok(result.chaincode_install_package)
}
pub async fn approve_chaincode_definition(
&self,
channel_name: impl Into<String>,
args: ApproveChaincodeDefinitionForMyOrgArgs,
) -> Result<CommitStatusResponse, LifecycleError> {
let channel = channel_name.into();
let signed_proposal = self.build_lifecycle_proposal(
&channel,
"ApproveChaincodeDefinitionForMyOrg",
args.encode_to_vec(),
)?;
let mut envelope = self.endorse_lifecycle(signed_proposal).await?;
envelope.submit(self.client).await.map_err(LifecycleError::from)?;
envelope.wait_for_commit(self.client).await.map_err(LifecycleError::from)
}
pub async fn check_commit_readiness(
&self,
channel_name: impl Into<String>,
args: CheckCommitReadinessArgs,
) -> Result<CheckCommitReadinessResult, LifecycleError> {
let channel = channel_name.into();
let result_bytes = self
.evaluate_lifecycle(&channel, "CheckCommitReadiness", args.encode_to_vec())
.await?;
CheckCommitReadinessResult::decode(result_bytes.as_slice())
.map_err(|_| LifecycleError::DecodeError("Failed to decode CheckCommitReadinessResult"))
}
pub async fn commit_chaincode_definition(
&self,
channel_name: impl Into<String>,
args: CommitChaincodeDefinitionArgs,
endorsing_peers: &[&Client],
) -> Result<CommitStatusResponse, LifecycleError> {
let channel = channel_name.into();
let signed_proposal = self.build_lifecycle_proposal(
&channel,
"CommitChaincodeDefinition",
args.encode_to_vec(),
)?;
let mut all_peers: Vec<&Client> = vec![self.client];
all_peers.extend_from_slice(endorsing_peers);
let responses = self.collect_endorsements(&signed_proposal, &all_peers).await?;
let mut envelope = self.build_envelope(signed_proposal, responses)?;
envelope.submit(self.client).await.map_err(LifecycleError::from)?;
envelope.wait_for_commit(self.client).await.map_err(LifecycleError::from)
}
pub async fn query_chaincode_definition(
&self,
channel_name: impl Into<String>,
name: impl Into<String>,
) -> Result<QueryChaincodeDefinitionResult, LifecycleError> {
let channel = channel_name.into();
let args = QueryChaincodeDefinitionArgs { name: name.into() };
let result_bytes = self
.evaluate_lifecycle(&channel, "QueryChaincodeDefinition", args.encode_to_vec())
.await?;
QueryChaincodeDefinitionResult::decode(result_bytes.as_slice()).map_err(|_| {
LifecycleError::DecodeError("Failed to decode QueryChaincodeDefinitionResult")
})
}
pub async fn query_chaincode_definitions(
&self,
channel_name: impl Into<String>,
) -> Result<QueryChaincodeDefinitionsResult, LifecycleError> {
let channel = channel_name.into();
let args = QueryChaincodeDefinitionsArgs {};
let result_bytes = self
.evaluate_lifecycle(&channel, "QueryChaincodeDefinitions", args.encode_to_vec())
.await?;
QueryChaincodeDefinitionsResult::decode(result_bytes.as_slice()).map_err(|_| {
LifecycleError::DecodeError("Failed to decode QueryChaincodeDefinitionsResult")
})
}
pub async fn query_approved_chaincode_definition(
&self,
channel_name: impl Into<String>,
name: impl Into<String>,
sequence: i64,
) -> Result<QueryApprovedChaincodeDefinitionResult, LifecycleError> {
let channel = channel_name.into();
let args = QueryApprovedChaincodeDefinitionArgs {
name: name.into(),
sequence,
};
let result_bytes = self
.evaluate_lifecycle(
&channel,
"QueryApprovedChaincodeDefinition",
args.encode_to_vec(),
)
.await?;
QueryApprovedChaincodeDefinitionResult::decode(result_bytes.as_slice()).map_err(|_| {
LifecycleError::DecodeError(
"Failed to decode QueryApprovedChaincodeDefinitionResult",
)
})
}
pub async fn query_approved_chaincode_definitions(
&self,
channel_name: impl Into<String>,
) -> Result<QueryApprovedChaincodeDefinitionsResult, LifecycleError> {
let channel = channel_name.into();
let args = QueryApprovedChaincodeDefinitionsArgs {};
let result_bytes = self
.evaluate_lifecycle(
&channel,
"QueryApprovedChaincodeDefinitions",
args.encode_to_vec(),
)
.await?;
QueryApprovedChaincodeDefinitionsResult::decode(result_bytes.as_slice()).map_err(|_| {
LifecycleError::DecodeError(
"Failed to decode QueryApprovedChaincodeDefinitionsResult",
)
})
}
async fn endorse_lifecycle(
&self,
signed_proposal: SignedProposal,
) -> Result<Envelope, LifecycleError> {
let responses = self.collect_endorsements(&signed_proposal, &[self.client]).await?;
self.build_envelope(signed_proposal, responses)
}
async fn collect_endorsements(
&self,
signed_proposal: &SignedProposal,
peers: &[&Client],
) -> Result<Vec<ProposalResponse>, LifecycleError> {
let mut responses = Vec::with_capacity(peers.len());
for peer in peers {
let response = peer
.process_proposal(signed_proposal.clone())
.await
.map_err(LifecycleError::from)?;
responses.push(response);
}
Ok(responses)
}
fn build_envelope(
&self,
signed_proposal: SignedProposal,
responses: Vec<ProposalResponse>,
) -> Result<Envelope, LifecycleError> {
if responses.is_empty() {
return Err(LifecycleError::EmptyResponse);
}
for pr in &responses {
let response = pr.response.as_ref().ok_or(LifecycleError::EmptyResponse)?;
if response.status != 200 {
return Err(LifecycleError::NodeError(response.message.clone()));
}
}
let mut iter = responses.into_iter();
let first = iter.next().unwrap();
let proposal_response_payload = first.payload;
let mut endorsements: Vec<_> = first.endorsement.into_iter().collect();
for pr in iter {
endorsements.extend(pr.endorsement.into_iter());
}
let proposal = signed_proposal
.get_proposal()
.map_err(|_| LifecycleError::DecodeError("Failed to decode proposal"))?;
let header = proposal
.get_header()
.map_err(|_| LifecycleError::DecodeError("Failed to decode header"))?;
let chaincode_action_payload = ChaincodeActionPayload {
chaincode_proposal_payload: proposal.payload,
action: Some(ChaincodeEndorsedAction {
proposal_response_payload,
endorsements,
}),
};
let transaction = Transaction {
actions: vec![TransactionAction {
header: header.signature_header.clone(),
payload: chaincode_action_payload.encode_to_vec(),
}],
};
let payload = Payload {
header: Some(header),
data: transaction.encode_to_vec(),
};
let payload_bytes = payload.encode_to_vec();
let signature = self.client.identity.sign_message(&payload_bytes);
Ok(Envelope {
payload: payload_bytes,
signature,
})
}
fn build_lifecycle_proposal(
&self,
channel_name: &str,
function_name: &str,
args_bytes: Vec<u8>,
) -> Result<SignedProposal, LifecycleError> {
let mut builder = self.client.get_chaincode_call_builder();
builder
.with_chaincode_id(LIFECYCLE_CHAINCODE)
.map_err(LifecycleError::from)?;
if !channel_name.is_empty() {
builder
.with_channel_name(channel_name)
.map_err(LifecycleError::from)?;
}
builder
.with_function_name(function_name)
.map_err(LifecycleError::from)?;
builder
.with_function_args([args_bytes])
.map_err(LifecycleError::from)?;
builder.build().map_err(LifecycleError::from)
}
async fn evaluate_lifecycle(
&self,
channel_name: &str,
function_name: &str,
args_bytes: Vec<u8>,
) -> Result<Vec<u8>, LifecycleError> {
let signed_proposal =
self.build_lifecycle_proposal(channel_name, function_name, args_bytes)?;
if channel_name.is_empty() {
let proposal_response = self
.client
.process_proposal(signed_proposal)
.await
.map_err(LifecycleError::from)?;
let response = proposal_response
.response
.ok_or(LifecycleError::EmptyResponse)?;
if response.status != 200 {
return Err(LifecycleError::NodeError(response.message));
}
return Ok(response.payload);
}
let channel_header = signed_proposal
.get_proposal()
.map_err(|_| LifecycleError::DecodeError("Failed to decode proposal"))?
.get_header()
.map_err(|_| LifecycleError::DecodeError("Failed to decode header"))?
.get_channel_header()
.map_err(|_| LifecycleError::DecodeError("Failed to decode channel header"))?;
self.client
.evaluate(signed_proposal, channel_header.tx_id, channel_header.channel_id)
.await
.map_err(LifecycleError::from)
}
}