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

chore: create duniter-core repository

parent 6cd5dd6d
Branches
Tags
No related merge requests found
Pipeline #11845 failed
Showing with 4313 additions and 2 deletions
# Vim swap files
*~
*.swp
*.swo
# vscode
.vscode
# rust binaries
bin/duniter
neon/native/index.node
target
# files generated by rust tests
test2.bin.gz
**/*.wot
stages:
- tests
- quality
.env:
image: registry.duniter.org/docker/rust/rust-x64-stable-ci:latest
tags:
- redshift
before_script:
- export PATH="$HOME/.cargo/bin:$PATH"
- rustup show && rustc --version && cargo --version
tests:
<<: *env
rules:
- if: $CI_COMMIT_REF_NAME =~ /^wip*$/
when: manual
- if: $CI_COMMIT_TAG
when: never
- if: '$CI_MERGE_REQUEST_ID ||$CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH =~ /^release/'
- when: manual
stage: tests
script:
- cargo test --all --all-features
coverage: '/<coverage>(\d+.\d+\%)<\/coverage>/'
fmt_and_clippy:
extends: .env
rules:
- if: $CI_COMMIT_TAG
when: never
- if: $CI_MERGE_REQUEST_ID
- when: on_success
stage: quality
script:
- cargo fmt -- --version
- cargo fmt -- --check
- cargo clippy -- -V
- cargo clippy --all --tests -- -D warnings --verbose
audit_dependencies:
extends: .rust_env
before_script:
- cargo deny -V
stage: quality
script:
- cargo deny --workspace check
[hooks]
pre-commit = "cargo fmt -- --check"
[logging]
verbose = true
# Contributing
For any addition of feature or modification of existing feature, please discuss it beforehand via an issue of this repository by tagging one or more maintainers.
## Commit Message Guidelines
We have very precise rules over how our git commit messages can be formatted. This leads to **more
readable messages** that are easy to follow when looking through the **project history**.
### Commit Message Format
Each commit message consists of a **header**, a **body** and a **footer**. The header has a special
format that includes a **type**, a **scope** and a **subject**:
```txt
<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>
```
The **header** is mandatory and the **scope** of the header is optional.
Any line of the commit message cannot be longer 100 characters! This allows the message to be easier
to read on GitHub as well as in various git tools.
The footer should contain a [closing reference to an issue](https://help.github.com/articles/closing-issues-via-commit-messages/) if any.
```txt
docs(changelog): update changelog to beta.5
```
```txt
fix(release): need to depend on latest rxjs and zone.js
The version in our package.json gets copied to the one we publish, and users need the latest of these.
```
### Revert
If the commit reverts a previous commit, it should begin with `revert: `, followed by the header of the reverted commit. In the body it should say: `This reverts commit <hash>.`, where the hash is the SHA of the commit being reverted.
### Type
Must be one of the following:
* **build**: Changes that affect the build system or external dependencies (example scopes: crypto, wot)
* **chore**: Modification of the repository architecture
* **ci**: Changes to our CI configuration files and scripts (example scopes: Github Actions, Gitlab CI)
* **docs**: Documentation only changes
* **feat**: Add a new feature
* **mod**: Modify an existing feature
* **fix**: A bug fix
* **perf**: A code change that improves performance
* **refactor**: A code change that neither fixes a bug nor adds a feature nor modify an existing feature
* **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
* **test**: Adding missing tests or correcting existing tests
### Subject
The subject contains a succinct description of the change:
* use the imperative, present tense: "change" not "changed" nor "changes"
* don't capitalize the first letter
* no dot (.) at the end
### Body
Just as in the **subject**, use the imperative, present tense: "change" not "changed" nor "changes".
The body should include the motivation for the change and contrast this with previous behavior.
### Footer
The footer should contain any information about **Breaking Changes** and is also the place to
reference issues that this commit **Closes**.
**Breaking Changes** should start with the word `BREAKING CHANGE:` with a space or two newlines. The rest of the commit message is then used for this.
Cargo.lock 0 → 100644
This diff is collapsed.
[package]
authors = ["elois <elois@duniter.org>"]
description = "Duniter core."
edition = "2018"
keywords = ["duniter"]
license = "AGPL-3.0"
name = "duniter-core"
repository = "https://git.duniter.org/nodes/rust/duniter-core"
version = "1.8.1"
[dependencies]
duniter-bc-reader = { path = "bc-reader" }
duniter-conf = { path = "conf" }
duniter-dbs = { path = "dbs" }
duniter-dbs-write-ops = { path = "dbs-write-ops" }
duniter-mempools = { path = "mempools" }
duniter-module = { path = "module" }
duniter-global = { path = "global" }
[dev-dependencies]
rusty-hook = "0.11.2"
[workspace]
members = [
"dubp-wot",
"bc-reader",
"conf",
"dbs",
"dbs-write-ops",
"mempools",
"module",
"global",
"tools/kv_typed"
]
[patch.crates-io]
#dubp = { git = "https://git.duniter.org/libs/dubp-rs-libs" }
#dubp = { path = "../dubp-rs-libs" }
#leveldb_minimal = { path = "../../../../rust/leveldb_minimal" }
LICENSE 0 → 100644
This diff is collapsed.
......@@ -3,7 +3,7 @@
[![pipeline status](https://git.duniter.org/duniter/duniter-core/badges/master/pipeline.svg)](https://git.duniter.org/libs/dubp-rs-libs/-/commits/master)
[![dependency status](https://deps.rs/repo/github/duniter/duniter-core/status.svg)](https://deps.rs/repo/github/duniter/duniter-core)
Set of Rust libraries that implement the core Duniter processing and expose everything needed by Duniter modules.
Set of Rust libraries that implement the core Duniter logic and expose everything needed by Duniter modules.
This repository includes (non-hexaustive list):
......@@ -16,6 +16,21 @@ This repository includes (non-hexaustive list):
## Duniter modules
The main Duniter modules are in the `duniter/modules' subgroup of [duniter gitlab](https://git.duniter.org/duniter/modules). Some modules may be located elsewhere, if their creator or maintainer has decided so.
The main Duniter modules are in the [nodes/rust/modules] subgroup of [duniter gitlab]. Some modules may be located elsewhere, if their creator or maintainer has decided so.
To implement your own module, you just need to define a Rust type that implements the `DuniterModule` trait.
## Other repositories
Duniter's code is separated into several git repositories:
* **[dubp-rs-libs]** contains the logic common to Duniter and its customers.
* **[duniter-core]** contains the core code of Duniter.
* The gitlab subgroup **[nodes/rust/modules]** contains the main Duniter modules code (gva, admin, etc).
* The **[duniter]** subgroup contains the "official" implementations of the "duniter-cli" and "duniter-desktop" programs with their default modules (also contains the historical implementation being migrated).
[duniter gitlab]: https://git.duniter.org
[dubp-rs-libs]: https://git.duniter.org/libs/dubp-rs-libs
[duniter-core]: https://git.duniter.org/nodes/rust/duniter-core
[nodes/rust/modules]: https://git.duniter.org/nodes/rust/modules
[duniter]: https://git.duniter.org/nodes/typescript/duniter
[package]
name = "duniter-bc-reader"
version = "0.1.0"
authors = ["elois <elois@duniter.org>"]
description = "Duniter DBs read operations"
repository = "https://git.duniter.org/nodes/typescript/duniter"
keywords = ["dubp", "duniter", "blockchain", "database"]
license = "AGPL-3.0"
edition = "2018"
[lib]
path = "src/lib.rs"
[dependencies]
anyhow = "1.0.34"
duniter-dbs = { path = "../dbs" }
dubp = { version = "0.51.0", features = ["duniter"] }
resiter = "0.4.0"
[dev-dependencies]
smallvec = { version = "1.4.0", features = ["serde", "write"] }
// 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/>.
#![deny(
clippy::unwrap_used,
missing_copy_implementations,
trivial_casts,
trivial_numeric_casts,
unstable_features,
unused_import_braces
)]
use dubp::crypto::hashs::Hash;
use duniter_dbs::{databases::bc_v2::BcV2DbReadable, kv_typed::prelude::*, HashKeyV2};
pub fn tx_exist<BcDb: BcV2DbReadable>(bc_db_ro: &BcDb, hash: Hash) -> KvResult<bool> {
bc_db_ro.txs_hashs().contains_key(&HashKeyV2(hash))
}
[package]
name = "duniter-conf"
version = "0.1.0"
authors = ["librelois <elois@duniter.org>"]
license = "AGPL-3.0"
edition = "2018"
[dependencies]
dubp = { version = "0.51.0", features = ["duniter"] }
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 std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use crate::*;
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GvaConf {
ip4: Option<Ipv4Addr>,
ip6: Option<Ipv6Addr>,
port: Option<u16>,
path: Option<String>,
subscriptions_path: Option<String>,
remote_host: Option<String>,
remote_port: Option<u16>,
remote_path: Option<String>,
remote_subscriptions_path: Option<String>,
remote_tls: Option<bool>,
whitelist: Option<Vec<IpAddr>>,
}
impl GvaConf {
pub fn get_ip4(&self) -> Ipv4Addr {
self.ip4.unwrap_or(Ipv4Addr::LOCALHOST)
}
pub fn get_ip6(&self) -> Option<Ipv6Addr> {
self.ip6
}
pub fn get_port(&self) -> u16 {
self.port.unwrap_or(30901)
}
pub fn get_path(&self) -> String {
if let Some(mut path) = self.path.clone() {
if path.starts_with('/') {
path.remove(0);
path
} else {
path
}
} else {
"localhost".to_owned()
}
}
pub fn get_subscriptions_path(&self) -> String {
if let Some(mut subscriptions_path) = self.subscriptions_path.clone() {
if subscriptions_path.starts_with('/') {
subscriptions_path.remove(0);
subscriptions_path
} else {
subscriptions_path
}
} else {
"localhost".to_owned()
}
}
pub fn get_remote_host(&self) -> String {
if let Some(ref remote_host) = self.remote_host {
remote_host.to_owned()
} else if let Some(ip6) = self.ip6 {
format!("{} [{}]", self.get_ip4(), ip6)
} else {
self.get_ip4().to_string()
}
}
pub fn get_remote_port(&self) -> u16 {
if let Some(remote_port) = self.remote_port {
remote_port
} else {
self.get_port()
}
}
pub fn get_remote_path(&self) -> String {
if let Some(ref remote_path) = self.remote_path {
remote_path.to_owned()
} else {
self.get_path()
}
}
pub fn get_remote_subscriptions_path(&self) -> String {
if let Some(ref remote_subscriptions_path) = self.remote_subscriptions_path {
remote_subscriptions_path.to_owned()
} else {
self.get_subscriptions_path()
}
}
pub fn get_remote_tls(&self) -> bool {
self.remote_tls.unwrap_or(false)
}
pub fn get_whitelist(&self) -> &[IpAddr] {
if let Some(ref whitelist) = self.whitelist {
whitelist
} else {
&[
IpAddr::V4(Ipv4Addr::LOCALHOST),
IpAddr::V6(Ipv6Addr::LOCALHOST),
]
}
}
}
// 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/>.
#![deny(
clippy::unwrap_used,
missing_copy_implementations,
trivial_casts,
trivial_numeric_casts,
unstable_features,
unused_import_braces
)]
pub mod gva_conf;
use crate::gva_conf::GvaConf;
use dubp::crypto::keys::ed25519::Ed25519KeyPair;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug)]
pub struct DuniterConf {
pub gva: Option<GvaConf>,
pub self_key_pair: Ed25519KeyPair,
pub txs_mempool_size: usize,
}
impl Default for DuniterConf {
fn default() -> Self {
DuniterConf {
gva: None,
self_key_pair: Ed25519KeyPair::generate_random().expect("fail to gen random keypair"),
txs_mempool_size: 0,
}
}
}
/// Duniter mode
#[derive(Clone, Copy, Debug)]
#[non_exhaustive]
pub enum DuniterMode {
Start,
Sync,
}
[package]
name = "duniter-dbs-write-ops"
version = "0.1.0"
authors = ["elois <elois@duniter.org>"]
description = "Duniter DBs write operations"
repository = "https://git.duniter.org/nodes/typescript/duniter"
keywords = ["dubp", "duniter", "blockchain", "database"]
license = "AGPL-3.0"
edition = "2018"
[lib]
path = "src/lib.rs"
[dependencies]
chrono = "0.4.19"
dubp = { version = "0.51.0", features = ["duniter"] }
duniter-dbs = { path = "../dbs" }
duniter-global = { path = "../global" }
fast-threadpool = "0.2.3"
flume = "0.10"
log = "0.4.11"
resiter = "0.4.0"
[dev-dependencies]
anyhow = "1.0.34"
duniter-dbs = { path = "../dbs", features = ["mem"] }
maplit = "1.0.2"
serde_json = "1.0.53"
[features]
default = ["sled_backend"]
explorer = ["duniter-dbs/explorer"]
leveldb_backend = ["duniter-dbs/leveldb_backend"]
sled_backend = ["duniter-dbs/sled_backend"]
// 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::*;
pub fn apply_block(
bc_db: &BcV2Db<FileBackend>,
block: Arc<DubpBlockV10>,
current_opt: Option<BlockMetaV2>,
dbs_pool: &fast_threadpool::ThreadPoolSyncHandler<SharedDbs<FileBackend>>,
global_sender: &flume::Sender<GlobalBackGroundTaskMsg>,
throw_chainability: bool,
) -> KvResult<BlockMetaV2> {
if let Some(current) = current_opt {
if block.number().0 == current.number + 1 {
apply_block_inner(bc_db, dbs_pool, block, global_sender)
} else if throw_chainability {
Err(KvError::Custom(
format!(
"block #{} not chainable on current #{}",
block.number().0,
current.number
)
.into(),
))
} else {
Ok(current)
}
} else if block.number() == BlockNumber(0) {
apply_block_inner(bc_db, dbs_pool, block, global_sender)
} else {
Err(KvError::Custom(
"Try to apply non genesis block on empty blockchain".into(),
))
}
}
#[inline(always)]
pub fn apply_chunk(
bc_db: &BcV2Db<FileBackend>,
current_opt: Option<BlockMetaV2>,
dbs_pool: &fast_threadpool::ThreadPoolSyncHandler<SharedDbs<FileBackend>>,
blocks: Arc<[DubpBlockV10]>,
global_sender: Option<&flume::Sender<GlobalBackGroundTaskMsg>>,
) -> KvResult<BlockMetaV2> {
verify_chunk_chainability(current_opt, &blocks)?;
apply_chunk_inner(bc_db, dbs_pool, blocks, global_sender)
}
fn verify_chunk_chainability(
current_opt: Option<BlockMetaV2>,
blocks: &[DubpBlockV10],
) -> KvResult<()> {
if let Some(mut current) = current_opt {
for block in blocks {
if block.number().0 == current.number + 1 {
current.number += 1;
} else {
return Err(KvError::Custom(
format!(
"block #{} not chainable on current #{}",
blocks[0].number().0,
current.number
)
.into(),
));
}
}
Ok(())
} else if blocks[0].number() == BlockNumber(0) {
let mut current_number = 0;
for block in &blocks[1..] {
if block.number().0 == current_number + 1 {
current_number += 1;
} else {
return Err(KvError::Custom(
format!(
"block #{} not chainable on current #{}",
block.number().0,
current_number
)
.into(),
));
}
}
Ok(())
} else {
Err(KvError::Custom(
"Try to apply non genesis block on empty blockchain".into(),
))
}
}
fn apply_block_inner(
bc_db: &BcV2Db<FileBackend>,
dbs_pool: &fast_threadpool::ThreadPoolSyncHandler<SharedDbs<FileBackend>>,
block: Arc<DubpBlockV10>,
global_sender: &flume::Sender<GlobalBackGroundTaskMsg>,
) -> KvResult<BlockMetaV2> {
let block_for_cm = Arc::clone(&block);
let block_for_txs_mp = Arc::clone(&block);
// Cm
crate::cm::update_current_meta(&block_for_cm, &global_sender);
//TxsMp
let txs_mp_handle = dbs_pool
.launch(move |dbs| {
crate::txs_mp::apply_block(block_for_txs_mp.transactions(), &dbs.txs_mp_db)?;
Ok::<_, KvError>(())
})
.expect("dbs pool disconnected");
// Bc
let new_current = crate::bc::apply_block(bc_db, &block)?;
txs_mp_handle.join().expect("dbs pool disconnected")?;
Ok(new_current)
}
fn apply_chunk_inner(
bc_db: &BcV2Db<FileBackend>,
dbs_pool: &fast_threadpool::ThreadPoolSyncHandler<SharedDbs<FileBackend>>,
blocks: Arc<[DubpBlockV10]>,
global_sender: Option<&flume::Sender<GlobalBackGroundTaskMsg>>,
) -> KvResult<BlockMetaV2> {
let blocks_len = blocks.len();
let blocks_for_txs_mp = Arc::clone(&blocks);
// Cm
if let Some(global_sender) = global_sender {
let chunk_len = blocks.len();
crate::cm::update_current_meta(&&blocks.deref()[chunk_len - 1], &global_sender);
}
//TxsMp
//log::info!("apply_chunk: launch txs_mp job...");
let txs_mp_handle = dbs_pool
.launch(move |dbs| {
for block in blocks_for_txs_mp.deref() {
crate::txs_mp::apply_block(block.transactions(), &dbs.txs_mp_db)?;
}
Ok::<_, KvError>(())
})
.expect("apply_chunk_inner:txs_mp: dbs pool disconnected");
// Bc
//log::info!("apply_chunk: launch bc job...");
for block in &blocks[..(blocks_len - 1)] {
crate::bc::apply_block(bc_db, block)?;
}
let current_block = crate::bc::apply_block(bc_db, &blocks[blocks_len - 1])?;
txs_mp_handle
.join()
.expect("txs_mp_recv: dbs pool disconnected")?;
//log::info!("apply_chunk: txs_mp job finish.");
Ok(current_block)
}
// 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 identities;
mod txs;
mod uds;
use crate::*;
use duniter_dbs::databases::bc_v2::BcV2DbWritable;
pub fn apply_block<B: Backend>(
bc_db: &duniter_dbs::databases::bc_v2::BcV2Db<B>,
block: &DubpBlockV10,
) -> KvResult<BlockMetaV2> {
//log::info!("apply_block #{}", block.number().0);
let block_meta = BlockMetaV2::from(block);
(
bc_db.blocks_meta_write(),
bc_db.identities_write(),
bc_db.txs_hashs_write(),
bc_db.uds_write(),
bc_db.uds_reval_write(),
bc_db.uids_index_write(),
bc_db.utxos_write(),
bc_db.consumed_utxos_write(),
)
.write(
|(
mut blocks_meta,
mut identities,
mut txs_hashs,
mut uds,
mut uds_reval,
mut uids_index,
mut utxos,
mut consumed_utxos,
)| {
blocks_meta.upsert(U32BE(block.number().0), block_meta);
identities::update_identities::<B>(&block, &mut identities)?;
for idty in block.identities() {
let pubkey = idty.issuers()[0];
let username = idty.username().to_owned();
uids_index.upsert(username, PubKeyValV2(pubkey));
}
if let Some(dividend) = block.dividend() {
uds::create_uds::<B>(
block.number(),
dividend,
&mut identities,
&mut uds,
&mut uds_reval,
)?;
}
txs::apply_txs::<B>(
block.number(),
block.transactions(),
&mut txs_hashs,
&mut uds,
&mut utxos,
&mut consumed_utxos,
)?;
Ok(())
},
)?;
if block_meta.number > ROLL_BACK_MAX {
prune_bc_db(bc_db, BlockNumber(block_meta.number))?;
}
Ok(block_meta)
}
fn prune_bc_db<B: Backend>(
bc_db: &duniter_dbs::databases::bc_v2::BcV2Db<B>,
current_block_number: BlockNumber,
) -> KvResult<()> {
bc_db
.consumed_utxos_write()
.remove(U32BE(current_block_number.0 - ROLL_BACK_MAX))?;
Ok(())
}
pub fn revert_block<B: Backend>(
bc_db: &duniter_dbs::databases::bc_v2::BcV2Db<B>,
block: &DubpBlockV10,
) -> KvResult<Option<BlockMetaV2>> {
(
bc_db.blocks_meta_write(),
bc_db.identities_write(),
bc_db.txs_hashs_write(),
bc_db.uds_write(),
bc_db.uds_reval_write(),
bc_db.uids_index_write(),
bc_db.utxos_write(),
bc_db.consumed_utxos_write(),
)
.write(
|(
mut blocks_meta,
mut identities,
mut txs_hashs,
mut uds,
mut uds_reval,
mut uids_index,
mut utxos,
mut consumed_utxos,
)| {
txs::revert_txs::<B>(
block.number(),
block.transactions(),
&mut txs_hashs,
&mut uds,
&mut utxos,
&mut consumed_utxos,
)?;
if block.dividend().is_some() {
uds::revert_uds::<B>(
block.number(),
&mut identities,
&mut uds,
&mut uds_reval,
)?;
}
identities::revert_identities::<B>(&block, &mut identities)?;
for idty in block.identities() {
let username = idty.username().to_owned();
uids_index.remove(username);
}
blocks_meta.remove(U32BE(block.number().0));
Ok(if block.number() == BlockNumber(0) {
None
} else {
blocks_meta.get(&U32BE(block.number().0 - 1))?
})
},
)
}
#[cfg(test)]
mod tests {
use super::*;
use dubp::{
crypto::keys::{ed25519::PublicKey, PublicKey as _},
documents::transaction::TransactionDocumentV10Stringified,
documents_parser::prelude::FromStringObject,
};
use duniter_dbs::{
databases::bc_v2::*, BlockUtxosV2Db, UtxoIdDbV2, WalletScriptWithSourceAmountV1Db,
};
use maplit::hashmap;
#[test]
fn test_bc_apply_block() -> anyhow::Result<()> {
let bc_db = BcV2Db::<Mem>::open(MemConf::default())?;
let s1 = WalletScriptV10::single_sig(PublicKey::from_base58(
"D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx",
)?);
let s2 = WalletScriptV10::single_sig(PublicKey::from_base58(
"4fHMTFBMo5sTQEc5p1CNWz28S4mnnqdUBmECq1zt4n2m",
)?);
let b0 = DubpBlockV10::from_string_object(&DubpBlockV10Stringified {
version: 10,
median_time: 5_243,
dividend: Some(1000),
joiners: vec!["D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx:FFeyrvYio9uYwY5aMcDGswZPNjGLrl8THn9l3EPKSNySD3SDSHjCljSfFEwb87sroyzJQoVzPwER0sW/cbZMDg==:0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855:0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855:elois".to_owned()],
inner_hash: Some("0000000A65A12DB95B3153BCD05DB4D5C30CC7F0B1292D9FFBC3DE67F72F6040".to_owned()),
signature: "7B0hvcfajE2G8nBLp0vLVaQcQdQIyli21Gu8F2l+nimKHRe+fUNi+MWd1e/u29BYZa+RZ1yxhbHIbFzytg7fAA==".to_owned(),
hash: Some("0000000000000000000000000000000000000000000000000000000000000000".to_owned()),
..Default::default()
})?;
apply_block(&bc_db, &b0)?;
assert_eq!(bc_db.blocks_meta().count()?, 1);
assert_eq!(bc_db.uds().count()?, 1);
assert_eq!(bc_db.utxos().count()?, 0);
assert_eq!(bc_db.consumed_utxos().count()?, 0);
let b1 = DubpBlockV10::from_string_object(&DubpBlockV10Stringified {
number: 1,
version: 10,
median_time: 5_245,
transactions: vec![TransactionDocumentV10Stringified {
currency: "test".to_owned(),
blockstamp: "0-0000000000000000000000000000000000000000000000000000000000000000".to_owned(),
locktime: 0,
issuers: vec!["D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx".to_owned()],
inputs: vec!["1000:0:D:D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx:0".to_owned()],
unlocks: vec![],
outputs: vec![
"600:0:SIG(4fHMTFBMo5sTQEc5p1CNWz28S4mnnqdUBmECq1zt4n2m)".to_owned(),
"400:0:SIG(D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx)".to_owned(),
],
comment: "".to_owned(),
signatures: vec![],
hash: Some("0000000000000000000000000000000000000000000000000000000000000000".to_owned()),
}],
inner_hash: Some("0000000A65A12DB95B3153BCD05DB4D5C30CC7F0B1292D9FFBC3DE67F72F6040".to_owned()),
signature: "7B0hvcfajE2G8nBLp0vLVaQcQdQIyli21Gu8F2l+nimKHRe+fUNi+MWd1e/u29BYZa+RZ1yxhbHIbFzytg7fAA==".to_owned(),
hash: Some("0000000000000000000000000000000000000000000000000000000000000000".to_owned()),
..Default::default()
})?;
apply_block(&bc_db, &b1)?;
assert_eq!(bc_db.blocks_meta().count()?, 2);
assert_eq!(bc_db.uds().count()?, 0);
assert_eq!(bc_db.utxos().count()?, 2);
assert_eq!(
bc_db
.utxos()
.iter(.., |it| it.collect::<KvResult<Vec<_>>>())?,
vec![
(
UtxoIdDbV2(Hash::default(), 0),
WalletScriptWithSourceAmountV1Db {
wallet_script: s2.clone(),
source_amount: SourceAmount::with_base0(600)
}
),
(
UtxoIdDbV2(Hash::default(), 1),
WalletScriptWithSourceAmountV1Db {
wallet_script: s1.clone(),
source_amount: SourceAmount::with_base0(400)
}
)
]
);
assert_eq!(bc_db.consumed_utxos().count()?, 0);
let b2 = DubpBlockV10::from_string_object(&DubpBlockV10Stringified {
number: 2,
version: 10,
median_time: 5_247,
transactions: vec![TransactionDocumentV10Stringified {
currency: "test".to_owned(),
blockstamp: "0-0000000000000000000000000000000000000000000000000000000000000000".to_owned(),
locktime: 0,
issuers: vec!["D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx".to_owned()],
inputs: vec!["400:0:T:0000000000000000000000000000000000000000000000000000000000000000:1".to_owned()],
unlocks: vec![],
outputs: vec![
"300:0:SIG(D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx)".to_owned(),
"100:0:SIG(4fHMTFBMo5sTQEc5p1CNWz28S4mnnqdUBmECq1zt4n2m)".to_owned(),
],
comment: "".to_owned(),
signatures: vec![],
hash: Some("0101010101010101010101010101010101010101010101010101010101010101".to_owned()),
}],
inner_hash: Some("0000000A65A12DB95B3153BCD05DB4D5C30CC7F0B1292D9FFBC3DE67F72F6040".to_owned()),
signature: "7B0hvcfajE2G8nBLp0vLVaQcQdQIyli21Gu8F2l+nimKHRe+fUNi+MWd1e/u29BYZa+RZ1yxhbHIbFzytg7fAA==".to_owned(),
hash: Some("0000000000000000000000000000000000000000000000000000000000000000".to_owned()),
..Default::default()
})?;
apply_block(&bc_db, &b2)?;
assert_eq!(bc_db.blocks_meta().count()?, 3);
assert_eq!(bc_db.uds().count()?, 0);
assert_eq!(bc_db.utxos().count()?, 3);
assert_eq!(bc_db.consumed_utxos().count()?, 1);
assert_eq!(
bc_db
.consumed_utxos()
.iter(.., |it| it.collect::<KvResult<Vec<_>>>())?,
vec![(
U32BE(2),
BlockUtxosV2Db(
hashmap![UtxoIdV10 { tx_hash: Hash::default(), output_index: 1 } => WalletScriptWithSourceAmountV1Db {
wallet_script: s1.clone(),
source_amount: SourceAmount::with_base0(400)
}]
)
)]
);
assert_eq!(
bc_db
.utxos()
.iter(.., |it| it.collect::<KvResult<Vec<_>>>())?,
vec![
(
UtxoIdDbV2(Hash::default(), 0),
WalletScriptWithSourceAmountV1Db {
wallet_script: s2.clone(),
source_amount: SourceAmount::with_base0(600)
}
),
(
UtxoIdDbV2(Hash([1; 32]), 0),
WalletScriptWithSourceAmountV1Db {
wallet_script: s1,
source_amount: SourceAmount::with_base0(300)
}
),
(
UtxoIdDbV2(Hash([1; 32]), 1),
WalletScriptWithSourceAmountV1Db {
wallet_script: s2,
source_amount: SourceAmount::with_base0(100)
}
)
]
);
Ok(())
}
}
// 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 duniter_dbs::databases::bc_v2::IdentitiesEvent;
use duniter_dbs::IdtyDbV2;
pub(crate) fn update_identities<B: Backend>(
block: &DubpBlockV10,
identities: &mut TxColRw<B::Col, IdentitiesEvent>,
) -> KvResult<()> {
for idty in block.identities() {
let pubkey = idty.issuers()[0];
let username = idty.username().to_owned();
identities.upsert(
PubKeyKeyV2(pubkey),
IdtyDbV2 {
is_member: true,
username,
},
)
}
for mb in block.joiners() {
let pubkey = mb.issuers()[0];
let username = mb.identity_username().to_owned();
identities.upsert(
PubKeyKeyV2(pubkey),
IdtyDbV2 {
is_member: true,
username,
},
)
}
for revo in block.revoked() {
let pubkey = revo.issuer;
if let Some(mut idty) = identities.get(&PubKeyKeyV2(pubkey))? {
idty.is_member = false;
identities.upsert(PubKeyKeyV2(pubkey), idty)
}
}
for pubkey in block.excluded().iter().copied() {
if let Some(mut idty) = identities.get(&PubKeyKeyV2(pubkey))? {
idty.is_member = false;
identities.upsert(PubKeyKeyV2(pubkey), idty)
}
}
Ok(())
}
pub(crate) fn revert_identities<B: Backend>(
block: &DubpBlockV10,
identities: &mut TxColRw<B::Col, IdentitiesEvent>,
) -> KvResult<()> {
for mb in block.joiners() {
let pubkey = mb.issuers()[0];
let username = mb.identity_username().to_owned();
identities.upsert(
PubKeyKeyV2(pubkey),
IdtyDbV2 {
is_member: false,
username,
},
)
}
for idty in block.identities() {
let pubkey = idty.issuers()[0];
identities.remove(PubKeyKeyV2(pubkey));
}
for revo in block.revoked() {
let pubkey = revo.issuer;
if let Some(mut idty) = identities.get(&PubKeyKeyV2(pubkey))? {
idty.is_member = true;
identities.upsert(PubKeyKeyV2(pubkey), idty)
}
}
for pubkey in block.excluded().iter().copied() {
if let Some(mut idty) = identities.get(&PubKeyKeyV2(pubkey))? {
idty.is_member = true;
identities.upsert(PubKeyKeyV2(pubkey), idty)
}
}
Ok(())
}
// 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 std::collections::HashMap;
use crate::*;
use dubp::documents::transaction::TransactionOutputV10;
use duniter_dbs::{
databases::bc_v2::{ConsumedUtxosEvent, TxsHashsEvent, UdsEvent, UtxosEvent},
BlockUtxosV2Db, UdIdV2, UtxoIdDbV2, WalletScriptWithSourceAmountV1Db,
};
pub(crate) fn apply_txs<B: Backend>(
block_number: BlockNumber,
block_txs: &[TransactionDocumentV10],
txs_hashs: &mut TxColRw<B::Col, TxsHashsEvent>,
uds: &mut TxColRw<B::Col, UdsEvent>,
utxos: &mut TxColRw<B::Col, UtxosEvent>,
consumed_utxos: &mut TxColRw<B::Col, ConsumedUtxosEvent>,
) -> KvResult<()> {
if !block_txs.is_empty() {
let mut block_consumed_utxos = HashMap::with_capacity(block_txs.len() * 3);
for tx in block_txs {
let tx_hash = tx.get_hash();
txs_hashs.upsert(HashKeyV2(tx_hash), ());
for input in tx.get_inputs() {
match input.id {
SourceIdV10::Ud(UdSourceIdV10 {
issuer,
block_number,
}) => {
uds.remove(UdIdV2(issuer, block_number));
}
SourceIdV10::Utxo(utxo_id) => {
let utxo_id_db = UtxoIdDbV2(utxo_id.tx_hash, utxo_id.output_index as u32);
if let Some(wallet_script_with_sa) = utxos.get(&utxo_id_db)? {
utxos.remove(utxo_id_db);
block_consumed_utxos.insert(utxo_id, wallet_script_with_sa);
} else {
return Err(KvError::Custom(
format!("db corrupted: not found utxo {:?}", utxo_id_db).into(),
));
}
}
}
}
for (output_index, TransactionOutputV10 { amount, conditions }) in
tx.get_outputs().iter().enumerate()
{
let utxo_id = UtxoIdDbV2(tx_hash, output_index as u32);
let wallet_script_with_sa = WalletScriptWithSourceAmountV1Db {
wallet_script: conditions.script.clone(),
source_amount: *amount,
};
utxos.upsert(utxo_id, wallet_script_with_sa);
}
}
if !block_consumed_utxos.is_empty() {
consumed_utxos.upsert(U32BE(block_number.0), BlockUtxosV2Db(block_consumed_utxos));
}
}
Ok(())
}
pub(crate) fn revert_txs<B: Backend>(
block_number: BlockNumber,
block_txs: &[TransactionDocumentV10],
txs_hashs: &mut TxColRw<B::Col, TxsHashsEvent>,
uds: &mut TxColRw<B::Col, UdsEvent>,
utxos: &mut TxColRw<B::Col, UtxosEvent>,
consumed_utxos: &mut TxColRw<B::Col, ConsumedUtxosEvent>,
) -> KvResult<()> {
for tx in block_txs {
let tx_hash = tx.get_hash();
txs_hashs.remove(HashKeyV2(tx_hash));
for input in tx.get_inputs() {
match input.id {
SourceIdV10::Ud(UdSourceIdV10 {
issuer,
block_number,
}) => {
uds.upsert(UdIdV2(issuer, block_number), ());
}
SourceIdV10::Utxo(utxo_id) => {
let utxo_id_db = UtxoIdDbV2(utxo_id.tx_hash, utxo_id.output_index as u32);
if let Some(block_utxos) = consumed_utxos.get(&U32BE(block_number.0))? {
if let Some(wallet_script_with_sa) = block_utxos.0.get(&utxo_id) {
utxos.upsert(utxo_id_db, wallet_script_with_sa.clone());
} else {
return Err(KvError::Custom(
format!("db corrupted: not found consumed utxos {}", utxo_id)
.into(),
));
}
} else {
return Err(KvError::Custom(
format!("db corrupted: not found consumed utxos {:?}", utxo_id_db)
.into(),
));
}
}
}
if let SourceIdV10::Ud(UdSourceIdV10 {
issuer,
block_number,
}) = input.id
{
uds.upsert(UdIdV2(issuer, block_number), ());
}
}
for output_index in 0..tx.get_outputs().len() {
let utxo_id = UtxoIdDbV2(tx_hash, output_index as u32);
utxos.remove(utxo_id);
}
}
consumed_utxos.remove(U32BE(block_number.0));
Ok(())
}
// 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 duniter_dbs::{
databases::bc_v2::{IdentitiesEvent, UdsEvent, UdsRevalEvent},
UdIdV2,
};
pub(crate) fn create_uds<B: Backend>(
block_number: BlockNumber,
dividend: SourceAmount,
identities: &mut TxColRw<B::Col, IdentitiesEvent>,
uds: &mut TxColRw<B::Col, UdsEvent>,
uds_reval: &mut TxColRw<B::Col, UdsRevalEvent>,
) -> KvResult<()> {
let previous_ud_amount = uds_reval
.iter_rev(.., |it| it.values().next_res())?
.unwrap_or(SourceAmountValV2(SourceAmount::ZERO));
if dividend > previous_ud_amount.0 {
uds_reval.upsert(U32BE(block_number.0), SourceAmountValV2(dividend));
}
let members = identities.iter(.., |it| {
it.filter_map_ok(|(pk, idty)| if idty.is_member { Some(pk.0) } else { None })
.collect::<KvResult<Vec<_>>>()
})?;
for member in members {
uds.upsert(UdIdV2(member, block_number), ());
}
Ok(())
}
pub(crate) fn revert_uds<B: Backend>(
block_number: BlockNumber,
identities: &mut TxColRw<B::Col, IdentitiesEvent>,
uds: &mut TxColRw<B::Col, UdsEvent>,
uds_reval: &mut TxColRw<B::Col, UdsRevalEvent>,
) -> KvResult<()> {
let previous_reval_block_number = uds_reval
.iter_rev(.., |it| it.keys().next_res())?
.expect("corrupted db")
.0;
if block_number.0 == previous_reval_block_number {
uds_reval.remove(U32BE(block_number.0));
}
let members = identities.iter(.., |it| {
it.filter_map_ok(|(pk, idty)| if idty.is_member { Some(pk.0) } else { None })
.collect::<KvResult<Vec<_>>>()
})?;
for member in members {
uds.remove(UdIdV2(member, block_number));
}
Ok(())
}
// 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::*;
#[inline(always)]
pub(crate) fn update_current_meta(
block: &DubpBlockV10,
global_sender: &flume::Sender<GlobalBackGroundTaskMsg>,
) {
let current_block_meta = block_to_block_meta(block);
global_sender
.send(GlobalBackGroundTaskMsg::NewCurrentBlock(current_block_meta))
.expect("global task disconnected");
}
fn block_to_block_meta(block: &DubpBlockV10) -> BlockMetaV2 {
BlockMetaV2 {
version: 10,
number: block.number().0,
hash: block.hash().0,
signature: block.signature(),
inner_hash: block.inner_hash(),
previous_hash: block.previous_hash(),
issuer: block.issuer(),
previous_issuer: dubp::crypto::keys::ed25519::PublicKey::default(),
time: block.local_time(),
pow_min: block.pow_min() as u32,
members_count: block.members_count() as u64,
issuers_count: block.issuers_count() as u32,
issuers_frame: block.issuers_frame() as u64,
issuers_frame_var: 0,
median_time: block.common_time(),
nonce: block.nonce(),
monetary_mass: block.monetary_mass(),
unit_base: block.unit_base() as u32,
dividend: block.dividend(),
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment