Commit 02a24c84 authored by Éloïs's avatar Éloïs
Browse files

[feat] gva: add subset bca

parent b5cd4abe
......@@ -25,6 +25,37 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"
[[package]]
name = "aes"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "884391ef1066acaa41e766ba8f596341b96e93ce34f9a43e7d24bf0a0eaf0561"
dependencies = [
"aes-soft",
"aesni",
"cipher",
]
[[package]]
name = "aes-soft"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be14c7498ea50828a38d0e24a765ed2effe92a705885b57d029cd67d45744072"
dependencies = [
"cipher",
"opaque-debug 0.3.0",
]
[[package]]
name = "aesni"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea2e11f5e94c2f7d386164cc2aa1f97823fed6f259e486940a71c174dd01b0ce"
dependencies = [
"cipher",
"opaque-debug 0.3.0",
]
[[package]]
name = "ahash"
version = "0.3.8"
......@@ -83,6 +114,21 @@ dependencies = [
"syn",
]
[[package]]
name = "async-bincode"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a31c08aa335b3ab414d29bdefe1f4353408abf93f3db1e3e2cc78d3ec4f0d43"
dependencies = [
"bincode",
"byteorder",
"bytes 1.0.1",
"futures-core",
"futures-sink",
"serde",
"tokio",
]
[[package]]
name = "async-channel"
version = "1.5.1"
......@@ -603,6 +649,15 @@ dependencies = [
"envmnt",
]
[[package]]
name = "cipher"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12f8e7987cbd042a63249497f41aed09f8e65add917ea6566effbc56578d6801"
dependencies = [
"generic-array 0.14.4",
]
[[package]]
name = "clap"
version = "2.33.3"
......@@ -1083,6 +1138,36 @@ dependencies = [
"smallvec",
]
[[package]]
name = "duniter-bca"
version = "0.1.0"
dependencies = [
"anyhow",
"async-bincode",
"bincode",
"dubp",
"duniter-bca-types",
"duniter-dbs",
"duniter-gva-db",
"duniter-gva-dbs-reader",
"fast-threadpool",
"futures",
"mockall",
"once_cell",
"smallvec",
"tokio",
"uninit",
]
[[package]]
name = "duniter-bca-types"
version = "0.1.0"
dependencies = [
"dubp",
"serde",
"smallvec",
]
[[package]]
name = "duniter-conf"
version = "0.1.0"
......@@ -1165,7 +1250,9 @@ dependencies = [
"async-graphql",
"async-mutex",
"async-trait",
"bytes 1.0.1",
"dubp",
"duniter-bca",
"duniter-conf",
"duniter-dbs",
"duniter-gva-db",
......@@ -1376,12 +1463,16 @@ version = "0.49.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0be04829b31b18bacf5317001366d807e5fbd02085ee6348508c1299b5bcaf6c"
dependencies = [
"aes",
"arrayvec",
"base64 0.13.0",
"blake3",
"bs58 0.4.0",
"byteorder",
"cryptoxide",
"ed25519-bip32",
"getrandom 0.2.2",
"once_cell",
"ring",
"serde",
"thiserror",
......@@ -1389,6 +1480,15 @@ dependencies = [
"zeroize",
]
[[package]]
name = "ed25519-bip32"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8827180a2b511141fbe49141e50b31a8d542465e0fb572f81f36feea2addfe92"
dependencies = [
"cryptoxide",
]
[[package]]
name = "either"
version = "1.6.1"
......
......@@ -42,6 +42,8 @@ members = [
"rust-libs/duniter-module",
"rust-libs/duniter-server",
"rust-libs/modules/gva",
"rust-libs/modules/gva/bca",
"rust-libs/modules/gva/bca/types",
"rust-libs/modules/gva/dbs-reader",
"rust-libs/modules/gva/gql",
"rust-libs/modules/gva/indexer",
......
......@@ -11,7 +11,9 @@ arrayvec = "0.5.1"
async-graphql = "2.2.0"
async-mutex = "1.4.0"
async-trait = "0.1.41"
bytes = "1.0"
dubp = { version = "0.49.0", features = ["duniter"] }
duniter-bca = { path = "./bca" }
duniter-conf = { path = "../../duniter-conf" }
duniter-dbs = { path = "../../duniter-dbs" }
duniter-gva-db = { path = "./db" }
......
[package]
name = "duniter-bca"
version = "0.1.0"
authors = ["librelois <elois@duniter.org>"]
license = "AGPL-3.0"
edition = "2018"
[dependencies]
anyhow = "1.0.33"
async-bincode = "0.6.1"
bincode = "1.3"
dubp = { version = "0.49.0", features = ["duniter"] }
duniter-bca-types = { path = "types", features = ["duniter"] }
duniter-dbs = { path = "../../../duniter-dbs" }
duniter-gva-db = { path = "../db" }
duniter-gva-dbs-reader = { path = "../dbs-reader" }
fast-threadpool = "0.2.3"
futures = "0.3.6"
once_cell = "1.5"
smallvec = { version = "1.4.0", features = ["serde", "write"] }
tokio = { version = "1.2", features = ["macros", "rt-multi-thread"] }
uninit = "0.4.0"
[dev-dependencies]
duniter-dbs = { path = "../../../duniter-dbs", features = ["mem"] }
tokio = { version = "1.2", features = ["macros", "rt-multi-thread", "time"] }
mockall = "0.8.0"
// 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/>.
mod members_count;
mod prepare_simple_payment;
use crate::*;
#[derive(Debug, PartialEq)]
pub(super) struct ExecReqTypeError(pub(super) String);
impl<E> From<E> for ExecReqTypeError
where
E: ToString,
{
fn from(e: E) -> Self {
Self(e.to_string())
}
}
pub(super) async fn execute_req_type(
bca_executor: &BcaExecutor,
req_type: BcaReqTypeV0,
_is_whitelisted: bool,
) -> Result<BcaRespTypeV0, ExecReqTypeError> {
match req_type {
BcaReqTypeV0::MembersCount => members_count::exec_req_members_count(bca_executor).await,
BcaReqTypeV0::PrepareSimplePayment(params) => {
prepare_simple_payment::exec_req_prepare_simple_payment(bca_executor, params).await
}
BcaReqTypeV0::Ping => Ok(BcaRespTypeV0::Pong),
}
}
// 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::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)
}
Ok(None) => BcaRespTypeV0::Error("no blockchain".to_owned()),
Err(e) => BcaRespTypeV0::Error(e.to_string()),
})
.await?)
}
// Copyright (C) 2020 Éloïs SANCHEZ.
//
// This program is free software current_block_number: (), current_block_hash: (), inputs: (), inputs_sum: (): 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::wallet::prelude::*;
use duniter_bca_types::prepare_payment::{PrepareSimplePayment, PrepareSimplePaymentResp};
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;
let dbs_reader = bca_executor.dbs_reader();
let (amount, current_block, (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 current_base = current_block.unit_base as i64;
if amount.base() > current_base {
Err("too long base".into())
} else {
while amount.base() < current_base {
amount = amount.increment_base();
}
Ok::<_, ExecReqTypeError>((
amount,
current_block,
dbs_reader.find_inputs(
&dbs.bc_db_ro,
&dbs.txs_mp_db,
amount,
&WalletScriptV10::single(WalletConditionV10::Sig(issuer)),
false,
)?,
))
}
} else {
Err("no blockchain".into())
}
})
.await??;
if inputs_sum < amount {
return Err("insufficient balance".into());
}
Ok(BcaRespTypeV0::PrepareSimplePayment(
PrepareSimplePaymentResp {
current_block_number: current_block.number,
current_block_hash: current_block.hash,
inputs,
inputs_sum,
},
))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::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>>()
.times(1)
.returning(|_| Ok(None));
let bca_executor = create_bca_executor(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),
},
)
.await;
assert_eq!(resp_res, Err(ExecReqTypeError("no blockchain".into())));
}
#[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>>()
.times(1)
.returning(|_| Ok(Some(BlockMetaV2::default())));
let bca_executor = create_bca_executor(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),
},
)
.await;
assert_eq!(resp_res, Err(ExecReqTypeError("too long base".into())));
}
#[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>>()
.times(1)
.returning(|_| Ok(Some(BlockMetaV2::default())));
dbs_reader
.expect_find_inputs::<BcV2DbRo<FileBackend>, TxsMpV2Db<FileBackend>>()
.times(1)
.returning(|_, _, _, _, _| Ok((vec![], SourceAmount::default())));
let bca_executor = create_bca_executor(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),
},
)
.await;
assert_eq!(
resp_res,
Err(ExecReqTypeError("insufficient balance".into()))
);
}
#[tokio::test]
async fn test_exec_req_prepare_simple_payment_ok() -> Result<(), ExecReqTypeError> {
let input = TransactionInputV10 {
amount: SourceAmount::with_base0(57),
id: SourceIdV10::Utxo(UtxoIdV10 {
tx_hash: Hash::default(),
output_index: 3,
}),
};
let mut dbs_reader = MockDbsReader::new();
dbs_reader
.expect_get_current_block_meta::<CmV1Db<MemSingleton>>()
.times(1)
.returning(|_| Ok(Some(BlockMetaV2::default())));
dbs_reader
.expect_find_inputs::<BcV2DbRo<FileBackend>, 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 resp = exec_req_prepare_simple_payment(
&bca_executor,
PrepareSimplePayment {
issuer: PublicKey::default(),
amount: SourceAmount::new(42, 0),
},
)
.await?;
assert_eq!(
resp,
BcaRespTypeV0::PrepareSimplePayment(PrepareSimplePaymentResp {
current_block_number: 0,
current_block_hash: Hash::default(),
inputs: vec![input],
inputs_sum: SourceAmount::with_base0(57),
})
);
Ok(())
}
}
// Copyright (C) 2020 Éloïs req_id: (), resp_type: ()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/>.
#![deny(
clippy::unwrap_used,
missing_copy_implementations,
trivial_casts,
trivial_numeric_casts,
unstable_features,
unused_import_braces
)]
mod exec_req_type;
mod utils;
const RESP_MIN_SIZE: usize = 64;
type RespBytes = SmallVec<[u8; RESP_MIN_SIZE]>;
use crate::exec_req_type::ExecReqTypeError;
use crate::utils::AsyncReader;
use async_bincode::AsyncBincodeReader;
use bincode::Options as _;
use duniter_bca_types::{BcaReq, BcaReqTypeV0, BcaResp, BcaRespTypeV0, BcaRespV0, ReqExecError};
use duniter_dbs::{FileBackend, SharedDbs};
use futures::{prelude::stream::FuturesUnordered, StreamExt, TryStream, TryStreamExt};
use once_cell::sync::OnceCell;
use smallvec::SmallVec;
use tokio::task::JoinError;
#[cfg(test)]
use crate::tests::DbsReader;
#[cfg(not(test))]
use duniter_gva_dbs_reader::DbsReader;
static BCA_EXECUTOR: OnceCell<BcaExecutor> = OnceCell::new();
pub fn set_bca_executor(
dbs_pool: fast_threadpool::ThreadPoolAsyncHandler<SharedDbs<FileBackend>>,
dbs_reader: DbsReader,
) {
BCA_EXECUTOR
.set(BcaExecutor {
dbs_pool,
dbs_reader,
})
.unwrap_or_else(|_| panic!("BCA_EXECUTOR already set !"))
}
#[cfg(not(test))]
pub async fn execute<R: 'static + futures::AsyncRead + Send + Unpin>(
query_body_reader: R,
is_whitelisted: bool,
) -> Vec<u8> {
unsafe {
BCA_EXECUTOR
.get_unchecked()
.execute(query_body_reader, is_whitelisted)
.await
}
}
#[derive(Clone)]
struct BcaExecutor {
dbs_pool: fast_threadpool::ThreadPoolAsyncHandler<SharedDbs<FileBackend>>,
dbs_reader: DbsReader,
}
use uninit::extension_traits::VecCapacity;
impl BcaExecutor {
pub async fn execute<R: futures::AsyncRead + Send + Unpin>(
&self,
query_body_reader: R,
is_whitelisted: bool,
) -> Vec<u8> {
let async_bincode_reader =
AsyncBincodeReader::<AsyncReader<R>, BcaReq>::from(AsyncReader(query_body_reader));
self.execute_inner(async_bincode_reader, is_whitelisted)
.await
.into_iter()
.fold(Vec::new(), |mut vec, elem| {
// Write resp len
let out = vec.reserve_uninit(4);
out.copy_from_slice(&u32::to_be_bytes(elem.len() as u32)[..]);
unsafe {
// # Safety
//
// - `.copy_from_slice()` contract guarantees initialization
// of `out`, which, in turn, from `reserve_uninit`'s contract,
// leads to the `vec` extra capacity having been initialized.
vec.set_len(vec.len() + 4);
}
// Write resp content
let out = vec.reserve_uninit(elem.len());
out.copy_from_slice(&elem[..]);
unsafe {
// # Safety
//
// - `.copy_from_slice()` contract guarantees initialization
// of `out`, which, in turn, from `reserve_uninit`'s contract,
// leads to the `vec` extra capacity having been initialized.
vec.set_len(vec.len() + elem.len());
}
vec
})
}
async fn execute_inner(
&self,
stream: impl TryStream<Ok = BcaReq, Error = bincode::Error>,
is_whitelisted: bool,
) -> Vec<RespBytes> {
match stream
.map_ok(|req| {
let self_clone = self.clone();
tokio::spawn(async move { self_clone.execute_req(req, is_whitelisted).await })
})
.try_collect::<FuturesUnordered<_>>()
.await
{
Ok(futures_unordered) => {
futures_unordered
.map(|req_res: Result<BcaResp, JoinError>| {
let resp = match req_res {
Ok(resp) => Ok(resp),
Err(e) => Err(if e.is_cancelled() {