diff --git a/.github/workflows/auto_merge_prs.yml b/.github/workflows/auto_merge_prs.yml new file mode 100644 index 0000000..5116a1d --- /dev/null +++ b/.github/workflows/auto_merge_prs.yml @@ -0,0 +1,38 @@ +# auto merge workflow. +# +# Auto merge PR if commit msg begins with `chore(release):`, +# or if it has been raised by Dependabot. +# Uses https://github.com/ridedott/merge-me-action. + +name: Merge Version Change and Dependabot PRs automatically + +on: pull_request + +jobs: + merge: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: '0' + + - name: get commit message + run: | + commitmsg=$(git log --format=%s -n 1 ${{ github.event.pull_request.head.sha }}) + echo "commitmsg=${commitmsg}" >> $GITHUB_ENV + - name: show commit message + run : echo $commitmsg + + - name: Merge Version change PR + if: startsWith( env.commitmsg, 'chore(release):') + uses: ridedott/merge-me-action@81667e6ae186ddbe6d3c3186d27d91afa7475e2c + with: + GITHUB_LOGIN: dirvine + GITHUB_TOKEN: ${{ secrets.MERGE_BUMP_BRANCH_TOKEN }} + MERGE_METHOD: REBASE + + - name: Dependabot Merge + uses: ridedott/merge-me-action@v2 + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + MERGE_METHOD: REBASE \ No newline at end of file diff --git a/src/builder.rs b/src/builder.rs index 669372f..909beb8 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -8,7 +8,7 @@ use crate::{ transaction::{DbcTransaction, Output}, - DbcId, DerivationIndex, DerivedKey, Input, PublicAddress, Spend, + DbcId, DerivationIndex, DerivedKey, FeeOutput, Input, PublicAddress, Spend, }; use crate::{Dbc, DbcCiphers, Error, Hash, Result, SignedSpend, Token}; #[cfg(feature = "serde")] @@ -24,6 +24,7 @@ pub type InputSrcTx = DbcTransaction; pub struct TransactionBuilder { inputs: Vec, outputs: Vec, + fee: FeeOutput, input_details: BTreeMap, output_details: BTreeMap, } @@ -100,6 +101,12 @@ impl TransactionBuilder { self } + /// Sets the given fee output. + pub fn set_fee_output(mut self, output: FeeOutput) -> Self { + self.fee = output; + self + } + /// Get a list of input ids. pub fn input_ids(&self) -> Vec { self.inputs.iter().map(|i| i.dbc_id()).collect() @@ -113,7 +120,12 @@ impl TransactionBuilder { /// Get sum of output Tokens. pub fn outputs_tokens_sum(&self) -> Token { - let amount = self.outputs.iter().map(|o| o.token.as_nano()).sum(); + let amount = self + .outputs + .iter() + .map(|o| o.token.as_nano()) + .chain(std::iter::once(self.fee.token.as_nano())) + .sum(); Token::from_nano(amount) } @@ -132,6 +144,7 @@ impl TransactionBuilder { let spent_tx = DbcTransaction { inputs: self.inputs.clone(), outputs: self.outputs.clone(), + fee: self.fee.clone(), }; let signed_spends: BTreeSet<_> = self .inputs diff --git a/src/dbc.rs b/src/dbc.rs index 4089f3b..1ad8fc9 100644 --- a/src/dbc.rs +++ b/src/dbc.rs @@ -8,7 +8,7 @@ use crate::{ dbc_id::PublicAddress, transaction::DbcTransaction, DbcCiphers, DbcId, DerivationIndex, - DerivedKey, Error, Hash, MainKey, Result, SignedSpend, Token, + DerivedKey, Error, FeeOutput, Hash, MainKey, Result, SignedSpend, Token, }; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -96,6 +96,11 @@ impl Dbc { self.ciphers.derivation_index(main_key) } + /// Return the fee output used in the source transaction + pub fn fee_output(&self) -> &FeeOutput { + &self.src_tx.fee + } + /// Return the reason why this Dbc was spent. /// Will be the default Hash (empty) if reason is none. pub fn reason(&self) -> Hash { @@ -194,7 +199,7 @@ pub(crate) mod tests { mock, rand::{CryptoRng, RngCore}, transaction::Output, - Hash, Token, + FeeOutput, Hash, Token, }; use blsttc::{PublicKey, SecretKey}; use std::convert::TryInto; @@ -209,6 +214,7 @@ pub(crate) mod tests { let tx = DbcTransaction { inputs: vec![], outputs: vec![Output::new(derived_key.dbc_id(), amount)], + fee: FeeOutput::new(Hash::default(), 3_500, Hash::default()), }; let ciphers = DbcCiphers::from((&main_key.public_address(), &derivation_index)); let dbc = Dbc { @@ -222,6 +228,10 @@ pub(crate) mod tests { let dbc = Dbc::from_hex(&hex)?; assert_eq!(dbc.token()?.as_nano(), 1_530_000_000); + + let fee_amount = dbc.fee_output().token; + assert_eq!(fee_amount, Token::from_nano(3_500)); + Ok(()) } @@ -235,6 +245,7 @@ pub(crate) mod tests { let tx = DbcTransaction { inputs: vec![], outputs: vec![Output::new(derived_key.dbc_id(), amount)], + fee: FeeOutput::new(Hash::default(), 2_500, Hash::default()), }; let ciphers = DbcCiphers::from((&main_key.public_address(), &derivation_index)); let dbc = Dbc { @@ -249,6 +260,9 @@ pub(crate) mod tests { assert_eq!(dbc.token()?, dbc_from_hex.token()?); + let fee_amount = dbc.fee_output().token; + assert_eq!(fee_amount, Token::from_nano(2_500)); + Ok(()) } @@ -286,6 +300,7 @@ pub(crate) mod tests { let tx = DbcTransaction { inputs: vec![], outputs: vec![Output::new(derived_key.dbc_id(), amount)], + fee: FeeOutput::default(), }; let ciphers = DbcCiphers::from((&main_key.public_address(), &derivation_index)); diff --git a/src/fee_output.rs b/src/fee_output.rs new file mode 100644 index 0000000..776933e --- /dev/null +++ b/src/fee_output.rs @@ -0,0 +1,56 @@ +// Copyright (c) 2023, MaidSafe. +// All rights reserved. +// +// This SAFE Network Software is licensed under the BSD-3-Clause license. +// Please see the LICENSE file for more details. + +use crate::{Hash, Token}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct FeeOutput { + /// The id is expected (in order to be accepted by the network) to be built from: hash(root_hash + inputs' ids). + /// This requirement makes it possible for this output to be used as an input in a rewards/farming + /// claiming Tx, by making its spend location deterministic, analogous to how any other output + /// is spent using its id to determine the location to store the signed spend. + pub id: Hash, + /// Amount being paid as storage fee to the network. + pub token: Token, + /// The root hash of the proof's Merkletree corresponding to the content being paid for. + pub root_hash: Hash, +} + +impl Default for FeeOutput { + fn default() -> Self { + Self { + id: Hash::default(), + token: Token::zero(), + root_hash: Hash::default(), + } + } +} + +impl FeeOutput { + pub fn new(id: Hash, amount: u64, root_hash: Hash) -> Self { + Self { + id, + token: Token::from_nano(amount), + root_hash, + } + } + + pub fn is_free(&self) -> bool { + self.token.is_zero() + } + + pub fn to_bytes(&self) -> Vec { + let mut v = Vec::::new(); + v.extend(self.id.slice()); + v.extend(self.token.to_bytes()); + v.extend(self.root_hash.slice()); + v + } +} diff --git a/src/lib.rs b/src/lib.rs index 7eeaba9..a48b7b8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,7 @@ mod dbc; mod dbc_ciphers; mod dbc_id; mod error; +mod fee_output; mod signed_spend; mod spentbook; mod token; @@ -27,6 +28,7 @@ pub use crate::{ dbc_ciphers::DbcCiphers, dbc_id::{random_derivation_index, DbcId, DerivationIndex, DerivedKey, MainKey, PublicAddress}, error::{Error, Result}, + fee_output::FeeOutput, signed_spend::{SignedSpend, Spend}, token::Token, transaction::{DbcTransaction, Input, Output}, diff --git a/src/mock/genesis_material.rs b/src/mock/genesis_material.rs index f3074cf..c559117 100644 --- a/src/mock/genesis_material.rs +++ b/src/mock/genesis_material.rs @@ -8,7 +8,7 @@ use crate::{ builder::InputSrcTx, transaction::Output, DbcId, DbcTransaction, DerivationIndex, DerivedKey, - Input, MainKey, + FeeOutput, Input, MainKey, }; use blsttc::IntoFr; @@ -51,12 +51,10 @@ impl Default for GenesisMaterial { let genesis_tx = DbcTransaction { inputs: vec![input], outputs: vec![output], + fee: FeeOutput::default(), }; - let input_src_tx = DbcTransaction { - inputs: vec![], - outputs: vec![], - }; + let input_src_tx = DbcTransaction::empty(); Self { input_dbc_id: input_derived_key.dbc_id(), // the id of the fictional dbc being reissued to genesis dbc diff --git a/src/token.rs b/src/token.rs index 4dc89ea..1d58e2b 100644 --- a/src/token.rs +++ b/src/token.rs @@ -31,6 +31,11 @@ impl Token { Self(0) } + /// Returns whether it's a representation of zero Token. + pub const fn is_zero(&self) -> bool { + self.0 == 0 + } + /// New value from a number of nano tokens. pub const fn from_nano(value: u64) -> Self { Self(value) diff --git a/src/transaction.rs b/src/transaction.rs index bfca8be..96027bb 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -4,7 +4,7 @@ // This SAFE Network Software is licensed under the BSD-3-Clause license. // Please see the LICENSE file for more details. -use crate::{DbcId, SignedSpend, Token}; +use crate::{DbcId, FeeOutput, SignedSpend, Token}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use std::{cmp::Ordering, collections::BTreeSet}; @@ -73,6 +73,7 @@ impl Output { pub struct DbcTransaction { pub inputs: Vec, pub outputs: Vec, + pub fee: FeeOutput, } impl PartialEq for DbcTransaction { @@ -96,6 +97,14 @@ impl Ord for DbcTransaction { } impl DbcTransaction { + pub fn empty() -> Self { + Self { + inputs: vec![], + outputs: vec![], + fee: FeeOutput::default(), + } + } + pub fn to_bytes(&self) -> Vec { let mut v: Vec = Default::default(); v.extend("inputs".as_bytes()); @@ -106,6 +115,8 @@ impl DbcTransaction { for o in self.outputs.iter() { v.extend(&o.to_bytes()); } + v.extend("fee".as_bytes()); + v.extend(&self.fee.to_bytes()); v.extend("end".as_bytes()); v } @@ -145,6 +156,7 @@ impl DbcTransaction { .outputs .iter() .map(|o| o.token) + .chain(std::iter::once(self.fee.token)) .try_fold(0, |acc: u64, o| { acc.checked_add(o.as_nano()).ok_or(Error::NumericOverflow) })?;