Skip to content
Snippets Groups Projects
Commit bbff027a authored by Éloïs's avatar Éloïs
Browse files

Merge branch 'ref/cm-to-global' into 'dev'

[feat] server: create global cache

See merge request nodes/typescript/duniter!1366
parents 8fb9eca8 6ce60364
Branches
No related tags found
1 merge request!1366[feat] server: create global cache
Showing
with 414 additions and 193 deletions
......@@ -20,6 +20,7 @@ duniter-gva-db = { path = "./db" }
duniter-gva-dbs-reader = { path = "./dbs-reader" }
duniter-gva-indexer = { path = "./indexer" }
duniter-gva-gql = { path = "./gql" }
duniter-global = { path = "../../duniter-global" }
duniter-mempools = { path = "../../duniter-mempools" }
duniter-module = { path = "../../duniter-module" }
fast-threadpool = "0.2.3"
......
......@@ -7,6 +7,7 @@ edition = "2018"
[dependencies]
anyhow = "1.0.33"
arrayvec = { version = "0.5.1", features = ["serde"] }
async-bincode = "0.6.1"
async_io_stream = { version = "0.3.1", features = [ "tokio_io"] }
bincode = "1.3"
......@@ -15,6 +16,7 @@ duniter-bca-types = { path = "types", features = ["duniter"] }
duniter-dbs = { path = "../../../duniter-dbs" }
duniter-gva-db = { path = "../db" }
duniter-gva-dbs-reader = { path = "../dbs-reader" }
duniter-global = { path = "../../../duniter-global" }
duniter-mempools = { path = "../../../duniter-mempools" }
fast-threadpool = "0.2.3"
futures = "0.3.6"
......@@ -26,5 +28,6 @@ uninit = "0.4.0"
[dev-dependencies]
duniter-dbs = { path = "../../../duniter-dbs", features = ["mem"] }
duniter-gva-dbs-reader = { path = "../dbs-reader", features = ["mock"] }
duniter-global = { path = "../../../duniter-global", features = ["mock"] }
tokio = { version = "1.2", features = ["macros", "rt-multi-thread", "time"] }
mockall = "0.9.1"
......@@ -13,10 +13,12 @@
// 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/>.
mod balances;
mod last_blockstamp_out_of_fork_window;
mod members_count;
mod prepare_simple_payment;
mod send_txs;
mod utxos;
use dubp::crypto::keys::KeyPair;
......@@ -40,6 +42,13 @@ pub(super) async fn execute_req_type(
_is_whitelisted: bool,
) -> Result<BcaRespTypeV0, ExecReqTypeError> {
match req_type {
BcaReqTypeV0::BalancesOfPubkeys(pubkeys) => {
balances::exec_req_balances_of_pubkeys(bca_executor, pubkeys).await
}
BcaReqTypeV0::FirstUtxosOfPubkeys {
amount_target_opt,
pubkeys,
} => utxos::exec_req_first_utxos_of_pubkeys(bca_executor, amount_target_opt, pubkeys).await,
BcaReqTypeV0::LastBlockstampOutOfForkWindow => {
last_blockstamp_out_of_fork_window::exec_req_last_blockstamp_out_of_fork_window(
bca_executor,
......
// 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::{crypto::keys::ed25519::PublicKey, wallet::prelude::WalletScriptV10};
pub(super) async fn exec_req_balances_of_pubkeys(
bca_executor: &BcaExecutor,
pubkeys: ArrayVec<[PublicKey; 16]>,
) -> Result<BcaRespTypeV0, ExecReqTypeError> {
let dbs_reader = bca_executor.dbs_reader();
Ok(BcaRespTypeV0::Balances(
bca_executor
.dbs_pool
.execute(move |_| {
pubkeys
.into_iter()
.map(|pubkey| {
dbs_reader
.get_account_balance(&WalletScriptV10::single_sig(pubkey))
.map(|balance_opt| balance_opt.map(|balance| balance.0))
})
.collect::<Result<ArrayVec<_>, _>>()
})
.await??,
))
}
......@@ -19,15 +19,19 @@ use dubp::common::prelude::*;
pub(super) async fn exec_req_last_blockstamp_out_of_fork_window(
bca_executor: &BcaExecutor,
) -> Result<BcaRespTypeV0, ExecReqTypeError> {
if let Some(current_block_number) = bca_executor
.cm_accessor
.get_current_meta(|cm| cm.current_block_meta.number)
.await
{
let dbs_reader = bca_executor.dbs_reader();
bca_executor
.dbs_pool
.execute(move |dbs| {
if let Some(current_block) = dbs_reader.get_current_block_meta(&dbs.cm_db)? {
let block_ref_number = if current_block.number < 101 {
let block_ref_number = if current_block_number < 101 {
0
} else {
current_block.number - 101
current_block_number - 101
};
let block_ref_hash = dbs_reader
.block(&dbs.bc_db_ro, U32BE(block_ref_number))?
......@@ -39,11 +43,11 @@ pub(super) async fn exec_req_last_blockstamp_out_of_fork_window(
hash: BlockHash(block_ref_hash),
},
))
})
.await?
} else {
Err("no blockchain".into())
}
})
.await?
}
#[cfg(test)]
......@@ -53,12 +57,14 @@ mod tests {
#[tokio::test]
async fn test_exec_req_last_blockstamp_out_of_fork_window_no_blockchain() {
let mut dbs_reader = MockDbsReader::new();
dbs_reader
.expect_get_current_block_meta::<CmV1Db<MemSingleton>>()
let mut cm_mock = MockAsyncAccessor::new();
cm_mock
.expect_get_current_meta::<u32>()
.times(1)
.returning(|_| Ok(None));
let bca_executor = create_bca_executor(dbs_reader).expect("fail to create bca executor");
.returning(|_| None);
let dbs_reader = MockDbsReader::new();
let bca_executor =
create_bca_executor(cm_mock, dbs_reader).expect("fail to create bca executor");
let resp_res = exec_req_last_blockstamp_out_of_fork_window(&bca_executor).await;
......@@ -67,17 +73,19 @@ mod tests {
#[tokio::test]
async fn test_exec_req_last_blockstamp_out_of_fork_window_ok() -> Result<(), ExecReqTypeError> {
let mut dbs_reader = MockDbsReader::new();
dbs_reader
.expect_get_current_block_meta::<CmV1Db<MemSingleton>>()
let mut cm_mock = MockAsyncAccessor::new();
cm_mock
.expect_get_current_meta::<u32>()
.times(1)
.returning(|_| Ok(Some(BlockMetaV2::default())));
.returning(|f| Some(f(&CurrentMeta::default())));
let mut dbs_reader = MockDbsReader::new();
dbs_reader
.expect_block()
.times(1)
.returning(|_, _| Ok(Some(BlockMetaV2::default())));
let bca_executor = create_bca_executor(dbs_reader).expect("fail to create bca executor");
let bca_executor =
create_bca_executor(cm_mock, dbs_reader).expect("fail to create bca executor");
let resp = exec_req_last_blockstamp_out_of_fork_window(&bca_executor).await?;
......
......@@ -14,20 +14,39 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::*;
use dubp::block::prelude::*;
pub(super) async fn exec_req_members_count(
bca_executor: &BcaExecutor,
) -> Result<BcaRespTypeV0, ExecReqTypeError> {
let dbs_reader = bca_executor.dbs_reader();
Ok(bca_executor
.dbs_pool
.execute(move |dbs| match dbs_reader.get_current_block(&dbs.cm_db) {
Ok(Some(current_block)) => {
BcaRespTypeV0::MembersCount(current_block.members_count() as u64)
if let Some(members_count) = bca_executor
.cm_accessor
.get_current_meta(|cm| cm.current_block_meta.members_count)
.await
{
Ok(BcaRespTypeV0::MembersCount(members_count))
} else {
Err("no blockchain".into())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tests::*;
#[tokio::test]
async fn test_exec_req_members_count() {
let mut cm_mock = MockAsyncAccessor::new();
cm_mock
.expect_get_current_meta::<u64>()
.times(1)
.returning(|f| Some(f(&CurrentMeta::default())));
let dbs_reader = MockDbsReader::new();
let bca_executor =
create_bca_executor(cm_mock, dbs_reader).expect("fail to create bca executor");
let resp_res = exec_req_members_count(&bca_executor).await;
assert_eq!(resp_res, Ok(BcaRespTypeV0::MembersCount(0)));
}
Ok(None) => BcaRespTypeV0::Error("no blockchain".to_owned()),
Err(e) => BcaRespTypeV0::Error(e.to_string()),
})
.await?)
}
......@@ -21,24 +21,26 @@ pub(super) async fn exec_req_prepare_simple_payment(
bca_executor: &BcaExecutor,
params: PrepareSimplePayment,
) -> Result<BcaRespTypeV0, ExecReqTypeError> {
let mut amount = params.amount;
let issuer = params.issuer;
if let Some(current_meta) = bca_executor.cm_accessor.get_current_meta(|cm| *cm).await {
let current_block_meta = current_meta.current_block_meta;
let current_ud = current_meta.current_ud;
let dbs_reader = bca_executor.dbs_reader();
let (amount, block_ref_number, block_ref_hash, (inputs, inputs_sum)) = bca_executor
.dbs_pool
.execute(move |dbs| {
if let Some(current_block) = dbs_reader.get_current_block_meta(&dbs.cm_db)? {
let block_ref_number = if current_block.number < 101 {
let mut amount = params.amount.to_cents(current_ud);
let block_ref_number = if current_block_meta.number < 101 {
0
} else {
current_block.number - 101
current_block_meta.number - 101
};
let block_ref_hash = dbs_reader
.block(&dbs.bc_db_ro, U32BE(block_ref_number))?
.expect("unreachable")
.hash;
let current_base = current_block.unit_base as i64;
let current_base = current_block_meta.unit_base as i64;
if amount.base() > current_base {
Err("too long base".into())
......@@ -59,9 +61,6 @@ pub(super) async fn exec_req_prepare_simple_payment(
)?,
))
}
} else {
Err("no blockchain".into())
}
})
.await??;
......@@ -77,6 +76,9 @@ pub(super) async fn exec_req_prepare_simple_payment(
inputs_sum,
},
))
} else {
Err("no blockchain".into())
}
}
#[cfg(test)]
......@@ -86,18 +88,20 @@ mod tests {
#[tokio::test]
async fn test_exec_req_prepare_simple_payment_no_blockchain() {
let mut dbs_reader = MockDbsReader::new();
dbs_reader
.expect_get_current_block_meta::<CmV1Db<MemSingleton>>()
let mut mock_cm = MockAsyncAccessor::new();
mock_cm
.expect_get_current_meta::<CurrentMeta>()
.times(1)
.returning(|_| Ok(None));
let bca_executor = create_bca_executor(dbs_reader).expect("fail to create bca executor");
.returning(|_| None);
let dbs_reader = MockDbsReader::new();
let bca_executor =
create_bca_executor(mock_cm, dbs_reader).expect("fail to create bca executor");
let resp_res = exec_req_prepare_simple_payment(
&bca_executor,
PrepareSimplePayment {
issuer: PublicKey::default(),
amount: SourceAmount::new(42, 0),
amount: Amount::Cents(SourceAmount::new(42, 0)),
},
)
.await;
......@@ -107,22 +111,24 @@ mod tests {
#[tokio::test]
async fn test_exec_req_prepare_simple_payment_too_long_base() {
let mut dbs_reader = MockDbsReader::new();
dbs_reader
.expect_get_current_block_meta::<CmV1Db<MemSingleton>>()
let mut mock_cm = MockAsyncAccessor::new();
mock_cm
.expect_get_current_meta::<CurrentMeta>()
.times(1)
.returning(|_| Ok(Some(BlockMetaV2::default())));
.returning(|f| Some(f(&CurrentMeta::default())));
let mut dbs_reader = MockDbsReader::new();
dbs_reader
.expect_block()
.times(1)
.returning(|_, _| Ok(Some(BlockMetaV2::default())));
let bca_executor = create_bca_executor(dbs_reader).expect("fail to create bca executor");
let bca_executor =
create_bca_executor(mock_cm, dbs_reader).expect("fail to create bca executor");
let resp_res = exec_req_prepare_simple_payment(
&bca_executor,
PrepareSimplePayment {
issuer: PublicKey::default(),
amount: SourceAmount::new(42, 1),
amount: Amount::Cents(SourceAmount::new(42, 1)),
},
)
.await;
......@@ -132,11 +138,12 @@ mod tests {
#[tokio::test]
async fn test_exec_req_prepare_simple_payment_insufficient_balance() {
let mut dbs_reader = MockDbsReader::new();
dbs_reader
.expect_get_current_block_meta::<CmV1Db<MemSingleton>>()
let mut mock_cm = MockAsyncAccessor::new();
mock_cm
.expect_get_current_meta::<CurrentMeta>()
.times(1)
.returning(|_| Ok(Some(BlockMetaV2::default())));
.returning(|f| Some(f(&CurrentMeta::default())));
let mut dbs_reader = MockDbsReader::new();
dbs_reader
.expect_block()
.times(1)
......@@ -145,13 +152,14 @@ mod tests {
.expect_find_inputs::<TxsMpV2Db<FileBackend>>()
.times(1)
.returning(|_, _, _, _, _| Ok((vec![], SourceAmount::default())));
let bca_executor = create_bca_executor(dbs_reader).expect("fail to create bca executor");
let bca_executor =
create_bca_executor(mock_cm, dbs_reader).expect("fail to create bca executor");
let resp_res = exec_req_prepare_simple_payment(
&bca_executor,
PrepareSimplePayment {
issuer: PublicKey::default(),
amount: SourceAmount::new(42, 0),
amount: Amount::Cents(SourceAmount::new(42, 0)),
},
)
.await;
......@@ -172,11 +180,12 @@ mod tests {
}),
};
let mut dbs_reader = MockDbsReader::new();
dbs_reader
.expect_get_current_block_meta::<CmV1Db<MemSingleton>>()
let mut mock_cm = MockAsyncAccessor::new();
mock_cm
.expect_get_current_meta::<CurrentMeta>()
.times(1)
.returning(|_| Ok(Some(BlockMetaV2::default())));
.returning(|f| Some(f(&CurrentMeta::default())));
let mut dbs_reader = MockDbsReader::new();
dbs_reader
.expect_block()
.times(1)
......@@ -185,13 +194,14 @@ mod tests {
.expect_find_inputs::<TxsMpV2Db<FileBackend>>()
.times(1)
.returning(move |_, _, _, _, _| Ok((vec![input], SourceAmount::with_base0(57))));
let bca_executor = create_bca_executor(dbs_reader).expect("fail to create bca executor");
let bca_executor =
create_bca_executor(mock_cm, dbs_reader).expect("fail to create bca executor");
let resp = exec_req_prepare_simple_payment(
&bca_executor,
PrepareSimplePayment {
issuer: PublicKey::default(),
amount: SourceAmount::new(42, 0),
amount: Amount::Cents(SourceAmount::new(42, 0)),
},
)
.await?;
......
// 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::{crypto::keys::ed25519::PublicKey, wallet::prelude::WalletScriptV10};
pub(super) async fn exec_req_first_utxos_of_pubkeys(
bca_executor: &BcaExecutor,
amount_target_opt: Option<Amount>,
pubkeys: ArrayVec<[PublicKey; 16]>,
) -> Result<BcaRespTypeV0, ExecReqTypeError> {
if let Some(current_ud) = bca_executor
.cm_accessor
.get_current_meta(|cm| cm.current_ud)
.await
{
let dbs_reader = bca_executor.dbs_reader();
let scripts: ArrayVec<[WalletScriptV10; 16]> = pubkeys
.into_iter()
.map(WalletScriptV10::single_sig)
.collect();
if let Some(amount_target) = amount_target_opt {
Ok(BcaRespTypeV0::FirstUtxosOfPubkeys(
bca_executor
.dbs_pool
.execute(move |_| {
Ok::<_, ExecReqTypeError>(dbs_reader.first_scripts_utxos(
Some(amount_target.to_cents(current_ud)),
40,
&scripts,
)?)
})
.await??,
))
} else {
Ok(BcaRespTypeV0::FirstUtxosOfPubkeys(
bca_executor
.dbs_pool
.execute(move |_| dbs_reader.first_scripts_utxos(None, 40, &scripts))
.await??,
))
}
} else {
Err("no blockchain".into())
}
}
......@@ -24,20 +24,28 @@
mod exec_req_type;
const MAX_BATCH_SIZE: usize = 10;
const RESP_MIN_SIZE: usize = 64;
type RespBytes = SmallVec<[u8; RESP_MIN_SIZE]>;
use crate::exec_req_type::ExecReqTypeError;
#[cfg(test)]
use crate::tests::AsyncAccessor;
use arrayvec::ArrayVec;
use async_bincode::AsyncBincodeReader;
use async_io_stream::IoStream;
use bincode::Options as _;
use dubp::crypto::keys::{ed25519::Ed25519KeyPair, Signator};
use duniter_bca_types::{
bincode_opts, BcaReq, BcaReqExecError, BcaReqTypeV0, BcaResp, BcaRespTypeV0, BcaRespV0,
amount::Amount, bincode_opts, BcaReq, BcaReqExecError, BcaReqTypeV0, BcaResp, BcaRespTypeV0,
BcaRespV0,
};
pub use duniter_dbs::kv_typed::prelude::*;
use duniter_dbs::{FileBackend, SharedDbs};
#[cfg(not(test))]
use duniter_global::AsyncAccessor;
use duniter_gva_dbs_reader::DbsReader;
use futures::{prelude::stream::FuturesUnordered, StreamExt, TryStream, TryStreamExt};
use once_cell::sync::OnceCell;
use smallvec::SmallVec;
......@@ -52,6 +60,7 @@ static BCA_EXECUTOR: OnceCell<BcaExecutor> = OnceCell::new();
pub fn set_bca_executor(
currency: String,
cm_accessor: AsyncAccessor,
dbs_pool: fast_threadpool::ThreadPoolAsyncHandler<SharedDbs<FileBackend>>,
dbs_reader: DbsReaderImpl,
self_keypair: Ed25519KeyPair,
......@@ -61,6 +70,7 @@ pub fn set_bca_executor(
BCA_EXECUTOR
.set(BcaExecutor {
currency,
cm_accessor,
dbs_pool,
dbs_reader,
self_keypair,
......@@ -86,6 +96,7 @@ where
#[derive(Clone)]
struct BcaExecutor {
cm_accessor: AsyncAccessor,
currency: String,
dbs_pool: fast_threadpool::ThreadPoolAsyncHandler<SharedDbs<FileBackend>>,
dbs_reader: DbsReaderImpl,
......@@ -132,6 +143,7 @@ impl BcaExecutor {
vec
})
}
async fn execute_inner(
&self,
stream: impl TryStream<Ok = BcaReq, Error = bincode::Error>,
......@@ -142,6 +154,7 @@ impl BcaExecutor {
let self_clone = self.clone();
tokio::spawn(async move { self_clone.execute_req(req, is_whitelisted).await })
})
.take(MAX_BATCH_SIZE)
.try_collect::<FuturesUnordered<_>>()
.await
{
......@@ -225,9 +238,11 @@ mod tests {
pub use duniter_dbs::databases::cm_v1::{CmV1Db, CmV1DbReadable};
pub use duniter_dbs::databases::txs_mp_v2::{TxsMpV2Db, TxsMpV2DbReadable};
pub use duniter_dbs::BlockMetaV2;
pub use duniter_global::{CurrentMeta, MockAsyncAccessor};
pub use duniter_gva_dbs_reader::MockDbsReader;
pub use futures::TryStreamExt;
pub type AsyncAccessor = duniter_dbs::kv_typed::prelude::Arc<MockAsyncAccessor>;
pub type DbsReaderImpl = duniter_dbs::kv_typed::prelude::Arc<MockDbsReader>;
impl BcaExecutor {
......@@ -237,11 +252,15 @@ mod tests {
}
}
pub(crate) fn create_bca_executor(mock_dbs_reader: MockDbsReader) -> KvResult<BcaExecutor> {
pub(crate) fn create_bca_executor(
mock_cm: MockAsyncAccessor,
mock_dbs_reader: MockDbsReader,
) -> KvResult<BcaExecutor> {
let dbs = SharedDbs::mem()?;
let threadpool =
fast_threadpool::ThreadPool::start(fast_threadpool::ThreadPoolConfig::low(), dbs);
Ok(BcaExecutor {
cm_accessor: duniter_dbs::kv_typed::prelude::Arc::new(mock_cm),
currency: "g1".to_owned(),
dbs_pool: threadpool.into_async_handler(),
dbs_reader: duniter_dbs::kv_typed::prelude::Arc::new(mock_dbs_reader),
......@@ -275,12 +294,13 @@ mod tests {
//println!("bytes_for_bincode={:?}", &bytes[4..]);
assert_eq!(req, bincode_opts().deserialize(&bytes[4..])?);
let mut dbs_reader = MockDbsReader::new();
dbs_reader
.expect_get_current_block::<CmV1Db<MemSingleton>>()
let mut mock_cm = MockAsyncAccessor::new();
mock_cm
.expect_get_current_meta::<u64>()
.times(1)
.returning(|_| Ok(Some(DubpBlockV10::default())));
let bca_executor = create_bca_executor(dbs_reader).expect("fail to create bca executor");
.returning(|f| Some(f(&CurrentMeta::default())));
let bca_executor = create_bca_executor(mock_cm, MockDbsReader::new())
.expect("fail to create bca executor");
//println!("bytes={:?}", bytes);
let bytes_res = bca_executor.execute(io_stream(bytes), false).await;
......@@ -317,8 +337,8 @@ mod tests {
//println!("bytes_for_bincode={:?}", &bytes[4..]);
assert_eq!(req, bincode_opts().deserialize(&bytes[4..])?);
let bca_executor =
create_bca_executor(MockDbsReader::new()).expect("fail to create bca executor");
let bca_executor = create_bca_executor(MockAsyncAccessor::new(), MockDbsReader::new())
.expect("fail to create bca executor");
//println!("bytes={:?}", bytes);
let bytes_res = bca_executor.execute(io_stream(bytes), false).await;
......@@ -357,12 +377,13 @@ mod tests {
bincode_opts().serialize_into(&mut bytes[11..], &req2)?;
bytes[10] = 3;
let mut dbs_reader = MockDbsReader::new();
dbs_reader
.expect_get_current_block::<CmV1Db<MemSingleton>>()
let mut mock_cm = MockAsyncAccessor::new();
mock_cm
.expect_get_current_meta::<u64>()
.times(1)
.returning(|_| Ok(Some(DubpBlockV10::default())));
let bca_executor = create_bca_executor(dbs_reader).expect("fail to create bca executor");
.returning(|f| Some(f(&CurrentMeta::default())));
let bca_executor = create_bca_executor(mock_cm, MockDbsReader::new())
.expect("fail to create bca executor");
//println!("bytes={:?}", bytes);
let bytes_res = bca_executor.execute(io_stream(bytes), false).await;
......
......@@ -6,6 +6,7 @@ license = "AGPL-3.0"
edition = "2018"
[dependencies]
arrayvec = { version = "0.5.1", features = ["serde"] }
bincode = "1.3"
dubp = { version = "0.50.0" }
serde = { version = "1.0.105", features = ["derive"] }
......
// 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::*;
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub enum Amount {
Cents(SourceAmount),
Uds(f64),
}
impl Default for Amount {
fn default() -> Self {
Self::Cents(SourceAmount::ZERO)
}
}
impl Amount {
pub fn to_cents(self, ud_amount: SourceAmount) -> SourceAmount {
match self {
Amount::Cents(sa) => sa,
Amount::Uds(f64_) => {
if !f64_.is_finite() || f64_ <= 0f64 {
SourceAmount::ZERO
} else {
SourceAmount::new(
f64::round(ud_amount.amount() as f64 * f64_) as i64,
ud_amount.base(),
)
}
}
}
}
}
......@@ -22,11 +22,16 @@
unused_import_braces
)]
pub mod amount;
pub mod prepare_payment;
pub mod rejected_tx;
pub mod utxo;
use crate::amount::Amount;
use crate::prepare_payment::{PrepareSimplePayment, PrepareSimplePaymentResp};
use crate::utxo::Utxo;
use arrayvec::ArrayVec;
use bincode::Options as _;
use dubp::crypto::keys::ed25519::{PublicKey, Signature};
use dubp::wallet::prelude::*;
......@@ -35,33 +40,38 @@ use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
use thiserror::Error;
pub fn bincode_opts() -> impl bincode::Options {
bincode::options()
.with_limit(u32::max_value() as u64)
.allow_trailing_bytes()
}
// Constants
pub const MAX_FIRST_UTXOS: usize = 40;
// Request
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub enum BcaReq {
V0(BcaReqV0),
_V1,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct BcaReqV0 {
pub req_id: usize,
pub req_type: BcaReqTypeV0,
}
#[allow(clippy::large_enum_variant)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub enum BcaReqTypeV0 {
BalancesOfPubkeys(ArrayVec<[PublicKey; 16]>),
FirstUtxosOfPubkeys {
amount_target_opt: Option<Amount>,
pubkeys: ArrayVec<[PublicKey; 16]>,
},
LastBlockstampOutOfForkWindow,
MembersCount,
PrepareSimplePayment(PrepareSimplePayment),
ProofServerPubkey { challenge: [u8; 16] },
ProofServerPubkey {
challenge: [u8; 16],
},
Ping,
SendTxs(Txs),
}
......@@ -84,9 +94,12 @@ pub struct BcaRespV0 {
pub resp_type: BcaRespTypeV0,
}
#[allow(clippy::large_enum_variant)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
pub enum BcaRespTypeV0 {
Error(String),
Balances(ArrayVec<[Option<SourceAmount>; 16]>),
FirstUtxosOfPubkeys(Vec<ArrayVec<[Utxo; MAX_FIRST_UTXOS]>>),
ProofServerPubkey {
challenge: [u8; 16],
server_pubkey: PublicKey,
......@@ -114,3 +127,11 @@ pub enum BcaReqExecError {
#[error("Unknown error")]
Unknown,
}
// Bincode configuration
pub fn bincode_opts() -> impl bincode::Options {
bincode::options()
.with_limit(u32::max_value() as u64)
.allow_trailing_bytes()
}
......@@ -16,10 +16,10 @@
use crate::*;
use dubp::documents::transaction::TransactionInputV10;
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub struct PrepareSimplePayment {
pub issuer: PublicKey,
pub amount: SourceAmount,
pub amount: Amount,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
......
......@@ -14,18 +14,10 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::*;
use duniter_dbs::databases::bc_v2::BcV2DbReadable;
pub(super) fn fill_current_meta_db(shared_dbs: &SharedDbs<FileBackend>) -> anyhow::Result<()> {
if let Some(current_block_meta) = shared_dbs
.bc_db_ro
.blocks_meta()
.iter_rev(.., |it| it.values().next_res())?
{
shared_dbs
.cm_db
.current_block_meta_write()
.upsert((), current_block_meta)?;
}
Ok(())
#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq, Serialize)]
pub struct Utxo {
pub amount: SourceAmount,
pub tx_hash: Hash,
pub output_index: u8,
}
......@@ -17,6 +17,7 @@ mock = ["mockall"]
[dependencies]
anyhow = "1.0.34"
arrayvec = "0.5.1"
duniter-bca-types = { path = "../bca/types" }
duniter-dbs = { path = "../../../duniter-dbs" }
duniter-gva-db = { path = "../db" }
dubp = { version = "0.50.0", features = ["duniter"] }
......
......@@ -18,23 +18,16 @@ use duniter_dbs::BlockMetaV2;
use crate::*;
impl DbsReaderImpl {
pub(super) fn get_current_frame_<
BcDb: 'static + BcV2DbReadable,
CmDb: 'static + CmV1DbReadable,
>(
pub(super) fn get_current_frame_<BcDb: 'static + BcV2DbReadable>(
&self,
bc_db: &BcDb,
cm_db: &CmDb,
current_block_meta: &BlockMetaV2,
) -> anyhow::Result<Vec<BlockMetaV2>> {
if let Some(current_block_meta) = self.get_current_block_meta(cm_db)? {
let issuers_frame = current_block_meta.issuers_frame;
let start = U32BE(current_block_meta.number + 1 - issuers_frame as u32);
bc_db
.blocks_meta()
.iter_rev(start.., |it| it.values().collect::<KvResult<_>>())
.map_err(Into::into)
} else {
Ok(Vec::with_capacity(0))
}
}
}
......@@ -32,6 +32,7 @@ pub mod uds_of_pubkey;
pub mod utxos;
pub use crate::pagination::{PageInfo, PagedData};
pub use duniter_bca_types::MAX_FIRST_UTXOS;
use crate::pagination::{has_next_page, has_previous_page};
use arrayvec::ArrayVec;
......@@ -39,6 +40,7 @@ use dubp::common::crypto::keys::ed25519::PublicKey;
use dubp::documents::transaction::TransactionDocumentV10;
use dubp::{block::DubpBlockV10, common::crypto::hashs::Hash};
use dubp::{common::prelude::BlockNumber, wallet::prelude::*};
use duniter_bca_types::utxo::Utxo;
use duniter_dbs::FileBackend;
use duniter_dbs::{
databases::{
......@@ -103,9 +105,10 @@ pub trait DbsReader {
) -> anyhow::Result<PagedData<utxos::UtxosWithSum>>;
fn first_scripts_utxos(
&self,
amount_target_opt: Option<SourceAmount>,
first: usize,
scripts: &[WalletScriptV10],
) -> anyhow::Result<Vec<arrayvec::ArrayVec<[utxos::Utxo; utxos::MAX_FIRST_UTXOS]>>>;
) -> anyhow::Result<Vec<arrayvec::ArrayVec<[Utxo; MAX_FIRST_UTXOS]>>>;
fn get_account_balance(
&self,
account_script: &WalletScriptV10,
......@@ -115,19 +118,11 @@ pub trait DbsReader {
&self,
cm_db: &CmDb,
) -> KvResult<Option<DubpBlockV10>>;
fn get_current_block_meta<CmDb: 'static + CmV1DbReadable>(
&self,
cm_db: &CmDb,
) -> KvResult<Option<BlockMetaV2>>;
fn get_current_frame<BcDb: 'static + BcV2DbReadable, CmDb: 'static + CmV1DbReadable>(
fn get_current_frame<BcDb: 'static + BcV2DbReadable>(
&self,
bc_db: &BcDb,
cm_db: &CmDb,
) -> anyhow::Result<Vec<duniter_dbs::BlockMetaV2>>;
fn get_current_ud<BcDb: 'static + BcV2DbReadable>(
&self,
bc_db: &BcDb,
) -> KvResult<Option<SourceAmount>>;
current_block_meta: &BlockMetaV2,
) -> anyhow::Result<Vec<BlockMetaV2>>;
fn get_txs_history_bc_received(
&self,
from: Option<u64>,
......@@ -217,10 +212,11 @@ impl DbsReader for DbsReaderImpl {
fn first_scripts_utxos(
&self,
amount_target_opt: Option<SourceAmount>,
first: usize,
scripts: &[WalletScriptV10],
) -> anyhow::Result<Vec<ArrayVec<[utxos::Utxo; utxos::MAX_FIRST_UTXOS]>>> {
self.first_scripts_utxos_(first, scripts)
) -> anyhow::Result<Vec<ArrayVec<[Utxo; MAX_FIRST_UTXOS]>>> {
self.first_scripts_utxos_(amount_target_opt, first, scripts)
}
fn get_account_balance(
......@@ -247,25 +243,12 @@ impl DbsReader for DbsReaderImpl {
Ok(cm_db.current_block().get(&())?.map(|db_block| db_block.0))
}
fn get_current_block_meta<CmDb: CmV1DbReadable>(
&self,
cm_db: &CmDb,
) -> KvResult<Option<BlockMetaV2>> {
cm_db.current_block_meta().get(&())
}
fn get_current_frame<BcDb: 'static + BcV2DbReadable, CmDb: 'static + CmV1DbReadable>(
fn get_current_frame<BcDb: 'static + BcV2DbReadable>(
&self,
bc_db: &BcDb,
cm_db: &CmDb,
current_block_meta: &BlockMetaV2,
) -> anyhow::Result<Vec<BlockMetaV2>> {
self.get_current_frame_(bc_db, cm_db)
}
fn get_current_ud<BcDb: BcV2DbReadable>(&self, bc_db: &BcDb) -> KvResult<Option<SourceAmount>> {
bc_db
.uds_reval()
.iter_rev(.., |it| it.values().map_ok(|v| v.0).next_res())
self.get_current_frame_(bc_db, current_block_meta)
}
fn get_txs_history_bc_received(
......
......@@ -18,8 +18,6 @@ use duniter_dbs::SourceAmountValV2;
use crate::*;
pub const MAX_FIRST_UTXOS: usize = 40;
#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
pub struct UtxoCursor {
pub block_number: BlockNumber,
......@@ -60,13 +58,6 @@ impl FromStr for UtxoCursor {
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Utxo {
pub amount: SourceAmount,
pub tx_hash: Hash,
pub output_index: u8,
}
#[derive(Debug, Default)]
pub struct UtxosWithSum {
pub utxos: Vec<(UtxoCursor, SourceAmount)>,
......@@ -180,12 +171,11 @@ impl DbsReaderImpl {
}
pub(super) fn first_scripts_utxos_(
&self,
amount_target_opt: Option<SourceAmount>,
first: usize,
scripts: &[WalletScriptV10],
) -> anyhow::Result<Vec<ArrayVec<[Utxo; MAX_FIRST_UTXOS]>>> {
Ok(scripts
.iter()
.map(|script| {
let iter = scripts.iter().map(|script| {
let (k_min, k_max) =
GvaUtxoIdDbV1::script_interval(Hash::compute(script.to_string().as_bytes()));
self.0.gva_utxos().iter(k_min..k_max, |it| {
......@@ -197,8 +187,22 @@ impl DbsReaderImpl {
})
.collect::<KvResult<_>>()
})
});
if let Some(amount_target) = amount_target_opt {
let mut sum = SourceAmount::ZERO;
Ok(iter
.take_while(|utxos_res: &KvResult<ArrayVec<[Utxo; MAX_FIRST_UTXOS]>>| {
if let Ok(utxos) = utxos_res {
sum = sum + utxos.iter().map(|utxo| utxo.amount).sum();
sum <= amount_target
} else {
true
}
})
.collect::<KvResult<Vec<_>>>()?)
} else {
Ok(iter.collect::<KvResult<Vec<_>>>()?)
}
}
}
......@@ -324,7 +328,7 @@ mod tests {
)?;
assert_eq!(
db_reader.first_scripts_utxos(2, &[script, script2])?,
db_reader.first_scripts_utxos(None, 2, &[script, script2])?,
vec![
[
Utxo {
......
......@@ -16,6 +16,7 @@ duniter-dbs = { path = "../../../duniter-dbs" }
duniter-bc-reader = { path = "../../../duniter-bc-reader" }
duniter-gva-db = { path = "../db" }
duniter-gva-dbs-reader = { path = "../dbs-reader" }
duniter-global = { path = "../../../duniter-global" }
duniter-mempools = { path = "../../../duniter-mempools" }
duniter-module = { path = "../../../duniter-module" }
fast-threadpool = "0.2.3"
......@@ -28,6 +29,7 @@ serde = { version = "1.0.105", features = ["derive"] }
[dev-dependencies]
duniter-dbs = { path = "../../../duniter-dbs", features = ["mem"] }
duniter-gva-dbs-reader = { path = "../dbs-reader", features = ["mock"] }
duniter-global = { path = "../../../duniter-global", features = ["mock"] }
mockall = "0.9.1"
serde_json = "1.0.53"
tokio = { version = "1.2", features = ["macros", "rt-multi-thread", "time"] }
......
......@@ -48,6 +48,8 @@ use crate::inputs_validators::TxCommentValidator;
use crate::pagination::Pagination;
use crate::scalars::{PkOrScriptGva, PubKeyGva};
#[cfg(test)]
use crate::tests::AsyncAccessor;
#[cfg(test)]
use crate::tests::DbsReaderImpl;
use async_graphql::connection::{Connection, Edge, EmptyFields};
use async_graphql::validators::{IntGreaterThan, IntRange, ListMaxLength, ListMinLength};
......@@ -61,6 +63,8 @@ use dubp::wallet::prelude::*;
use duniter_dbs::databases::txs_mp_v2::TxsMpV2DbReadable;
use duniter_dbs::prelude::*;
use duniter_dbs::{kv_typed::prelude::*, FileBackend};
#[cfg(not(test))]
use duniter_global::AsyncAccessor;
use duniter_gva_dbs_reader::pagination::PageInfo;
use duniter_gva_dbs_reader::DbsReader;
#[cfg(not(test))]
......@@ -84,18 +88,24 @@ pub struct ServerMetaData {
#[cfg(test)]
mod tests {
pub use duniter_global::{CurrentMeta, MockAsyncAccessor};
pub use duniter_gva_dbs_reader::MockDbsReader;
use super::*;
use fast_threadpool::ThreadPoolConfig;
pub type AsyncAccessor = duniter_dbs::kv_typed::prelude::Arc<MockAsyncAccessor>;
pub type DbsReaderImpl = duniter_dbs::kv_typed::prelude::Arc<MockDbsReader>;
pub(crate) fn create_schema(dbs_ops: MockDbsReader) -> KvResult<GvaSchema> {
pub(crate) fn create_schema(
mock_cm: MockAsyncAccessor,
dbs_ops: MockDbsReader,
) -> KvResult<GvaSchema> {
let dbs = SharedDbs::mem()?;
let threadpool = fast_threadpool::ThreadPool::start(ThreadPoolConfig::default(), dbs);
Ok(schema::build_schema_with_data(
schema::GvaSchemaData {
cm_accessor: Arc::new(mock_cm),
dbs_pool: threadpool.into_async_handler(),
dbs_reader: Arc::new(dbs_ops),
server_meta_data: ServerMetaData {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment