diff --git a/Cargo.lock b/Cargo.lock index 8e70bf2a0c3826498535a64d81f0d07a5874d3e3..4685faa99276051b91ab88a9cfa1fb727d28eb50 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1110,6 +1110,7 @@ dependencies = [ "dubp", "duniter-dbs", "resiter", + "smallvec", ] [[package]] diff --git a/rust-libs/duniter-dbs-read-ops/Cargo.toml b/rust-libs/duniter-dbs-read-ops/Cargo.toml index 15385297dc2e757616cb36cd7e1fe2ac579fe89b..7e0ac6c62d7d6061175d5eb5609e190af3e1c7e0 100644 --- a/rust-libs/duniter-dbs-read-ops/Cargo.toml +++ b/rust-libs/duniter-dbs-read-ops/Cargo.toml @@ -15,3 +15,6 @@ path = "src/lib.rs" duniter-dbs = { path = "../duniter-dbs" } dubp = { version = "0.30.0" } resiter = "0.4.0" + +[dev-dependencies] +smallvec = { version = "1.4.0", features = ["serde", "write"] } diff --git a/rust-libs/duniter-dbs-read-ops/src/find_inputs.rs b/rust-libs/duniter-dbs-read-ops/src/find_inputs.rs new file mode 100644 index 0000000000000000000000000000000000000000..bcdd0a333bf886faad345144b87b9f01316459fe --- /dev/null +++ b/rust-libs/duniter-dbs-read-ops/src/find_inputs.rs @@ -0,0 +1,188 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// 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/>. + +use crate::*; +use dubp::{documents::transaction::TransactionInputV10, wallet::prelude::*}; + +pub fn find_inputs<BcDb: BcV2DbReadable, GvaDb: GvaV1DbReadable, TxsMpDb: TxsMpV2DbReadable>( + bc_db: &BcDb, + gva_db: &GvaDb, + txs_mp_db: &TxsMpDb, + amount: SourceAmount, + script: &WalletScriptV10, + max_inputs_count: usize, +) -> KvResult<(BlockMetaV2, Vec<TransactionInputV10>, SourceAmount)> { + if let Some(current_block) = crate::get_current_block_meta(bc_db)? { + let (mut inputs, uds_sum) = if script.nodes.is_empty() { + if let WalletSubScriptV10::Single(WalletConditionV10::Sig(issuer)) = script.root { + let pending_uds_bn = txs_mp_db.uds_ids().iter(.., |it| { + it.keys() + .map_ok(|duniter_dbs::UdIdV2(_pk, bn)| bn) + .collect::<KvResult<_>>() + })?; + + let (uds, uds_sum) = crate::uds_of_pubkey::uds_of_pubkey( + bc_db, + issuer, + .., + Some(&pending_uds_bn), + Some(max_inputs_count), + Some(amount), + )?; + let inputs = uds + .into_iter() + .map(|(block_number, source_amount)| TransactionInputV10 { + amount: source_amount, + id: SourceIdV10::Ud(UdSourceIdV10 { + issuer, + block_number, + }), + }) + .collect::<Vec<_>>(); + (inputs, uds_sum) + } else { + (vec![], SourceAmount::ZERO) + } + } else { + (vec![], SourceAmount::ZERO) + }; + if uds_sum < amount { + let (utxos, utxos_sum) = crate::utxos::find_script_utxos( + gva_db, + txs_mp_db, + amount - uds_sum, + max_inputs_count - inputs.len(), + &script, + )?; + inputs.extend( + utxos + .into_iter() + .map( + |(_written_time, utxo_id, source_amount)| TransactionInputV10 { + amount: source_amount, + id: SourceIdV10::Utxo(utxo_id), + }, + ), + ); + let inputs_sum = uds_sum + utxos_sum; + if inputs_sum < amount { + Err(KvError::Custom( + "Amount need too many sources or issuer's account has an insufficient balance." + .into(), + )) + } else { + Ok::<_, KvError>((current_block, inputs, inputs_sum)) + } + } else { + Ok::<_, KvError>((current_block, inputs, uds_sum)) + } + } else { + Err(KvError::Custom("no blockchain".into())) + } +} + +#[cfg(test)] +mod tests { + use std::collections::BTreeMap; + + use super::*; + use duniter_dbs::{ + bc_v2::BcV2DbWritable, gva_v1::GvaV1DbWritable, txs_mp_v2::TxsMpV2DbWritable, + SourceAmountValV2, UdIdV2, UtxoIdDbV2, UtxosOfScriptV1, WalletConditionsV2, + }; + + const UD0: i64 = 10; + + #[test] + fn test_find_inputs() -> KvResult<()> { + let bc_db = duniter_dbs::bc_v2::BcV2Db::<Mem>::open(MemConf::default())?; + let gva_db = duniter_dbs::gva_v1::GvaV1Db::<Mem>::open(MemConf::default())?; + let txs_mp_db = duniter_dbs::txs_mp_v2::TxsMpV2Db::<Mem>::open(MemConf::default())?; + + let b0 = BlockMetaV2 { + dividend: Some(SourceAmount::with_base0(UD0)), + ..Default::default() + }; + let pk = PublicKey::default(); + let script = WalletScriptV10::single(WalletConditionV10::Sig(pk)); + let mut utxos = BTreeMap::new(); + utxos.insert( + 0, + smallvec::smallvec![ + ( + UtxoIdV10 { + tx_hash: Hash::default(), + output_index: 0 + }, + SourceAmount::with_base0(50) + ), + ( + UtxoIdV10 { + tx_hash: Hash::default(), + output_index: 1 + }, + SourceAmount::with_base0(80) + ) + ], + ); + + bc_db.blocks_meta_write().upsert(U32BE(0), b0)?; + bc_db + .uds_reval_write() + .upsert(U32BE(0), SourceAmountValV2(SourceAmount::with_base0(UD0)))?; + bc_db + .uds_write() + .upsert(UdIdV2(PublicKey::default(), BlockNumber(0)), ())?; + gva_db + .utxos_by_script_write() + .upsert(WalletConditionsV2(script.clone()), UtxosOfScriptV1(utxos))?; + + // Gen tx1 + let (cb, inputs, inputs_sum) = find_inputs( + &bc_db, + &gva_db, + &txs_mp_db, + SourceAmount::with_base0(55), + &script, + 2, + )?; + assert_eq!(cb, b0); + assert_eq!(inputs.len(), 2); + assert_eq!(inputs_sum, SourceAmount::with_base0(60)); + + // Insert tx1 inputs in mempool + txs_mp_db + .uds_ids_write() + .upsert(UdIdV2(pk, BlockNumber(0)), ())?; + txs_mp_db + .utxos_ids_write() + .upsert(UtxoIdDbV2(Hash::default(), 0), ())?; + + // Gen tx2 + let (cb, inputs, inputs_sum) = find_inputs( + &bc_db, + &gva_db, + &txs_mp_db, + SourceAmount::with_base0(55), + &script, + 2, + )?; + assert_eq!(cb, b0); + assert_eq!(inputs.len(), 1); + assert_eq!(inputs_sum, SourceAmount::with_base0(80)); + + Ok(()) + } +} diff --git a/rust-libs/duniter-dbs-read-ops/src/lib.rs b/rust-libs/duniter-dbs-read-ops/src/lib.rs index 3f7bc6b0a3b36b96f1f8a5111176194284e66ba4..83db8c7d6b3891dae9d7c3572887edf625a9e75f 100644 --- a/rust-libs/duniter-dbs-read-ops/src/lib.rs +++ b/rust-libs/duniter-dbs-read-ops/src/lib.rs @@ -22,6 +22,7 @@ unused_import_braces )] +pub mod find_inputs; pub mod txs_history; pub mod uds_of_pubkey; pub mod utxos; @@ -36,7 +37,10 @@ use duniter_dbs::{ TxsMpV2DbReadable, }; use resiter::map::Map; -use std::ops::{Bound, RangeBounds}; +use std::{ + collections::BTreeSet, + ops::{Bound, RangeBounds}, +}; pub fn get_current_block_meta<BcDb: BcV2DbReadable>(bc_db: &BcDb) -> KvResult<Option<BlockMetaV2>> { bc_db diff --git a/rust-libs/duniter-dbs-read-ops/src/uds_of_pubkey.rs b/rust-libs/duniter-dbs-read-ops/src/uds_of_pubkey.rs index 498924f0341c09d8e55c0c44327020bc2be22fa0..4d5f8ccf97fd10e5f9db54e1af6c0f01827e1ca1 100644 --- a/rust-libs/duniter-dbs-read-ops/src/uds_of_pubkey.rs +++ b/rust-libs/duniter-dbs-read-ops/src/uds_of_pubkey.rs @@ -17,10 +17,11 @@ use crate::*; use duniter_dbs::bc_v2::UdsRevalEvent; use duniter_dbs::UdIdV2; -pub fn uds_of_pubkey<DB: BcV2DbReadable, R: 'static + RangeBounds<BlockNumber>>( - bc_db: &DB, +pub fn uds_of_pubkey<BcDb: BcV2DbReadable, R: 'static + RangeBounds<BlockNumber>>( + bc_db: &BcDb, pubkey: PublicKey, range: R, + bn_to_exclude_opt: Option<&BTreeSet<BlockNumber>>, limit_opt: Option<usize>, total_opt: Option<SourceAmount>, ) -> KvResult<(Vec<(BlockNumber, SourceAmount)>, SourceAmount)> { @@ -48,23 +49,38 @@ pub fn uds_of_pubkey<DB: BcV2DbReadable, R: 'static + RangeBounds<BlockNumber>>( it.reverse().keys().next_res() })? .expect("corrupted db"); - let blocks_numbers = blocks_numbers.into_iter(); + let blocks_numbers_len = blocks_numbers.len(); + let blocks_numbers = blocks_numbers.into_iter().filter(|bn| { + if let Some(bn_to_exclude) = bn_to_exclude_opt { + !bn_to_exclude.contains(bn) + } else { + true + } + }); if let Some(limit) = limit_opt { collect_uds( blocks_numbers.take(limit), + blocks_numbers_len, first_reval, uds_reval, total_opt, ) } else { - collect_uds(blocks_numbers, first_reval, uds_reval, total_opt) + collect_uds( + blocks_numbers, + blocks_numbers_len, + first_reval, + uds_reval, + total_opt, + ) } } }) } -fn collect_uds<BC: BackendCol, I: ExactSizeIterator<Item = BlockNumber>>( +fn collect_uds<BC: BackendCol, I: Iterator<Item = BlockNumber>>( mut blocks_numbers: I, + blocks_numbers_len: usize, first_reval: U32BE, uds_reval: TxColRo<BC, UdsRevalEvent>, amount_opt: Option<SourceAmount>, @@ -75,7 +91,7 @@ fn collect_uds<BC: BackendCol, I: ExactSizeIterator<Item = BlockNumber>>( Ok((vec![], SourceAmount::ZERO)) } else { let mut current_ud = (uds_revals[0].1).0; - let mut uds = Vec::with_capacity(blocks_numbers.len()); + let mut uds = Vec::with_capacity(blocks_numbers_len); let mut sum = SourceAmount::ZERO; // Uds before last reval diff --git a/rust-libs/duniter-dbs-read-ops/src/utxos.rs b/rust-libs/duniter-dbs-read-ops/src/utxos.rs index d45b96ecff1761840858311c53dcf4160bc8c2dc..d77f6d60097de7711b05c60483aa0a0587a81343 100644 --- a/rust-libs/duniter-dbs-read-ops/src/utxos.rs +++ b/rust-libs/duniter-dbs-read-ops/src/utxos.rs @@ -23,6 +23,32 @@ pub type UtxoV10 = (i64, UtxoIdV10, SourceAmount); pub fn get_script_utxos<GvaDb: GvaV1DbReadable>( gva_db_ro: &GvaDb, script: &WalletScriptV10, +) -> KvResult<(Vec<UtxoV10>, SourceAmount)> { + find_script_utxos_inner::<_, duniter_dbs::txs_mp_v2::TxsMpV2DbRo<Mem>>( + gva_db_ro, None, script, None, None, + ) +} + +pub fn find_script_utxos<GvaDb: GvaV1DbReadable, TxsMpDb: TxsMpV2DbReadable>( + gva_db_ro: &GvaDb, + txs_mp_db_ro: &TxsMpDb, + amount: SourceAmount, + limit: usize, + script: &WalletScriptV10, +) -> KvResult<(Vec<UtxoV10>, SourceAmount)> { + find_script_utxos_inner( + gva_db_ro, + Some(txs_mp_db_ro), + script, + Some(limit), + Some(amount), + ) +} + +fn find_script_utxos_inner<GvaDb: GvaV1DbReadable, TxsMpDb: TxsMpV2DbReadable>( + gva_db_ro: &GvaDb, + txs_mp_db_ro: Option<&TxsMpDb>, + script: &WalletScriptV10, limit_opt: Option<usize>, total_opt: Option<SourceAmount>, ) -> KvResult<(Vec<UtxoV10>, SourceAmount)> { @@ -36,17 +62,27 @@ pub fn get_script_utxos<GvaDb: GvaV1DbReadable>( Vec::with_capacity(utxos_of_script.0.len() * 2); for (written_time, utxos_) in utxos_of_script.0 { for (utxo_id, source_amount) in utxos_ { - utxos.push((written_time, utxo_id, source_amount)); - if let Some(limit) = limit_opt { - count += 1; - if count == limit { - return Ok((utxos, total)); + if txs_mp_db_ro.is_none() + || !txs_mp_db_ro + .expect("unreachable") + .utxos_ids() + .contains_key(&duniter_dbs::UtxoIdDbV2( + utxo_id.tx_hash, + utxo_id.output_index as u32, + ))? + { + utxos.push((written_time, utxo_id, source_amount)); + total = total + source_amount; + if let Some(limit) = limit_opt { + count += 1; + if count == limit { + return Ok((utxos, total)); + } } - } - total = total + source_amount; - if let Some(total_target) = total_opt { - if total >= total_target { - return Ok((utxos, total)); + if let Some(total_target) = total_opt { + if total >= total_target { + return Ok((utxos, total)); + } } } } diff --git a/rust-libs/duniter-dbs-write-ops/src/txs_mp.rs b/rust-libs/duniter-dbs-write-ops/src/txs_mp.rs index c5ee49925f4f2385bd61f11f8bf4c786e9bba423..7cf0fdaaa19c616e6559fd0fda64c5b22272936b 100644 --- a/rust-libs/duniter-dbs-write-ops/src/txs_mp.rs +++ b/rust-libs/duniter-dbs-write-ops/src/txs_mp.rs @@ -55,9 +55,18 @@ pub fn add_pending_tx< txs_mp_db.txs_by_issuer_write(), txs_mp_db.txs_by_recipient_write(), txs_mp_db.txs_write(), + txs_mp_db.uds_ids_write(), + txs_mp_db.utxos_ids_write(), ) .write( - |(mut txs_by_recv_time, mut txs_by_issuer, mut txs_by_recipient, mut txs)| { + |( + mut txs_by_recv_time, + mut txs_by_issuer, + mut txs_by_recipient, + mut txs, + mut uds_ids, + mut utxos_ids, + )| { control(&tx, &txs)?; // Insert on col `txs_by_recv_time` let mut hashs = txs_by_recv_time.get(&received_time)?.unwrap_or_default(); @@ -77,6 +86,20 @@ pub fn add_pending_tx< hashs.insert(tx.get_hash()); txs_by_recipient.upsert(PubKeyKeyV2(pubkey), hashs); } + // Insert tx inputs in cols `uds_ids` and `utxos_ids` + for input in tx.get_inputs() { + match input.id { + SourceIdV10::Ud(UdSourceIdV10 { + issuer, + block_number, + }) => uds_ids.upsert(duniter_dbs::UdIdV2(issuer, block_number), ()), + SourceIdV10::Utxo(UtxoIdV10 { + tx_hash, + output_index, + }) => utxos_ids + .upsert(duniter_dbs::UtxoIdDbV2(tx_hash, output_index as u32), ()), + } + } // Insert tx itself txs.upsert(HashKeyV2(tx_hash), PendingTxDbV2(tx.into_owned())); Ok(()) @@ -89,6 +112,8 @@ pub fn remove_all_pending_txs<B: Backend>(txs_mp_db: &TxsMpV2Db<B>) -> KvResult< txs_mp_db.txs_by_issuer_write().clear()?; txs_mp_db.txs_by_recipient_write().clear()?; txs_mp_db.txs_write().clear()?; + txs_mp_db.uds_ids_write().clear()?; + txs_mp_db.utxos_ids_write().clear()?; Ok(()) } @@ -128,26 +153,45 @@ fn remove_one_pending_tx<B: Backend>(txs_mp_db: &TxsMpV2Db<B>, tx_hash: Hash) -> txs_mp_db.txs_by_issuer_write(), txs_mp_db.txs_by_recipient_write(), txs_mp_db.txs_write(), + txs_mp_db.uds_ids_write(), + txs_mp_db.utxos_ids_write(), ) - .write(|(mut txs_by_issuer, mut txs_by_recipient, mut txs)| { - // Remove tx hash in col `txs_by_issuer` - for pubkey in tx.0.issuers() { - let mut hashs_ = txs_by_issuer.get(&PubKeyKeyV2(pubkey))?.unwrap_or_default(); - hashs_.remove(&tx_hash); - txs_by_issuer.upsert(PubKeyKeyV2(pubkey), hashs_) - } - // Remove tx hash in col `txs_by_recipient` - for pubkey in tx.0.recipients_keys() { - let mut hashs_ = txs_by_recipient - .get(&PubKeyKeyV2(pubkey))? - .unwrap_or_default(); - hashs_.remove(&tx_hash); - txs_by_recipient.upsert(PubKeyKeyV2(pubkey), hashs_) - } - // Remove tx itself - txs.remove(HashKeyV2(tx_hash)); - Ok(true) - }) + .write( + |(mut txs_by_issuer, mut txs_by_recipient, mut txs, mut uds_ids, mut utxos_ids)| { + // Remove tx inputs in cols `uds_ids` and `utxos_ids` + for input in tx.0.get_inputs() { + match input.id { + SourceIdV10::Ud(UdSourceIdV10 { + issuer, + block_number, + }) => uds_ids.remove(duniter_dbs::UdIdV2(issuer, block_number)), + SourceIdV10::Utxo(UtxoIdV10 { + tx_hash, + output_index, + }) => utxos_ids + .remove(duniter_dbs::UtxoIdDbV2(tx_hash, output_index as u32)), + } + } + // Remove tx hash in col `txs_by_issuer` + for pubkey in tx.0.issuers() { + let mut hashs_ = + txs_by_issuer.get(&PubKeyKeyV2(pubkey))?.unwrap_or_default(); + hashs_.remove(&tx_hash); + txs_by_issuer.upsert(PubKeyKeyV2(pubkey), hashs_) + } + // Remove tx hash in col `txs_by_recipient` + for pubkey in tx.0.recipients_keys() { + let mut hashs_ = txs_by_recipient + .get(&PubKeyKeyV2(pubkey))? + .unwrap_or_default(); + hashs_.remove(&tx_hash); + txs_by_recipient.upsert(PubKeyKeyV2(pubkey), hashs_) + } + // Remove tx itself + txs.remove(HashKeyV2(tx_hash)); + Ok(true) + }, + ) } else { Ok(false) } diff --git a/rust-libs/duniter-dbs/src/keys.rs b/rust-libs/duniter-dbs/src/keys.rs index 35cb4e6dc5aa8456eb1296ae661c9f164e920f2c..70afba42bffbfa157e84cf7f9ba1779a1b204863 100644 --- a/rust-libs/duniter-dbs/src/keys.rs +++ b/rust-libs/duniter-dbs/src/keys.rs @@ -23,4 +23,5 @@ pub mod source_key; pub mod timestamp; pub mod ud_id; pub mod uid; +pub mod utxo_id; pub mod wallet_conditions; diff --git a/rust-libs/duniter-dbs/src/keys/block_number.rs b/rust-libs/duniter-dbs/src/keys/block_number.rs index fb651ddb1437c99626d1df738cb52226bf368a59..ffb804c674b36b3f8e9037a0610c55ee0fa490f5 100644 --- a/rust-libs/duniter-dbs/src/keys/block_number.rs +++ b/rust-libs/duniter-dbs/src/keys/block_number.rs @@ -61,51 +61,6 @@ impl ExplorableKey for BlockNumberKeyV1 { } } -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd)] -#[repr(transparent)] -pub struct BlockNumberKeyV2(pub BlockNumber); - -impl KeyAsBytes for BlockNumberKeyV2 { - fn as_bytes<T, F: FnMut(&[u8]) -> T>(&self, mut f: F) -> T { - f(&(self.0).0.to_be_bytes()[..]) - } -} - -impl FromBytes for BlockNumberKeyV2 { - type Err = StringErr; - - fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> { - Ok(Self(BlockNumber( - zerocopy::LayoutVerified::<_, zerocopy::U32<byteorder::BigEndian>>::new(bytes) - .ok_or_else(|| { - StringErr( - "Corrupted DB: BlockNumber bytes are invalid length or unaligned" - .to_owned(), - ) - })? - .get(), - ))) - } -} - -impl ToDumpString for BlockNumberKeyV2 { - fn to_dump_string(&self) -> String { - todo!() - } -} - -#[cfg(feature = "explorer")] -impl ExplorableKey for BlockNumberKeyV2 { - fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> { - Ok(Self( - BlockNumber::from_str(source).map_err(|e| StringErr(format!("{}", e)))?, - )) - } - fn to_explorer_string(&self) -> KvResult<String> { - Ok(format!("{}", (self.0).0)) - } -} - #[cfg(test)] mod tests { @@ -116,12 +71,4 @@ mod tests { BlockNumberKeyV1(BlockNumber(35)) .as_bytes(|bytes| assert_eq!(bytes, &[48, 48, 48, 48, 48, 48, 48, 48, 51, 53])) } - - #[test] - fn block_number_key_v2() { - let k = BlockNumberKeyV2(BlockNumber(3)); - k.as_bytes(|bytes| { - assert_eq!(bytes, &[0, 0, 0, 3]); - }); - } } diff --git a/rust-libs/duniter-dbs/src/keys/utxo_id.rs b/rust-libs/duniter-dbs/src/keys/utxo_id.rs new file mode 100644 index 0000000000000000000000000000000000000000..93767ceb88981ebbc004cabfd636c5aad3b2396b --- /dev/null +++ b/rust-libs/duniter-dbs/src/keys/utxo_id.rs @@ -0,0 +1,124 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// 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/>. + +use crate::*; +use uninit::prelude::*; + +type OutputIndex = u32; + +#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] +pub struct UtxoIdDbV2(pub Hash, pub OutputIndex); + +impl PartialOrd for UtxoIdDbV2 { + fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { + match self.0.partial_cmp(&other.0) { + Some(std::cmp::Ordering::Equal) => self.1.partial_cmp(&other.1), + o => o, + } + } +} +impl Ord for UtxoIdDbV2 { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + match self.0.cmp(&other.0) { + std::cmp::Ordering::Equal => self.1.cmp(&other.1), + o => o, + } + } +} + +impl KeyAsBytes for UtxoIdDbV2 { + fn as_bytes<T, F: FnMut(&[u8]) -> T>(&self, mut f: F) -> T { + let mut buffer = uninit_array![u8; 36]; + let (hash_buffer, index_buffer) = buffer.as_out().split_at_out(32); + let hash_buffer = hash_buffer.copy_from_slice(self.0.as_ref()); + index_buffer.copy_from_slice(&(self.1).to_be_bytes()); + f(unsafe { std::slice::from_raw_parts_mut(hash_buffer.as_mut_ptr(), 36) }) + } +} + +impl FromBytes for UtxoIdDbV2 { + type Err = StringErr; + + fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> { + let hash = zerocopy::LayoutVerified::<_, Hash>::new(&bytes[..32]).ok_or_else(|| { + StringErr("Corrupted DB: Hash bytes are invalid length or unaligned".to_owned()) + })?; + let output_index = + zerocopy::LayoutVerified::<_, zerocopy::U32<byteorder::BigEndian>>::new(&bytes[32..]) + .ok_or_else(|| { + StringErr( + "Corrupted DB: OutputIndex bytes are invalid length or unaligned" + .to_owned(), + ) + })? + .get(); + Ok(UtxoIdDbV2(*hash, output_index)) + } +} + +impl ToDumpString for UtxoIdDbV2 { + fn to_dump_string(&self) -> String { + todo!() + } +} + +#[cfg(feature = "explorer")] +impl ExplorableKey for UtxoIdDbV2 { + fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> { + let mut source = source.split(':'); + if let Some(hash_str) = source.next() { + let hash = + Hash::from_hex(&hash_str).map_err(|e| StringErr(format!("{}: {}", e, hash_str)))?; + if let Some(output_index_str) = source.next() { + Ok(UtxoIdDbV2( + hash, + u32::from_str(output_index_str).map_err(|e| StringErr(format!("{}", e)))?, + )) + } else { + Err(StringErr("UtxoIdDbV2: Invalid format".to_owned())) + } + } else { + Err(StringErr("UtxoIdDbV2: Invalid format".to_owned())) + } + } + fn to_explorer_string(&self) -> KvResult<String> { + Ok(format!("{}:{}", self.0.to_hex(), self.1)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn utxo_id_v2_as_bytes() -> std::result::Result<(), StringErr> { + let utxo_id = UtxoIdDbV2(Hash::default(), 3); + + let utxo_id_2_res = utxo_id.as_bytes(|bytes| { + assert_eq!( + bytes, + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 3 + ] + ); + UtxoIdDbV2::from_bytes(bytes) + }); + + assert_eq!(utxo_id_2_res?, utxo_id); + + Ok(()) + } +} diff --git a/rust-libs/duniter-dbs/src/lib.rs b/rust-libs/duniter-dbs/src/lib.rs index 710d1fe535f3a4174019b59b360acd21ed5b78a3..3b7251837aab1fd95128aea50eeb167d0a994cbb 100644 --- a/rust-libs/duniter-dbs/src/lib.rs +++ b/rust-libs/duniter-dbs/src/lib.rs @@ -58,6 +58,7 @@ pub use crate::errors::Result; pub use crate::open_dbs::open_dbs; // Export profession types +pub use crate::keys::utxo_id::UtxoIdDbV2; pub use bc_v1::{BcV1Db, BcV1DbReadable, BcV1DbRo, BcV1DbWritable, MainBlocksEvent, UidsEvent}; pub use gva_v1::{GvaV1Db, GvaV1DbReadable, GvaV1DbRo, GvaV1DbWritable}; pub use keys::all::AllKeyV1; diff --git a/rust-libs/duniter-dbs/src/txs_mp_v2.rs b/rust-libs/duniter-dbs/src/txs_mp_v2.rs index 95b6cbaebd94cbfe1e9b463c3c64616282514780..00574a255e60ea44372e09afd87b6b80d3bb29b0 100644 --- a/rust-libs/duniter-dbs/src/txs_mp_v2.rs +++ b/rust-libs/duniter-dbs/src/txs_mp_v2.rs @@ -22,5 +22,7 @@ db_schema!( ["txs_by_issuer", TxsByIssuer, PubKeyKeyV2, BTreeSet<Hash>], ["txs_by_recipient", TxsByRecipient, PubKeyKeyV2, BTreeSet<Hash>], ["txs_by_received_time", TxsByRecvTime, i64, BTreeSet<Hash>], + ["uds_ids", UdsIds, UdIdV2, ()], + ["utxos_ids", UtxosIds, UtxoIdDbV2, ()], ] ); diff --git a/rust-libs/modules/duniter-gva/src/queries/gen_txs.rs b/rust-libs/modules/duniter-gva/src/queries/gen_txs.rs index 8889f7248bc6783355d8ef0b8c693ffdf3f86bd0..df736ad0fe539d78be7c39d24f7cc2d86f196ee7 100644 --- a/rust-libs/modules/duniter-gva/src/queries/gen_txs.rs +++ b/rust-libs/modules/duniter-gva/src/queries/gen_txs.rs @@ -15,9 +15,6 @@ use crate::*; -const MAX_UDS_INPUTS: usize = 20; -const MAX_UTXOS_INPUTS: usize = 20; - #[derive(Default)] pub(crate) struct GenTxsQuery; #[async_graphql::Object] @@ -45,62 +42,14 @@ impl GenTxsQuery { let (current_block, inputs, inputs_sum) = data .dbs_pool .execute(move |dbs| { - if let Some(current_block) = - duniter_dbs_read_ops::get_current_block_meta(&dbs.bc_db)? - { - let (uds, uds_sum) = duniter_dbs_read_ops::uds_of_pubkey::uds_of_pubkey( - &dbs.bc_db, - issuer, - .., - Some(MAX_UDS_INPUTS), - Some(amount), - )?; - let mut inputs = uds - .into_iter() - .map( - |(block_number, source_amount)| TransactionInputV10 { - amount: source_amount, - id: SourceIdV10::Ud(UdSourceIdV10 { - issuer, - block_number, - }), - }, - ) - .collect::<Vec<_>>(); - if uds_sum < amount { - let (utxos, utxos_sum) = duniter_dbs_read_ops::utxos::get_script_utxos( - &dbs.gva_db, - &WalletScriptV10::single(WalletConditionV10::Sig(issuer)), - Some(MAX_UTXOS_INPUTS), - Some(amount - uds_sum), - )?; - inputs.extend(utxos.into_iter() - .map( - |(_written_time, utxo_id, source_amount)| TransactionInputV10 { - amount: source_amount, - id: SourceIdV10::Utxo(utxo_id), - }, - )); - let inputs_sum = uds_sum + utxos_sum; - if inputs_sum < amount { - Err(KvError::Custom("Amount need too many sources or issuer's account has an insufficient balance.".into())) - } else { - Ok::<_, KvError>(( - current_block, - inputs, - inputs_sum - )) - } - } else { - Ok::<_, KvError>(( - current_block, - inputs, - uds_sum - )) - } - } else { - Err(KvError::Custom("no blockchain".into())) - } + duniter_dbs_read_ops::find_inputs::find_inputs( + &dbs.bc_db, + &dbs.gva_db, + &dbs.txs_mp_db, + amount, + &WalletScriptV10::single(WalletConditionV10::Sig(issuer)), + 40, + ) }) .await??; diff --git a/rust-libs/modules/duniter-gva/src/queries/uds.rs b/rust-libs/modules/duniter-gva/src/queries/uds.rs index 7cab2002838c19e1422775a411d671e046b9b29e..bc36f3b8f688ad7dba026256dca2c1a4ec857931 100644 --- a/rust-libs/modules/duniter-gva/src/queries/uds.rs +++ b/rust-libs/modules/duniter-gva/src/queries/uds.rs @@ -55,6 +55,7 @@ impl UdsQuery { .., None, None, + None, ) }) .await??; diff --git a/rust-libs/modules/duniter-gva/src/queries/utxos.rs b/rust-libs/modules/duniter-gva/src/queries/utxos.rs index 60ae3437a2013e96ba1b46b5ed471a580d728bcd..911748f15c8a13771ec2bf729e901266e223fe52 100644 --- a/rust-libs/modules/duniter-gva/src/queries/utxos.rs +++ b/rust-libs/modules/duniter-gva/src/queries/utxos.rs @@ -36,9 +36,7 @@ impl UtxosQuery { let (utxos, _balance) = data .dbs_pool - .execute(move |dbs| { - duniter_dbs_read_ops::utxos::get_script_utxos(&dbs.gva_db, &script, None, None) - }) + .execute(move |dbs| duniter_dbs_read_ops::utxos::get_script_utxos(&dbs.gva_db, &script)) .await??; let utxos: Vec<UtxoGva> = utxos