-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #212 from teonite/remove_transaction_builder_depen…
…dency Remove external transaction & deploy builder dependency
- Loading branch information
Showing
14 changed files
with
1,033 additions
and
18 deletions.
There are no files selected for viewing
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,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 | ||
} |
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,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 {} |
Oops, something went wrong.