Skip to content
This repository was archived by the owner on May 20, 2025. It is now read-only.

Commit b2d5ddc

Browse files
Ledger workers
1 parent 02a3cb6 commit b2d5ddc

File tree

28 files changed

+2574
-8
lines changed

28 files changed

+2574
-8
lines changed

bee-api/bee-rest-api/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ keywords = ["iota", "bee", "framework", "node", "api"]
1111
homepage = "https://www.iota.org"
1212

1313
[dependencies]
14-
bee-ledger = { version = "0.2.0", path = "../../bee-ledger" }
14+
bee-ledger = { version = "0.3.0", path = "../../bee-ledger" }
1515
bee-message = { version = "0.1.3", path = "../../bee-message" }
1616
bee-protocol = { version = "0.1.0", path = "../../bee-protocol" }
1717

bee-ledger/CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1919
2020
### Security -->
2121

22-
## 0.3.0 - 2021-05-10
22+
## 0.3.0 - 2021-05-11
23+
24+
### Added
2325

2426
### Removed
2527

bee-ledger/Cargo.toml

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "bee-ledger"
3-
version = "0.2.0"
3+
version = "0.3.0"
44
authors = ["IOTA Stiftung"]
55
edition = "2018"
66
description = "All types and features required to compute and maintain the ledger state"
@@ -13,6 +13,42 @@ homepage = "https://www.iota.org"
1313
[dependencies]
1414
bee-common = { version = "0.4.1", path = "../bee-common/bee-common" }
1515
bee-message = { version = "0.1.3", path = "../bee-message" }
16+
bee-runtime = { version = "0.1.1-alpha", path = "../bee-runtime", optional = true }
17+
bee-storage = { version = "0.3.0", path = "../bee-storage/bee-storage", optional = true }
18+
bee-tangle = { version = "0.1.1", path = "../bee-tangle", optional = true }
19+
bee-ternary = { version = "0.4.2-alpha", path = "../bee-ternary", optional = true }
1620

17-
serde = { version = "1.0", optional = true }
21+
async-trait = { version = "0.1", optional = true }
22+
chrono = { version = "0.4", optional = true }
23+
digest = { version = "0.9", optional = true }
24+
futures = { version = "0.3", optional = true }
25+
hex = { version = "0.4", optional = true }
26+
iota-crypto = { version = "0.5.0", features = ["blake2b"], optional = true }
27+
log = { version = "0.4", optional = true }
28+
reqwest = { version = "0.11", features = ["stream"], optional = true }
29+
serde = { version = "1.0", features = ["derive" ], optional = true }
1830
thiserror = { version = "1.0" }
31+
tokio = { version = "1.4", features = ["sync"], optional = true }
32+
tokio-stream = { version = "0.1", optional = true }
33+
34+
[features]
35+
workers = [
36+
"bee-runtime",
37+
"bee-storage",
38+
"bee-tangle",
39+
"bee-ternary",
40+
"async-trait",
41+
"chrono",
42+
"digest",
43+
"futures",
44+
"hex",
45+
"iota-crypto",
46+
"log",
47+
"reqwest",
48+
"serde",
49+
"tokio",
50+
"tokio-stream"
51+
]
52+
53+
[dev-dependencies]
54+
rand = "0.8"

bee-ledger/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
//! A crate that contains all types and features required to compute and maintain the ledger state.
55
6-
#![deny(missing_docs, warnings)]
6+
//#![warn(missing_docs)]
77

88
pub mod types;
9+
#[cfg(feature = "workers")]
10+
pub mod workers;
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// Copyright 2020-2021 IOTA Stiftung
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use bee_message::MessageId;
5+
6+
use crypto::hashes::{Digest, Output};
7+
8+
use std::marker::PhantomData;
9+
10+
/// Leaf domain separation prefix.
11+
const LEAF_HASH_PREFIX: u8 = 0x00;
12+
/// Node domain separation prefix.
13+
const NODE_HASH_PREFIX: u8 = 0x01;
14+
15+
/// Computes the largest power of two inferior to `n`.
16+
fn largest_power_of_two(n: u32) -> usize {
17+
1 << (32 - n.leading_zeros() - 1)
18+
}
19+
20+
/// A Merkle hasher based on a digest function.
21+
pub(crate) struct MerkleHasher<D> {
22+
marker: PhantomData<D>,
23+
}
24+
25+
impl<D: Default + Digest> MerkleHasher<D> {
26+
/// Creates a new Merkle hasher.
27+
pub(crate) fn new() -> Self {
28+
Self { marker: PhantomData }
29+
}
30+
31+
/// Returns the digest of the empty hash.
32+
fn empty(&mut self) -> Output<D> {
33+
D::digest(&[])
34+
}
35+
36+
/// Returns the digest of a Merkle leaf.
37+
fn leaf(&mut self, message_id: MessageId) -> Output<D> {
38+
let mut hasher = D::default();
39+
40+
hasher.update([LEAF_HASH_PREFIX]);
41+
hasher.update(message_id);
42+
hasher.finalize()
43+
}
44+
45+
/// Returns the digest of a Merkle node.
46+
fn node(&mut self, message_ids: &[MessageId]) -> Output<D> {
47+
let mut hasher = D::default();
48+
let (left, right) = message_ids.split_at(largest_power_of_two(message_ids.len() as u32 - 1));
49+
50+
hasher.update([NODE_HASH_PREFIX]);
51+
hasher.update(self.digest_inner(left));
52+
hasher.update(self.digest_inner(right));
53+
hasher.finalize()
54+
}
55+
56+
/// Returns the digest of a list of hashes as an `Output<D>`.
57+
fn digest_inner(&mut self, message_ids: &[MessageId]) -> Output<D> {
58+
match message_ids.len() {
59+
0 => self.empty(),
60+
1 => self.leaf(message_ids[0]),
61+
_ => self.node(message_ids),
62+
}
63+
}
64+
65+
/// Returns the digest of a list of hashes as a `Vec<u8>`.
66+
pub(crate) fn digest(&mut self, message_ids: &[MessageId]) -> Vec<u8> {
67+
self.digest_inner(message_ids).to_vec()
68+
}
69+
}
70+
71+
#[cfg(test)]
72+
mod tests {
73+
74+
use super::*;
75+
76+
use crypto::hashes::blake2b::Blake2b256;
77+
78+
use std::str::FromStr;
79+
80+
#[test]
81+
fn tree() {
82+
let mut hashes = Vec::new();
83+
84+
for hash in [
85+
"52fdfc072182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c649",
86+
"81855ad8681d0d86d1e91e00167939cb6694d2c422acd208a0072939487f6999",
87+
"eb9d18a44784045d87f3c67cf22746e995af5a25367951baa2ff6cd471c483f1",
88+
"5fb90badb37c5821b6d95526a41a9504680b4e7c8b763a1b1d49d4955c848621",
89+
"6325253fec738dd7a9e28bf921119c160f0702448615bbda08313f6a8eb668d2",
90+
"0bf5059875921e668a5bdf2c7fc4844592d2572bcd0668d2d6c52f5054e2d083",
91+
"6bf84c7174cb7476364cc3dbd968b0f7172ed85794bb358b0c3b525da1786f9f",
92+
]
93+
.iter()
94+
{
95+
hashes.push(MessageId::from_str(hash).unwrap());
96+
}
97+
98+
let hash = MerkleHasher::<Blake2b256>::new().digest(&hashes);
99+
100+
assert_eq!(
101+
hex::encode(hash),
102+
"bf67ce7ba23e8c0951b5abaec4f5524360d2c26d971ff226d3359fa70cdb0beb"
103+
)
104+
}
105+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright 2020-2021 IOTA Stiftung
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use crate::types::{BalanceDiffs, ConsumedOutput, CreatedOutput};
5+
6+
use bee_ledger_types::types::ConflictReason;
7+
use bee_message::{milestone::MilestoneIndex, output::OutputId, MessageId};
8+
9+
use std::collections::HashMap;
10+
11+
/// White flag metadata of a milestone confirmation.
12+
#[derive(Default)]
13+
pub struct WhiteFlagMetadata {
14+
/// Index of the confirmed milestone.
15+
pub(crate) index: MilestoneIndex,
16+
/// The number of messages which were referenced by the confirmed milestone.
17+
pub(crate) referenced_messages: usize,
18+
/// The messages which were excluded because they did not include a transaction.
19+
pub(crate) excluded_no_transaction_messages: Vec<MessageId>,
20+
/// The messages which were excluded because they were conflicting with the ledger state.
21+
pub(crate) excluded_conflicting_messages: Vec<(MessageId, ConflictReason)>,
22+
// The messages which mutate the ledger in the order in which they were applied.
23+
pub(crate) included_messages: Vec<MessageId>,
24+
/// The outputs created within the confirmed milestone.
25+
pub(crate) created_outputs: HashMap<OutputId, CreatedOutput>,
26+
/// The outputs consumed within the confirmed milestone.
27+
pub(crate) consumed_outputs: HashMap<OutputId, (CreatedOutput, ConsumedOutput)>,
28+
/// The balance diffs occurring within the confirmed milestone.
29+
pub(crate) balance_diffs: BalanceDiffs,
30+
/// The merkle proof of the milestone.
31+
pub(crate) merkle_proof: Vec<u8>,
32+
}
33+
34+
impl WhiteFlagMetadata {
35+
/// Creates a new `WhiteFlagMetadata`.
36+
pub fn new(index: MilestoneIndex) -> WhiteFlagMetadata {
37+
WhiteFlagMetadata {
38+
index,
39+
..Self::default()
40+
}
41+
}
42+
43+
/// Returns the merkle proof of a `WhiteFlagMetadata`.
44+
pub fn merkle_proof(&self) -> &[u8] {
45+
&self.merkle_proof
46+
}
47+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Copyright 2020-2021 IOTA Stiftung
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
pub(crate) mod merkle_hasher;
5+
pub(crate) mod metadata;
6+
pub(crate) mod state;
7+
pub(crate) mod white_flag;
8+
pub(crate) mod worker;
9+
10+
pub use metadata::WhiteFlagMetadata;
11+
pub use white_flag::white_flag;
12+
pub use worker::{ConsensusWorker, ConsensusWorkerEvent};
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// Copyright 2020-2021 IOTA Stiftung
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use crate::{
5+
types::{Balance, Unspent},
6+
workers::{
7+
error::Error,
8+
storage::{self, StorageBackend},
9+
},
10+
};
11+
12+
use bee_message::{
13+
address::Address,
14+
constants::IOTA_SUPPLY,
15+
output::{self, dust_outputs_max},
16+
};
17+
use bee_storage::access::AsStream;
18+
19+
use futures::StreamExt;
20+
21+
async fn validate_ledger_unspent_state<B: StorageBackend>(storage: &B, treasury: u64) -> Result<(), Error> {
22+
let mut supply: u64 = 0;
23+
let mut stream = AsStream::<Unspent, ()>::stream(storage)
24+
.await
25+
.map_err(|e| Error::Storage(Box::new(e)))?;
26+
27+
while let Some((output_id, _)) = stream.next().await {
28+
let output = storage::fetch_output(storage, &*output_id)
29+
.await?
30+
.ok_or(Error::MissingUnspentOutput(output_id))?;
31+
32+
let amount = match output.inner() {
33+
output::Output::SignatureLockedSingle(output) => output.amount(),
34+
output::Output::SignatureLockedDustAllowance(output) => output.amount(),
35+
output => return Err(Error::UnsupportedOutputKind(output.kind())),
36+
};
37+
38+
supply = supply
39+
.checked_add(amount)
40+
.ok_or_else(|| Error::LedgerStateOverflow(supply as u128 + amount as u128))?;
41+
}
42+
43+
if supply
44+
.checked_add(treasury)
45+
.ok_or_else(|| Error::LedgerStateOverflow(supply as u128 + treasury as u128))?
46+
!= IOTA_SUPPLY
47+
{
48+
return Err(Error::InvalidLedgerUnspentState(supply));
49+
}
50+
51+
Ok(())
52+
}
53+
54+
async fn validate_ledger_balance_state<B: StorageBackend>(storage: &B, treasury: u64) -> Result<(), Error> {
55+
let mut supply: u64 = 0;
56+
let mut stream = AsStream::<Address, Balance>::stream(storage)
57+
.await
58+
.map_err(|e| Error::Storage(Box::new(e)))?;
59+
60+
while let Some((address, balance)) = stream.next().await {
61+
if balance.dust_outputs() > dust_outputs_max(balance.dust_allowance()) {
62+
return Err(Error::InvalidLedgerDustState(address, balance));
63+
}
64+
supply = supply
65+
.checked_add(balance.amount())
66+
.ok_or_else(|| Error::LedgerStateOverflow(supply as u128 + balance.amount() as u128))?;
67+
}
68+
69+
if supply
70+
.checked_add(treasury)
71+
.ok_or_else(|| Error::LedgerStateOverflow(supply as u128 + treasury as u128))?
72+
!= IOTA_SUPPLY
73+
{
74+
return Err(Error::InvalidLedgerBalanceState(supply));
75+
}
76+
77+
Ok(())
78+
}
79+
80+
pub(crate) async fn validate_ledger_state<B: StorageBackend>(storage: &B) -> Result<(), Error> {
81+
let treasury = storage::fetch_unspent_treasury_output(storage).await?.inner().amount();
82+
83+
validate_ledger_unspent_state(storage, treasury).await?;
84+
validate_ledger_balance_state(storage, treasury).await
85+
}

0 commit comments

Comments
 (0)