Skip to content

Commit

Permalink
Merge pull request #212 from teonite/remove_transaction_builder_depen…
Browse files Browse the repository at this point in the history
…dency

Remove external transaction & deploy builder dependency
  • Loading branch information
wojcik91 authored Nov 29, 2024
2 parents 8c0134c + 85aeb95 commit db55fdd
Show file tree
Hide file tree
Showing 14 changed files with 1,033 additions and 18 deletions.
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

0 comments on commit db55fdd

Please sign in to comment.