use dup_crypto::hashs::*;
use durs_common_tools::fatal_error;
use pest::iterators::Pair;
use pest::iterators::Pairs;
use pest::Parser;
use std::ops::{Add, Deref, Sub};
use std::str::FromStr;
use unwrap::unwrap;
use crate::blockstamp::Blockstamp;
use crate::documents::*;
use crate::text_document_traits::*;
#[derive(Debug, Copy, Clone, Eq, Ord, PartialEq, PartialOrd, Deserialize, Hash, Serialize)]
pub struct TxAmount(pub isize);
impl Add for TxAmount {
type Output = TxAmount;
fn add(self, a: TxAmount) -> Self::Output {
TxAmount(self.0 + a.0)
}
}
impl Sub for TxAmount {
type Output = TxAmount;
fn sub(self, a: TxAmount) -> Self::Output {
TxAmount(self.0 - a.0)
}
}
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Deserialize, Hash, Serialize)]
pub struct TxBase(pub usize);
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
pub struct TxIndex(pub usize);
#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub enum TransactionInput {
D(TxAmount, TxBase, PubKey, BlockNumber),
T(TxAmount, TxBase, Hash, TxIndex),
}
impl ToString for TransactionInput {
fn to_string(&self) -> String {
match *self {
TransactionInput::D(amount, base, pubkey, block_number) => {
format!("{}:{}:D:{}:{}", amount.0, base.0, pubkey, block_number.0)
}
TransactionInput::T(amount, base, ref tx_hash, tx_index) => {
format!("{}:{}:T:{}:{}", amount.0, base.0, tx_hash, tx_index.0)
}
}
}
}
impl TransactionInput {
fn from_pest_pair(mut pairs: Pairs<Rule>) -> TransactionInput {
let tx_input_type_pair = pairs.next().unwrap();
match tx_input_type_pair.as_rule() {
Rule::tx_input_du => {
let mut inner_rules = tx_input_type_pair.into_inner();
TransactionInput::D(
TxAmount(inner_rules.next().unwrap().as_str().parse().unwrap()),
TxBase(inner_rules.next().unwrap().as_str().parse().unwrap()),
PubKey::Ed25519(
ed25519::PublicKey::from_base58(inner_rules.next().unwrap().as_str())
.unwrap(),
),
BlockNumber(inner_rules.next().unwrap().as_str().parse().unwrap()),
)
}
Rule::tx_input_tx => {
let mut inner_rules = tx_input_type_pair.into_inner();
TransactionInput::T(
TxAmount(inner_rules.next().unwrap().as_str().parse().unwrap()),
TxBase(inner_rules.next().unwrap().as_str().parse().unwrap()),
Hash::from_hex(inner_rules.next().unwrap().as_str()).unwrap(),
TxIndex(inner_rules.next().unwrap().as_str().parse().unwrap()),
)
}
_ => fatal_error!("unexpected rule: {:?}", tx_input_type_pair.as_rule()),
}
}
}
impl FromStr for TransactionInput {
type Err = TextDocumentParseError;
fn from_str(source: &str) -> Result<Self, Self::Err> {
match DocumentsParser::parse(Rule::tx_input, source) {
Ok(mut pairs) => Ok(TransactionInput::from_pest_pair(
pairs.next().unwrap().into_inner(),
)),
Err(_) => Err(TextDocumentParseError::InvalidInnerFormat(
"Invalid unlocks !".to_owned(),
)),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub enum TransactionUnlockProof {
Sig(usize),
Xhx(String),
}
impl ToString for TransactionUnlockProof {
fn to_string(&self) -> String {
match *self {
TransactionUnlockProof::Sig(ref index) => format!("SIG({})", index),
TransactionUnlockProof::Xhx(ref hash) => format!("XHX({})", hash),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct TransactionInputUnlocks {
pub index: usize,
pub unlocks: Vec<TransactionUnlockProof>,
}
impl ToString for TransactionInputUnlocks {
fn to_string(&self) -> String {
let mut result: String = format!("{}:", self.index);
for unlock in &self.unlocks {
result.push_str(&format!("{} ", unlock.to_string()));
}
let new_size = result.len() - 1;
result.truncate(new_size);
result
}
}
impl TransactionInputUnlocks {
fn from_pest_pair(pairs: Pairs<Rule>) -> TransactionInputUnlocks {
let mut input_index = 0;
let mut unlock_conds = Vec::new();
for unlock_field in pairs {
match unlock_field.as_rule() {
Rule::input_index => input_index = unlock_field.as_str().parse().unwrap(),
Rule::unlock_sig => unlock_conds.push(TransactionUnlockProof::Sig(
unlock_field
.into_inner()
.next()
.unwrap()
.as_str()
.parse()
.unwrap(),
)),
Rule::unlock_xhx => unlock_conds.push(TransactionUnlockProof::Xhx(String::from(
unlock_field.into_inner().next().unwrap().as_str(),
))),
_ => fatal_error!("unexpected rule: {:?}", unlock_field.as_rule()),
}
}
TransactionInputUnlocks {
index: input_index,
unlocks: unlock_conds,
}
}
}
impl FromStr for TransactionInputUnlocks {
type Err = TextDocumentParseError;
fn from_str(source: &str) -> Result<Self, Self::Err> {
match DocumentsParser::parse(Rule::tx_unlock, source) {
Ok(mut pairs) => Ok(TransactionInputUnlocks::from_pest_pair(
pairs.next().unwrap().into_inner(),
)),
Err(_) => Err(TextDocumentParseError::InvalidInnerFormat(
"Invalid unlocks !".to_owned(),
)),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
pub enum TransactionOutputCondition {
Sig(PubKey),
Xhx(Hash),
Cltv(u64),
Csv(u64),
}
impl ToString for TransactionOutputCondition {
fn to_string(&self) -> String {
match *self {
TransactionOutputCondition::Sig(ref pubkey) => format!("SIG({})", pubkey),
TransactionOutputCondition::Xhx(ref hash) => format!("XHX({})", hash),
TransactionOutputCondition::Cltv(timestamp) => format!("CLTV({})", timestamp),
TransactionOutputCondition::Csv(duration) => format!("CSV({})", duration),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
pub struct UTXOConditions {
pub origin_str: Option<String>,
pub conditions: UTXOConditionsGroup,
}
impl UTXOConditions {
pub fn reduce(&mut self) {
if self.origin_str.is_some()
&& self.origin_str.clone().expect("safe unwrap") == self.conditions.to_string()
{
self.origin_str = None;
}
}
pub fn check(&self) -> bool {
!(self.origin_str.is_some()
&& self.origin_str.clone().expect("safe unwrap") != self.conditions.to_string())
}
}
impl ToString for UTXOConditions {
fn to_string(&self) -> String {
if let Some(ref origin_str) = self.origin_str {
origin_str.to_string()
} else {
self.conditions.to_string()
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
pub enum UTXOConditionsGroup {
Single(TransactionOutputCondition),
Brackets(Box<UTXOConditionsGroup>),
And(Box<UTXOConditionsGroup>, Box<UTXOConditionsGroup>),
Or(Box<UTXOConditionsGroup>, Box<UTXOConditionsGroup>),
}
macro_rules! utxo_conds_wrap_op_chain {
($op:expr, $fn_name:ident) => {
fn $fn_name(conds_subgroups: &mut Vec<UTXOConditionsGroup>) -> UTXOConditionsGroup {
if conds_subgroups.len() == 2 {
$op(
Box::new(conds_subgroups[0].clone()),
Box::new(conds_subgroups[1].clone()),
)
} else if conds_subgroups.len() > 2 {
let last_subgroup = conds_subgroups.pop().unwrap();
let previous_last_subgroup = conds_subgroups.pop().unwrap();
conds_subgroups.push($op(
Box::new(previous_last_subgroup),
Box::new(last_subgroup),
));
UTXOConditionsGroup::$fn_name(conds_subgroups)
} else {
fatal_error!(
"Grammar should ensure that and chain contains at least two conditions subgroups !"
)
}
}
}
}
impl UTXOConditionsGroup {
utxo_conds_wrap_op_chain!(UTXOConditionsGroup::And, new_and_chain);
utxo_conds_wrap_op_chain!(UTXOConditionsGroup::Or, new_or_chain);
pub fn wrap_utxo_conds(pair: Pair<Rule>) -> UTXOConditionsGroup {
match pair.as_rule() {
Rule::output_and_group => {
let and_pairs = pair.into_inner();
let mut conds_subgroups: Vec<UTXOConditionsGroup> = and_pairs
.map(UTXOConditionsGroup::wrap_utxo_conds)
.collect();
UTXOConditionsGroup::Brackets(Box::new(UTXOConditionsGroup::new_and_chain(
&mut conds_subgroups,
)))
}
Rule::output_or_group => {
let or_pairs = pair.into_inner();
let mut conds_subgroups: Vec<UTXOConditionsGroup> =
or_pairs.map(UTXOConditionsGroup::wrap_utxo_conds).collect();
UTXOConditionsGroup::Brackets(Box::new(UTXOConditionsGroup::new_or_chain(
&mut conds_subgroups,
)))
}
Rule::output_cond_sig => {
UTXOConditionsGroup::Single(TransactionOutputCondition::Sig(PubKey::Ed25519(
ed25519::PublicKey::from_base58(pair.into_inner().next().unwrap().as_str())
.unwrap(),
)))
}
Rule::output_cond_xhx => UTXOConditionsGroup::Single(TransactionOutputCondition::Xhx(
Hash::from_hex(pair.into_inner().next().unwrap().as_str()).unwrap(),
)),
Rule::output_cond_csv => UTXOConditionsGroup::Single(TransactionOutputCondition::Csv(
pair.into_inner().next().unwrap().as_str().parse().unwrap(),
)),
Rule::output_cond_cltv => {
UTXOConditionsGroup::Single(TransactionOutputCondition::Cltv(
pair.into_inner().next().unwrap().as_str().parse().unwrap(),
))
}
_ => fatal_error!("unexpected rule: {:?}", pair.as_rule()),
}
}
}
impl ToString for UTXOConditionsGroup {
fn to_string(&self) -> String {
match *self {
UTXOConditionsGroup::Single(ref condition) => condition.to_string(),
UTXOConditionsGroup::Brackets(ref condition_group) => {
format!("({})", condition_group.deref().to_string())
}
UTXOConditionsGroup::And(ref condition_group_1, ref condition_group_2) => format!(
"{} && {}",
condition_group_1.deref().to_string(),
condition_group_2.deref().to_string()
),
UTXOConditionsGroup::Or(ref condition_group_1, ref condition_group_2) => format!(
"{} || {}",
condition_group_1.deref().to_string(),
condition_group_2.deref().to_string()
),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct TransactionOutput {
pub amount: TxAmount,
pub base: TxBase,
pub conditions: UTXOConditions,
}
impl TransactionOutput {
fn reduce(&mut self) {
self.conditions.reduce()
}
pub fn check(&self) -> bool {
self.conditions.check()
}
}
impl ToString for TransactionOutput {
fn to_string(&self) -> String {
format!(
"{}:{}:{}",
self.amount.0,
self.base.0,
self.conditions.to_string()
)
}
}
impl TransactionOutput {
fn from_pest_pair(mut utxo_pairs: Pairs<Rule>) -> TransactionOutput {
let amount = TxAmount(utxo_pairs.next().unwrap().as_str().parse().unwrap());
let base = TxBase(utxo_pairs.next().unwrap().as_str().parse().unwrap());
let conditions_pairs = utxo_pairs.next().unwrap();
let conditions_origin_str = conditions_pairs.as_str();
TransactionOutput {
amount,
base,
conditions: UTXOConditions {
origin_str: Some(String::from(conditions_origin_str)),
conditions: UTXOConditionsGroup::wrap_utxo_conds(conditions_pairs),
},
}
}
}
impl FromStr for TransactionOutput {
type Err = TextDocumentParseError;
fn from_str(source: &str) -> Result<Self, Self::Err> {
let output_parts: Vec<&str> = source.split(':').collect();
let amount = output_parts.get(0);
let base = output_parts.get(1);
let conditions_origin_str = output_parts.get(2);
let str_to_parse = if amount.is_some() && base.is_some() && conditions_origin_str.is_some()
{
format!(
"{}:{}:({})",
unwrap!(amount),
unwrap!(base),
unwrap!(conditions_origin_str)
)
} else {
source.to_owned()
};
match DocumentsParser::parse(Rule::tx_output, &str_to_parse) {
Ok(mut utxo_pairs) => {
let mut output =
TransactionOutput::from_pest_pair(utxo_pairs.next().unwrap().into_inner());
output.conditions.origin_str = conditions_origin_str.map(ToString::to_string);
Ok(output)
}
Err(_) => match DocumentsParser::parse(Rule::tx_output, source) {
Ok(mut utxo_pairs) => {
let mut output =
TransactionOutput::from_pest_pair(utxo_pairs.next().unwrap().into_inner());
output.conditions.origin_str = conditions_origin_str.map(ToString::to_string);
Ok(output)
}
Err(e) => Err(TextDocumentParseError::InvalidInnerFormat(format!(
"Invalid output : {}",
e
))),
},
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct TransactionDocument {
text: Option<String>,
currency: String,
blockstamp: Blockstamp,
locktime: u64,
issuers: Vec<PubKey>,
inputs: Vec<TransactionInput>,
unlocks: Vec<TransactionInputUnlocks>,
outputs: Vec<TransactionOutput>,
comment: String,
signatures: Vec<Sig>,
hash: Option<Hash>,
}
#[derive(Clone, Debug, Deserialize, Hash, Serialize, PartialEq, Eq)]
pub struct TransactionDocumentStringified {
pub currency: String,
pub blockstamp: String,
pub locktime: u64,
pub issuers: Vec<String>,
pub inputs: Vec<String>,
pub unlocks: Vec<String>,
pub outputs: Vec<String>,
pub comment: String,
pub signatures: Vec<String>,
pub hash: Option<String>,
}
impl ToStringObject for TransactionDocument {
type StringObject = TransactionDocumentStringified;
fn to_string_object(&self) -> TransactionDocumentStringified {
TransactionDocumentStringified {
currency: self.currency.clone(),
blockstamp: format!("{}", self.blockstamp),
locktime: self.locktime,
issuers: self.issuers.iter().map(|p| format!("{}", p)).collect(),
inputs: self
.inputs
.iter()
.map(TransactionInput::to_string)
.collect(),
unlocks: self
.unlocks
.iter()
.map(TransactionInputUnlocks::to_string)
.collect(),
outputs: self
.outputs
.iter()
.map(TransactionOutput::to_string)
.collect(),
comment: self.comment.clone(),
signatures: self.signatures.iter().map(|s| format!("{}", s)).collect(),
hash: if let Some(hash) = self.hash {
Some(hash.to_string())
} else {
None
},
}
}
}
impl TransactionDocument {
pub fn compute_hash(&mut self) -> Hash {
let mut hashing_text = if let Some(ref text) = self.text {
text.clone()
} else {
fatal_error!("Try to compute_hash of tx with None text !")
};
for sig in &self.signatures {
hashing_text.push_str(&sig.to_string());
hashing_text.push_str("\n");
}
self.hash = Some(Hash::compute_str(&hashing_text));
self.hash.expect("Try to get hash of a reduce tx !")
}
pub fn get_hash_opt(&self) -> Option<Hash> {
self.hash
}
pub fn get_hash(&mut self) -> Hash {
if let Some(hash) = self.hash {
hash
} else {
self.compute_hash()
}
}
pub fn get_inputs(&self) -> &[TransactionInput] {
&self.inputs
}
pub fn get_outputs(&self) -> &[TransactionOutput] {
&self.outputs
}
pub fn reduce(&mut self) {
self.text = None;
self.hash = None;
for output in &mut self.outputs {
output.reduce()
}
}
pub fn from_pest_pair(pair: Pair<Rule>) -> Result<TransactionDocument, TextDocumentParseError> {
let doc = pair.as_str();
let mut currency = "";
let mut blockstamp = Blockstamp::default();
let mut locktime = 0;
let mut issuers = Vec::new();
let mut inputs = Vec::new();
let mut unlocks = Vec::new();
let mut outputs = Vec::new();
let mut comment = "";
let mut sigs = Vec::new();
for field in pair.into_inner() {
match field.as_rule() {
Rule::currency => currency = field.as_str(),
Rule::blockstamp => {
let mut inner_rules = field.into_inner();
let block_id: &str = inner_rules.next().unwrap().as_str();
let block_hash: &str = inner_rules.next().unwrap().as_str();
blockstamp = Blockstamp {
id: BlockNumber(block_id.parse().unwrap()),
hash: BlockHash(Hash::from_hex(block_hash).unwrap()),
};
}
Rule::tx_locktime => locktime = field.as_str().parse().unwrap(),
Rule::pubkey => issuers.push(PubKey::Ed25519(
ed25519::PublicKey::from_base58(field.as_str()).unwrap(),
)),
Rule::tx_input => inputs.push(TransactionInput::from_pest_pair(field.into_inner())),
Rule::tx_unlock => {
unlocks.push(TransactionInputUnlocks::from_pest_pair(field.into_inner()))
}
Rule::tx_output => {
outputs.push(TransactionOutput::from_pest_pair(field.into_inner()))
}
Rule::tx_comment => comment = field.as_str(),
Rule::ed25519_sig => {
sigs.push(Sig::Ed25519(
ed25519::Signature::from_base64(field.as_str()).unwrap(),
));
}
Rule::EOI => (),
_ => fatal_error!("unexpected rule: {:?}", field.as_rule()),
}
}
Ok(TransactionDocument {
text: Some(doc.to_owned()),
currency: currency.to_owned(),
blockstamp,
locktime,
issuers,
inputs,
unlocks,
outputs,
comment: comment.to_owned(),
signatures: sigs,
hash: None,
})
}
}
impl Document for TransactionDocument {
type PublicKey = PubKey;
fn version(&self) -> u16 {
10
}
fn currency(&self) -> &str {
&self.currency
}
fn blockstamp(&self) -> Blockstamp {
self.blockstamp
}
fn issuers(&self) -> &Vec<PubKey> {
&self.issuers
}
fn signatures(&self) -> &Vec<Sig> {
&self.signatures
}
fn as_bytes(&self) -> &[u8] {
self.as_text_without_signature().as_bytes()
}
}
impl CompactTextDocument for TransactionDocument {
fn as_compact_text(&self) -> String {
let mut issuers_str = String::from("");
for issuer in self.issuers.clone() {
issuers_str.push_str("\n");
issuers_str.push_str(&issuer.to_string());
}
let mut inputs_str = String::from("");
for input in self.inputs.clone() {
inputs_str.push_str("\n");
inputs_str.push_str(&input.to_string());
}
let mut unlocks_str = String::from("");
for unlock in self.unlocks.clone() {
unlocks_str.push_str("\n");
unlocks_str.push_str(&unlock.to_string());
}
let mut outputs_str = String::from("");
for output in self.outputs.clone() {
outputs_str.push_str("\n");
outputs_str.push_str(&output.to_string());
}
let mut comment_str = self.comment.clone();
if !comment_str.is_empty() {
comment_str.push_str("\n");
}
let mut signatures_str = String::from("");
for sig in self.signatures.clone() {
signatures_str.push_str(&sig.to_string());
signatures_str.push_str("\n");
}
signatures_str.pop();
format!(
"TX:10:{issuers_count}:{inputs_count}:{unlocks_count}:{outputs_count}:{has_comment}:{locktime}
{blockstamp}{issuers}{inputs}{unlocks}{outputs}\n{comment}{signatures}",
issuers_count = self.issuers.len(),
inputs_count = self.inputs.len(),
unlocks_count = self.unlocks.len(),
outputs_count = self.outputs.len(),
has_comment = if self.comment.is_empty() { 0 } else { 1 },
locktime = self.locktime,
blockstamp = self.blockstamp,
issuers = issuers_str,
inputs = inputs_str,
unlocks = unlocks_str,
outputs = outputs_str,
comment = comment_str,
signatures = signatures_str,
)
}
}
impl TextDocument for TransactionDocument {
type CompactTextDocument_ = TransactionDocument;
fn as_text(&self) -> &str {
if let Some(ref text) = self.text {
text
} else {
fatal_error!("Try to get text of tx whti None text !")
}
}
fn to_compact_document(&self) -> Self::CompactTextDocument_ {
self.clone()
}
}
#[derive(Debug, Copy, Clone)]
pub struct TransactionDocumentBuilder<'a> {
pub currency: &'a str,
pub blockstamp: &'a Blockstamp,
pub locktime: &'a u64,
pub issuers: &'a Vec<PubKey>,
pub inputs: &'a Vec<TransactionInput>,
pub unlocks: &'a Vec<TransactionInputUnlocks>,
pub outputs: &'a Vec<TransactionOutput>,
pub comment: &'a str,
pub hash: Option<Hash>,
}
impl<'a> TransactionDocumentBuilder<'a> {
fn build_with_text_and_sigs(self, text: String, signatures: Vec<Sig>) -> TransactionDocument {
TransactionDocument {
text: Some(text),
currency: self.currency.to_string(),
blockstamp: *self.blockstamp,
locktime: *self.locktime,
issuers: self.issuers.clone(),
inputs: self.inputs.clone(),
unlocks: self.unlocks.clone(),
outputs: self.outputs.clone(),
comment: String::from(self.comment),
signatures,
hash: self.hash,
}
}
}
impl<'a> DocumentBuilder for TransactionDocumentBuilder<'a> {
type Document = TransactionDocument;
type PrivateKey = PrivKey;
fn build_with_signature(&self, signatures: Vec<Sig>) -> TransactionDocument {
self.build_with_text_and_sigs(self.generate_text(), signatures)
}
fn build_and_sign(&self, private_keys: Vec<PrivKey>) -> TransactionDocument {
let (text, signatures) = self.build_signed_text(private_keys);
self.build_with_text_and_sigs(text, signatures)
}
}
impl<'a> TextDocumentBuilder for TransactionDocumentBuilder<'a> {
fn generate_text(&self) -> String {
let mut issuers_string: String = "".to_owned();
let mut inputs_string: String = "".to_owned();
let mut unlocks_string: String = "".to_owned();
let mut outputs_string: String = "".to_owned();
for issuer in self.issuers {
issuers_string.push_str(&format!("{}\n", issuer.to_string()))
}
for input in self.inputs {
inputs_string.push_str(&format!("{}\n", input.to_string()))
}
for unlock in self.unlocks {
unlocks_string.push_str(&format!("{}\n", unlock.to_string()))
}
for output in self.outputs {
outputs_string.push_str(&format!("{}\n", output.to_string()))
}
format!(
"Version: 10
Type: Transaction
Currency: {currency}
Blockstamp: {blockstamp}
Locktime: {locktime}
Issuers:
{issuers}Inputs:
{inputs}Unlocks:
{unlocks}Outputs:
{outputs}Comment: {comment}
",
currency = self.currency,
blockstamp = self.blockstamp,
locktime = self.locktime,
issuers = issuers_string,
inputs = inputs_string,
unlocks = unlocks_string,
outputs = outputs_string,
comment = self.comment,
)
}
}
#[derive(Debug, Clone, Copy)]
pub struct TransactionDocumentParser;
impl TextDocumentParser<Rule> for TransactionDocumentParser {
type DocumentType = TransactionDocument;
fn parse(doc: &str) -> Result<Self::DocumentType, TextDocumentParseError> {
let mut tx_pairs = DocumentsParser::parse(Rule::tx, doc)?;
let tx_pair = tx_pairs.next().unwrap();
Self::from_pest_pair(tx_pair)
}
#[inline]
fn from_pest_pair(pair: Pair<Rule>) -> Result<Self::DocumentType, TextDocumentParseError> {
let tx_vx_pair = pair.into_inner().next().unwrap();
match tx_vx_pair.as_rule() {
Rule::tx_v10 => Ok(TransactionDocument::from_pest_pair(tx_vx_pair)?),
_ => Err(TextDocumentParseError::UnexpectedRule(format!(
"{:#?}",
tx_vx_pair.as_rule()
))),
}
}
#[inline]
fn from_versioned_pest_pair(
version: u16,
pair: Pair<Rule>,
) -> Result<Self::DocumentType, TextDocumentParseError> {
match version {
10 => Ok(TransactionDocument::from_pest_pair(pair)?),
v => Err(TextDocumentParseError::UnexpectedVersion(format!(
"Unsupported version: {}",
v
))),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Document;
#[test]
fn generate_real_document() {
let pubkey = PubKey::Ed25519(
ed25519::PublicKey::from_base58("DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV")
.unwrap(),
);
let prikey = PrivKey::Ed25519(
ed25519::PrivateKey::from_base58(
"468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5G\
iERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7",
)
.unwrap(),
);
let sig = Sig::Ed25519(ed25519::Signature::from_base64(
"pRQeKlzCsvPNmYAAkEP5jPPQO1RwrtFMRfCajEfkkrG0UQE0DhoTkxG3Zs2JFmvAFLw67pn1V5NQ08zsSfJkBg==",
).unwrap());
let block = Blockstamp::from_string(
"0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855",
)
.unwrap();
let builder = TransactionDocumentBuilder {
currency: "duniter_unit_test_currency",
blockstamp: &block,
locktime: &0,
issuers: &vec![pubkey],
inputs: &vec![TransactionInput::D(
TxAmount(10),
TxBase(0),
PubKey::Ed25519(
ed25519::PublicKey::from_base58("DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV")
.unwrap(),
),
BlockNumber(0),
)],
unlocks: &vec![TransactionInputUnlocks {
index: 0,
unlocks: vec![TransactionUnlockProof::Sig(0)],
}],
outputs: &vec![TransactionOutput::from_str(
"10:0:SIG(FD9wujR7KABw88RyKEGBYRLz8PA6jzVCbcBAsrBXBqSa)",
)
.expect("fail to parse output !")],
comment: "test",
hash: None,
};
println!(
"Signature = {:?}",
builder.build_and_sign(vec![prikey]).signatures()
);
assert!(builder
.build_with_signature(vec![sig])
.verify_signatures()
.is_ok());
assert!(builder
.build_and_sign(vec![prikey])
.verify_signatures()
.is_ok());
}
#[test]
fn compute_transaction_hash() {
let pubkey = PubKey::Ed25519(
ed25519::PublicKey::from_base58("FEkbc4BfJukSWnCU6Hed6dgwwTuPFTVdgz5LpL4iHr9J")
.unwrap(),
);
let sig = Sig::Ed25519(ed25519::Signature::from_base64(
"XEwKwKF8AI1gWPT7elR4IN+bW3Qn02Dk15TEgrKtY/S2qfZsNaodsLofqHLI24BBwZ5aadpC88ntmjo/UW9oDQ==",
).unwrap());
let block = Blockstamp::from_string(
"60-00001FE00410FCD5991EDD18AA7DDF15F4C8393A64FA92A1DB1C1CA2E220128D",
)
.unwrap();
let builder = TransactionDocumentBuilder {
currency: "g1",
blockstamp: &block,
locktime: &0,
issuers: &vec![pubkey],
inputs: &vec![TransactionInput::T(
TxAmount(950),
TxBase(0),
Hash::from_hex("2CF1ACD8FE8DC93EE39A1D55881C50D87C55892AE8E4DB71D4EBAB3D412AA8FD")
.unwrap(),
TxIndex(1),
)],
unlocks: &vec![
TransactionInputUnlocks::from_str("0:SIG(0)").expect("fail to parse unlock !")
],
outputs: &vec![
TransactionOutput::from_str(
"30:0:SIG(38MEAZN68Pz1DTvT3tqgxx4yQP6snJCQhPqEFxbDk4aE)",
)
.expect("fail to parse output !"),
TransactionOutput::from_str(
"920:0:SIG(FEkbc4BfJukSWnCU6Hed6dgwwTuPFTVdgz5LpL4iHr9J)",
)
.expect("fail to parse output !"),
],
comment: "Pour cesium merci",
hash: None,
};
let mut tx_doc = builder.build_with_signature(vec![sig]);
tx_doc.hash = None;
assert!(tx_doc.verify_signatures().is_ok());
assert_eq!(
tx_doc.get_hash(),
Hash::from_hex("876D2430E0B66E2CE4467866D8F923D68896CACD6AA49CDD8BDD0096B834DEF1")
.expect("fail to parse hash")
);
}
#[test]
fn parse_transaction_document() {
let doc = "Version: 10
Type: Transaction
Currency: duniter_unit_test_currency
Blockstamp: 204-00003E2B8A35370BA5A7064598F628A62D4E9EC1936BE8651CE9A85F2E06981B
Locktime: 0
Issuers:
DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV
4tNQ7d9pj2Da5wUVoW9mFn7JjuPoowF977au8DdhEjVR
FD9wujR7KABw88RyKEGBYRLz8PA6jzVCbcBAsrBXBqSa
Inputs:
40:2:T:6991C993631BED4733972ED7538E41CCC33660F554E3C51963E2A0AC4D6453D3:2
70:2:T:3A09A20E9014110FD224889F13357BAB4EC78A72F95CA03394D8CCA2936A7435:8
20:2:D:DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV:46
70:2:T:A0D9B4CDC113ECE1145C5525873821398890AE842F4B318BD076095A23E70956:3
20:2:T:67F2045B5318777CC52CD38B424F3E40DDA823FA0364625F124BABE0030E7B5B:5
15:2:D:FD9wujR7KABw88RyKEGBYRLz8PA6jzVCbcBAsrBXBqSa:46
Unlocks:
0:SIG(0)
1:XHX(7665798292)
2:SIG(0)
3:SIG(0) SIG(2)
4:SIG(0) SIG(1) SIG(2)
5:SIG(2)
Outputs:
120:2:SIG(BYfWYFrsyjpvpFysgu19rGK3VHBkz4MqmQbNyEuVU64g)
146:2:SIG(DSz4rgncXCytsUMW2JU2yhLquZECD2XpEkpP9gG5HyAx)
49:2:(SIG(6DyGr5LFtFmbaJYRvcs9WmBsr4cbJbJ1EV9zBbqG7A6i) || XHX(3EB4702F2AC2FD3FA4FDC46A4FC05AE8CDEE1A85F2AC2FD3FA4FDC46A4FC01CA))
Comment: -----@@@----- (why not this comment?)
kL59C1izKjcRN429AlKdshwhWbasvyL7sthI757zm1DfZTdTIctDWlKbYeG/tS7QyAgI3gcfrTHPhu1E1lKCBw==
e3LpgB2RZ/E/BCxPJsn+TDDyxGYzrIsMyDt//KhJCjIQD6pNUxr5M5jrq2OwQZgwmz91YcmoQ2XRQAUDpe4BAw==
w69bYgiQxDmCReB0Dugt9BstXlAKnwJkKCdWvCeZ9KnUCv0FJys6klzYk/O/b9t74tYhWZSX0bhETWHiwfpWBw==";
let doc = TransactionDocumentParser::parse(doc)
.expect("fail to parse test transaction document !");
println!("{}", doc.generate_compact_text());
assert!(doc.verify_signatures().is_ok());
assert_eq!(
doc.generate_compact_text(),
"TX:10:3:6:6:3:1:0
204-00003E2B8A35370BA5A7064598F628A62D4E9EC1936BE8651CE9A85F2E06981B
DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV
4tNQ7d9pj2Da5wUVoW9mFn7JjuPoowF977au8DdhEjVR
FD9wujR7KABw88RyKEGBYRLz8PA6jzVCbcBAsrBXBqSa
40:2:T:6991C993631BED4733972ED7538E41CCC33660F554E3C51963E2A0AC4D6453D3:2
70:2:T:3A09A20E9014110FD224889F13357BAB4EC78A72F95CA03394D8CCA2936A7435:8
20:2:D:DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV:46
70:2:T:A0D9B4CDC113ECE1145C5525873821398890AE842F4B318BD076095A23E70956:3
20:2:T:67F2045B5318777CC52CD38B424F3E40DDA823FA0364625F124BABE0030E7B5B:5
15:2:D:FD9wujR7KABw88RyKEGBYRLz8PA6jzVCbcBAsrBXBqSa:46
0:SIG(0)
1:XHX(7665798292)
2:SIG(0)
3:SIG(0) SIG(2)
4:SIG(0) SIG(1) SIG(2)
5:SIG(2)
120:2:SIG(BYfWYFrsyjpvpFysgu19rGK3VHBkz4MqmQbNyEuVU64g)
146:2:SIG(DSz4rgncXCytsUMW2JU2yhLquZECD2XpEkpP9gG5HyAx)
49:2:(SIG(6DyGr5LFtFmbaJYRvcs9WmBsr4cbJbJ1EV9zBbqG7A6i) || XHX(3EB4702F2AC2FD3FA4FDC46A4FC05AE8CDEE1A85F2AC2FD3FA4FDC46A4FC01CA))
-----@@@----- (why not this comment?)
kL59C1izKjcRN429AlKdshwhWbasvyL7sthI757zm1DfZTdTIctDWlKbYeG/tS7QyAgI3gcfrTHPhu1E1lKCBw==
e3LpgB2RZ/E/BCxPJsn+TDDyxGYzrIsMyDt//KhJCjIQD6pNUxr5M5jrq2OwQZgwmz91YcmoQ2XRQAUDpe4BAw==
w69bYgiQxDmCReB0Dugt9BstXlAKnwJkKCdWvCeZ9KnUCv0FJys6klzYk/O/b9t74tYhWZSX0bhETWHiwfpWBw=="
);
}
}