Refactor transaction building combinators (#3057)
* tx combinators now take operate on Result to allow for more robust errors handling replace with_fee() and with_lock_height() with a more flexible with_features() * pass kernel features in as arg to build::transaction() * fix chain tests * fix pool tests * do not pass kernel around in the combinators just set it once on the tx when building * build::partial_transaction now takes a existing tx to build on
This commit is contained in:
@@ -463,42 +463,17 @@ impl TxKernel {
|
||||
|
||||
/// Build an empty tx kernel with zero values.
|
||||
pub fn empty() -> TxKernel {
|
||||
TxKernel::with_features(KernelFeatures::Plain { fee: 0 })
|
||||
}
|
||||
|
||||
/// Build an empty tx kernel with the provided kernel features.
|
||||
pub fn with_features(features: KernelFeatures) -> TxKernel {
|
||||
TxKernel {
|
||||
features: KernelFeatures::Plain { fee: 0 },
|
||||
features,
|
||||
excess: Commitment::from_vec(vec![0; 33]),
|
||||
excess_sig: secp::Signature::from_raw_data(&[0; 64]).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a new tx kernel with the provided fee.
|
||||
/// Will panic if we cannot safely do this on the existing kernel.
|
||||
/// i.e. Do not try and set a fee on a coinbase kernel.
|
||||
pub fn with_fee(self, fee: u64) -> TxKernel {
|
||||
match self.features {
|
||||
KernelFeatures::Plain { .. } => {
|
||||
let features = KernelFeatures::Plain { fee };
|
||||
TxKernel { features, ..self }
|
||||
}
|
||||
KernelFeatures::HeightLocked { lock_height, .. } => {
|
||||
let features = KernelFeatures::HeightLocked { fee, lock_height };
|
||||
TxKernel { features, ..self }
|
||||
}
|
||||
KernelFeatures::Coinbase => panic!("fee not supported on coinbase kernel"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a new tx kernel with the provided lock_height.
|
||||
/// Will panic if we cannot safely do this on the existing kernel.
|
||||
/// i.e. Do not try and set a lock_height on a coinbase kernel.
|
||||
pub fn with_lock_height(self, lock_height: u64) -> TxKernel {
|
||||
match self.features {
|
||||
KernelFeatures::Plain { fee } | KernelFeatures::HeightLocked { fee, .. } => {
|
||||
let features = KernelFeatures::HeightLocked { fee, lock_height };
|
||||
TxKernel { features, ..self }
|
||||
}
|
||||
KernelFeatures::Coinbase => panic!("lock_height not supported on coinbase kernel"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Enum of possible tx weight verification options -
|
||||
@@ -684,6 +659,13 @@ impl TransactionBody {
|
||||
self
|
||||
}
|
||||
|
||||
/// Builds a new TransactionBody replacing any existing kernels with the provided kernel.
|
||||
pub fn replace_kernel(mut self, kernel: TxKernel) -> TransactionBody {
|
||||
self.kernels.clear();
|
||||
self.kernels.push(kernel);
|
||||
self
|
||||
}
|
||||
|
||||
/// Total fee for a TransactionBody is the sum of fees of all fee carrying kernels.
|
||||
pub fn fee(&self) -> u64 {
|
||||
self.kernels
|
||||
@@ -1012,8 +994,8 @@ impl Transaction {
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a new transaction with the provided output added. Existing
|
||||
/// outputs, if any, are kept intact.
|
||||
/// Builds a new transaction with the provided kernel added. Existing
|
||||
/// kernels, if any, are kept intact.
|
||||
/// Sort order is maintained.
|
||||
pub fn with_kernel(self, kernel: TxKernel) -> Transaction {
|
||||
Transaction {
|
||||
@@ -1022,6 +1004,14 @@ impl Transaction {
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a new transaction replacing any existing kernels with the provided kernel.
|
||||
pub fn replace_kernel(self, kernel: TxKernel) -> Transaction {
|
||||
Transaction {
|
||||
body: self.body.replace_kernel(kernel),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Get inputs
|
||||
pub fn inputs(&self) -> &Vec<Input> {
|
||||
&self.body.inputs
|
||||
|
||||
+63
-112
@@ -22,10 +22,16 @@
|
||||
//! _transaction_ function.
|
||||
//!
|
||||
//! Example:
|
||||
//! build::transaction(vec![input_rand(75), output_rand(42), output_rand(32),
|
||||
//! with_fee(1)])
|
||||
//! build::transaction(
|
||||
//! KernelFeatures::Plain{ fee: 2 },
|
||||
//! vec![
|
||||
//! input_rand(75),
|
||||
//! output_rand(42),
|
||||
//! output_rand(32),
|
||||
//! ]
|
||||
//! )
|
||||
|
||||
use crate::core::{Input, Output, OutputFeatures, Transaction, TxKernel};
|
||||
use crate::core::{Input, KernelFeatures, Output, OutputFeatures, Transaction, TxKernel};
|
||||
use crate::keychain::{BlindSum, BlindingFactor, Identifier, Keychain};
|
||||
use crate::libtx::proof::{self, ProofBuild};
|
||||
use crate::libtx::{aggsig, Error};
|
||||
@@ -44,11 +50,12 @@ where
|
||||
}
|
||||
|
||||
/// Function type returned by the transaction combinators. Transforms a
|
||||
/// (Transaction, BlindSum) pair into another, provided some context.
|
||||
/// (Transaction, BlindSum) tuple into another, given the provided context.
|
||||
/// Will return an Err if seomthing went wrong at any point during transaction building.
|
||||
pub type Append<K, B> = dyn for<'a> Fn(
|
||||
&'a mut Context<'_, K, B>,
|
||||
(Transaction, TxKernel, BlindSum),
|
||||
) -> (Transaction, TxKernel, BlindSum);
|
||||
Result<(Transaction, BlindSum), Error>,
|
||||
) -> Result<(Transaction, BlindSum), Error>;
|
||||
|
||||
/// Adds an input with the provided value and blinding key to the transaction
|
||||
/// being built.
|
||||
@@ -58,17 +65,21 @@ where
|
||||
B: ProofBuild,
|
||||
{
|
||||
Box::new(
|
||||
move |build, (tx, kern, sum)| -> (Transaction, TxKernel, BlindSum) {
|
||||
let commit = build
|
||||
.keychain
|
||||
.commit(value, &key_id, &SwitchCommitmentType::Regular)
|
||||
.unwrap(); // TODO: proper support for different switch commitment schemes
|
||||
let input = Input::new(features, commit);
|
||||
(
|
||||
tx.with_input(input),
|
||||
kern,
|
||||
sum.sub_key_id(key_id.to_value_path(value)),
|
||||
)
|
||||
move |build, acc| -> Result<(Transaction, BlindSum), Error> {
|
||||
if let Ok((tx, sum)) = acc {
|
||||
let commit =
|
||||
build
|
||||
.keychain
|
||||
.commit(value, &key_id, &SwitchCommitmentType::Regular)?;
|
||||
// TODO: proper support for different switch commitment schemes
|
||||
let input = Input::new(features, commit);
|
||||
Ok((
|
||||
tx.with_input(input),
|
||||
sum.sub_key_id(key_id.to_value_path(value)),
|
||||
))
|
||||
} else {
|
||||
acc
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -105,11 +116,13 @@ where
|
||||
B: ProofBuild,
|
||||
{
|
||||
Box::new(
|
||||
move |build, (tx, kern, sum)| -> (Transaction, TxKernel, BlindSum) {
|
||||
move |build, acc| -> Result<(Transaction, BlindSum), Error> {
|
||||
let (tx, sum) = acc?;
|
||||
|
||||
// TODO: proper support for different switch commitment schemes
|
||||
let switch = &SwitchCommitmentType::Regular;
|
||||
|
||||
let commit = build.keychain.commit(value, &key_id, switch).unwrap();
|
||||
let commit = build.keychain.commit(value, &key_id, switch)?;
|
||||
|
||||
debug!("Building output: {}, {:?}", value, commit);
|
||||
|
||||
@@ -121,44 +134,16 @@ where
|
||||
switch,
|
||||
commit,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
)?;
|
||||
|
||||
(
|
||||
Ok((
|
||||
tx.with_output(Output {
|
||||
features: OutputFeatures::Plain,
|
||||
commit,
|
||||
proof: rproof,
|
||||
}),
|
||||
kern,
|
||||
sum.add_key_id(key_id.to_value_path(value)),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Sets the fee on the transaction being built.
|
||||
pub fn with_fee<K, B>(fee: u64) -> Box<Append<K, B>>
|
||||
where
|
||||
K: Keychain,
|
||||
B: ProofBuild,
|
||||
{
|
||||
Box::new(
|
||||
move |_build, (tx, kern, sum)| -> (Transaction, TxKernel, BlindSum) {
|
||||
(tx, kern.with_fee(fee), sum)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Sets the lock_height on the transaction being built.
|
||||
pub fn with_lock_height<K, B>(lock_height: u64) -> Box<Append<K, B>>
|
||||
where
|
||||
K: Keychain,
|
||||
B: ProofBuild,
|
||||
{
|
||||
Box::new(
|
||||
move |_build, (tx, kern, sum)| -> (Transaction, TxKernel, BlindSum) {
|
||||
(tx, kern.with_lock_height(lock_height), sum)
|
||||
))
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -172,53 +157,32 @@ where
|
||||
B: ProofBuild,
|
||||
{
|
||||
Box::new(
|
||||
move |_build, (tx, kern, sum)| -> (Transaction, TxKernel, BlindSum) {
|
||||
(tx, kern, sum.add_blinding_factor(excess.clone()))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Sets a known tx "offset". Used in final step of tx construction.
|
||||
pub fn with_offset<K, B>(offset: BlindingFactor) -> Box<Append<K, B>>
|
||||
where
|
||||
K: Keychain,
|
||||
B: ProofBuild,
|
||||
{
|
||||
Box::new(
|
||||
move |_build, (tx, kern, sum)| -> (Transaction, TxKernel, BlindSum) {
|
||||
(tx.with_offset(offset.clone()), kern, sum)
|
||||
move |_build, acc| -> Result<(Transaction, BlindSum), Error> {
|
||||
acc.map(|(tx, sum)| (tx, sum.add_blinding_factor(excess.clone())))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Sets an initial transaction to add to when building a new transaction.
|
||||
/// We currently only support building a tx with a single kernel with
|
||||
/// build::transaction()
|
||||
pub fn initial_tx<K, B>(mut tx: Transaction) -> Box<Append<K, B>>
|
||||
pub fn initial_tx<K, B>(tx: Transaction) -> Box<Append<K, B>>
|
||||
where
|
||||
K: Keychain,
|
||||
B: ProofBuild,
|
||||
{
|
||||
assert_eq!(tx.kernels().len(), 1);
|
||||
let kern = tx.kernels_mut().remove(0);
|
||||
Box::new(
|
||||
move |_build, (_, _, sum)| -> (Transaction, TxKernel, BlindSum) {
|
||||
(tx.clone(), kern.clone(), sum)
|
||||
move |_build, acc| -> Result<(Transaction, BlindSum), Error> {
|
||||
acc.map(|(_, sum)| (tx.clone(), sum))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Builds a new transaction by combining all the combinators provided in a
|
||||
/// Vector. Transactions can either be built "from scratch" with a list of
|
||||
/// inputs or outputs or from a pre-existing transaction that gets added to.
|
||||
/// Takes an existing transaction and partially builds on it.
|
||||
///
|
||||
/// Example:
|
||||
/// let (tx1, sum) = build::transaction(vec![input_rand(4), output_rand(1),
|
||||
/// with_fee(1)], keychain).unwrap();
|
||||
/// let (tx2, _) = build::transaction(vec![initial_tx(tx1), with_excess(sum),
|
||||
/// output_rand(2)], keychain).unwrap();
|
||||
/// let (tx, sum) = build::transaction(tx, vec![input_rand(4), output_rand(1))], keychain)?;
|
||||
///
|
||||
pub fn partial_transaction<K, B>(
|
||||
tx: Transaction,
|
||||
elems: Vec<Box<Append<K, B>>>,
|
||||
keychain: &K,
|
||||
builder: &B,
|
||||
@@ -228,22 +192,16 @@ where
|
||||
B: ProofBuild,
|
||||
{
|
||||
let mut ctx = Context { keychain, builder };
|
||||
let (tx, kern, sum) = elems.iter().fold(
|
||||
(Transaction::empty(), TxKernel::empty(), BlindSum::new()),
|
||||
|acc, elem| elem(&mut ctx, acc),
|
||||
);
|
||||
let (tx, sum) = elems
|
||||
.iter()
|
||||
.fold(Ok((tx, BlindSum::new())), |acc, elem| elem(&mut ctx, acc))?;
|
||||
let blind_sum = ctx.keychain.blind_sum(&sum)?;
|
||||
|
||||
// we only support building a tx with a single kernel via build::transaction()
|
||||
assert!(tx.kernels().is_empty());
|
||||
|
||||
let tx = tx.with_kernel(kern);
|
||||
|
||||
Ok((tx, blind_sum))
|
||||
}
|
||||
|
||||
/// Builds a complete transaction.
|
||||
pub fn transaction<K, B>(
|
||||
features: KernelFeatures,
|
||||
elems: Vec<Box<Append<K, B>>>,
|
||||
keychain: &K,
|
||||
builder: &B,
|
||||
@@ -253,10 +211,11 @@ where
|
||||
B: ProofBuild,
|
||||
{
|
||||
let mut ctx = Context { keychain, builder };
|
||||
let (mut tx, mut kern, sum) = elems.iter().fold(
|
||||
(Transaction::empty(), TxKernel::empty(), BlindSum::new()),
|
||||
|acc, elem| elem(&mut ctx, acc),
|
||||
);
|
||||
let (mut tx, sum) = elems
|
||||
.iter()
|
||||
.fold(Ok((Transaction::empty(), BlindSum::new())), |acc, elem| {
|
||||
elem(&mut ctx, acc)
|
||||
})?;
|
||||
let blind_sum = ctx.keychain.blind_sum(&sum)?;
|
||||
|
||||
// Split the key so we can generate an offset for the tx.
|
||||
@@ -264,6 +223,8 @@ where
|
||||
let k1 = split.blind_1;
|
||||
let k2 = split.blind_2;
|
||||
|
||||
let mut kern = TxKernel::with_features(features);
|
||||
|
||||
// Construct the message to be signed.
|
||||
let msg = kern.msg_to_sign()?;
|
||||
|
||||
@@ -271,17 +232,14 @@ where
|
||||
let skey = k1.secret_key(&keychain.secp())?;
|
||||
kern.excess = ctx.keychain.secp().commit(0, skey)?;
|
||||
let pubkey = &kern.excess.to_pubkey(&keychain.secp())?;
|
||||
kern.excess_sig =
|
||||
aggsig::sign_with_blinding(&keychain.secp(), &msg, &k1, Some(&pubkey)).unwrap();
|
||||
kern.excess_sig = aggsig::sign_with_blinding(&keychain.secp(), &msg, &k1, Some(&pubkey))?;
|
||||
|
||||
// Store the kernel offset (k2) on the tx.
|
||||
// Commitments will sum correctly when accounting for the offset.
|
||||
tx.offset = k2.clone();
|
||||
|
||||
// Set the kernel on the tx (assert this is now a single-kernel tx).
|
||||
assert!(tx.kernels().is_empty());
|
||||
let tx = tx.with_kernel(kern);
|
||||
assert_eq!(tx.kernels().len(), 1);
|
||||
// Set the kernel on the tx.
|
||||
let tx = tx.replace_kernel(kern);
|
||||
|
||||
Ok(tx)
|
||||
}
|
||||
@@ -313,12 +271,8 @@ mod test {
|
||||
let vc = verifier_cache();
|
||||
|
||||
let tx = transaction(
|
||||
vec![
|
||||
input(10, key_id1),
|
||||
input(12, key_id2),
|
||||
output(20, key_id3),
|
||||
with_fee(2),
|
||||
],
|
||||
KernelFeatures::Plain { fee: 2 },
|
||||
vec![input(10, key_id1), input(12, key_id2), output(20, key_id3)],
|
||||
&keychain,
|
||||
&builder,
|
||||
)
|
||||
@@ -338,12 +292,8 @@ mod test {
|
||||
let vc = verifier_cache();
|
||||
|
||||
let tx = transaction(
|
||||
vec![
|
||||
input(10, key_id1),
|
||||
input(12, key_id2),
|
||||
output(20, key_id3),
|
||||
with_fee(2),
|
||||
],
|
||||
KernelFeatures::Plain { fee: 2 },
|
||||
vec![input(10, key_id1), input(12, key_id2), output(20, key_id3)],
|
||||
&keychain,
|
||||
&builder,
|
||||
)
|
||||
@@ -362,7 +312,8 @@ mod test {
|
||||
let vc = verifier_cache();
|
||||
|
||||
let tx = transaction(
|
||||
vec![input(6, key_id1), output(2, key_id2), with_fee(4)],
|
||||
KernelFeatures::Plain { fee: 4 },
|
||||
vec![input(6, key_id1), output(2, key_id2)],
|
||||
&keychain,
|
||||
&builder,
|
||||
)
|
||||
|
||||
@@ -44,6 +44,9 @@ pub enum ErrorKind {
|
||||
/// Rangeproof error
|
||||
#[fail(display = "Rangeproof Error")]
|
||||
RangeProof(String),
|
||||
/// Other error
|
||||
#[fail(display = "Other Error")]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
impl Fail for Error {
|
||||
|
||||
Reference in New Issue
Block a user