Skip to content

Commit

Permalink
Exchange pallet benchmarks (paritytech#158)
Browse files Browse the repository at this point in the history
* exchange benchmarks: framework

* updated comment about tx size

Co-authored-by: Tomasz Drwięga <tomasz@parity.io>
  • Loading branch information
svyatonik and tomusdrw authored Jul 16, 2020
1 parent 0b6d8ed commit fda64c8
Show file tree
Hide file tree
Showing 10 changed files with 281 additions and 45 deletions.
15 changes: 14 additions & 1 deletion bin/node/runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ version = "1.3.1"
default-features = false
features = ["derive"]

[dependencies.libsecp256k1]
optional = true
version = "0.3.4"
default-features = false
features = ["hmac"]

[dependencies.serde]
version = "1.0.114"
optional = true
Expand Down Expand Up @@ -200,10 +206,15 @@ tag = 'v2.0.0-rc4'
default-features = false
git = "https://github.com/paritytech/substrate/"

[dev-dependencies.libsecp256k1]
version = "0.3.4"
default-features = false
features = ["hmac"]

[dev-dependencies.sp-bridge-eth-poa]
version = "0.1.0"
default-features = false
features = ["test-helpers"]
features = ["std", "test-helpers"]
path = "../../../primitives/ethereum-poa"

[build-dependencies.wasm-builder-runner]
Expand Down Expand Up @@ -250,7 +261,9 @@ runtime-benchmarks = [
"frame-benchmarking",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"libsecp256k1",
"pallet-bridge-currency-exchange/runtime-benchmarks",
"pallet-bridge-eth-poa/runtime-benchmarks",
"sp-bridge-eth-poa/test-helpers",
"sp-runtime/runtime-benchmarks",
]
82 changes: 55 additions & 27 deletions bin/node/runtime/src/exchange.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,57 @@ impl MaybeLockFundsTransaction for EthTransaction {
}
}

/// Prepares everything required to bench claim of funds locked by given transaction.
#[cfg(feature = "runtime-benchmarks")]
pub(crate) fn prepare_environment_for_claim<T: pallet_bridge_eth_poa::Trait>(
transactions: &[RawTransaction],
) -> sp_bridge_eth_poa::H256 {
use pallet_bridge_eth_poa::{
test_utils::{insert_header, validator_utils::validator, HeaderBuilder},
BridgeStorage, Storage,
};
use sp_bridge_eth_poa::compute_merkle_root;

let mut storage = BridgeStorage::<T>::new();
let header = HeaderBuilder::with_parent_number_on_runtime::<T>(0)
.with_transactions_root(compute_merkle_root(transactions.iter()))
.sign_by(&validator(0));
let header_id = header.compute_id();
insert_header(&mut storage, header);
storage.finalize_and_prune_headers(Some(header_id), 0);

header_id.hash
}

/// Prepare signed ethereum lock-funds transaction.
#[cfg(any(feature = "runtime-benchmarks", test))]
pub(crate) fn prepare_ethereum_transaction(
recipient: &crate::AccountId,
editor: impl Fn(&mut sp_bridge_eth_poa::UnsignedTransaction),
) -> Vec<u8> {
use sp_bridge_eth_poa::signatures::SignTransaction;

// prepare tx for OpenEthereum private dev chain:
// chain id is 0x11
// sender secret is 0x4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7
let chain_id = 0x11;
let signer = secp256k1::SecretKey::parse(&hex!(
"4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7"
))
.unwrap();
let recipient_raw: &[u8; 32] = recipient.as_ref();
let mut eth_tx = sp_bridge_eth_poa::UnsignedTransaction {
nonce: 0.into(),
to: Some(LOCK_FUNDS_ADDRESS.into()),
value: 100.into(),
gas: 100_000.into(),
gas_price: 100_000.into(),
payload: recipient_raw.to_vec(),
};
editor(&mut eth_tx);
eth_tx.sign_by(&signer.into(), Some(chain_id))
}

#[cfg(test)]
mod tests {
use super::*;
Expand All @@ -158,33 +209,10 @@ mod tests {
hex!("1cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c").into()
}

fn prepare_ethereum_transaction(editor: impl Fn(&mut UnsignedTransaction)) -> Vec<u8> {
// prepare tx for OpenEthereum private dev chain:
// chain id is 0x11
// sender secret is 0x4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7
let chain_id = 0x11_u64;
let signer = SecretKey::parse(&hex!(
"4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7"
))
.unwrap();
let ferdie_id = ferdie();
let ferdie_raw: &[u8; 32] = ferdie_id.as_ref();
let mut eth_tx = UnsignedTransaction {
nonce: 0.into(),
to: Some(LOCK_FUNDS_ADDRESS.into()),
value: 100.into(),
gas: 100_000.into(),
gas_price: 100_000.into(),
payload: ferdie_raw.to_vec(),
};
editor(&mut eth_tx);
eth_tx.sign_by(&signer, Some(chain_id))
}

#[test]
fn valid_transaction_accepted() {
assert_eq!(
EthTransaction::parse(&prepare_ethereum_transaction(|_| {})),
EthTransaction::parse(&prepare_ethereum_transaction(&ferdie(), |_| {})),
Ok(LockFundsTransaction {
id: EthereumTransactionTag {
account: hex!("00a329c0648769a73afac7f9381e08fb43dbea72"),
Expand All @@ -207,7 +235,7 @@ mod tests {
#[test]
fn transaction_with_invalid_peer_recipient_rejected() {
assert_eq!(
EthTransaction::parse(&prepare_ethereum_transaction(|tx| {
EthTransaction::parse(&prepare_ethereum_transaction(&ferdie(), |tx| {
tx.to = None;
})),
Err(ExchangeError::InvalidTransaction),
Expand All @@ -217,7 +245,7 @@ mod tests {
#[test]
fn transaction_with_invalid_recipient_rejected() {
assert_eq!(
EthTransaction::parse(&prepare_ethereum_transaction(|tx| {
EthTransaction::parse(&prepare_ethereum_transaction(&ferdie(), |tx| {
tx.payload.clear();
})),
Err(ExchangeError::InvalidRecipient),
Expand All @@ -227,7 +255,7 @@ mod tests {
#[test]
fn transaction_with_invalid_amount_rejected() {
assert_eq!(
EthTransaction::parse(&prepare_ethereum_transaction(|tx| {
EthTransaction::parse(&prepare_ethereum_transaction(&ferdie(), |tx| {
tx.value = sp_core::U256::from(u128::max_value()) + sp_core::U256::from(1);
})),
Err(ExchangeError::InvalidAmount),
Expand Down
45 changes: 44 additions & 1 deletion bin/node/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -641,9 +641,52 @@ impl_runtime_apis! {
) -> Result<Vec<frame_benchmarking::BenchmarkBatch>, sp_runtime::RuntimeString> {
use frame_benchmarking::{Benchmarking, BenchmarkBatch, add_benchmark};
let mut batches = Vec::<BenchmarkBatch>::new();
let params = (&pallet, &benchmark, &lowest_range_values, &highest_range_values, &steps, repeat);

let whitelist: Vec<Vec<u8>> = vec![];
let params = (&pallet, &benchmark, &lowest_range_values, &highest_range_values, &steps, repeat, &whitelist);

use pallet_bridge_currency_exchange::benchmarking::{
Module as BridgeCurrencyExchangeBench,
Trait as BridgeCurrencyExchangeTrait,
ProofParams as BridgeCurrencyExchangeProofParams,
};

impl BridgeCurrencyExchangeTrait for Runtime {
fn make_proof(
proof_params: BridgeCurrencyExchangeProofParams<AccountId>,
) -> crate::exchange::EthereumTransactionInclusionProof {
use sp_currency_exchange::DepositInto;

if proof_params.recipient_exists {
<Runtime as pallet_bridge_currency_exchange::Trait>::DepositInto::deposit_into(
proof_params.recipient.clone(),
ExistentialDeposit::get(),
).unwrap();
}

let transaction = crate::exchange::prepare_ethereum_transaction(
&proof_params.recipient,
|tx| {
// our runtime only supports transactions where data is exactly 32 bytes long
// (receiver key)
// => we are ignoring `transaction_size_factor` here
tx.value = (ExistentialDeposit::get() * 10).into();
},
);
let transactions = sp_std::iter::repeat(transaction.clone())
.take(1 + proof_params.proof_size_factor as usize)
.collect::<Vec<_>>();
let block_hash = crate::exchange::prepare_environment_for_claim::<Runtime>(&transactions);
crate::exchange::EthereumTransactionInclusionProof {
block: block_hash,
index: 0,
proof: transactions,
}
}
}

add_benchmark!(params, batches, b"bridge-eth-poa", BridgeEthPoA);
add_benchmark!(params, batches, b"bridge-currency-exchange", BridgeCurrencyExchangeBench::<Runtime>);

if batches.is_empty() { return Err("Benchmark not found for this pallet.".into()) }
Ok(batches)
Expand Down
134 changes: 134 additions & 0 deletions modules/currency-exchange/src/benchmarking.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// Copyright 2019-2020 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.

// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Parity Bridges Common 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 General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.

//! Exchange module complexity is mostly determined by callbacks, defined by runtime.
//! So we are giving runtime opportunity to prepare environment and construct proof
//! before invoking module calls.

use super::{Blockchain, Call, Module as CurrencyExchangeModule, Trait as CurrencyExchangeTrait};
use sp_std::prelude::*;

use frame_benchmarking::{account, benchmarks};
use frame_system::RawOrigin;

const SEED: u32 = 0;
const WORST_TX_SIZE_FACTOR: u32 = 1000;
const WORST_PROOF_SIZE_FACTOR: u32 = 1000;

/// Module we're benchmarking here.
pub struct Module<T: Trait>(CurrencyExchangeModule<T>);

/// Proof benchmarking parameters.
pub struct ProofParams<Recipient> {
/// Funds recipient.
pub recipient: Recipient,
/// When true, recipient must exists before import.
pub recipient_exists: bool,
/// When 0, transaction should have minimal possible size. When this value has non-zero value n,
/// transaction size should be (if possible) near to MIN_SIZE + n * SIZE_FACTOR.
pub transaction_size_factor: u32,
/// When 0, proof should have minimal possible size. When this value has non-zero value n,
/// proof size should be (if possible) near to MIN_SIZE + n * SIZE_FACTOR.
pub proof_size_factor: u32,
}

/// Trait that must be implemented by runtime.
pub trait Trait: CurrencyExchangeTrait {
/// Prepare proof for importing exchange transaction.
fn make_proof(
proof_params: ProofParams<Self::AccountId>,
) -> <<Self as CurrencyExchangeTrait>::PeerBlockchain as Blockchain>::TransactionInclusionProof;
}

benchmarks! {
_ { }

// Benchmark `import_peer_transaction` extrinsic with the best possible conditions:
// * Proof is the transaction itself.
// * Transaction has minimal size.
// * Recipient account exists.
import_peer_transaction_best_case {
let i in 1..100;

let recipient: T::AccountId = account("recipient", i, SEED);
let proof = T::make_proof(ProofParams {
recipient: recipient.clone(),
recipient_exists: true,
transaction_size_factor: 0,
proof_size_factor: 0,
});
}: import_peer_transaction(RawOrigin::Signed(recipient), proof)

// Benchmark `import_peer_transaction` extrinsic when recipient account does not exists.
import_peer_transaction_when_recipient_does_not_exists {
let i in 1..100;

let recipient: T::AccountId = account("recipient", i, SEED);
let proof = T::make_proof(ProofParams {
recipient: recipient.clone(),
recipient_exists: false,
transaction_size_factor: 0,
proof_size_factor: 0,
});
}: import_peer_transaction(RawOrigin::Signed(recipient), proof)

// Benchmark `import_peer_transaction` when transaction size increases.
import_peer_transaction_when_transaction_size_increases {
let i in 1..100;
let n in 1..WORST_TX_SIZE_FACTOR;

let recipient: T::AccountId = account("recipient", i, SEED);
let proof = T::make_proof(ProofParams {
recipient: recipient.clone(),
recipient_exists: true,
transaction_size_factor: n,
proof_size_factor: 0,
});
}: import_peer_transaction(RawOrigin::Signed(recipient), proof)

// Benchmark `import_peer_transaction` when proof size increases.
import_peer_transaction_when_proof_size_increases {
let i in 1..100;
let n in 1..WORST_PROOF_SIZE_FACTOR;

let recipient: T::AccountId = account("recipient", i, SEED);
let proof = T::make_proof(ProofParams {
recipient: recipient.clone(),
recipient_exists: true,
transaction_size_factor: 0,
proof_size_factor: n,
});
}: import_peer_transaction(RawOrigin::Signed(recipient), proof)

// Benchmark `import_peer_transaction` extrinsic with the worst possible conditions:
// * Proof is large.
// * Transaction has large size.
// * Recipient account does not exists.
import_peer_transaction_worst_case {
let i in 1..100;
let m in WORST_TX_SIZE_FACTOR..WORST_TX_SIZE_FACTOR+1;
let n in WORST_PROOF_SIZE_FACTOR..WORST_PROOF_SIZE_FACTOR+1;

let recipient: T::AccountId = account("recipient", i, SEED);
let proof = T::make_proof(ProofParams {
recipient: recipient.clone(),
recipient_exists: false,
transaction_size_factor: m,
proof_size_factor: n,
});
}: import_peer_transaction(RawOrigin::Signed(recipient), proof)

}
9 changes: 6 additions & 3 deletions modules/currency-exchange/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ use sp_currency_exchange::{
};
use sp_runtime::DispatchResult;

#[cfg(feature = "runtime-benchmarks")]
pub mod benchmarking;

/// Called when transaction is submitted to the exchange module.
pub trait OnTransactionSubmitted<AccountId> {
/// Called when valid transaction is submitted and accepted by the module.
Expand Down Expand Up @@ -229,7 +232,7 @@ mod tests {

fn parse(tx: &Self::Transaction) -> sp_currency_exchange::Result<RawTransaction> {
match tx.id {
INVALID_TRANSACTION_ID => Err(sp_currency_exchange::Error::InvalidTransaction),
INVALID_TRANSACTION_ID => Err(ExchangeError::InvalidTransaction),
_ => Ok(tx.clone()),
}
}
Expand All @@ -243,7 +246,7 @@ mod tests {

fn map(peer_recipient: Self::PeerRecipient) -> sp_currency_exchange::Result<Self::Recipient> {
match peer_recipient {
UNKNOWN_RECIPIENT_ID => Err(sp_currency_exchange::Error::FailedToMapRecipients),
UNKNOWN_RECIPIENT_ID => Err(ExchangeError::FailedToMapRecipients),
_ => Ok(peer_recipient * 10),
}
}
Expand All @@ -257,7 +260,7 @@ mod tests {

fn convert(amount: Self::SourceAmount) -> sp_currency_exchange::Result<Self::TargetAmount> {
match amount {
INVALID_AMOUNT => Err(sp_currency_exchange::Error::FailedToConvertCurrency),
INVALID_AMOUNT => Err(ExchangeError::FailedToConvertCurrency),
_ => Ok(amount * 10),
}
}
Expand Down
1 change: 0 additions & 1 deletion modules/ethereum/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ benchmarks! {
header
},
);

}: import_unsigned_header(RawOrigin::None, header, None)
verify {
let storage = BridgeStorage::<T>::new();
Expand Down
Loading

0 comments on commit fda64c8

Please sign in to comment.