Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove external transaction & deploy builder dependency #212

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ uint = "0.9.5"
tempfile = "3.8.1"

[patch.crates-io]
casper-types = { git = "https://github.com/casper-network/casper-node.git", tag = "v2.0.0-rc5" }
casper-types = { git = "https://github.com/casper-network/casper-node.git", branch = "feat-2.0" }

[package.metadata.deb]
features = ["vendored-openssl"]
Expand Down
6 changes: 6 additions & 0 deletions lib/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@

/// Functions for creating Deploys.
pub mod deploy;
mod deploy_builder;
mod deploy_str_params;
mod dictionary_item_str_params;
mod error;
mod fields_container;
mod json_args;
pub mod parse;
mod payment_str_params;
Expand All @@ -35,6 +37,7 @@ mod tests;
mod transaction;
mod transaction_builder_params;
mod transaction_str_params;
mod transaction_v1_builder;

#[cfg(feature = "std-fs-io")]
use serde::Serialize;
Expand Down Expand Up @@ -70,9 +73,11 @@ pub use deploy::{
make_deploy, make_transfer, put_deploy, send_deploy_file, sign_deploy_file,
speculative_put_deploy, speculative_send_deploy_file, speculative_transfer, transfer,
};
pub(crate) use deploy_builder::{DeployBuilder, DeployBuilderError};
pub use deploy_str_params::DeployStrParams;
pub use dictionary_item_str_params::DictionaryItemStrParams;
pub use error::{CliError, FromDecStrErr};
pub(crate) use fields_container::{FieldsContainer, FieldsContainerError};
pub use json_args::{
help as json_args_help, Error as JsonArgsError, ErrorDetails as JsonArgsErrorDetails, JsonArg,
};
Expand All @@ -86,6 +91,7 @@ pub use transaction::{
};
pub use transaction_builder_params::TransactionBuilderParams;
pub use transaction_str_params::TransactionStrParams;
pub(crate) use transaction_v1_builder::{TransactionV1Builder, TransactionV1BuilderError};

/// Retrieves a [`casper_types::Deploy`] from the network.
///
Expand Down
17 changes: 11 additions & 6 deletions lib/cli/deploy.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
use casper_types::{
account::AccountHash, AsymmetricType, Deploy, DeployBuilder, PublicKey, TransferTarget,
UIntParseError, URef, U512,
account::AccountHash, AsymmetricType, Deploy, PublicKey, TransferTarget, UIntParseError, URef,
U512,
};

use super::transaction::get_maybe_secret_key;
use super::{parse, CliError, DeployStrParams, PaymentStrParams, SessionStrParams};
use crate::rpcs::results::{PutDeployResult, SpeculativeExecResult};
use crate::{SuccessResponse, MAX_SERIALIZED_SIZE_OF_DEPLOY};
use super::{
parse, transaction::get_maybe_secret_key, CliError, DeployStrParams, PaymentStrParams,
SessionStrParams,
};
use crate::{
cli::DeployBuilder,
rpcs::results::{PutDeployResult, SpeculativeExecResult},
SuccessResponse, MAX_SERIALIZED_SIZE_OF_DEPLOY,
};

const DEFAULT_GAS_PRICE: u64 = 1;

Expand Down
219 changes: 219 additions & 0 deletions lib/cli/deploy_builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
mod error;

use casper_types::{
bytesrepr::ToBytes, Deploy, DeployHash, DeployHeader, Digest, ExecutableDeployItem,
InitiatorAddr, PublicKey, SecretKey, TimeDiff, Timestamp, TransferTarget, URef, U512,
};
pub use error::DeployBuilderError;
use itertools::Itertools;

use crate::types::InitiatorAddrAndSecretKey;

/// A builder for constructing a [`Deploy`].
pub struct DeployBuilder<'a> {
account: Option<PublicKey>,
secret_key: Option<&'a SecretKey>,
timestamp: Timestamp,
ttl: TimeDiff,
gas_price: u64,
dependencies: Vec<DeployHash>,
chain_name: String,
payment: Option<ExecutableDeployItem>,
session: ExecutableDeployItem,
}

impl<'a> DeployBuilder<'a> {
/// The default time-to-live for `Deploy`s, i.e. 30 minutes.
pub const DEFAULT_TTL: TimeDiff = TimeDiff::from_millis(30 * 60 * 1_000);
/// The default gas price for `Deploy`s, i.e. `1`.
pub const DEFAULT_GAS_PRICE: u64 = 1;

/// Returns a new `DeployBuilder`.
///
/// # Note
///
/// Before calling [`build`](Self::build), you must ensure
/// * that an account is provided by either calling [`with_account`](Self::with_account) or
/// [`with_secret_key`](Self::with_secret_key)
/// * that payment code is provided by calling
/// [`with_payment`](Self::with_payment)
pub fn new<C: Into<String>>(chain_name: C, session: ExecutableDeployItem) -> Self {
#[cfg(any(feature = "std-fs-io", test))]
let timestamp = Timestamp::now();
#[cfg(not(any(feature = "std-fs-io", test)))]
let timestamp = Timestamp::zero();

DeployBuilder {
account: None,
secret_key: None,
timestamp,
ttl: Self::DEFAULT_TTL,
gas_price: Self::DEFAULT_GAS_PRICE,
dependencies: vec![],
chain_name: chain_name.into(),
payment: None,
session,
}
}

/// Returns a new `DeployBuilder` with session code suitable for a transfer.
///
/// If `maybe_source` is None, the account's main purse is used as the source of the transfer.
///
/// # Note
///
/// Before calling [`build`](Self::build), you must ensure
/// * that an account is provided by either calling [`with_account`](Self::with_account) or
/// [`with_secret_key`](Self::with_secret_key)
/// * that payment code is provided by calling
/// [`with_payment`](Self::with_payment)
pub fn new_transfer<C: Into<String>, A: Into<U512>, T: Into<TransferTarget>>(
chain_name: C,
amount: A,
maybe_source: Option<URef>,
target: T,
maybe_transfer_id: Option<u64>,
) -> Self {
let session =
ExecutableDeployItem::new_transfer(amount, maybe_source, target, maybe_transfer_id);
DeployBuilder::new(chain_name, session)
}

/// Sets the `account` in the `Deploy`.
///
/// If not provided, the public key derived from the secret key used in the `DeployBuilder` will
/// be used as the `account` in the `Deploy`.
pub fn with_account(mut self, account: PublicKey) -> Self {
self.account = Some(account);
self
}

/// Sets the gas price in the `Deploy` to the provided amount.
///
/// If not provided, the `Deploy` will use `DEFAULT_GAS_PRICE` (1) as the gas price for the
/// `deploy`
pub fn with_gas_price(mut self, gas_price: u64) -> Self {
self.gas_price = gas_price;
self
}

/// Sets the secret key used to sign the `Deploy` on calling [`build`](Self::build).
///
/// If not provided, the `Deploy` can still be built, but will be unsigned and will be invalid
/// until subsequently signed.
pub fn with_secret_key(mut self, secret_key: &'a SecretKey) -> Self {
self.secret_key = Some(secret_key);
self
}

/// Sets the `payment` in the `Deploy`.
pub fn with_payment(mut self, payment: ExecutableDeployItem) -> Self {
self.payment = Some(payment);
self
}

/// Sets the `timestamp` in the `Deploy`.
///
/// If not provided, the timestamp will be set to the time when the `DeployBuilder` was
/// constructed.
pub fn with_timestamp(mut self, timestamp: Timestamp) -> Self {
self.timestamp = timestamp;
self
}

/// Sets the `ttl` (time-to-live) in the `Deploy`.
///
/// If not provided, the ttl will be set to [`Self::DEFAULT_TTL`].
pub fn with_ttl(mut self, ttl: TimeDiff) -> Self {
self.ttl = ttl;
self
}

#[allow(clippy::too_many_arguments)]
fn build_deploy_inner(
timestamp: Timestamp,
ttl: TimeDiff,
gas_price: u64,
dependencies: Vec<DeployHash>,
chain_name: String,
payment: ExecutableDeployItem,
session: ExecutableDeployItem,
initiator_addr_and_secret_key: InitiatorAddrAndSecretKey,
) -> Deploy {
let serialized_body = serialize_body(&payment, &session);
let body_hash = Digest::hash(serialized_body);

let account = match initiator_addr_and_secret_key.initiator_addr() {
InitiatorAddr::PublicKey(public_key) => public_key,
InitiatorAddr::AccountHash(_) => unreachable!(),
};

let dependencies = dependencies.into_iter().unique().collect();
let header = DeployHeader::new(
account,
timestamp,
ttl,
gas_price,
body_hash,
dependencies,
chain_name,
);
let serialized_header = serialize_header(&header);
let hash = DeployHash::new(Digest::hash(serialized_header));

let mut deploy = Deploy::new(hash, header, payment, session);

if let Some(secret_key) = initiator_addr_and_secret_key.secret_key() {
deploy.sign(secret_key);
}
deploy
}

/// Returns the new `Deploy`, or an error if
/// [`with_payment`](Self::with_payment) wasn't previously called.
pub fn build(self) -> Result<Deploy, DeployBuilderError> {
let initiator_addr_and_secret_key = match (self.account, self.secret_key) {
(Some(account), Some(secret_key)) => InitiatorAddrAndSecretKey::Both {
initiator_addr: InitiatorAddr::PublicKey(account),
secret_key,
},
(Some(account), None) => {
InitiatorAddrAndSecretKey::InitiatorAddr(InitiatorAddr::PublicKey(account))
}
(None, Some(secret_key)) => InitiatorAddrAndSecretKey::SecretKey(secret_key),
(None, None) => return Err(DeployBuilderError::DeployMissingSessionAccount),
};

let payment = self
.payment
.ok_or(DeployBuilderError::DeployMissingPaymentCode)?;
let deploy = Self::build_deploy_inner(
self.timestamp,
self.ttl,
self.gas_price,
self.dependencies,
self.chain_name,
payment,
self.session,
initiator_addr_and_secret_key,
);
Ok(deploy)
}
}

fn serialize_header(header: &DeployHeader) -> Vec<u8> {
header
.to_bytes()
.unwrap_or_else(|error| panic!("should serialize deploy header: {}", error))
}

fn serialize_body(payment: &ExecutableDeployItem, session: &ExecutableDeployItem) -> Vec<u8> {
let mut buffer = Vec::with_capacity(payment.serialized_length() + session.serialized_length());
payment
.write_bytes(&mut buffer)
.unwrap_or_else(|error| panic!("should serialize payment code: {}", error));
session
.write_bytes(&mut buffer)
.unwrap_or_else(|error| panic!("should serialize session code: {}", error));
buffer
}
42 changes: 42 additions & 0 deletions lib/cli/deploy_builder/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use core::fmt::{self, Display, Formatter};
use std::error::Error as StdError;

#[cfg(doc)]
use super::{Deploy, DeployBuilder};

/// Errors returned while building a [`Deploy`] using a [`DeployBuilder`].
#[derive(Clone, Eq, PartialEq, Debug)]
#[non_exhaustive]
pub enum DeployBuilderError {
/// Failed to build `Deploy` due to missing session account.
///
/// Call [`DeployBuilder::with_account`] or [`DeployBuilder::with_secret_key`] before
/// calling [`DeployBuilder::build`].
DeployMissingSessionAccount,
/// Failed to build `Deploy` due to missing payment code.
///
/// Call [`DeployBuilder::with_payment`] before
/// calling [`DeployBuilder::build`].
DeployMissingPaymentCode,
}

impl Display for DeployBuilderError {
fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
match self {
DeployBuilderError::DeployMissingSessionAccount => {
write!(
formatter,
"deploy requires session account - use `with_account` or `with_secret_key`"
)
}
DeployBuilderError::DeployMissingPaymentCode => {
write!(
formatter,
"deploy requires payment code - use `with_payment`"
)
}
}
}
}

impl StdError for DeployBuilderError {}
Loading
Loading