Skip to content

Commit

Permalink
feat: increase simulation likelihood of low probability events (#68)
Browse files Browse the repository at this point in the history
* feat: add self-transfers to simulation
* feat: add GenerateReceivingKeys, FlushToPublic, and SelfTransfer to sim
* feat: add zero-transaction to simulation
* feat: add simulation action
  • Loading branch information
bhgomes authored May 19, 2022
1 parent d49e321 commit 358f6e3
Show file tree
Hide file tree
Showing 13 changed files with 799 additions and 156 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ jobs:
steps:
- uses: actions/checkout@v3
- run: rustup update nightly && rustup default nightly
- run: RUSTDOCFLAGS="-D warnings --cfg doc_cfg" cargo doc --workspace --all-features --no-deps
- run: RUSTDOCFLAGS="-D warnings --cfg doc_cfg" cargo doc --workspace --all-features --no-deps --document-private-items
bench:
name: Bench (${{ matrix.os }})
strategy:
Expand Down
33 changes: 33 additions & 0 deletions .github/workflows/simulation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Simulation
on:
workflow_dispatch:
env:
CARGO_TERM_COLOR: always
RUSTFLAGS: -D warnings
RUST_BACKTRACE: full
jobs:
simulation:
name: Simulation (${{ matrix.os }})
strategy:
fail-fast: false
matrix:
agents:
- 1
- 2
- 3
- 10
- 100
rounds:
- 10
- 100
- 1000
- 10000
os:
- ubuntu-latest
channel:
- nightly
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- run: rustup update ${{ matrix.channel }} --no-self-update && rustup default ${{ matrix.channel }}
- run: cargo run --package manta-pay --all-features --release --bin simulation ${{ matrix.agents }} ${{ matrix.rounds }} 10 100000
3 changes: 1 addition & 2 deletions manta-accounting/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ std = ["manta-crypto/std", "manta-util/std"]
test = [
"futures",
"indexmap",
"manta-crypto/rand",
"parking_lot",
"rand/alloc",
"statrs"
]

Expand All @@ -62,7 +62,6 @@ indexmap = { version = "1.8.0", optional = true, default-features = false }
manta-crypto = { path = "../manta-crypto", default-features = false }
manta-util = { path = "../manta-util", default-features = false, features = ["alloc"] }
parking_lot = { version = "0.12.0", optional = true, default-features = false }
rand = { version = "0.8.4", optional = true, default-features = false }
rand_chacha = { version = "0.3.1", optional = true, default-features = false }
statrs = { version = "0.15.0", optional = true, default-features = false }

Expand Down
11 changes: 11 additions & 0 deletions manta-accounting/src/asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,17 @@ impl AssetId {
pub const fn into_bytes(self) -> [u8; Self::SIZE] {
self.0.to_le_bytes()
}

/// Samples an [`Asset`] by uniformly choosing between zero and `maximum` when selecting coins.
#[cfg(feature = "test")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "test")))]
#[inline]
pub fn sample_up_to<R>(self, maximum: AssetValue, rng: &mut R) -> Asset
where
R: CryptoRng + RngCore + ?Sized,
{
self.value(rng.gen_range(0..maximum.0))
}
}

impl From<AssetId> for [u8; AssetId::SIZE] {
Expand Down
10 changes: 10 additions & 0 deletions manta-accounting/src/transfer/canonical.rs
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,16 @@ where
}
}

/// Returns `true` if `self` is a [`Transaction`] which transfers zero value.
#[inline]
pub fn is_zero(&self) -> bool {
match self {
Self::Mint(asset) => asset.is_zero(),
Self::PrivateTransfer(asset, _) => asset.is_zero(),
Self::Reclaim(asset) => asset.is_zero(),
}
}

/// Returns a transaction summary given the asset `metadata`.
#[inline]
pub fn display<F>(&self, metadata: &AssetMetadata, f: F) -> String
Expand Down
119 changes: 95 additions & 24 deletions manta-accounting/src/wallet/balance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,21 +99,6 @@ impl BalanceState for AssetList {
}
}

/// Performs a withdraw on `balance` returning `false` if it would overflow.
#[inline]
fn withdraw(balance: Option<&mut AssetValue>, withdraw: AssetValue) -> bool {
match balance {
Some(balance) => {
*balance = match balance.checked_sub(withdraw) {
Some(balance) => balance,
_ => return false,
};
true
}
_ => false,
}
}

/// Adds implementation of [`BalanceState`] for a map type with the given `$entry` type.
macro_rules! impl_balance_state_map_body {
($entry:tt) => {
Expand All @@ -138,7 +123,18 @@ macro_rules! impl_balance_state_map_body {
#[inline]
fn withdraw(&mut self, asset: Asset) -> bool {
if !asset.is_zero() {
withdraw(self.get_mut(&asset.id), asset.value)
if let $entry::Occupied(mut entry) = self.entry(asset.id) {
let balance = entry.get_mut();
if let Some(next_balance) = balance.checked_sub(asset.value) {
if next_balance == 0 {
entry.remove();
} else {
*balance = next_balance;
}
return true;
}
}
false
} else {
true
}
Expand Down Expand Up @@ -205,22 +201,97 @@ pub mod test {
);
}

/// Tests valid withdrawals for an [`AssetList`] balance state.
#[test]
fn asset_list_valid_withdraw() {
assert_valid_withdraw(&mut AssetList::new(), &mut OsRng);
/// Asserts that a maximal withdraw that leaves the state with no value should delete its memory
/// for this process.
#[inline]
pub fn assert_full_withdraw_should_remove_entry<S, R>(rng: &mut R)
where
S: BalanceState,
for<'s> &'s S: IntoIterator,
for<'s> <&'s S as IntoIterator>::IntoIter: ExactSizeIterator,
R: CryptoRng + RngCore + ?Sized,
{
let mut state = S::default();
let asset = Asset::gen(rng);
let initial_length = state.into_iter().len();
state.deposit(asset);
assert_eq!(
initial_length + 1,
state.into_iter().len(),
"Length should have increased by one after depositing a new asset."
);
let balance = state.balance(asset.id);
state.withdraw(asset.id.with(balance));
assert_eq!(
state.balance(asset.id),
0,
"Balance in the removed AssetId should be zero."
);
assert_eq!(
initial_length,
state.into_iter().len(),
"Removed AssetId should remove its entry in the database."
);
}

/// Tests valid withdrawals for a [`BTreeMapBalanceState`] balance state.
#[test]
fn btree_map_valid_withdraw() {
assert_valid_withdraw(&mut BTreeMapBalanceState::new(), &mut OsRng);
/// Defines the tests across multiple different [`BalanceState`] types.
macro_rules! define_tests {
($((
$type:ty,
$doc:expr,
$valid_withdraw:ident,
$full_withdraw:ident
$(,)?)),*$(,)?) => {
$(
#[doc = "Tests valid withdrawals for an"]
#[doc = $doc]
#[doc = "balance state."]
#[test]
fn $valid_withdraw() {
let mut state = <$type>::default();
let mut rng = OsRng;
for _ in 0..0xFFFF {
assert_valid_withdraw(&mut state, &mut rng);
}
}

#[doc = "Tests that there are no empty entries in"]
#[doc = $doc]
#[doc = "with no value stored in them."]
#[test]
fn $full_withdraw() {
assert_full_withdraw_should_remove_entry::<$type, _>(&mut OsRng);
}
)*
}
}

define_tests!(
(
AssetList,
"[`AssetList`]",
asset_list_valid_withdraw,
asset_list_full_withdraw,
),
(
BTreeMapBalanceState,
"[`BTreeMapBalanceState`]",
btree_map_valid_withdraw,
btree_map_full_withdraw,
),
);

/// Tests valid withdrawals for a [`HashMapBalanceState`] balance state.
#[cfg(feature = "std")]
#[test]
fn hash_map_valid_withdraw() {
assert_valid_withdraw(&mut HashMapBalanceState::new(), &mut OsRng);
}

///
#[cfg(feature = "std")]
#[test]
fn hash_map_full_withdraw() {
assert_full_withdraw_should_remove_entry::<HashMapBalanceState, _>(&mut OsRng);
}
}
Loading

0 comments on commit 358f6e3

Please sign in to comment.