Skip to content
Permalink
Browse files

Initial version of the transaction builder.

Current version of the transaction builder is designed to be
as simple as possible and contain only barebone API. So it can
be later improved to fit all the needs.
Builder only works with stable fee algorithms i.e. the ones that
can perfectly estimate the fee cost. The only provided fee
algorithm exactly matches that criterion. It sums up inputs
and outputs count and calculates the cost.
  • Loading branch information...
qnikst committed Mar 13, 2019
1 parent e393f0b commit dc6202579d1f207ab8534f0d0f917f9fe8b3273b
Showing with 250 additions and 0 deletions.
  1. +68 −0 chain-impl-mockchain/src/fee.rs
  2. +2 −0 chain-impl-mockchain/src/lib.rs
  3. +180 −0 chain-impl-mockchain/src/txbuilder.rs
@@ -0,0 +1,68 @@
use crate::transaction as tx;
use crate::value::Value;
use chain_addr::Address;
use std::ops::{Add, Mul};

/// Linear fee using the basic affine formula `A * bytes(tx) + CONSTANT`.
#[derive(PartialEq, Eq, PartialOrd, Debug, Clone, Copy)]
pub struct LinearFee {
pub constant: Milli,
pub coefficient: Milli,
}

impl LinearFee {
pub fn new(constant: Milli, coefficient: Milli) -> Self {
LinearFee {
constant,
coefficient,
}
}
}

#[derive(PartialEq, Eq, PartialOrd, Debug, Clone, Copy)]
pub struct Milli(u128);
impl Milli {
pub fn new(i: u64, f: u64) -> Self {
Milli(i as u128 * 1000 + f as u128 % 1000)
}

pub fn integral(i: u64) -> Self {
Milli(i as u128 * 1000)
}

pub fn to_integral(self) -> u64 {
if self.0 % 1000 == 0 {
(self.0 / 1000) as u64
} else {
((self.0 / 1000) + 1) as u64
}
}
}

impl Add for Milli {
type Output = Milli;
fn add(self, other: Self) -> Self {
Milli(self.0 + other.0)
}
}

impl Mul for Milli {
type Output = Milli;

fn mul(self, other: Self) -> Self {
let v = self.0 as u128 * other.0 as u128;
Milli(v / 1000)
}
}

pub trait FeeAlgorithm {
fn calculate_for(&self, tx: &tx::Transaction<Address>) -> Value;
}

impl FeeAlgorithm for LinearFee {
fn calculate_for(&self, tx: &tx::Transaction<Address>) -> Value {
let msz = Milli::integral((tx.inputs.len() + tx.outputs.len()) as u64);
let fee = self.coefficient * msz + self.constant;
Value(fee.to_integral())
}
}
@@ -9,13 +9,15 @@ mod date;
// #[cfg(test)]
// pub mod environment;
pub mod error;
pub mod fee;
pub mod key;
pub mod leadership;
pub mod ledger;
pub mod setting;
pub mod stake;
pub mod state;
pub mod transaction;
pub mod txbuilder;
pub mod update;
pub mod utxo;
pub mod value;
@@ -0,0 +1,180 @@
use crate::fee::FeeAlgorithm;
use crate::key::SpendingSecretKey;
use crate::transaction as tx;
use crate::value::{Value, ValueError};
use chain_addr::Address;

/// Possible error for the builder.
#[derive(Debug, Clone)]
pub enum Error {
TxInvalidNoInput,
TxInvalidNoOutput,
TxNotEnoughTotalInput,
MathErr(ValueError),
}

/*
impl fmt::Display for Error {
match self {
Error::TxInvalidNoInput => write!(f, "Transaction is invalid no input"),
Error::TxInvalidNoOutput => write!(f, "Transaction is invalid no output"),
}
}
}
*/

/// Output policy to be used in transaction. This policy is used then
/// there is positive balance on in the OUTPUT+FEE-INPUT. Policy
/// explains how to use that balance. Rember that policy application
/// may change the amount of the fee.
#[derive(Debug, Clone)]
pub enum OutputPolicy {
/// Send all extra balance to the given address.
One(Address),
/// Forget everything, do not try to return money.
Forget,
}

#[derive(Clone)]
/// Transacion builder is helper to generate well
/// formed transaction in for the blockchain.
pub struct TransactionBuilder<Address>(tx::Transaction<Address>);

impl TransactionBuilder<Address> {
/// Create new transaction builder.
pub fn new() -> TransactionBuilder<Address> {
TransactionBuilder(tx::Transaction {
inputs: vec![],
outputs: vec![],
})
}

/// Add additional input.
///
/// Each input may extend the size of the required fee.
pub fn add_input(&mut self, input: &tx::Input) {
self.0.inputs.push(input.clone())
}

/// Add additional output.
///
/// Each output may extend the size of the required fee.
pub fn add_output(&mut self, address: Address, value: Value) {
self.0.outputs.push(tx::Output { address, value })
}

/// We finalize the transaction by passing fee rule and return
/// policy. Then after all calculations were made we can get
/// the information back to us.
///
pub fn finalize<F: FeeAlgorithm>(
mut self,
fee_algorithm: F,
policy: OutputPolicy,
) -> Result<(Balance, TransactionFinalizer), Error> {
if self.0.inputs.len() == 0 {
return Err(Error::TxInvalidNoInput);
}
if self.0.outputs.len() == 0 {
return Err(Error::TxInvalidNoOutput);
}
// calculate initial fee, maybe we can fit it without any
// additional calculations.
let fee = fee_algorithm.calculate_for(&self.0);
let pos = match balance(&self.0, fee) {
Ok(Balance::Negative(_)) => return Err(Error::TxNotEnoughTotalInput),
Ok(Balance::Positive(v)) => v,
Ok(Balance::Zero) => {
return Ok((Balance::Zero, TransactionFinalizer::new(self.0)));
}
Err(err) => return Err(Error::MathErr(err)),
};
// we have more money in the inputs then fee and outputs
// so we need to return some money back to us.
match policy {
OutputPolicy::Forget => {
let tx = TransactionFinalizer(tx::SignedTransaction {
transaction: self.0,
witnesses: vec![],
});
Ok((Balance::Positive(pos), tx))
}
// We will try to find the best matching value, for
// this reason we will try to reduce the set using
// value estimated by the current fee.
//
// We are searching in a range
// [min_value, max_value)
OutputPolicy::One(address) => {
// This is simplified version of the algorithm that
// works only in case in fee can perfectly estimate
// the required cost. We add an additional empty output
// hoping that it doesn't change fee count.
//
// Otherwise better estimation algorithm is needed.
let mut tx = self.0.clone();
tx.outputs.push(tx::Output {
address: address.clone(),
value: Value(0),
});
let fee = fee_algorithm.calculate_for(&tx);
match balance(&tx, fee) {
Ok(Balance::Positive(value)) => {
self.0.outputs.push(tx::Output { address, value });
return Ok((Balance::Zero, TransactionFinalizer::new(self.0)));
}
_ => {
return Ok((Balance::Positive(pos), TransactionFinalizer::new(self.0)));
}
}
}
}
}
}

/// Amount of the balance in the transaction.
pub enum Balance {
/// Balance is positive.
Positive(Value),
/// Balance is negative, such transaction can't be valid.
Negative(Value),
/// Balance is zero.
Zero,
}

fn balance(tx: &tx::Transaction<Address>, fee: Value) -> Result<Balance, ValueError> {
let inputs = Value::sum(tx.inputs.iter().map(|i| i.value))?;
let outputs = Value::sum(tx.outputs.iter().map(|o| o.value))?;
let z = (outputs + fee)?;
if inputs > z {
Ok(Balance::Positive((inputs - z)?))
} else if inputs < z {
Ok(Balance::Negative((z - inputs)?))
} else {
Ok(Balance::Zero)
}
}

pub struct TransactionFinalizer(tx::SignedTransaction<Address>);

impl TransactionFinalizer {
fn new(transaction: tx::Transaction<Address>) -> Self {
TransactionFinalizer(tx::SignedTransaction {
transaction,
witnesses: vec![],
})
}

/// Sign transaction.
pub fn sign(&mut self, pk: &SpendingSecretKey) {
// TODO: check if signature is required.
// TODO: check if signature matches address.
let id = self.0.transaction.hash();
let witness = tx::Witness::new(&id, pk);
self.0.witnesses.push(witness);
}

pub fn build(self) -> tx::SignedTransaction<Address> {
self.0
}
}

0 comments on commit dc62025

Please sign in to comment.
You can’t perform that action at this time.