diff --git a/lib/dubp/block-doc/src/block/v10.rs b/lib/dubp/block-doc/src/block/v10.rs index 4877671bffcdbe926c6a22a9c89d1d312e3fdd12..206f6db8229a9768f28f8f781e9e653698a63146 100644 --- a/lib/dubp/block-doc/src/block/v10.rs +++ b/lib/dubp/block-doc/src/block/v10.rs @@ -31,7 +31,9 @@ use dubp_user_docs::documents::membership::v10::{ }; use dubp_user_docs::documents::revocation::v10::CompactRevocationDocumentV10Stringified; use dubp_user_docs::documents::revocation::RevocationDocumentV10; -use dubp_user_docs::documents::transaction::{TransactionDocument, TransactionDocumentStringified}; +use dubp_user_docs::documents::transaction::v10::{ + TransactionDocumentV10, TransactionDocumentV10Stringified, +}; use dup_crypto::hashs::Hash; use dup_crypto::keys::*; use durs_common_tools::fatal_error; @@ -100,7 +102,7 @@ pub struct BlockDocumentV10 { /// Certifications pub certifications: Vec<TextDocumentFormat<CertificationDocumentV10>>, /// Transactions - pub transactions: Vec<TransactionDocument>, + pub transactions: Vec<TransactionDocumentV10>, } impl BlockDocumentTrait for BlockDocumentV10 { @@ -502,7 +504,7 @@ pub struct BlockDocumentV10Stringified { /// Certifications pub certifications: Vec<CompactCertificationDocumentV10Stringified>, /// Transactions - pub transactions: Vec<TransactionDocumentStringified>, + pub transactions: Vec<TransactionDocumentV10Stringified>, } impl ToStringObject for BlockDocumentV10 { @@ -594,7 +596,7 @@ mod tests { CertificationDocument, CertificationDocumentParser, }; use dubp_user_docs::documents::membership::{MembershipDocument, MembershipDocumentParser}; - use dubp_user_docs::documents::transaction::TransactionDocumentParser; + use dubp_user_docs::documents::transaction::{TransactionDocument, TransactionDocumentParser}; use unwrap::unwrap; #[test] @@ -670,7 +672,7 @@ UmseG2XKNwKcY8RFi6gUCT91udGnnNmSh7se10J1jeRVlwf+O2Tyb2Cccot9Dt7BO4+Kx2P6vFJB3oVG CertificationDocument::V10(cert_v10) => cert_v10, }; - let tx1 = TransactionDocumentParser::parse("Version: 10 + let TransactionDocument::V10(tx1) = TransactionDocumentParser::parse("Version: 10 Type: Transaction Currency: g1 Blockstamp: 107982-000001242F6DA51C06A915A96C58BAA37AB3D1EB51F6E1C630C707845ACF764B @@ -686,7 +688,7 @@ Outputs: Comment: DU symbolique pour demander le codage de nouvelles fonctionnalites cf. https://forum.monnaie-libre.fr/t/creer-de-nouvelles-fonctionnalites-dans-cesium-les-autres-applications/2025 Merci T0LlCcbIn7xDFws48H8LboN6NxxwNXXTovG4PROLf7tkUAueHFWjfwZFKQXeZEHxfaL1eYs3QspGtLWUHPRVCQ==").expect("Fail to parse tx1"); - let tx2 = TransactionDocumentParser::parse("Version: 10 + let TransactionDocument::V10(tx2) = TransactionDocumentParser::parse("Version: 10 Type: Transaction Currency: g1 Blockstamp: 107982-000001242F6DA51C06A915A96C58BAA37AB3D1EB51F6E1C630C707845ACF764B @@ -821,7 +823,7 @@ gvaZ1QnJf8FjjRDJ0cYusgpBgQ8r0NqEz39BooH6DtIrgX+WTeXuLSnjZDl35VCBjokvyjry+v0OkTT8 MembershipDocument::V10(ms_v10) => ms_v10, }; - let tx1 = TransactionDocumentParser::parse( + let TransactionDocument::V10(tx1) = TransactionDocumentParser::parse( "Version: 10 Type: Transaction Currency: g1 @@ -853,7 +855,7 @@ Comment: Panier mixte plus 40 pommes merci ) .expect("Fail to parse tx1"); - let tx2 = TransactionDocumentParser::parse( + let TransactionDocument::V10(tx2) = TransactionDocumentParser::parse( "Version: 10 Type: Transaction Currency: g1 diff --git a/lib/dubp/block-doc/src/parser.rs b/lib/dubp/block-doc/src/parser.rs index 8e8ae54db0168805e5e36d2b4382a30d5a15e2f7..eb64513fe55f7153dbe737ff2d61605462ace4ba 100644 --- a/lib/dubp/block-doc/src/parser.rs +++ b/lib/dubp/block-doc/src/parser.rs @@ -20,7 +20,6 @@ use dubp_common_doc::{BlockHash, BlockNumber}; use dubp_currency_params::genesis_block_params::v10::BlockV10Parameters; use dubp_currency_params::CurrencyName; use dubp_user_docs::documents::membership::v10::MembershipType; -use dubp_user_docs::documents::transaction::TransactionDocument; use dubp_user_docs::parsers::{serde_json_value_to_pest_json_value, DefaultHasher}; use dup_crypto::bases::BaseConvertionError; use dup_crypto::hashs::Hash; @@ -121,19 +120,10 @@ pub fn parse_json_block(json_block: &JSONValue<DefaultHasher>) -> Result<BlockDo certifications: dubp_user_docs::parsers::certifications::parse_certifications_into_compact( &get_str_array(json_block, "certifications")?, ), - transactions: json_block - .get("transactions") - .ok_or_else(|| ParseJsonError { - cause: "Fail to parse json block : field 'transactions' must exist !".to_owned(), - })? - .to_array() - .ok_or_else(|| ParseJsonError { - cause: "Fail to parse json block : field 'transactions' must be an array !" - .to_owned(), - })? - .iter() - .map(|tx| dubp_user_docs::parsers::transactions::parse_json_transaction(tx)) - .collect::<Result<Vec<TransactionDocument>, Error>>()?, + transactions: dubp_user_docs::parsers::transactions::parse_json_transactions(&get_array( + json_block, + "transactions", + )?)?, })) } @@ -141,6 +131,7 @@ pub fn parse_json_block(json_block: &JSONValue<DefaultHasher>) -> Result<BlockDo mod tests { use super::*; use crate::block::*; + use dubp_user_docs::documents::transaction::TransactionDocument; #[test] fn parse_empty_json_block() { @@ -351,7 +342,9 @@ mod tests { revoked: vec![], excluded: vec![], certifications: vec![], - transactions: vec![dubp_user_docs_tests_tools::mocks::tx::first_g1_tx_doc()], + transactions: vec![match dubp_user_docs_tests_tools::mocks::tx::first_g1_tx_doc() { + TransactionDocument::V10(tx_doc) => tx_doc, + }], }); assert_eq!( expected_block, diff --git a/lib/dubp/user-docs/src/documents/transaction.rs b/lib/dubp/user-docs/src/documents/transaction.rs index ea2d33a57ab86500179077b48966c59eddb16cb8..8b9425af9bc96fb41078715ef338ad38c07bc19d 100644 --- a/lib/dubp/user-docs/src/documents/transaction.rs +++ b/lib/dubp/user-docs/src/documents/transaction.rs @@ -15,22 +15,23 @@ //! Wrappers around Transaction documents. +pub mod v10; + use crate::documents::*; use dubp_common_doc::blockstamp::Blockstamp; use dubp_common_doc::parser::{DocumentsParser, TextDocumentParseError, TextDocumentParser}; use dubp_common_doc::traits::text::*; use dubp_common_doc::traits::{Document, DocumentBuilder, ToStringObject}; -use dubp_common_doc::{BlockHash, BlockNumber}; use dup_crypto::hashs::*; use dup_crypto::keys::*; -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; +pub use v10::{ + TransactionDocumentV10, TransactionDocumentV10Builder, TransactionDocumentV10Parser, + TransactionDocumentV10Stringified, TransactionInputV10, TransactionOutputV10, +}; + /// Wrap a transaction amount #[derive(Debug, Copy, Clone, Eq, Ord, PartialEq, PartialOrd, Deserialize, Hash, Serialize)] pub struct TxAmount(pub isize); @@ -57,113 +58,6 @@ pub struct TxBase(pub usize); #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] pub struct OutputIndex(pub usize); -/// Wrap a transaction input -#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)] -pub enum TransactionInput { - /// Universal Dividend Input - D(TxAmount, TxBase, PubKey, BlockNumber), - /// Previous Transaction Input - T(TxAmount, TxBase, Hash, OutputIndex), -} - -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 = unwrap!(pairs.next()); - match tx_input_type_pair.as_rule() { - Rule::tx_input_du => { - let mut inner_rules = tx_input_type_pair.into_inner(); // ${ tx_amount ~ ":" ~ tx_amount_base ~ ":D:" ~ pubkey ~ ":" ~ du_block_id } - - TransactionInput::D( - TxAmount(unwrap!(unwrap!(inner_rules.next()).as_str().parse())), - TxBase(unwrap!(unwrap!(inner_rules.next()).as_str().parse())), - PubKey::Ed25519(unwrap!(ed25519::PublicKey::from_base58( - unwrap!(inner_rules.next()).as_str() - ))), - BlockNumber(unwrap!(unwrap!(inner_rules.next()).as_str().parse())), - ) - } - Rule::tx_input_tx => { - let mut inner_rules = tx_input_type_pair.into_inner(); // ${ tx_amount ~ ":" ~ tx_amount_base ~ ":D:" ~ pubkey ~ ":" ~ du_block_id } - - TransactionInput::T( - TxAmount(unwrap!(unwrap!(inner_rules.next()).as_str().parse())), - TxBase(unwrap!(unwrap!(inner_rules.next()).as_str().parse())), - unwrap!(Hash::from_hex(unwrap!(inner_rules.next()).as_str())), - OutputIndex(unwrap!(unwrap!(inner_rules.next()).as_str().parse())), - ) - } - _ => fatal_error!("unexpected rule: {:?}", tx_input_type_pair.as_rule()), // Grammar ensures that we never reach this line - } - } -} - -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( - unwrap!(pairs.next()).into_inner(), - )), - Err(_) => Err(TextDocumentParseError::InvalidInnerFormat( - "Invalid unlocks !".to_owned(), - )), - } - } -} - -/*impl TransactionInput { - /// Parse Transaction Input from string. - pub fn from_str(source: &str) -> Result<TransactionInput, TextDocumentParseError> { - if let Some(caps) = D_INPUT_REGEX.captures(source) { - let amount = &caps["amount"]; - let base = &caps["base"]; - let pubkey = &caps["pubkey"]; - let block_number = &caps["block_number"]; - Ok(TransactionInput::D( - TxAmount(amount.parse().expect("fail to parse input amount !")), - TxBase(base.parse().expect("fail to parse input base !")), - PubKey::Ed25519( - ed25519::PublicKey::from_base58(pubkey).expect("fail to parse input pubkey !"), - ), - BlockNumber( - block_number - .parse() - .expect("fail to parse input block_number !"), - ), - )) - //Ok(TransactionInput::D(10, 0, PubKey::Ed25519(ed25519::PublicKey::from_base58("FD9wujR7KABw88RyKEGBYRLz8PA6jzVCbcBAsrBXBqSa").unwrap(), 0))) - } else if let Some(caps) = T_INPUT_REGEX.captures(source) { - let amount = &caps["amount"]; - let base = &caps["base"]; - let tx_hash = &caps["tx_hash"]; - let tx_index = &caps["tx_index"]; - Ok(TransactionInput::T( - TxAmount(amount.parse().expect("fail to parse input amount")), - TxBase(base.parse().expect("fail to parse base amount")), - Hash::from_hex(tx_hash).expect("fail to parse tx_hash"), - OutputIndex(tx_index.parse().expect("fail to parse tx_index amount")), - )) - } else { - println!("Fail to parse this input = {:?}", source); - Err(TextDocumentParseError::InvalidInnerFormat("Transaction2")) - } - } -}*/ - /// Wrap a transaction unlock proof #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] pub enum TransactionUnlockProof { @@ -182,70 +76,6 @@ impl ToString for TransactionUnlockProof { } } -/// Wrap a transaction unlocks input -#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] -pub struct TransactionInputUnlocks { - /// Input index - pub index: usize, - /// List of proof to unlock funds - 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 { - // ${ input_index ~ ":" ~ unlock_cond ~ (" " ~ unlock_cond)* } - match unlock_field.as_rule() { - Rule::input_index => input_index = unwrap!(unlock_field.as_str().parse()), - Rule::unlock_sig => { - unlock_conds.push(TransactionUnlockProof::Sig(unwrap!(unwrap!(unlock_field - .into_inner() - .next()) - .as_str() - .parse()))) - } - Rule::unlock_xhx => unlock_conds.push(TransactionUnlockProof::Xhx(String::from( - unwrap!(unlock_field.into_inner().next()).as_str(), - ))), - _ => fatal_error!("unexpected rule: {:?}", unlock_field.as_rule()), // Grammar ensures that we never reach this line - } - } - 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( - unwrap!(pairs.next()).into_inner(), - )), - Err(_) => Err(TextDocumentParseError::InvalidInnerFormat( - "Invalid unlocks !".to_owned(), - )), - } - } -} - /// Wrap a transaction ouput condition #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] pub enum TransactionOutputCondition { @@ -351,13 +181,12 @@ impl UTXOConditionsGroup { utxo_conds_wrap_op_chain!(UTXOConditionsGroup::Or, new_or_chain); /// Wrap UTXO conditions - pub fn wrap_utxo_conds(pair: Pair<Rule>) -> UTXOConditionsGroup { + pub fn from_pest_pair(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(); + let mut conds_subgroups: Vec<UTXOConditionsGroup> = + and_pairs.map(UTXOConditionsGroup::from_pest_pair).collect(); UTXOConditionsGroup::Brackets(Box::new(UTXOConditionsGroup::new_and_chain( &mut conds_subgroups, ))) @@ -365,7 +194,7 @@ impl UTXOConditionsGroup { Rule::output_or_group => { let or_pairs = pair.into_inner(); let mut conds_subgroups: Vec<UTXOConditionsGroup> = - or_pairs.map(UTXOConditionsGroup::wrap_utxo_conds).collect(); + or_pairs.map(UTXOConditionsGroup::from_pest_pair).collect(); UTXOConditionsGroup::Brackets(Box::new(UTXOConditionsGroup::new_or_chain( &mut conds_subgroups, ))) @@ -416,189 +245,37 @@ impl ToString for UTXOConditionsGroup { } } -/// Wrap a transaction ouput -#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] -pub struct TransactionOutput { - /// Amount - pub amount: TxAmount, - /// Base - pub base: TxBase, - /// List of conditions for consum this output - pub conditions: UTXOConditions, -} - -impl TransactionOutput { - /// Lightens the TransactionOutput (for example to store it while minimizing the space required) - fn reduce(&mut self) { - self.conditions.reduce() - } - /// Check validity of this output - 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(unwrap!(unwrap!(utxo_pairs.next()).as_str().parse())); - let base = TxBase(unwrap!(unwrap!(utxo_pairs.next()).as_str().parse())); - let conditions_pairs = unwrap!(utxo_pairs.next()); - 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(unwrap!(utxo_pairs.next()).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(unwrap!(utxo_pairs.next()).into_inner()); - output.conditions.origin_str = conditions_origin_str.map(ToString::to_string); - Ok(output) - } - Err(e) => Err(TextDocumentParseError::InvalidInnerFormat(format!( - "Invalid output : {}", - e - ))), - }, - } - } +pub trait TransactionDocumentTrait<'a> { + type Input: 'a; + type Inputs: AsRef<[Self::Input]>; + type Output: 'a; + type Outputs: AsRef<[Self::Output]>; + fn get_inputs(&'a self) -> Self::Inputs; + fn get_outputs(&'a self) -> Self::Outputs; } /// Wrap a Transaction document. /// /// Must be created by parsing a text document or using a builder. #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] -pub struct TransactionDocument { - /// Document as text. - /// - /// Is used to check signatures, and other values - /// must be extracted from it. - text: Option<String>, - - /// Currency. - currency: String, - /// Blockstamp - blockstamp: Blockstamp, - /// Locktime - locktime: u64, - /// Document issuer (there should be only one). - issuers: Vec<PubKey>, - /// Transaction inputs. - inputs: Vec<TransactionInput>, - /// Inputs unlocks. - unlocks: Vec<TransactionInputUnlocks>, - /// Transaction outputs. - outputs: Vec<TransactionOutput>, - /// Transaction comment - comment: String, - /// Document signature (there should be only one). - signatures: Vec<Sig>, - /// Transaction hash - hash: Option<Hash>, +pub enum TransactionDocument { + V10(TransactionDocumentV10), } #[derive(Clone, Debug, Deserialize, Hash, Serialize, PartialEq, Eq)] /// Transaction document stringifed -pub struct TransactionDocumentStringified { - /// Currency. - pub currency: String, - /// Blockstamp - pub blockstamp: String, - /// Locktime - pub locktime: u64, - /// Document issuer (there should be only one). - pub issuers: Vec<String>, - /// Transaction inputs. - pub inputs: Vec<String>, - /// Inputs unlocks. - pub unlocks: Vec<String>, - /// Transaction outputs. - pub outputs: Vec<String>, - /// Transaction comment - pub comment: String, - /// Document signatures - pub signatures: Vec<String>, - /// Transaction hash - pub hash: Option<String>, +pub enum TransactionDocumentStringified { + V10(TransactionDocumentV10Stringified), } 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 - }, + match self { + TransactionDocument::V10(tx_v10) => { + TransactionDocumentStringified::V10(tx_v10.to_string_object()) + } } } } @@ -606,109 +283,28 @@ impl ToStringObject for TransactionDocument { impl TransactionDocument { /// Compute transaction hash pub fn compute_hash(&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"); + match self { + TransactionDocument::V10(tx_v10) => tx_v10.compute_hash(), } - //println!("tx_text_hasing={}", hashing_text); - Hash::compute_str(&hashing_text) } /// get transaction hash option pub fn get_hash_opt(&self) -> Option<Hash> { - self.hash + match self { + TransactionDocument::V10(tx_v10) => tx_v10.get_hash_opt(), + } } /// Get transaction hash pub fn get_hash(&mut self) -> Hash { - if let Some(hash) = self.hash { - hash - } else { - self.hash = Some(self.compute_hash()); - self.hash.expect("unreach") + match self { + TransactionDocument::V10(tx_v10) => tx_v10.get_hash(), } } - /// Get transaction inputs - pub fn get_inputs(&self) -> &[TransactionInput] { - &self.inputs - } - /// Get transaction outputs - pub fn get_outputs(&self) -> &[TransactionOutput] { - &self.outputs - } /// Lightens the transaction (for example to store it while minimizing the space required) /// WARNING: do not remove the hash as it's necessary to reverse the transaction ! pub fn reduce(&mut self) { - self.hash = Some(self.compute_hash()); - self.text = None; - for output in &mut self.outputs { - output.reduce() - } - } - /// from pest parser pair - 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(); // ${ block_id ~ "-" ~ hash } - - let block_id: &str = unwrap!(inner_rules.next()).as_str(); - let block_hash: &str = unwrap!(inner_rules.next()).as_str(); - blockstamp = Blockstamp { - id: BlockNumber(unwrap!(block_id.parse())), // Grammar ensures that we have a digits string. - hash: BlockHash(unwrap!(Hash::from_hex(block_hash))), // Grammar ensures that we have an hexadecimal string. - }; - } - Rule::tx_locktime => locktime = unwrap!(field.as_str().parse()), // Grammar ensures that we have digits characters. - Rule::pubkey => issuers.push(PubKey::Ed25519( - unwrap!(ed25519::PublicKey::from_base58(field.as_str())), // Grammar ensures that we have a base58 string. - )), - 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( - unwrap!(ed25519::Signature::from_base64(field.as_str())), // Grammar ensures that we have a base64 string. - )); - } - Rule::EOI => (), - _ => fatal_error!("unexpected rule: {:?}", field.as_rule()), // Grammar ensures that we never reach this line - } - } - - 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, - }) + match self { + TransactionDocument::V10(tx_v10) => tx_v10.reduce(), + }; } } @@ -716,80 +312,47 @@ impl Document for TransactionDocument { type PublicKey = PubKey; fn version(&self) -> usize { - 10 + match self { + TransactionDocument::V10(tx_v10) => tx_v10.version(), + } } fn currency(&self) -> &str { - &self.currency + match self { + TransactionDocument::V10(tx_v10) => tx_v10.currency(), + } } fn blockstamp(&self) -> Blockstamp { - self.blockstamp + match self { + TransactionDocument::V10(tx_v10) => tx_v10.blockstamp(), + } } fn issuers(&self) -> &Vec<PubKey> { - &self.issuers + match self { + TransactionDocument::V10(tx_v10) => tx_v10.issuers(), + } } fn signatures(&self) -> &Vec<Sig> { - &self.signatures + match self { + TransactionDocument::V10(tx_v10) => tx_v10.signatures(), + } } fn as_bytes(&self) -> &[u8] { - self.as_text_without_signature().as_bytes() + match self { + TransactionDocument::V10(tx_v10) => tx_v10.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()); + match self { + TransactionDocument::V10(tx_v10) => tx_v10.as_compact_text(), } - 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"); - } - // Remove end line step - 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, - ) } } @@ -797,10 +360,8 @@ 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 with None text !") + match self { + TransactionDocument::V10(tx_v10) => tx_v10.as_text(), } } @@ -811,43 +372,8 @@ impl TextDocument for TransactionDocument { /// Transaction document builder. #[derive(Debug, Copy, Clone)] -pub struct TransactionDocumentBuilder<'a> { - /// Document currency. - pub currency: &'a str, - /// Reference blockstamp. - pub blockstamp: &'a Blockstamp, - /// Locktime - pub locktime: &'a u64, - /// Transaction Document issuers. - pub issuers: &'a Vec<PubKey>, - /// Transaction inputs. - pub inputs: &'a Vec<TransactionInput>, - /// Inputs unlocks. - pub unlocks: &'a Vec<TransactionInputUnlocks>, - /// Transaction ouputs. - pub outputs: &'a Vec<TransactionOutput>, - /// Transaction comment - pub comment: &'a str, - /// Transaction hash - 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, - } - } +pub enum TransactionDocumentBuilder<'a> { + V10(TransactionDocumentV10Builder<'a>), } impl<'a> DocumentBuilder for TransactionDocumentBuilder<'a> { @@ -855,60 +381,34 @@ impl<'a> DocumentBuilder for TransactionDocumentBuilder<'a> { type Signator = SignatorEnum; fn build_with_signature(&self, signatures: Vec<Sig>) -> TransactionDocument { - self.build_with_text_and_sigs(self.generate_text(), signatures) + match self { + TransactionDocumentBuilder::V10(tx_v10_builder) => { + TransactionDocument::V10(tx_v10_builder.build_with_signature(signatures)) + } + } } - fn build_and_sign(&self, private_keys: Vec<SignatorEnum>) -> TransactionDocument { - let (text, signatures) = self.build_signed_text(private_keys); - self.build_with_text_and_sigs(text, signatures) + match self { + TransactionDocumentBuilder::V10(tx_v10_builder) => { + TransactionDocument::V10(tx_v10_builder.build_and_sign(private_keys)) + } + } } } 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())) + match self { + TransactionDocumentBuilder::V10(tx_v10_builder) => tx_v10_builder.generate_text(), } - 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, - ) } } /// Transaction document parser #[derive(Debug, Clone, Copy)] -pub struct TransactionDocumentParser; +pub enum TransactionDocumentParser { + V10(TransactionDocumentV10Parser), +} impl TextDocumentParser<Rule> for TransactionDocumentParser { type DocumentType = TransactionDocument; @@ -923,7 +423,9 @@ impl TextDocumentParser<Rule> for TransactionDocumentParser { let tx_vx_pair = unwrap!(pair.into_inner().next()); // get and unwrap the `tx_vX` rule; never fails match tx_vx_pair.as_rule() { - Rule::tx_v10 => TransactionDocument::from_pest_pair(tx_vx_pair), + Rule::tx_v10 => { + TransactionDocumentV10::from_pest_pair(tx_vx_pair).map(TransactionDocument::V10) + } _ => Err(TextDocumentParseError::UnexpectedRule(format!( "{:#?}", tx_vx_pair.as_rule() @@ -936,7 +438,8 @@ impl TextDocumentParser<Rule> for TransactionDocumentParser { pair: Pair<Rule>, ) -> Result<Self::DocumentType, TextDocumentParseError> { match version { - 10 => Ok(TransactionDocument::from_pest_pair(pair)?), + 10 => TransactionDocumentV10Parser::from_versioned_pest_pair(version, pair) + .map(TransactionDocument::V10), v => Err(TextDocumentParseError::UnexpectedVersion(format!( "Unsupported version: {}", v @@ -949,6 +452,9 @@ impl TextDocumentParser<Rule> for TransactionDocumentParser { mod tests { use super::*; use dubp_common_doc::traits::Document; + use dubp_common_doc::BlockNumber; + use std::str::FromStr; + use v10::{TransactionInputUnlocksV10, TransactionOutputV10}; #[test] fn generate_real_document() { @@ -971,12 +477,12 @@ mod tests { "Fail to parse blockstamp" ); - let builder = TransactionDocumentBuilder { + let builder = TransactionDocumentV10Builder { currency: "duniter_unit_test_currency", blockstamp: &block, locktime: &0, issuers: &vec![pubkey], - inputs: &vec![TransactionInput::D( + inputs: &vec![TransactionInputV10::D( TxAmount(10), TxBase(0), PubKey::Ed25519(unwrap!( @@ -985,25 +491,17 @@ mod tests { )), BlockNumber(0), )], - unlocks: &vec![TransactionInputUnlocks { + unlocks: &vec![TransactionInputUnlocksV10 { index: 0, unlocks: vec![TransactionUnlockProof::Sig(0)], }], - outputs: &vec![TransactionOutput::from_str( + outputs: &vec![TransactionOutputV10::from_str( "10:0:SIG(FD9wujR7KABw88RyKEGBYRLz8PA6jzVCbcBAsrBXBqSa)", ) .expect("fail to parse output !")], comment: "test", hash: None, }; - /*println!( - "Signatures = {:?}", - builder - .build_and_sign(vec![SignatorEnum::Ed25519( - keypair.generate_signator().expect("fail to gen signator") - )]) - .signatures() - );*/ assert!(builder .build_with_signature(vec![sig]) .verify_signatures() @@ -1032,12 +530,12 @@ mod tests { "Fail to parse Blockstamp" ); - let builder = TransactionDocumentBuilder { + let builder = TransactionDocumentV10Builder { currency: "g1", blockstamp: &block, locktime: &0, issuers: &vec![pubkey], - inputs: &vec![TransactionInput::T( + inputs: &vec![TransactionInputV10::T( TxAmount(950), TxBase(0), unwrap!( @@ -1049,14 +547,14 @@ mod tests { OutputIndex(1), )], unlocks: &vec![ - TransactionInputUnlocks::from_str("0:SIG(0)").expect("fail to parse unlock !") + TransactionInputUnlocksV10::from_str("0:SIG(0)").expect("fail to parse unlock !") ], outputs: &vec![ - TransactionOutput::from_str( + TransactionOutputV10::from_str( "30:0:SIG(38MEAZN68Pz1DTvT3tqgxx4yQP6snJCQhPqEFxbDk4aE)", ) .expect("fail to parse output !"), - TransactionOutput::from_str( + TransactionOutputV10::from_str( "920:0:SIG(FEkbc4BfJukSWnCU6Hed6dgwwTuPFTVdgz5LpL4iHr9J)", ) .expect("fail to parse output !"), @@ -1064,9 +562,9 @@ mod tests { comment: "Pour cesium merci", hash: None, }; - let mut tx_doc = builder.build_with_signature(vec![sig]); - tx_doc.hash = None; + let mut tx_doc = TransactionDocument::V10(builder.build_with_signature(vec![sig])); assert!(tx_doc.verify_signatures().is_ok()); + assert!(tx_doc.get_hash_opt().is_none()); assert_eq!( tx_doc.get_hash(), Hash::from_hex("876D2430E0B66E2CE4467866D8F923D68896CACD6AA49CDD8BDD0096B834DEF1") diff --git a/lib/dubp/user-docs/src/documents/transaction/v10.rs b/lib/dubp/user-docs/src/documents/transaction/v10.rs new file mode 100644 index 0000000000000000000000000000000000000000..9f1c7666ccf4a9680c973e6ec41026ab24a14f15 --- /dev/null +++ b/lib/dubp/user-docs/src/documents/transaction/v10.rs @@ -0,0 +1,922 @@ +// Copyright (C) 2017-2019 The AXIOM TEAM Association. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +//! Wrappers around Transaction documents. + +use crate::documents::*; +use dubp_common_doc::blockstamp::Blockstamp; +use dubp_common_doc::parser::{DocumentsParser, TextDocumentParseError, TextDocumentParser}; +use dubp_common_doc::traits::text::*; +use dubp_common_doc::traits::{Document, DocumentBuilder, ToStringObject}; +use dubp_common_doc::{BlockHash, BlockNumber}; +use dup_crypto::hashs::*; +use dup_crypto::keys::*; +use durs_common_tools::fatal_error; +use pest::iterators::Pair; +use pest::iterators::Pairs; +use pest::Parser; +use std::str::FromStr; +use unwrap::unwrap; + +/// Wrap a transaction input +#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)] +pub enum TransactionInputV10 { + /// Universal Dividend Input + D(TxAmount, TxBase, PubKey, BlockNumber), + /// Previous Transaction Input + T(TxAmount, TxBase, Hash, OutputIndex), +} + +impl ToString for TransactionInputV10 { + fn to_string(&self) -> String { + match *self { + TransactionInputV10::D(amount, base, pubkey, block_number) => { + format!("{}:{}:D:{}:{}", amount.0, base.0, pubkey, block_number.0) + } + TransactionInputV10::T(amount, base, ref tx_hash, tx_index) => { + format!("{}:{}:T:{}:{}", amount.0, base.0, tx_hash, tx_index.0) + } + } + } +} + +impl TransactionInputV10 { + fn from_pest_pair(mut pairs: Pairs<Rule>) -> TransactionInputV10 { + let tx_input_type_pair = unwrap!(pairs.next()); + match tx_input_type_pair.as_rule() { + Rule::tx_input_du => { + let mut inner_rules = tx_input_type_pair.into_inner(); // ${ tx_amount ~ ":" ~ tx_amount_base ~ ":D:" ~ pubkey ~ ":" ~ du_block_id } + + TransactionInputV10::D( + TxAmount(unwrap!(unwrap!(inner_rules.next()).as_str().parse())), + TxBase(unwrap!(unwrap!(inner_rules.next()).as_str().parse())), + PubKey::Ed25519(unwrap!(ed25519::PublicKey::from_base58( + unwrap!(inner_rules.next()).as_str() + ))), + BlockNumber(unwrap!(unwrap!(inner_rules.next()).as_str().parse())), + ) + } + Rule::tx_input_tx => { + let mut inner_rules = tx_input_type_pair.into_inner(); // ${ tx_amount ~ ":" ~ tx_amount_base ~ ":D:" ~ pubkey ~ ":" ~ du_block_id } + + TransactionInputV10::T( + TxAmount(unwrap!(unwrap!(inner_rules.next()).as_str().parse())), + TxBase(unwrap!(unwrap!(inner_rules.next()).as_str().parse())), + unwrap!(Hash::from_hex(unwrap!(inner_rules.next()).as_str())), + OutputIndex(unwrap!(unwrap!(inner_rules.next()).as_str().parse())), + ) + } + _ => fatal_error!("unexpected rule: {:?}", tx_input_type_pair.as_rule()), // Grammar ensures that we never reach this line + } + } +} + +impl FromStr for TransactionInputV10 { + type Err = TextDocumentParseError; + + fn from_str(source: &str) -> Result<Self, Self::Err> { + match DocumentsParser::parse(Rule::tx_input, source) { + Ok(mut pairs) => Ok(TransactionInputV10::from_pest_pair( + unwrap!(pairs.next()).into_inner(), + )), + Err(_) => Err(TextDocumentParseError::InvalidInnerFormat( + "Invalid unlocks !".to_owned(), + )), + } + } +} + +impl<'a> TransactionDocumentTrait<'a> for TransactionDocumentV10 { + type Input = TransactionInputV10; + type Inputs = &'a [TransactionInputV10]; + type Output = TransactionOutputV10; + type Outputs = &'a [TransactionOutputV10]; + fn get_inputs(&'a self) -> Self::Inputs { + &self.inputs + } + fn get_outputs(&'a self) -> Self::Outputs { + &self.outputs + } +} + +/// Wrap a transaction unlocks input +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] +pub struct TransactionInputUnlocksV10 { + /// Input index + pub index: usize, + /// List of proof to unlock funds + pub unlocks: Vec<TransactionUnlockProof>, +} + +impl ToString for TransactionInputUnlocksV10 { + 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 TransactionInputUnlocksV10 { + fn from_pest_pair(pairs: Pairs<Rule>) -> TransactionInputUnlocksV10 { + let mut input_index = 0; + let mut unlock_conds = Vec::new(); + for unlock_field in pairs { + // ${ input_index ~ ":" ~ unlock_cond ~ (" " ~ unlock_cond)* } + match unlock_field.as_rule() { + Rule::input_index => input_index = unwrap!(unlock_field.as_str().parse()), + Rule::unlock_sig => { + unlock_conds.push(TransactionUnlockProof::Sig(unwrap!(unwrap!(unlock_field + .into_inner() + .next()) + .as_str() + .parse()))) + } + Rule::unlock_xhx => unlock_conds.push(TransactionUnlockProof::Xhx(String::from( + unwrap!(unlock_field.into_inner().next()).as_str(), + ))), + _ => fatal_error!("unexpected rule: {:?}", unlock_field.as_rule()), // Grammar ensures that we never reach this line + } + } + TransactionInputUnlocksV10 { + index: input_index, + unlocks: unlock_conds, + } + } +} + +impl FromStr for TransactionInputUnlocksV10 { + type Err = TextDocumentParseError; + + fn from_str(source: &str) -> Result<Self, Self::Err> { + match DocumentsParser::parse(Rule::tx_unlock, source) { + Ok(mut pairs) => Ok(TransactionInputUnlocksV10::from_pest_pair( + unwrap!(pairs.next()).into_inner(), + )), + Err(_) => Err(TextDocumentParseError::InvalidInnerFormat( + "Invalid unlocks !".to_owned(), + )), + } + } +} + +/// Wrap a transaction ouput +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] +pub struct TransactionOutputV10 { + /// Amount + pub amount: TxAmount, + /// Base + pub base: TxBase, + /// List of conditions for consum this output + pub conditions: UTXOConditions, +} + +impl TransactionOutputV10 { + /// Lightens the TransactionOutputV10 (for example to store it while minimizing the space required) + fn reduce(&mut self) { + self.conditions.reduce() + } + /// Check validity of this output + pub fn check(&self) -> bool { + self.conditions.check() + } +} + +impl ToString for TransactionOutputV10 { + fn to_string(&self) -> String { + format!( + "{}:{}:{}", + self.amount.0, + self.base.0, + self.conditions.to_string() + ) + } +} + +impl TransactionOutputV10 { + fn from_pest_pair(mut utxo_pairs: Pairs<Rule>) -> TransactionOutputV10 { + let amount = TxAmount(unwrap!(unwrap!(utxo_pairs.next()).as_str().parse())); + let base = TxBase(unwrap!(unwrap!(utxo_pairs.next()).as_str().parse())); + let conditions_pairs = unwrap!(utxo_pairs.next()); + let conditions_origin_str = conditions_pairs.as_str(); + TransactionOutputV10 { + amount, + base, + conditions: UTXOConditions { + origin_str: Some(String::from(conditions_origin_str)), + conditions: UTXOConditionsGroup::from_pest_pair(conditions_pairs), + }, + } + } +} + +impl FromStr for TransactionOutputV10 { + 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 = + TransactionOutputV10::from_pest_pair(unwrap!(utxo_pairs.next()).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 = TransactionOutputV10::from_pest_pair( + unwrap!(utxo_pairs.next()).into_inner(), + ); + output.conditions.origin_str = conditions_origin_str.map(ToString::to_string); + Ok(output) + } + Err(e) => Err(TextDocumentParseError::InvalidInnerFormat(format!( + "Invalid output : {}", + e + ))), + }, + } + } +} + +/// Wrap a Transaction document. +/// +/// Must be created by parsing a text document or using a builder. +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] +pub struct TransactionDocumentV10 { + /// Document as text. + /// + /// Is used to check signatures, and other values + /// must be extracted from it. + text: Option<String>, + + /// Currency. + currency: String, + /// Blockstamp + blockstamp: Blockstamp, + /// Locktime + locktime: u64, + /// Document issuers. + issuers: Vec<PubKey>, + /// Transaction inputs. + inputs: Vec<TransactionInputV10>, + /// Inputs unlocks. + unlocks: Vec<TransactionInputUnlocksV10>, + /// Transaction outputs. + outputs: Vec<TransactionOutputV10>, + /// Transaction comment + comment: String, + /// Document signatures. + signatures: Vec<Sig>, + /// Transaction hash + hash: Option<Hash>, +} + +#[derive(Clone, Debug, Deserialize, Hash, Serialize, PartialEq, Eq)] +/// Transaction document stringifed +pub struct TransactionDocumentV10Stringified { + /// Currency. + pub currency: String, + /// Blockstamp + pub blockstamp: String, + /// Locktime + pub locktime: u64, + /// Document issuers. + pub issuers: Vec<String>, + /// Transaction inputs. + pub inputs: Vec<String>, + /// Inputs unlocks. + pub unlocks: Vec<String>, + /// Transaction outputs. + pub outputs: Vec<String>, + /// Transaction comment + pub comment: String, + /// Document signatures + pub signatures: Vec<String>, + /// Transaction hash + pub hash: Option<String>, +} + +impl ToStringObject for TransactionDocumentV10 { + type StringObject = TransactionDocumentV10Stringified; + + fn to_string_object(&self) -> TransactionDocumentV10Stringified { + TransactionDocumentV10Stringified { + 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(TransactionInputV10::to_string) + .collect(), + unlocks: self + .unlocks + .iter() + .map(TransactionInputUnlocksV10::to_string) + .collect(), + outputs: self + .outputs + .iter() + .map(TransactionOutputV10::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 TransactionDocumentV10 { + /// Compute transaction hash + pub fn compute_hash(&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"); + } + Hash::compute_str(&hashing_text) + } + /// get transaction hash option + pub fn get_hash_opt(&self) -> Option<Hash> { + self.hash + } + /// Get transaction hash + pub fn get_hash(&mut self) -> Hash { + if let Some(hash) = self.hash { + hash + } else { + self.hash = Some(self.compute_hash()); + self.hash.expect("unreach") + } + } + /// Lightens the transaction (for example to store it while minimizing the space required) + /// WARNING: do not remove the hash as it's necessary to reverse the transaction ! + pub fn reduce(&mut self) { + self.hash = Some(self.compute_hash()); + self.text = None; + for output in &mut self.outputs { + output.reduce() + } + } + /// from pest parser pair + pub fn from_pest_pair( + pair: Pair<Rule>, + ) -> Result<TransactionDocumentV10, 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(); // ${ block_id ~ "-" ~ hash } + + let block_id: &str = unwrap!(inner_rules.next()).as_str(); + let block_hash: &str = unwrap!(inner_rules.next()).as_str(); + blockstamp = Blockstamp { + id: BlockNumber(unwrap!(block_id.parse())), // Grammar ensures that we have a digits string. + hash: BlockHash(unwrap!(Hash::from_hex(block_hash))), // Grammar ensures that we have an hexadecimal string. + }; + } + Rule::tx_locktime => locktime = unwrap!(field.as_str().parse()), // Grammar ensures that we have digits characters. + Rule::pubkey => issuers.push(PubKey::Ed25519( + unwrap!(ed25519::PublicKey::from_base58(field.as_str())), // Grammar ensures that we have a base58 string. + )), + Rule::tx_input => { + inputs.push(TransactionInputV10::from_pest_pair(field.into_inner())) + } + Rule::tx_unlock => unlocks.push(TransactionInputUnlocksV10::from_pest_pair( + field.into_inner(), + )), + Rule::tx_output => { + outputs.push(TransactionOutputV10::from_pest_pair(field.into_inner())) + } + Rule::tx_comment => comment = field.as_str(), + Rule::ed25519_sig => { + sigs.push(Sig::Ed25519( + unwrap!(ed25519::Signature::from_base64(field.as_str())), // Grammar ensures that we have a base64 string. + )); + } + Rule::EOI => (), + _ => fatal_error!("unexpected rule: {:?}", field.as_rule()), // Grammar ensures that we never reach this line + } + } + + Ok(TransactionDocumentV10 { + 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 TransactionDocumentV10 { + type PublicKey = PubKey; + + fn version(&self) -> usize { + 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 TransactionDocumentV10 { + 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"); + } + // Remove end line step + 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 TransactionDocumentV10 { + type CompactTextDocument_ = TransactionDocumentV10; + + fn as_text(&self) -> &str { + if let Some(ref text) = self.text { + text + } else { + fatal_error!("Try to get text of tx with None text !") + } + } + + fn to_compact_document(&self) -> Self::CompactTextDocument_ { + self.clone() + } +} + +/// Transaction document builder. +#[derive(Debug, Copy, Clone)] +pub struct TransactionDocumentV10Builder<'a> { + /// Document currency. + pub currency: &'a str, + /// Reference blockstamp. + pub blockstamp: &'a Blockstamp, + /// Locktime + pub locktime: &'a u64, + /// Transaction Document issuers. + pub issuers: &'a Vec<PubKey>, + /// Transaction inputs. + pub inputs: &'a Vec<TransactionInputV10>, + /// Inputs unlocks. + pub unlocks: &'a Vec<TransactionInputUnlocksV10>, + /// Transaction ouputs. + pub outputs: &'a Vec<TransactionOutputV10>, + /// Transaction comment + pub comment: &'a str, + /// Transaction hash + pub hash: Option<Hash>, +} + +impl<'a> TransactionDocumentV10Builder<'a> { + fn build_with_text_and_sigs( + self, + text: String, + signatures: Vec<Sig>, + ) -> TransactionDocumentV10 { + TransactionDocumentV10 { + 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 TransactionDocumentV10Builder<'a> { + type Document = TransactionDocumentV10; + type Signator = SignatorEnum; + + fn build_with_signature(&self, signatures: Vec<Sig>) -> TransactionDocumentV10 { + self.build_with_text_and_sigs(self.generate_text(), signatures) + } + + fn build_and_sign(&self, private_keys: Vec<SignatorEnum>) -> TransactionDocumentV10 { + let (text, signatures) = self.build_signed_text(private_keys); + self.build_with_text_and_sigs(text, signatures) + } +} + +impl<'a> TextDocumentBuilder for TransactionDocumentV10Builder<'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, + ) + } +} + +/// Transaction document parser +#[derive(Debug, Clone, Copy)] +pub struct TransactionDocumentV10Parser; + +impl TextDocumentParser<Rule> for TransactionDocumentV10Parser { + type DocumentType = TransactionDocumentV10; + + fn parse(doc: &str) -> Result<Self::DocumentType, TextDocumentParseError> { + let mut tx_pairs = DocumentsParser::parse(Rule::tx, doc)?; + let tx_pair = unwrap!(tx_pairs.next()); // get and unwrap the `tx` rule; never fails + Self::from_pest_pair(tx_pair) + } + #[inline] + fn from_pest_pair(pair: Pair<Rule>) -> Result<Self::DocumentType, TextDocumentParseError> { + let tx_vx_pair = unwrap!(pair.into_inner().next()); // get and unwrap the `tx_vX` rule; never fails + + match tx_vx_pair.as_rule() { + Rule::tx_v10 => TransactionDocumentV10::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(TransactionDocumentV10::from_pest_pair(pair)?), + v => Err(TextDocumentParseError::UnexpectedVersion(format!( + "Unsupported version: {}", + v + ))), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use dubp_common_doc::traits::Document; + + #[test] + fn generate_real_document() { + let keypair = ed25519::KeyPairFromSeed32Generator::generate(unwrap!( + Seed32::from_base58("DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV"), + "Fail to parse Seed32" + )); + let pubkey = PubKey::Ed25519(keypair.public_key()); + let signator = + SignatorEnum::Ed25519(keypair.generate_signator().expect("fail to gen signator")); + + let sig = Sig::Ed25519(unwrap!(ed25519::Signature::from_base64( + "cq86RugQlqAEyS8zFkB9o0PlWPSb+a6D/MEnLe8j+okyFYf/WzI6pFiBkQ9PSOVn5I0dwzVXg7Q4N1apMWeGAg==", + ), "Fail to parse Signature")); + + let block = unwrap!( + Blockstamp::from_string( + "0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855", + ), + "Fail to parse blockstamp" + ); + + let builder = TransactionDocumentV10Builder { + currency: "duniter_unit_test_currency", + blockstamp: &block, + locktime: &0, + issuers: &vec![pubkey], + inputs: &vec![TransactionInputV10::D( + TxAmount(10), + TxBase(0), + PubKey::Ed25519(unwrap!( + ed25519::PublicKey::from_base58("DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV"), + "Fail to parse PublicKey" + )), + BlockNumber(0), + )], + unlocks: &vec![TransactionInputUnlocksV10 { + index: 0, + unlocks: vec![TransactionUnlockProof::Sig(0)], + }], + outputs: &vec![TransactionOutputV10::from_str( + "10:0:SIG(FD9wujR7KABw88RyKEGBYRLz8PA6jzVCbcBAsrBXBqSa)", + ) + .expect("fail to parse output !")], + comment: "test", + hash: None, + }; + assert!(builder + .build_with_signature(vec![sig]) + .verify_signatures() + .is_ok()); + assert!(builder + .build_and_sign(vec![signator]) + .verify_signatures() + .is_ok()); + } + + #[test] + fn compute_transaction_hash() { + let pubkey = PubKey::Ed25519(unwrap!( + ed25519::PublicKey::from_base58("FEkbc4BfJukSWnCU6Hed6dgwwTuPFTVdgz5LpL4iHr9J"), + "Fail to parse PublicKey" + )); + + let sig = Sig::Ed25519(unwrap!(ed25519::Signature::from_base64( + "XEwKwKF8AI1gWPT7elR4IN+bW3Qn02Dk15TEgrKtY/S2qfZsNaodsLofqHLI24BBwZ5aadpC88ntmjo/UW9oDQ==", + ), "Fail to parse Signature")); + + let block = unwrap!( + Blockstamp::from_string( + "60-00001FE00410FCD5991EDD18AA7DDF15F4C8393A64FA92A1DB1C1CA2E220128D", + ), + "Fail to parse Blockstamp" + ); + + let builder = TransactionDocumentV10Builder { + currency: "g1", + blockstamp: &block, + locktime: &0, + issuers: &vec![pubkey], + inputs: &vec![TransactionInputV10::T( + TxAmount(950), + TxBase(0), + unwrap!( + Hash::from_hex( + "2CF1ACD8FE8DC93EE39A1D55881C50D87C55892AE8E4DB71D4EBAB3D412AA8FD" + ), + "Fail to parse Hash" + ), + OutputIndex(1), + )], + unlocks: &vec![ + TransactionInputUnlocksV10::from_str("0:SIG(0)").expect("fail to parse unlock !") + ], + outputs: &vec![ + TransactionOutputV10::from_str( + "30:0:SIG(38MEAZN68Pz1DTvT3tqgxx4yQP6snJCQhPqEFxbDk4aE)", + ) + .expect("fail to parse output !"), + TransactionOutputV10::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]); + assert!(tx_doc.verify_signatures().is_ok()); + assert!(tx_doc.get_hash_opt().is_none()); + 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 = TransactionDocumentV10Parser::parse(doc) + .expect("fail to parse test transaction document !"); + 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==" + ); + } + + #[test] + fn transaction_input_str() { + let expected_du = TransactionInputV10::D( + TxAmount(10), + TxBase(0), + PubKey::Ed25519(unwrap!( + ed25519::PublicKey::from_base58("DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV"), + "Fail to parse PublicKey" + )), + BlockNumber(0), + ); + let du = TransactionInputV10::from_str(&expected_du.to_string()); + assert!(du.is_ok()); + assert_eq!(expected_du, unwrap!(du)); + + let expected_tx = TransactionInputV10::T( + TxAmount(950), + TxBase(0), + unwrap!( + Hash::from_hex("2CF1ACD8FE8DC93EE39A1D55881C50D87C55892AE8E4DB71D4EBAB3D412AA8FD"), + "Fail to parse Hash" + ), + OutputIndex(1), + ); + let tx = TransactionInputV10::from_str(&expected_tx.to_string()); + assert!(tx.is_ok()); + assert_eq!(expected_tx, unwrap!(tx)); + } +} diff --git a/lib/dubp/user-docs/src/parsers/transactions.rs b/lib/dubp/user-docs/src/parsers/transactions.rs index c9736e998036f5b7379de3b6f6aadd86e97bd187..bd553a8ba048b19f6e7373cb48cebabdb64e0435 100644 --- a/lib/dubp/user-docs/src/parsers/transactions.rs +++ b/lib/dubp/user-docs/src/parsers/transactions.rs @@ -13,6 +13,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. +use crate::documents::transaction::v10::*; use crate::documents::transaction::*; use crate::parsers::DefaultHasher; use crate::*; @@ -32,6 +33,20 @@ pub enum ParseTxError { WrongFormat, } +/// Parse transactions documents from array of str +pub fn parse_json_transactions( + array_transactions: &[&JSONValue<DefaultHasher>], +) -> Result<Vec<TransactionDocumentV10>, Error> { + array_transactions + .iter() + .map(|tx| { + parse_json_transaction(tx).map(|tx_doc| match tx_doc { + TransactionDocument::V10(tx_doc_v10) => tx_doc_v10, + }) + }) + .collect::<Result<Vec<TransactionDocumentV10>, Error>>() +} + /// Parse transaction from json value pub fn parse_json_transaction( json_tx: &JSONValue<DefaultHasher>, @@ -45,40 +60,49 @@ pub fn parse_json_transaction( let json_tx = json_tx.to_object().expect("safe unwrap"); - let tx_doc_builder = TransactionDocumentBuilder { - currency: get_str(json_tx, "currency")?, - blockstamp: &Blockstamp::from_string(get_str(json_tx, "blockstamp")?)?, - locktime: &(get_number(json_tx, "locktime")?.trunc() as u64), - issuers: &get_str_array(json_tx, "issuers")? - .iter() - .map(|p| ed25519::PublicKey::from_base58(p)) - .map(|p| p.map(PubKey::Ed25519)) - .collect::<Result<Vec<PubKey>, BaseConvertionError>>()?, - inputs: &get_str_array(json_tx, "inputs")? - .iter() - .map(|i| TransactionInput::from_str(i)) - .collect::<Result<Vec<TransactionInput>, TextDocumentParseError>>()?, - unlocks: &get_str_array(json_tx, "unlocks")? - .iter() - .map(|i| TransactionInputUnlocks::from_str(i)) - .collect::<Result<Vec<TransactionInputUnlocks>, TextDocumentParseError>>()?, - outputs: &get_str_array(json_tx, "outputs")? - .iter() - .map(|i| TransactionOutput::from_str(i)) - .collect::<Result<Vec<TransactionOutput>, TextDocumentParseError>>()?, - comment: &durs_common_tools::fns::str_escape::unescape_str(get_str(json_tx, "comment")?), - hash: get_optional_str(json_tx, "hash")? - .map(Hash::from_hex) - .transpose()?, - }; - - Ok(tx_doc_builder.build_with_signature( - get_str_array(json_tx, "signatures")? - .iter() - .map(|p| ed25519::Signature::from_base64(p)) - .map(|p| p.map(Sig::Ed25519)) - .collect::<Result<Vec<Sig>, BaseConvertionError>>()?, - )) + match get_u64(json_tx, "version")? { + 10 => Ok( + TransactionDocumentBuilder::V10(TransactionDocumentV10Builder { + currency: get_str(json_tx, "currency")?, + blockstamp: &Blockstamp::from_string(get_str(json_tx, "blockstamp")?)?, + locktime: &(get_number(json_tx, "locktime")?.trunc() as u64), + issuers: &get_str_array(json_tx, "issuers")? + .iter() + .map(|p| ed25519::PublicKey::from_base58(p)) + .map(|p| p.map(PubKey::Ed25519)) + .collect::<Result<Vec<PubKey>, BaseConvertionError>>()?, + inputs: &get_str_array(json_tx, "inputs")? + .iter() + .map(|i| TransactionInputV10::from_str(i)) + .collect::<Result<Vec<TransactionInputV10>, TextDocumentParseError>>()?, + unlocks: &get_str_array(json_tx, "unlocks")? + .iter() + .map(|i| TransactionInputUnlocksV10::from_str(i)) + .collect::<Result<Vec<TransactionInputUnlocksV10>, TextDocumentParseError>>()?, + outputs: &get_str_array(json_tx, "outputs")? + .iter() + .map(|i| TransactionOutputV10::from_str(i)) + .collect::<Result<Vec<TransactionOutputV10>, TextDocumentParseError>>()?, + comment: &durs_common_tools::fns::str_escape::unescape_str(get_str( + json_tx, "comment", + )?), + hash: get_optional_str(json_tx, "hash")? + .map(Hash::from_hex) + .transpose()?, + }) + .build_with_signature( + get_str_array(json_tx, "signatures")? + .iter() + .map(|p| ed25519::Signature::from_base64(p)) + .map(|p| p.map(Sig::Ed25519)) + .collect::<Result<Vec<Sig>, BaseConvertionError>>()?, + ), + ), + version => Err(ParseJsonError { + cause: format!("Unhandled json transaction version: {} !", version), + } + .into()), + } } #[cfg(test)] @@ -89,7 +113,7 @@ mod tests { use std::str::FromStr; pub fn first_g1_tx_doc() -> TransactionDocument { - let expected_tx_builder = TransactionDocumentBuilder { + let expected_tx_builder = TransactionDocumentV10Builder { currency: &"g1", blockstamp: &Blockstamp::from_string( "50-00001DAA4559FEDB8320D1040B0F22B631459F36F237A0D9BC1EB923C12A12E7", @@ -100,19 +124,19 @@ mod tests { ed25519::PublicKey::from_base58("2ny7YAdmzReQxAayyJZsyVYwYhVyax2thKcGknmQy5nQ") .expect("Fail to parse issuer !"), )], - inputs: &vec![TransactionInput::from_str( + inputs: &vec![TransactionInputV10::from_str( "1000:0:D:2ny7YAdmzReQxAayyJZsyVYwYhVyax2thKcGknmQy5nQ:1", ) .expect("Fail to parse inputs")], unlocks: &vec![ - TransactionInputUnlocks::from_str("0:SIG(0)").expect("Fail to parse unlocks") + TransactionInputUnlocksV10::from_str("0:SIG(0)").expect("Fail to parse unlocks") ], outputs: &vec![ - TransactionOutput::from_str( + TransactionOutputV10::from_str( "1:0:SIG(Com8rJukCozHZyFao6AheSsfDQdPApxQRnz7QYFf64mm)", ) .expect("Fail to parse outputs"), - TransactionOutput::from_str( + TransactionOutputV10::from_str( "999:0:SIG(2ny7YAdmzReQxAayyJZsyVYwYhVyax2thKcGknmQy5nQ)", ) .expect("Fail to parse outputs"), @@ -121,7 +145,7 @@ mod tests { hash: None, }; - expected_tx_builder.build_with_signature(vec![Sig::Ed25519( + TransactionDocumentBuilder::V10(expected_tx_builder).build_with_signature(vec![Sig::Ed25519( ed25519::Signature::from_base64("fAH5Gor+8MtFzQZ++JaJO6U8JJ6+rkqKtPrRr/iufh3MYkoDGxmjzj6jCADQL+hkWBt8y8QzlgRkz0ixBcKHBw==").expect("Fail to parse sig !") )]) } diff --git a/lib/modules-lib/bc-db-reader/src/indexes/sources.rs b/lib/modules-lib/bc-db-reader/src/indexes/sources.rs index 073b89962e958e9d70d54f30fa2ebd2fb13c8ff1..30b84528f889253962295992e61b02ae7dc4d009 100644 --- a/lib/modules-lib/bc-db-reader/src/indexes/sources.rs +++ b/lib/modules-lib/bc-db-reader/src/indexes/sources.rs @@ -85,7 +85,7 @@ impl Sub for SourceAmount { #[derive(Debug, Clone, Deserialize, Serialize)] /// V10 Unused Transaction Output -pub struct UTXOV10(pub UniqueIdUTXOv10, pub TransactionOutput); +pub struct UTXOV10(pub UniqueIdUTXOv10, pub TransactionOutputV10); impl UTXOV10 { /// UTXO conditions @@ -128,7 +128,7 @@ impl UTXO { pub fn get_utxo_v10<DB: BcDbInReadTx>( db: &DB, utxo_id: UniqueIdUTXOv10, -) -> Result<Option<TransactionOutput>, DbError> { +) -> Result<Option<TransactionOutputV10>, DbError> { let utxo_id_bytes: Vec<u8> = utxo_id.into(); db.db() .get_store(UTXOS) @@ -141,7 +141,7 @@ pub fn get_utxo_v10<DB: BcDbInReadTx>( pub fn get_block_consumed_sources_<DB: BcDbInReadTx>( db: &DB, block_number: BlockNumber, -) -> Result<Option<HashMap<UniqueIdUTXOv10, TransactionOutput>>, DbError> { +) -> Result<Option<HashMap<UniqueIdUTXOv10, TransactionOutputV10>>, DbError> { db.db() .get_int_store(CONSUMED_UTXOS) .get(db.r(), block_number.0)? diff --git a/lib/modules/blockchain/bc-db-writer/src/indexes/transactions.rs b/lib/modules/blockchain/bc-db-writer/src/indexes/transactions.rs index 260dc8b9158ea9771e6fdf50f6f176cfacdb6ca1..bdaa05e5cb0f7bbc6ba112caa3a2bfbeaa748c7d 100644 --- a/lib/modules/blockchain/bc-db-writer/src/indexes/transactions.rs +++ b/lib/modules/blockchain/bc-db-writer/src/indexes/transactions.rs @@ -44,14 +44,16 @@ pub fn revert_tx<S: std::hash::BuildHasher>( db: &Db, w: &mut DbWriter, tx_doc: &TransactionDocument, - block_consumed_sources: &mut HashMap<UniqueIdUTXOv10, TransactionOutput, S>, + block_consumed_sources: &mut HashMap<UniqueIdUTXOv10, TransactionOutputV10, S>, ) -> Result<(), DbError> { let tx_hash = tx_doc .get_hash_opt() .unwrap_or_else(|| tx_doc.compute_hash()); + let TransactionDocument::V10(tx_doc_v10) = tx_doc; + // Index created utxos - let created_utxos: Vec<UTXOV10> = tx_doc + let created_utxos: Vec<UTXOV10> = tx_doc_v10 .get_outputs() .iter() .enumerate() @@ -68,14 +70,14 @@ pub fn revert_tx<S: std::hash::BuildHasher>( db.get_store(UTXOS).delete(w.as_mut(), &utxo_id_bytes)?; } // Index consumed sources - let consumed_sources_ids: HashSet<SourceUniqueIdV10> = tx_doc + let consumed_sources_ids: HashSet<SourceUniqueIdV10> = tx_doc_v10 .get_inputs() .iter() .map(|input| match *input { - TransactionInput::D(_tx_amout, _tx_amout_base, pubkey, block_id) => { + TransactionInputV10::D(_tx_amout, _tx_amout_base, pubkey, block_id) => { SourceUniqueIdV10::UD(pubkey, block_id) } - TransactionInput::T(_tx_amout, _tx_amout_base, hash, tx_index) => { + TransactionInputV10::T(_tx_amout, _tx_amout_base, hash, tx_index) => { SourceUniqueIdV10::UTXO(UniqueIdUTXOv10(hash, tx_index)) } }) @@ -118,15 +120,17 @@ pub fn apply_and_write_tx( let tx_hash = tx_doc .get_hash_opt() .unwrap_or_else(|| tx_doc.compute_hash()); + + let TransactionDocument::V10(tx_doc_v10) = tx_doc; // Index consumed sources - let consumed_sources_ids: HashSet<SourceUniqueIdV10> = tx_doc + let consumed_sources_ids: HashSet<SourceUniqueIdV10> = tx_doc_v10 .get_inputs() .iter() .map(|input| match *input { - TransactionInput::D(_tx_amout, _tx_amout_base, pubkey, block_id) => { + TransactionInputV10::D(_tx_amout, _tx_amout_base, pubkey, block_id) => { SourceUniqueIdV10::UD(pubkey, block_id) } - TransactionInput::T(_tx_amout, _tx_amout_base, hash, tx_index) => { + TransactionInputV10::T(_tx_amout, _tx_amout_base, hash, tx_index) => { SourceUniqueIdV10::UTXO(UniqueIdUTXOv10(hash, tx_index)) } }) @@ -145,13 +149,13 @@ pub fn apply_and_write_tx( .map(|utxo_id| { let utxo_id_bytes: Vec<u8> = (*utxo_id).into(); if let Some(value) = db.get_store(UTXOS).get(w.as_ref(), &utxo_id_bytes)? { - let utxo_content: TransactionOutput = from_db_value(value)?; + let utxo_content: TransactionOutputV10 = from_db_value(value)?; Ok((*utxo_id, utxo_content)) } else { fatal_error!("Try to persist unexist consumed source."); } }) - .collect::<Result<HashMap<UniqueIdUTXOv10, TransactionOutput>, DbError>>()?; + .collect::<Result<HashMap<UniqueIdUTXOv10, TransactionOutputV10>, DbError>>()?; let consumed_sources_bytes = durs_dbs_tools::to_bytes(&consumed_sources)?; let block_number = durs_bc_db_reader::current_metadata::get_current_blockstamp(&BcDbRwWithWriter { @@ -189,7 +193,7 @@ pub fn apply_and_write_tx( })?; } } - let created_utxos: Vec<UTXOV10> = tx_doc + let created_utxos: Vec<UTXOV10> = tx_doc_v10 .get_outputs() .iter() .enumerate() @@ -218,6 +222,7 @@ mod tests { use super::*; use dubp_common_doc::traits::{Document, DocumentBuilder}; use dubp_common_doc::BlockHash; + use dubp_user_docs::documents::transaction::v10::TransactionInputUnlocksV10; use durs_bc_db_reader::current_metadata::CurrentMetaDataKey; use durs_bc_db_reader::indexes::sources::SourceAmount; use std::str::FromStr; @@ -233,24 +238,24 @@ mod tests { let block = unwrap!(Blockstamp::from_string( "50-00001DAA4559FEDB8320D1040B0F22B631459F36F237A0D9BC1EB923C12A12E7", )); - let builder = TransactionDocumentBuilder { + let builder = TransactionDocumentV10Builder { currency: "g1", blockstamp: &block, locktime: &0, issuers: &vec![pubkey], - inputs: &vec![TransactionInput::from_str( + inputs: &vec![TransactionInputV10::from_str( "1000:0:D:2ny7YAdmzReQxAayyJZsyVYwYhVyax2thKcGknmQy5nQ:1", ) .expect("fail to parse input !")], unlocks: &vec![ - TransactionInputUnlocks::from_str("0:SIG(0)").expect("fail to parse unlock !") + TransactionInputUnlocksV10::from_str("0:SIG(0)").expect("fail to parse unlock !") ], outputs: &vec![ - TransactionOutput::from_str( + TransactionOutputV10::from_str( "1:0:SIG(Com8rJukCozHZyFao6AheSsfDQdPApxQRnz7QYFf64mm)", ) .expect("fail to parse output !"), - TransactionOutput::from_str( + TransactionOutputV10::from_str( "999:0:SIG(2ny7YAdmzReQxAayyJZsyVYwYhVyax2thKcGknmQy5nQ)", ) .expect("fail to parse output !"), @@ -258,7 +263,7 @@ mod tests { comment: "TEST", hash: None, }; - builder.build_with_signature(vec![sig]) + TransactionDocumentBuilder::V10(builder).build_with_signature(vec![sig]) } #[test] diff --git a/lib/modules/blockchain/bc-db-writer/src/writers/requests.rs b/lib/modules/blockchain/bc-db-writer/src/writers/requests.rs index 031954da43f1d3b5de393a60a0deefdba1f54095..09f8f9a13a3d3cf1de430517dcde64194b4c9080 100644 --- a/lib/modules/blockchain/bc-db-writer/src/writers/requests.rs +++ b/lib/modules/blockchain/bc-db-writer/src/writers/requests.rs @@ -264,7 +264,7 @@ impl CurrencyDBsWriteQuery { &self, db: &Db, w: &mut DbWriter, - block_consumed_sources_opt: Option<&mut HashMap<UniqueIdUTXOv10, TransactionOutput>>, + block_consumed_sources_opt: Option<&mut HashMap<UniqueIdUTXOv10, TransactionOutputV10>>, in_fork_window: bool, ) -> Result<(), DbError> { match *self { diff --git a/lib/modules/blockchain/blockchain/src/dubp/apply/mod.rs b/lib/modules/blockchain/blockchain/src/dubp/apply/mod.rs index bd07fc2e41ec86092ec335a9f0e72aed9b9c2c51..f3fdee17d56bbc6f610b0755503aaf28dbe65427 100644 --- a/lib/modules/blockchain/blockchain/src/dubp/apply/mod.rs +++ b/lib/modules/blockchain/blockchain/src/dubp/apply/mod.rs @@ -18,7 +18,7 @@ use dubp_block_doc::block::{BlockDocument, BlockDocumentV10}; use dubp_common_doc::traits::Document; use dubp_common_doc::BlockNumber; -use dubp_user_docs::documents::transaction::{TxAmount, TxBase}; +use dubp_user_docs::documents::transaction::{TransactionDocument, TxAmount, TxBase}; use dup_crypto::keys::*; use durs_bc_db_reader::blocks::BlockDb; use durs_bc_db_reader::indexes::sources::get_block_consumed_sources_; @@ -230,7 +230,9 @@ pub fn apply_valid_block_v10<W: WebOfTrust>( } for tx in &block.transactions { - currency_dbs_requests.push(CurrencyDBsWriteQuery::WriteTx(Box::new(tx.clone()))); + currency_dbs_requests.push(CurrencyDBsWriteQuery::WriteTx(Box::new( + TransactionDocument::V10(tx.clone()), + ))); } /*// Calculate the state of the wot diff --git a/lib/modules/blockchain/blockchain/src/dubp/check/local.rs b/lib/modules/blockchain/blockchain/src/dubp/check/local.rs index 233e62f42e40b0202850df8f2e42b2cd1b228e03..d5925e037d41f2b0fe1b968cf5ca5cb33defa013 100644 --- a/lib/modules/blockchain/blockchain/src/dubp/check/local.rs +++ b/lib/modules/blockchain/blockchain/src/dubp/check/local.rs @@ -154,7 +154,7 @@ pub fn verify_local_validity_block_v10( // Check transactions for tx in &block.transactions { - self::tx_doc::local_verify_tx_doc(block.version(), tx)?; + self::tx_doc::local_verify_tx_doc_v10(block.version(), tx)?; } Ok(()) diff --git a/lib/modules/blockchain/blockchain/src/dubp/check/local/tx_doc.rs b/lib/modules/blockchain/blockchain/src/dubp/check/local/tx_doc.rs index 33adb27f7d8b7677bd7fd9b1ea0ad55e6a282a44..731c5c4696f78284d1351f43787df94a10b05a4b 100644 --- a/lib/modules/blockchain/blockchain/src/dubp/check/local/tx_doc.rs +++ b/lib/modules/blockchain/blockchain/src/dubp/check/local/tx_doc.rs @@ -18,7 +18,8 @@ use dubp_common_doc::errors::DocumentSigsErr; use dubp_common_doc::traits::text::CompactTextDocument; use dubp_common_doc::traits::Document; -use dubp_user_docs::documents::transaction::TransactionDocument; +use dubp_user_docs::documents::transaction::v10::TransactionDocumentV10; +use dubp_user_docs::documents::transaction::TransactionDocumentTrait; use durs_common_tools::traits::bool_ext::BoolExt; #[derive(Debug, PartialEq)] @@ -36,9 +37,9 @@ pub enum TransactionDocumentError { } /// Local verification of a Tx Document -pub fn local_verify_tx_doc( +pub fn local_verify_tx_doc_v10( dubp_version: usize, - tx_doc: &TransactionDocument, + tx_doc: &TransactionDocumentV10, ) -> Result<(), TransactionDocumentError> { // A transaction in compact format must measure less than 100 lines (tx_doc.as_compact_text().lines().count() < 100).or_err(TransactionDocumentError::TooLong { @@ -69,11 +70,12 @@ mod tests { use super::*; use dubp_common_doc::traits::DocumentBuilder; use dubp_common_doc::Blockstamp; + use dubp_user_docs::documents::transaction::v10::TransactionDocumentV10Builder; + use dubp_user_docs::documents::transaction::v10::TransactionInputUnlocksV10; + use dubp_user_docs::documents::transaction::v10::TransactionInputV10; + use dubp_user_docs::documents::transaction::v10::TransactionOutputV10; use dubp_user_docs::documents::transaction::OutputIndex; - use dubp_user_docs::documents::transaction::TransactionDocumentBuilder; - use dubp_user_docs::documents::transaction::TransactionInput; - use dubp_user_docs::documents::transaction::TransactionInputUnlocks; - use dubp_user_docs::documents::transaction::TransactionOutput; + use dubp_user_docs::documents::transaction::TransactionDocument; use dubp_user_docs::documents::transaction::TransactionUnlockProof; use dubp_user_docs::documents::transaction::TxAmount; use dubp_user_docs::documents::transaction::TxBase; @@ -107,8 +109,8 @@ mod tests { } #[inline] - fn input1() -> TransactionInput { - TransactionInput::T( + fn input1() -> TransactionInputV10 { + TransactionInputV10::T( TxAmount(950), TxBase(0), Hash::from_hex("2CF1ACD8FE8DC93EE39A1D55881C50D87C55892AE8E4DB71D4EBAB3D412AA8FD") @@ -118,29 +120,29 @@ mod tests { } #[inline] - fn unlocks() -> Vec<TransactionInputUnlocks> { - vec![TransactionInputUnlocks { + fn unlocks() -> Vec<TransactionInputUnlocksV10> { + vec![TransactionInputUnlocksV10 { index: 0, unlocks: vec![TransactionUnlockProof::Sig(0)], }] } #[inline] - fn outputs() -> Vec<TransactionOutput> { - vec![ - TransactionOutput::from_str("10:0:SIG(FD9wujR7KABw88RyKEGBYRLz8PA6jzVCbcBAsrBXBqSa)") - .expect("fail to parse output !"), - ] + fn outputs() -> Vec<TransactionOutputV10> { + vec![TransactionOutputV10::from_str( + "10:0:SIG(FD9wujR7KABw88RyKEGBYRLz8PA6jzVCbcBAsrBXBqSa)", + ) + .expect("fail to parse output !")] } fn tx_builder<'a>( blockstamp: &'a Blockstamp, issuers: &'a Vec<PubKey>, - inputs: &'a Vec<TransactionInput>, - unlocks: &'a Vec<TransactionInputUnlocks>, - outputs: &'a Vec<TransactionOutput>, - ) -> TransactionDocumentBuilder<'a> { - TransactionDocumentBuilder { + inputs: &'a Vec<TransactionInputV10>, + unlocks: &'a Vec<TransactionInputUnlocksV10>, + outputs: &'a Vec<TransactionOutputV10>, + ) -> TransactionDocumentV10Builder<'a> { + TransactionDocumentV10Builder { currency: "duniter_unit_test_currency", blockstamp, locktime: &0, @@ -155,8 +157,8 @@ mod tests { #[test] fn test_tx_valid() { - let tx = gen_mock_tx_doc(); - assert_eq!(Ok(()), local_verify_tx_doc(10, &tx)); + let TransactionDocument::V10(tx) = gen_mock_tx_doc(); + assert_eq!(Ok(()), local_verify_tx_doc_v10(10, &tx)); } #[test] @@ -170,7 +172,7 @@ mod tests { let tx = tx_builder.build_with_signature(vec![sig1()]); let expected = Err(TransactionDocumentError::MissingInput); - let actual = local_verify_tx_doc(10, &tx); + let actual = local_verify_tx_doc_v10(10, &tx); assert_eq!(expected, actual); } @@ -188,7 +190,7 @@ mod tests { expected_max_length: 100, actual_length: 107, }); - let actual = local_verify_tx_doc(10, &tx); + let actual = local_verify_tx_doc_v10(10, &tx); assert_eq!(expected, actual); } diff --git a/lib/modules/blockchain/blockchain/src/fork/revert_block.rs b/lib/modules/blockchain/blockchain/src/fork/revert_block.rs index a8f1b2e7453bd300db66b2b25ea1ee773fe0640a..0bea8a77b6ae5ca8911a3dc88c60b9b406db7b4c 100644 --- a/lib/modules/blockchain/blockchain/src/fork/revert_block.rs +++ b/lib/modules/blockchain/blockchain/src/fork/revert_block.rs @@ -18,7 +18,7 @@ use dubp_block_doc::block::{BlockDocument, BlockDocumentTrait, BlockDocumentV10}; use dubp_common_doc::traits::Document; use dubp_common_doc::{BlockNumber, Blockstamp}; -use dubp_user_docs::documents::transaction::{TxAmount, TxBase}; +use dubp_user_docs::documents::transaction::{TransactionDocument, TxAmount, TxBase}; use dup_crypto::keys::*; use durs_bc_db_reader::blocks::BlockDb; use durs_bc_db_reader::indexes::sources::SourceAmount; @@ -82,7 +82,9 @@ pub fn revert_block_v10<W: WebOfTrust>( let mut currency_dbs_requests = Vec::new(); // Revert transactions for tx_doc in block.transactions.iter().rev() { - currency_dbs_requests.push(CurrencyDBsWriteQuery::RevertTx(Box::new(tx_doc.clone()))); + currency_dbs_requests.push(CurrencyDBsWriteQuery::RevertTx(Box::new( + TransactionDocument::V10(tx_doc.clone()), + ))); } // Revert UD if let Some(du_amount) = block.dividend { diff --git a/lib/modules/ws2p-v1-legacy/src/serializers/transaction.rs b/lib/modules/ws2p-v1-legacy/src/serializers/transaction.rs index fce889544472c7a21661253d0cbd88996c46dbe2..506a706c817dfcafc8ed07a94661a5633234a798 100644 --- a/lib/modules/ws2p-v1-legacy/src/serializers/transaction.rs +++ b/lib/modules/ws2p-v1-legacy/src/serializers/transaction.rs @@ -16,9 +16,18 @@ //! Sub-module that serialize TransactionDocument into WS2Pv1 json format use super::IntoWS2Pv1Json; +use dubp_user_docs::documents::transaction::v10::TransactionDocumentV10Stringified; use dubp_user_docs::documents::transaction::TransactionDocumentStringified; impl IntoWS2Pv1Json for TransactionDocumentStringified { + fn into_ws2p_v1_json(self) -> serde_json::Value { + match self { + TransactionDocumentStringified::V10(tx_doc_v10) => tx_doc_v10.into_ws2p_v1_json(), + } + } +} + +impl IntoWS2Pv1Json for TransactionDocumentV10Stringified { fn into_ws2p_v1_json(self) -> serde_json::Value { json!( { "blockstamp": self.blockstamp, diff --git a/lib/tests-tools/blocks-tests-tools/src/mocks.rs b/lib/tests-tools/blocks-tests-tools/src/mocks.rs index dbf497f8d27e4741aec8f6552dfcef7aff239016..aa316541d4cb8a24c890c9de49897ef8d44fa081 100644 --- a/lib/tests-tools/blocks-tests-tools/src/mocks.rs +++ b/lib/tests-tools/blocks-tests-tools/src/mocks.rs @@ -156,8 +156,8 @@ CertTimestamp: 106669-000003682E6FE38C44433DCE92E8B2A26C69B6D7867A2BAED231E788DD UmseG2XKNwKcY8RFi6gUCT91udGnnNmSh7se10J1jeRVlwf+O2Tyb2Cccot9Dt7BO4+Kx2P6vFJB3oVGGHMxBA==").expect("Fail to parse cert1"); let CertificationDocument::V10(cert1) = cert1; - let tx1 = dubp_user_docs_tests_tools::mocks::tx::gen_mock_tx_doc(); - let tx2 = TransactionDocumentParser::parse("Version: 10 + let TransactionDocument::V10(tx1) = dubp_user_docs_tests_tools::mocks::tx::gen_mock_tx_doc(); + let TransactionDocument::V10(tx2) = TransactionDocumentParser::parse("Version: 10 Type: Transaction Currency: g1 Blockstamp: 107982-000001242F6DA51C06A915A96C58BAA37AB3D1EB51F6E1C630C707845ACF764B diff --git a/lib/tests-tools/user-docs-tests-tools/src/mocks/tx.rs b/lib/tests-tools/user-docs-tests-tools/src/mocks/tx.rs index f6abdd0beebafdf3e3641183361c5826da321a38..2b84391ab4a93f5b55ba83ab141b36022fa848ef 100644 --- a/lib/tests-tools/user-docs-tests-tools/src/mocks/tx.rs +++ b/lib/tests-tools/user-docs-tests-tools/src/mocks/tx.rs @@ -18,13 +18,14 @@ use dubp_common_doc::parser::TextDocumentParser; use dubp_common_doc::traits::DocumentBuilder; use dubp_common_doc::Blockstamp; +use dubp_user_docs::documents::transaction::v10::TransactionInputUnlocksV10; use dubp_user_docs::documents::transaction::*; use dup_crypto::keys::*; use std::str::FromStr; /// Generate first G1 transaction ! pub fn first_g1_tx_doc() -> TransactionDocument { - let expected_tx_builder = TransactionDocumentBuilder { + let expected_tx_builder = TransactionDocumentV10Builder { currency: &"g1", blockstamp: &Blockstamp::from_string( "50-00001DAA4559FEDB8320D1040B0F22B631459F36F237A0D9BC1EB923C12A12E7", @@ -35,24 +36,26 @@ pub fn first_g1_tx_doc() -> TransactionDocument { ed25519::PublicKey::from_base58("2ny7YAdmzReQxAayyJZsyVYwYhVyax2thKcGknmQy5nQ") .expect("Fail to parse issuer !"), )], - inputs: &vec![TransactionInput::from_str( + inputs: &vec![TransactionInputV10::from_str( "1000:0:D:2ny7YAdmzReQxAayyJZsyVYwYhVyax2thKcGknmQy5nQ:1", ) .expect("Fail to parse inputs")], unlocks: &vec![ - TransactionInputUnlocks::from_str("0:SIG(0)").expect("Fail to parse unlocks") + TransactionInputUnlocksV10::from_str("0:SIG(0)").expect("Fail to parse unlocks") ], outputs: &vec![ - TransactionOutput::from_str("1:0:SIG(Com8rJukCozHZyFao6AheSsfDQdPApxQRnz7QYFf64mm)") - .expect("Fail to parse outputs"), - TransactionOutput::from_str("999:0:SIG(2ny7YAdmzReQxAayyJZsyVYwYhVyax2thKcGknmQy5nQ)") + TransactionOutputV10::from_str("1:0:SIG(Com8rJukCozHZyFao6AheSsfDQdPApxQRnz7QYFf64mm)") .expect("Fail to parse outputs"), + TransactionOutputV10::from_str( + "999:0:SIG(2ny7YAdmzReQxAayyJZsyVYwYhVyax2thKcGknmQy5nQ)", + ) + .expect("Fail to parse outputs"), ], comment: "TEST", hash: None, }; - expected_tx_builder.build_with_signature(vec![Sig::Ed25519( + TransactionDocumentBuilder::V10(expected_tx_builder).build_with_signature(vec![Sig::Ed25519( ed25519::Signature::from_base64("fAH5Gor+8MtFzQZ++JaJO6U8JJ6+rkqKtPrRr/iufh3MYkoDGxmjzj6jCADQL+hkWBt8y8QzlgRkz0ixBcKHBw==").expect("Fail to parse sig !") )]) } diff --git a/lib/tools/json-pest-parser/src/lib.rs b/lib/tools/json-pest-parser/src/lib.rs index 97f7273f7287125d1ea1347bad6153c32e6e2883..9b2fd8db8c991a99747b90f4ec92c4f69fcd8237 100644 --- a/lib/tools/json-pest-parser/src/lib.rs +++ b/lib/tools/json-pest-parser/src/lib.rs @@ -408,6 +408,24 @@ pub fn get_str_array<'a, S: std::hash::BuildHasher>( .collect() } +pub fn get_array<'a, S: std::hash::BuildHasher>( + json_block: &'a HashMap<&str, JSONValue<S>, S>, + field: &str, +) -> Result<Vec<&'a JSONValue<'a, S>>, ParseJsonError> { + Ok(json_block + .get(field) + .ok_or_else(|| ParseJsonError { + cause: format!("Fail to parse json : field '{}' must exist !", field), + })? + .to_array() + .ok_or_else(|| ParseJsonError { + cause: format!("Fail to parse json : field '{}' must be an array !", field), + })? + .iter() + .map(|v| v) + .collect()) +} + pub fn get_object_array<'a, S: std::hash::BuildHasher>( json_block: &'a JsonObject<'a, S>, field: &str,