duniter-gva/dbs-reader/src/find_inputs.rs
2021-05-20 14:30:47 +02:00

244 lines
8.7 KiB
Rust

// 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::{
uds_of_pubkey::UdsWithSum,
utxos::{UtxoCursor, UtxosWithSum},
*,
};
use duniter_core::{documents::transaction::TransactionInputV10, wallet::prelude::*};
pub(super) const MIN_AMOUNT: i64 = 100;
impl DbsReaderImpl {
pub(super) fn find_inputs_<TxsMpDb: 'static + TxsMpV2DbReadable>(
&self,
bc_db: &BcV2DbRo<FileBackend>,
txs_mp_db: &TxsMpDb,
amount: SourceAmount,
script: &WalletScriptV10,
use_mempool_sources: bool,
) -> anyhow::Result<(Vec<TransactionInputV10>, SourceAmount)> {
// Pending UTXOs
let (mut inputs, mut inputs_sum) = if use_mempool_sources {
txs_mp_db
.outputs_by_script()
.get_ref_slice(
duniter_core::dbs::WalletConditionsV2::from_ref(script),
|utxos| {
let mut sum = SourceAmount::ZERO;
let inputs = utxos
.iter()
.filter(|utxo| {
!txs_mp_db
.utxos_ids()
.contains_key(&UtxoIdDbV2(*utxo.tx_hash(), utxo.output_index()))
.unwrap_or(true)
})
.copied()
.map(|utxo| {
let amount = *utxo.amount();
sum = sum + amount;
TransactionInputV10 {
amount,
id: SourceIdV10::Utxo(UtxoIdV10 {
tx_hash: *utxo.tx_hash(),
output_index: utxo.output_index() as usize,
}),
}
})
.collect();
Ok((inputs, sum))
},
)?
.unwrap_or((Vec::with_capacity(500), SourceAmount::ZERO))
} else {
(Vec::with_capacity(500), SourceAmount::ZERO)
};
// UDs
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_core::dbs::UdIdV2(_pk, bn)| bn)
.collect::<KvResult<_>>()
})?;
let PagedData {
data: UdsWithSum { uds, sum: uds_sum },
..
} = self.unspent_uds_of_pubkey(
bc_db,
issuer,
PageInfo::default(),
Some(pending_uds_bn),
Some(amount - inputs_sum),
)?;
inputs.extend(uds.into_iter().map(|(block_number, source_amount)| {
TransactionInputV10 {
amount: source_amount,
id: SourceIdV10::Ud(UdSourceIdV10 {
issuer,
block_number,
}),
}
}));
inputs_sum = inputs_sum + uds_sum;
}
}
if inputs_sum < amount {
// Written UTXOs
let PagedData {
data:
UtxosWithSum {
utxos: written_utxos,
sum: written_utxos_sum,
},
..
} = self.find_script_utxos(
txs_mp_db,
Some(amount - inputs_sum),
PageInfo::default(),
&script,
)?;
inputs.extend(written_utxos.into_iter().map(
|(
UtxoCursor {
tx_hash,
output_index,
..
},
source_amount,
)| TransactionInputV10 {
amount: source_amount,
id: SourceIdV10::Utxo(UtxoIdV10 {
tx_hash,
output_index: output_index as usize,
}),
},
));
Ok((inputs, inputs_sum + written_utxos_sum))
} else {
Ok((inputs, inputs_sum))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use duniter_core::dbs::{
databases::{bc_v2::BcV2DbWritable, txs_mp_v2::TxsMpV2DbWritable},
BlockMetaV2, SourceAmountValV2, UdIdV2, UtxoIdDbV2, UtxoValV2, WalletConditionsV2,
};
use duniter_gva_db::{GvaUtxoIdDbV1, GvaV1DbWritable};
const UD0: i64 = 100;
#[test]
fn test_find_inputs() -> anyhow::Result<()> {
let bc_db = duniter_core::dbs::databases::bc_v2::BcV2Db::<Mem>::open(MemConf::default())?;
let bc_db_ro = bc_db.get_ro_handler();
let gva_db = duniter_gva_db::GvaV1Db::<Mem>::open(MemConf::default())?;
let db_reader = create_dbs_reader(unsafe { std::mem::transmute(&gva_db.get_ro_handler()) });
let txs_mp_db =
duniter_core::dbs::databases::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 pending_utxos = BTreeSet::new();
pending_utxos.insert(UtxoValV2::new(
SourceAmount::with_base0(900),
Hash::default(),
10,
));
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
.blockchain_time_write()
.upsert(U32BE(0), b0.median_time)?;
gva_db.gva_utxos_write().upsert(
GvaUtxoIdDbV1::new(script.clone(), 0, Hash::default(), 0),
SourceAmountValV2(SourceAmount::with_base0(500)),
)?;
gva_db.gva_utxos_write().upsert(
GvaUtxoIdDbV1::new(script.clone(), 0, Hash::default(), 1),
SourceAmountValV2(SourceAmount::with_base0(800)),
)?;
txs_mp_db
.outputs_by_script_write()
.upsert(WalletConditionsV2(script.clone()), pending_utxos)?;
// Gen tx1
let (inputs, inputs_sum) = db_reader.find_inputs(
&bc_db_ro,
&txs_mp_db,
SourceAmount::with_base0(550),
&script,
false,
)?;
assert_eq!(inputs.len(), 2);
assert_eq!(inputs_sum, SourceAmount::with_base0(600));
// 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 (inputs, inputs_sum) = db_reader.find_inputs(
&bc_db_ro,
&txs_mp_db,
SourceAmount::with_base0(550),
&script,
false,
)?;
assert_eq!(inputs.len(), 1);
assert_eq!(inputs_sum, SourceAmount::with_base0(800));
// Insert tx2 inputs in mempool
txs_mp_db
.utxos_ids_write()
.upsert(UtxoIdDbV2(Hash::default(), 1), ())?;
// Gen tx3 (use pending utxo)
let (inputs, inputs_sum) = db_reader.find_inputs(
&bc_db_ro,
&txs_mp_db,
SourceAmount::with_base0(750),
&script,
true,
)?;
assert_eq!(inputs.len(), 1);
assert_eq!(inputs_sum, SourceAmount::with_base0(900));
Ok(())
}
}