Merge commit '114f487fd9daef4b4cd791446372a9a690c137ac' into update-bridges-subtree-r/w

This commit is contained in:
antonio-dropulic
2021-12-01 16:34:30 +01:00
183 changed files with 1017 additions and 21238 deletions
-6
View File
@@ -204,8 +204,6 @@ build:
- mv -v ./target/release/rialto-parachain-collator ./artifacts/
- strip ./target/release/millau-bridge-node
- mv -v ./target/release/millau-bridge-node ./artifacts/
- strip ./target/release/ethereum-poa-relay
- mv -v ./target/release/ethereum-poa-relay ./artifacts/
- strip ./target/release/substrate-relay
- mv -v ./target/release/substrate-relay ./artifacts/
- mv -v ./deployments/local-scripts/bridge-entrypoint.sh ./artifacts/
@@ -286,10 +284,6 @@ millau-bridge-node:
stage: publish
<<: *build-push-image
ethereum-poa-relay:
stage: publish
<<: *build-push-image
substrate-relay:
stage: publish
<<: *build-push-image
+21
View File
@@ -0,0 +1,21 @@
# Lists some code owners.
#
# A codeowner just oversees some part of the codebase. If an owned file is changed then the
# corresponding codeowner receives a review request. An approval of the codeowner might be
# required for merging a PR (depends on repository settings).
#
# For details about syntax, see:
# https://help.github.com/en/articles/about-code-owners
# But here are some important notes:
#
# - Glob syntax is git-like, e.g. `/core` means the core directory in the root, unlike `core`
# which can be everywhere.
# - Multiple owners are supported.
# - Either handle (e.g, @github_user or @github_org/team) or email can be used. Keep in mind,
# that handles might work better because they are more recognizable on GitHub,
# eyou can use them for mentioning unlike an email.
# - The latest matching rule, if multiple, takes precedence.
# CI
/.github/ @paritytech/ci
/.gitlab-ci.yml @paritytech/ci
+2 -2
View File
@@ -13,7 +13,7 @@ WORKDIR /parity-bridges-common
COPY . .
ARG PROJECT=ethereum-poa-relay
ARG PROJECT=substrate-relay
RUN cargo build --release --verbose -p ${PROJECT} && \
strip ./target/release/${PROJECT}
@@ -42,7 +42,7 @@ USER user
WORKDIR /home/user
ARG PROJECT=ethereum-poa-relay
ARG PROJECT=substrate-relay
COPY --chown=user:user --from=builder /parity-bridges-common/target/release/${PROJECT} ./
COPY --chown=user:user --from=builder /parity-bridges-common/deployments/local-scripts/bridge-entrypoint.sh ./
+1 -2
View File
@@ -6,7 +6,7 @@ These components include Substrate pallets for syncing headers, passing arbitrar
as libraries for building relayers to provide cross-chain communication capabilities.
Three bridge nodes are also available. The nodes can be used to run test networks which bridge other
Substrate chains or Ethereum Proof-of-Authority chains.
Substrate chains.
🚧 The bridges are currently under construction - a hardhat is recommended beyond this point 🚧
@@ -104,7 +104,6 @@ the `relays` which are used to pass messages between chains.
├── diagrams // Pretty pictures of the project architecture
│ └── ...
├── modules // Substrate Runtime Modules (a.k.a Pallets)
│ ├── ethereum // Ethereum PoA Header Sync Module
│ ├── grandpa // On-Chain GRANDPA Light Client
│ ├── messages // Cross Chain Message Passing
│ ├── dispatch // Target Chain Message Execution
@@ -23,9 +23,13 @@ pallet-bridge-messages = { path = "../../../modules/messages" }
# Substrate Dependencies
beefy-gadget = { git = "https://github.com/paritytech/substrate", branch = "master" }
beefy-gadget-rpc = { git = "https://github.com/paritytech/substrate", branch = "master" }
beefy-primitives = { git = "https://github.com/paritytech/substrate", branch = "master" }
frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "master" }
frame-benchmarking-cli = { git = "https://github.com/paritytech/substrate", branch = "master" }
node-inspect = { git = "https://github.com/paritytech/substrate", branch = "master" }
pallet-mmr-rpc = { git = "https://github.com/paritytech/substrate", branch = "master" }
pallet-transaction-payment-rpc = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-basic-authorship = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-cli = { git = "https://github.com/paritytech/substrate", branch = "master", features = ["wasmtime"] }
@@ -14,11 +14,12 @@
// 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/>.
use beefy_primitives::crypto::AuthorityId as BeefyId;
use bp_millau::derive_account_from_rialto_id;
use millau_runtime::{
AccountId, AuraConfig, BalancesConfig, BridgeRialtoMessagesConfig, BridgeWestendGrandpaConfig,
GenesisConfig, GrandpaConfig, SessionConfig, SessionKeys, Signature, SudoConfig, SystemConfig,
WASM_BINARY,
AccountId, AuraConfig, BalancesConfig, BeefyConfig, BridgeRialtoMessagesConfig,
BridgeWestendGrandpaConfig, GenesisConfig, GrandpaConfig, SessionConfig, SessionKeys,
Signature, SudoConfig, SystemConfig, WASM_BINARY,
};
use sp_consensus_aura::sr25519::AuthorityId as AuraId;
use sp_core::{sr25519, Pair, Public};
@@ -57,10 +58,11 @@ where
}
/// Helper function to generate an authority key for Aura
pub fn get_authority_keys_from_seed(s: &str) -> (AccountId, AuraId, GrandpaId) {
pub fn get_authority_keys_from_seed(s: &str) -> (AccountId, AuraId, BeefyId, GrandpaId) {
(
get_account_id_from_seed::<sr25519::Public>(s),
get_from_seed::<AuraId>(s),
get_from_seed::<BeefyId>(s),
get_from_seed::<GrandpaId>(s),
)
}
@@ -173,12 +175,12 @@ impl Alternative {
}
}
fn session_keys(aura: AuraId, grandpa: GrandpaId) -> SessionKeys {
SessionKeys { aura, grandpa }
fn session_keys(aura: AuraId, beefy: BeefyId, grandpa: GrandpaId) -> SessionKeys {
SessionKeys { aura, beefy, grandpa }
}
fn testnet_genesis(
initial_authorities: Vec<(AccountId, AuraId, GrandpaId)>,
initial_authorities: Vec<(AccountId, AuraId, BeefyId, GrandpaId)>,
root_key: AccountId,
endowed_accounts: Vec<AccountId>,
_enable_println: bool,
@@ -186,18 +188,20 @@ fn testnet_genesis(
GenesisConfig {
system: SystemConfig {
code: WASM_BINARY.expect("Millau development WASM not available").to_vec(),
changes_trie_config: Default::default(),
},
balances: BalancesConfig {
balances: endowed_accounts.iter().cloned().map(|k| (k, 1 << 50)).collect(),
},
aura: AuraConfig { authorities: Vec::new() },
beefy: BeefyConfig { authorities: Vec::new() },
grandpa: GrandpaConfig { authorities: Vec::new() },
sudo: SudoConfig { key: root_key },
session: SessionConfig {
keys: initial_authorities
.iter()
.map(|x| (x.0.clone(), x.0.clone(), session_keys(x.1.clone(), x.2.clone())))
.map(|x| {
(x.0.clone(), x.0.clone(), session_keys(x.1.clone(), x.2.clone(), x.3.clone()))
})
.collect::<Vec<_>>(),
},
bridge_westend_grandpa: BridgeWestendGrandpaConfig {
@@ -20,7 +20,7 @@ use crate::{
service::new_partial,
};
use millau_runtime::{Block, RuntimeApi};
use sc_cli::{ChainSpec, Role, RuntimeVersion, SubstrateCli};
use sc_cli::{ChainSpec, RuntimeVersion, SubstrateCli};
use sc_service::PartialComponents;
impl SubstrateCli for Cli {
@@ -72,7 +72,7 @@ impl SubstrateCli for Cli {
pub fn run() -> sc_cli::Result<()> {
let cli = Cli::from_args();
// make sure to set correct crypto version.
sp_core::crypto::set_default_ss58_version(sp_core::crypto::Ss58AddressFormat::Custom(
sp_core::crypto::set_default_ss58_version(sp_core::crypto::Ss58AddressFormat::custom(
millau_runtime::SS58Prefix::get() as u16,
));
@@ -146,11 +146,7 @@ pub fn run() -> sc_cli::Result<()> {
None => {
let runner = cli.create_runner(&cli.run)?;
runner.run_node_until_exit(|config| async move {
match config.role {
Role::Light => service::new_light(config),
_ => service::new_full(config),
}
.map_err(sc_cli::Error::Service)
service::new_full(config).map_err(sc_cli::Error::Service)
})
},
}
+57 -165
View File
@@ -21,18 +21,19 @@
// =====================================================================================
// UPDATE GUIDE:
// 1) replace everything with node-template/src/service.rs contents (found in main Substrate repo);
// 2) the only thing to keep from old code, is `rpc_extensions_builder` - we use our own custom
// RPCs; 3) fix compilation errors;
// 4) test :)
// 2) from old code keep `rpc_extensions_builder` - we use our own custom RPCs;
// 3) from old code keep the Beefy gadget;
// 4) fix compilation errors;
// 5) test :)
// =====================================================================================
// =====================================================================================
// =====================================================================================
use millau_runtime::{self, opaque::Block, RuntimeApi};
use sc_client_api::{ExecutorProvider, RemoteBackend};
use sc_client_api::ExecutorProvider;
use sc_consensus_aura::{ImportQueueParams, SlotProportion, StartAuraParams};
pub use sc_executor::NativeElseWasmExecutor;
use sc_finality_grandpa::SharedVoterState;
use sc_keystore::LocalKeystore;
use sc_service::{error::Error as ServiceError, Configuration, TaskManager};
use sc_telemetry::{Telemetry, TelemetryWorker};
@@ -40,13 +41,16 @@ use sp_consensus::SlotData;
use sp_consensus_aura::sr25519::AuthorityPair as AuraPair;
use std::{sync::Arc, time::Duration};
type Executor = NativeElseWasmExecutor<ExecutorDispatch>;
// Our native executor instance.
pub struct ExecutorDispatch;
impl sc_executor::NativeExecutionDispatch for ExecutorDispatch {
/// Only enable the benchmarking host functions when we actually want to benchmark.
#[cfg(feature = "runtime-benchmarks")]
type ExtendHostFunctions = frame_benchmarking::benchmarking::HostFunctions;
/// Otherwise we only use the default Substrate host functions.
#[cfg(not(feature = "runtime-benchmarks"))]
type ExtendHostFunctions = ();
fn dispatch(method: &str, data: &[u8]) -> Option<Vec<u8>> {
millau_runtime::api::dispatch(method, data)
@@ -62,7 +66,6 @@ type FullClient =
type FullBackend = sc_service::TFullBackend<Block>;
type FullSelectChain = sc_consensus::LongestChain<FullBackend, Block>;
#[allow(clippy::type_complexity)]
pub fn new_partial(
config: &Configuration,
) -> Result<
@@ -86,7 +89,7 @@ pub fn new_partial(
ServiceError,
> {
if config.keystore_remote.is_some() {
return Err(ServiceError::Other("Remote Keystores are not supported.".to_string()))
return Err(ServiceError::Other(format!("Remote Keystores are not supported.")))
}
let telemetry = config
@@ -107,15 +110,15 @@ pub fn new_partial(
);
let (client, backend, keystore_container, task_manager) =
sc_service::new_full_parts::<Block, RuntimeApi, Executor>(
config,
sc_service::new_full_parts::<Block, RuntimeApi, _>(
&config,
telemetry.as_ref().map(|(_, telemetry)| telemetry.handle()),
executor,
)?;
let client = Arc::new(client);
let telemetry = telemetry.map(|(worker, telemetry)| {
task_manager.spawn_handle().spawn("telemetry", worker.run());
task_manager.spawn_handle().spawn("telemetry", None, worker.run());
telemetry
});
@@ -175,7 +178,7 @@ pub fn new_partial(
})
}
fn remote_keystore(_url: &str) -> Result<Arc<LocalKeystore>, &'static str> {
fn remote_keystore(_url: &String) -> Result<Arc<LocalKeystore>, &'static str> {
// FIXME: here would the concrete keystore be built,
// must return a concrete type (NOT `LocalKeystore`) that
// implements `CryptoStore` and `SyncCryptoStore`
@@ -207,10 +210,11 @@ pub fn new_full(mut config: Configuration) -> Result<TaskManager, ServiceError>
}
config.network.extra_sets.push(sc_finality_grandpa::grandpa_peers_set_config());
config.network.extra_sets.push(beefy_gadget::beefy_peers_set_config());
let warp_sync = Arc::new(sc_finality_grandpa::warp_proof::NetworkProvider::new(
backend.clone(),
grandpa_link.shared_authority_set().clone(),
vec![],
Vec::default(),
));
let (network, system_rpc_tx, network_starter) =
@@ -220,7 +224,6 @@ pub fn new_full(mut config: Configuration) -> Result<TaskManager, ServiceError>
transaction_pool: transaction_pool.clone(),
spawn_handle: task_manager.spawn_handle(),
import_queue,
on_demand: None,
block_announce_validator_builder: None,
warp_sync: Some(warp_sync),
})?;
@@ -240,7 +243,9 @@ pub fn new_full(mut config: Configuration) -> Result<TaskManager, ServiceError>
let name = config.network.node_name.clone();
let enable_grandpa = !config.disable_grandpa;
let prometheus_registry = config.prometheus_registry().cloned();
let shared_voter_state = sc_finality_grandpa::SharedVoterState::empty();
let shared_voter_state = SharedVoterState::empty();
let (signed_commitment_sender, signed_commitment_stream) =
beefy_gadget::notification::BeefySignedCommitmentStream::channel();
let rpc_extensions_builder = {
use sc_finality_grandpa::FinalityProofProvider as GrandpaFinalityProofProvider;
@@ -263,7 +268,7 @@ pub fn new_full(mut config: Configuration) -> Result<TaskManager, ServiceError>
Some(shared_authority_set.clone()),
);
Box::new(move |_, subscription_executor| {
Box::new(move |_, subscription_executor: sc_rpc::SubscriptionTaskExecutor| {
let mut io = jsonrpc_core::IoHandler::default();
io.extend_with(SystemApi::to_delegate(FullSystem::new(
client.clone(),
@@ -277,9 +282,18 @@ pub fn new_full(mut config: Configuration) -> Result<TaskManager, ServiceError>
shared_authority_set.clone(),
shared_voter_state.clone(),
justification_stream.clone(),
subscription_executor,
subscription_executor.clone(),
finality_proof_provider.clone(),
)));
io.extend_with(beefy_gadget_rpc::BeefyApi::to_delegate(
beefy_gadget_rpc::BeefyRpcHandler::new(
signed_commitment_stream.clone(),
subscription_executor,
),
));
io.extend_with(pallet_mmr_rpc::MmrApi::to_delegate(pallet_mmr_rpc::Mmr::new(
client.clone(),
)));
Ok(io)
})
};
@@ -291,9 +305,7 @@ pub fn new_full(mut config: Configuration) -> Result<TaskManager, ServiceError>
task_manager: &mut task_manager,
transaction_pool: transaction_pool.clone(),
rpc_extensions_builder,
on_demand: None,
remote_blockchain: None,
backend,
backend: backend.clone(),
system_rpc_tx,
config,
telemetry: telemetry.as_mut(),
@@ -317,14 +329,15 @@ pub fn new_full(mut config: Configuration) -> Result<TaskManager, ServiceError>
let aura = sc_consensus_aura::start_aura::<AuraPair, _, _, _, _, _, _, _, _, _, _, _>(
StartAuraParams {
slot_duration,
client,
client: client.clone(),
select_chain,
block_import,
proposer_factory,
create_inherent_data_providers: move |_, ()| async move {
let timestamp = sp_timestamp::InherentDataProvider::from_system_time();
let slot = sp_consensus_aura::inherents::InherentDataProvider::from_timestamp_and_duration(
let slot =
sp_consensus_aura::inherents::InherentDataProvider::from_timestamp_and_duration(
*timestamp,
raw_slot_duration,
);
@@ -345,7 +358,9 @@ pub fn new_full(mut config: Configuration) -> Result<TaskManager, ServiceError>
// the AURA authoring task is considered essential, i.e. if it
// fails we take down the service with it.
task_manager.spawn_essential_handle().spawn_blocking("aura", aura);
task_manager
.spawn_essential_handle()
.spawn_blocking("aura", Some("block-authoring"), aura);
}
// if the node isn't actively participating in consensus then it doesn't
@@ -353,6 +368,23 @@ pub fn new_full(mut config: Configuration) -> Result<TaskManager, ServiceError>
let keystore =
if role.is_authority() { Some(keystore_container.sync_keystore()) } else { None };
let beefy_params = beefy_gadget::BeefyParams {
client,
backend,
key_store: keystore.clone(),
network: network.clone(),
signed_commitment_sender,
min_block_delta: 4,
prometheus_registry: prometheus_registry.clone(),
};
// Start the BEEFY bridge gadget.
task_manager.spawn_essential_handle().spawn_blocking(
"beefy-gadget",
None,
beefy_gadget::start_beefy_gadget::<_, _, _, _>(beefy_params),
);
let grandpa_config = sc_finality_grandpa::Config {
// FIXME #1578 make this available through chainspec
gossip_duration: Duration::from_millis(333),
@@ -385,6 +417,7 @@ pub fn new_full(mut config: Configuration) -> Result<TaskManager, ServiceError>
// if it fails we take down the service with it.
task_manager.spawn_essential_handle().spawn_blocking(
"grandpa-voter",
None,
sc_finality_grandpa::run_grandpa_voter(grandpa_config)?,
);
}
@@ -392,144 +425,3 @@ pub fn new_full(mut config: Configuration) -> Result<TaskManager, ServiceError>
network_starter.start_network();
Ok(task_manager)
}
/// Builds a new service for a light client.
pub fn new_light(mut config: Configuration) -> Result<TaskManager, ServiceError> {
let telemetry = config
.telemetry_endpoints
.clone()
.filter(|x| !x.is_empty())
.map(|endpoints| -> Result<_, sc_telemetry::Error> {
let worker = TelemetryWorker::new(16)?;
let telemetry = worker.handle().new_telemetry(endpoints);
Ok((worker, telemetry))
})
.transpose()?;
let executor = NativeElseWasmExecutor::<ExecutorDispatch>::new(
config.wasm_method,
config.default_heap_pages,
config.max_runtime_instances,
);
let (client, backend, keystore_container, mut task_manager, on_demand) =
sc_service::new_light_parts::<Block, RuntimeApi, Executor>(
&config,
telemetry.as_ref().map(|(_, telemetry)| telemetry.handle()),
executor,
)?;
let mut telemetry = telemetry.map(|(worker, telemetry)| {
task_manager.spawn_handle().spawn("telemetry", worker.run());
telemetry
});
config.network.extra_sets.push(sc_finality_grandpa::grandpa_peers_set_config());
let select_chain = sc_consensus::LongestChain::new(backend.clone());
let transaction_pool = Arc::new(sc_transaction_pool::BasicPool::new_light(
config.transaction_pool.clone(),
config.prometheus_registry(),
task_manager.spawn_essential_handle(),
client.clone(),
on_demand.clone(),
));
let (grandpa_block_import, grandpa_link) = sc_finality_grandpa::block_import(
client.clone(),
&(client.clone() as Arc<_>),
select_chain,
telemetry.as_ref().map(|x| x.handle()),
)?;
let slot_duration = sc_consensus_aura::slot_duration(&*client)?.slot_duration();
let import_queue =
sc_consensus_aura::import_queue::<AuraPair, _, _, _, _, _, _>(ImportQueueParams {
block_import: grandpa_block_import.clone(),
justification_import: Some(Box::new(grandpa_block_import)),
client: client.clone(),
create_inherent_data_providers: move |_, ()| async move {
let timestamp = sp_timestamp::InherentDataProvider::from_system_time();
let slot =
sp_consensus_aura::inherents::InherentDataProvider::from_timestamp_and_duration(
*timestamp,
slot_duration,
);
Ok((timestamp, slot))
},
spawner: &task_manager.spawn_essential_handle(),
can_author_with: sp_consensus::NeverCanAuthor,
registry: config.prometheus_registry(),
check_for_equivocation: Default::default(),
telemetry: telemetry.as_ref().map(|x| x.handle()),
})?;
let warp_sync = Arc::new(sc_finality_grandpa::warp_proof::NetworkProvider::new(
backend.clone(),
grandpa_link.shared_authority_set().clone(),
vec![],
));
let (network, system_rpc_tx, network_starter) =
sc_service::build_network(sc_service::BuildNetworkParams {
config: &config,
client: client.clone(),
transaction_pool: transaction_pool.clone(),
spawn_handle: task_manager.spawn_handle(),
import_queue,
on_demand: Some(on_demand.clone()),
block_announce_validator_builder: None,
warp_sync: Some(warp_sync),
})?;
if config.offchain_worker.enabled {
sc_service::build_offchain_workers(
&config,
task_manager.spawn_handle(),
client.clone(),
network.clone(),
);
}
let enable_grandpa = !config.disable_grandpa;
if enable_grandpa {
let name = config.network.node_name.clone();
let config = sc_finality_grandpa::Config {
gossip_duration: std::time::Duration::from_millis(333),
justification_period: 512,
name: Some(name),
observer_enabled: false,
keystore: None,
local_role: config.role.clone(),
telemetry: telemetry.as_ref().map(|x| x.handle()),
};
task_manager.spawn_handle().spawn_blocking(
"grandpa-observer",
sc_finality_grandpa::run_grandpa_observer(config, grandpa_link, network.clone())?,
);
}
sc_service::spawn_tasks(sc_service::SpawnTasksParams {
remote_blockchain: Some(backend.remote_blockchain()),
transaction_pool,
task_manager: &mut task_manager,
on_demand: Some(on_demand),
rpc_extensions_builder: Box::new(|_, _| Ok(())),
config,
client,
keystore: keystore_container.sync_keystore(),
backend,
network,
system_rpc_tx,
telemetry: telemetry.as_mut(),
})?;
network_starter.start_network();
Ok(task_manager)
}
@@ -30,6 +30,7 @@ pallet-shift-session-manager = { path = "../../../modules/shift-session-manager"
# Substrate Dependencies
beefy-primitives = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false, optional = true }
frame-executive = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
@@ -37,7 +38,11 @@ frame-system = { git = "https://github.com/paritytech/substrate", branch = "mast
frame-system-rpc-runtime-api = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
pallet-aura = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
pallet-beefy = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
pallet-beefy-mmr = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
pallet-grandpa = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
pallet-mmr = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
pallet-mmr-primitives = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
pallet-randomness-collective-flip = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
pallet-session = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
pallet-sudo = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
@@ -64,6 +69,7 @@ substrate-wasm-builder = { git = "https://github.com/paritytech/substrate", bran
[features]
default = ["std"]
std = [
"beefy-primitives/std",
"bp-header-chain/std",
"bp-messages/std",
"bp-millau/std",
@@ -78,11 +84,14 @@ std = [
"frame-system/std",
"pallet-aura/std",
"pallet-balances/std",
"pallet-beefy/std",
"pallet-beefy-mmr/std",
"pallet-bridge-dispatch/std",
"pallet-bridge-grandpa/std",
"pallet-bridge-messages/std",
"pallet-bridge-token-swap/std",
"pallet-grandpa/std",
"pallet-mmr/std",
"pallet-randomness-collective-flip/std",
"pallet-session/std",
"pallet-shift-session-manager/std",
+89 -4
View File
@@ -34,19 +34,23 @@ pub mod rialto_messages;
use crate::rialto_messages::{ToRialtoMessagePayload, WithRialtoMessageBridge};
use beefy_primitives::{crypto::AuthorityId as BeefyId, mmr::MmrLeafVersion, ValidatorSet};
use bridge_runtime_common::messages::{
source::estimate_message_dispatch_and_delivery_fee, MessageBridge,
};
use pallet_grandpa::{
fg_primitives, AuthorityId as GrandpaId, AuthorityList as GrandpaAuthorityList,
};
use pallet_mmr_primitives::{
DataOrHash, EncodableOpaqueLeaf, Error as MmrError, LeafDataProvider, Proof as MmrProof,
};
use pallet_transaction_payment::{FeeDetails, Multiplier, RuntimeDispatchInfo};
use sp_api::impl_runtime_apis;
use sp_consensus_aura::sr25519::AuthorityId as AuraId;
use sp_core::{crypto::KeyTypeId, OpaqueMetadata};
use sp_runtime::{
create_runtime_str, generic, impl_opaque_keys,
traits::{Block as BlockT, IdentityLookup, NumberFor, OpaqueKeys},
traits::{Block as BlockT, IdentityLookup, Keccak256, NumberFor, OpaqueKeys},
transaction_validity::{TransactionSource, TransactionValidity},
ApplyExtrinsicResult, FixedPointNumber, MultiSignature, MultiSigner, Perquintill,
};
@@ -100,9 +104,6 @@ pub type Hash = bp_millau::Hash;
/// Hashing algorithm used by the chain.
pub type Hashing = bp_millau::Hasher;
/// Digest item type.
pub type DigestItem = generic::DigestItem<Hash>;
/// Opaque types. These are used by the CLI to instantiate machinery that don't need to know
/// the specifics of the runtime. They can then be made to be agnostic over specific formats
/// of data like extrinsics, allowing for them to continue syncing the network through upgrades
@@ -123,6 +124,7 @@ pub mod opaque {
impl_opaque_keys! {
pub struct SessionKeys {
pub aura: Aura,
pub beefy: Beefy,
pub grandpa: Grandpa,
}
}
@@ -215,6 +217,11 @@ impl pallet_aura::Config for Runtime {
type MaxAuthorities = MaxAuthorities;
type DisabledValidators = ();
}
impl pallet_beefy::Config for Runtime {
type BeefyId = BeefyId;
}
impl pallet_bridge_dispatch::Config for Runtime {
type Event = Event;
type BridgeMessageId = (bp_messages::LaneId, bp_messages::MessageNonce);
@@ -243,6 +250,40 @@ impl pallet_grandpa::Config for Runtime {
type MaxAuthorities = MaxAuthorities;
}
type MmrHash = <Keccak256 as sp_runtime::traits::Hash>::Output;
impl pallet_mmr::Config for Runtime {
const INDEXING_PREFIX: &'static [u8] = b"mmr";
type Hashing = Keccak256;
type Hash = MmrHash;
type OnNewRoot = pallet_beefy_mmr::DepositBeefyDigest<Runtime>;
type WeightInfo = ();
type LeafData = pallet_beefy_mmr::Pallet<Runtime>;
}
parameter_types! {
/// Version of the produced MMR leaf.
///
/// The version consists of two parts;
/// - `major` (3 bits)
/// - `minor` (5 bits)
///
/// `major` should be updated only if decoding the previous MMR Leaf format from the payload
/// is not possible (i.e. backward incompatible change).
/// `minor` should be updated if fields are added to the previous MMR Leaf, which given SCALE
/// encoding does not prevent old leafs from being decoded.
///
/// Hence we expect `major` to be changed really rarely (think never).
/// See [`MmrLeafVersion`] type documentation for more details.
pub LeafVersion: MmrLeafVersion = MmrLeafVersion::new(0, 0);
}
impl pallet_beefy_mmr::Config for Runtime {
type LeafVersion = LeafVersion;
type BeefyAuthorityToMerkleLeaf = pallet_beefy_mmr::BeefyEcdsaToEthereum;
type ParachainHeads = ();
}
parameter_types! {
pub const MinimumPeriod: u64 = bp_millau::SLOT_DURATION / 2;
}
@@ -462,6 +503,11 @@ construct_runtime!(
ShiftSessionManager: pallet_shift_session_manager::{Pallet},
RandomnessCollectiveFlip: pallet_randomness_collective_flip::{Pallet, Storage},
// BEEFY Bridges support.
Beefy: pallet_beefy::{Pallet, Storage, Config<T>},
Mmr: pallet_mmr::{Pallet, Storage},
MmrLeaf: pallet_beefy_mmr::{Pallet, Storage},
// Rialto bridge modules.
BridgeRialtoGrandpa: pallet_bridge_grandpa::{Pallet, Call, Storage},
BridgeDispatch: pallet_bridge_dispatch::{Pallet, Event<T>},
@@ -606,6 +652,45 @@ impl_runtime_apis! {
}
}
impl beefy_primitives::BeefyApi<Block> for Runtime {
fn validator_set() -> ValidatorSet<BeefyId> {
Beefy::validator_set()
}
}
impl pallet_mmr_primitives::MmrApi<Block, MmrHash> for Runtime {
fn generate_proof(leaf_index: u64)
-> Result<(EncodableOpaqueLeaf, MmrProof<MmrHash>), MmrError>
{
Mmr::generate_proof(leaf_index)
.map(|(leaf, proof)| (EncodableOpaqueLeaf::from_leaf(&leaf), proof))
}
fn verify_proof(leaf: EncodableOpaqueLeaf, proof: MmrProof<MmrHash>)
-> Result<(), MmrError>
{
pub type Leaf = <
<Runtime as pallet_mmr::Config>::LeafData as LeafDataProvider
>::LeafData;
let leaf: Leaf = leaf
.into_opaque_leaf()
.try_decode()
.ok_or(MmrError::Verify)?;
Mmr::verify_leaf(leaf, proof)
}
fn verify_proof_stateless(
root: MmrHash,
leaf: EncodableOpaqueLeaf,
proof: MmrProof<MmrHash>
) -> Result<(), MmrError> {
type MmrHashing = <Runtime as pallet_mmr::Config>::Hashing;
let node = DataOrHash::Data(leaf.into_opaque_leaf());
pallet_mmr::verify_leaf_proof::<MmrHashing, _>(root, node, proof)
}
}
impl fg_primitives::GrandpaApi<Block> for Runtime {
fn current_set_id() -> fg_primitives::SetId {
Grandpa::current_set_id()
@@ -151,7 +151,6 @@ fn testnet_genesis(
code: rialto_parachain_runtime::WASM_BINARY
.expect("WASM binary was not build, please build it!")
.to_vec(),
changes_trie_config: Default::default(),
},
balances: rialto_parachain_runtime::BalancesConfig {
balances: endowed_accounts.iter().cloned().map(|k| (k, 1 << 60)).collect(),
@@ -103,6 +103,9 @@ pub struct Cli {
#[structopt(subcommand)]
pub subcommand: Option<Subcommand>,
#[structopt(long)]
pub parachain_id: Option<u32>,
#[structopt(flatten)]
pub run: cumulus_client_cli::RunCmd,
@@ -77,7 +77,7 @@ impl SubstrateCli for Cli {
}
fn load_spec(&self, id: &str) -> std::result::Result<Box<dyn sc_service::ChainSpec>, String> {
load_spec(id, self.run.parachain_id.unwrap_or(2000).into())
load_spec(id, self.parachain_id.unwrap_or(2000).into())
}
fn native_runtime_version(_: &Box<dyn ChainSpec>) -> &'static RuntimeVersion {
@@ -153,7 +153,7 @@ macro_rules! construct_async_run {
/// Parse command line arguments into service configuration.
pub fn run() -> Result<()> {
let cli = Cli::from_args();
sp_core::crypto::set_default_ss58_version(sp_core::crypto::Ss58AddressFormat::Custom(
sp_core::crypto::set_default_ss58_version(sp_core::crypto::Ss58AddressFormat::custom(
rialto_parachain_runtime::SS58Prefix::get() as u16,
));
@@ -273,7 +273,7 @@ pub fn run() -> Result<()> {
[RelayChainCli::executable_name()].iter().chain(cli.relaychain_args.iter()),
);
let id = ParaId::from(cli.run.parachain_id.or(para_id).expect("Missing ParaId"));
let id = ParaId::from(cli.parachain_id.or(para_id).expect("Missing ParaId"));
let parachain_account =
AccountIdConversion::<polkadot_primitives::v0::AccountId>::into_account(&id);
@@ -147,7 +147,7 @@ where
let telemetry_worker_handle = telemetry.as_ref().map(|(worker, _)| worker.handle());
let telemetry = telemetry.map(|(worker, telemetry)| {
task_manager.spawn_handle().spawn("telemetry", worker.run());
task_manager.spawn_handle().spawn("telemetry", None, worker.run());
telemetry
});
@@ -283,7 +283,6 @@ where
transaction_pool: transaction_pool.clone(),
spawn_handle: task_manager.spawn_handle(),
import_queue: import_queue.clone(),
on_demand: None,
block_announce_validator_builder: Some(Box::new(|_| block_announce_validator)),
warp_sync: None,
})?;
@@ -292,8 +291,6 @@ where
let rpc_extensions_builder = Box::new(move |_, _| Ok(rpc_ext_builder(rpc_client.clone())));
sc_service::spawn_tasks(sc_service::SpawnTasksParams {
on_demand: None,
remote_blockchain: None,
rpc_extensions_builder,
client: client.clone(),
transaction_pool: transaction_pool.clone(),
+9 -1
View File
@@ -28,10 +28,15 @@ rialto-runtime = { path = "../runtime" }
# Substrate Dependencies
beefy-gadget = { git = "https://github.com/paritytech/substrate", branch = "master" }
beefy-gadget-rpc = { git = "https://github.com/paritytech/substrate", branch = "master" }
beefy-primitives = { git = "https://github.com/paritytech/substrate", branch = "master" }
frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "master" }
frame-benchmarking-cli = { git = "https://github.com/paritytech/substrate", branch = "master" }
frame-system-rpc-runtime-api = { git = "https://github.com/paritytech/substrate", branch = "master" }
node-inspect = { git = "https://github.com/paritytech/substrate", branch = "master" }
pallet-mmr-primitives = { git = "https://github.com/paritytech/substrate", branch = "master" }
pallet-mmr-rpc = { git = "https://github.com/paritytech/substrate", branch = "master" }
pallet-transaction-payment-rpc = { git = "https://github.com/paritytech/substrate", branch = "master" }
pallet-transaction-payment-rpc-runtime-api = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-authority-discovery = { git = "https://github.com/paritytech/substrate", branch = "master" }
@@ -70,6 +75,10 @@ sp-timestamp = { git = "https://github.com/paritytech/substrate", branch = "mast
substrate-frame-rpc-system = { git = "https://github.com/paritytech/substrate", branch = "master" }
substrate-prometheus-endpoint = { git = "https://github.com/paritytech/substrate", branch = "master" }
# Polkadot Dependencies
polkadot-client = { git = "https://github.com/paritytech/polkadot", branch = "master" }
# Polkadot (parachain) Dependencies
polkadot-approval-distribution = { git = "https://github.com/paritytech/polkadot", branch = "master" }
@@ -88,7 +97,6 @@ polkadot-node-core-bitfield-signing = { git = "https://github.com/paritytech/pol
polkadot-node-core-candidate-validation = { git = "https://github.com/paritytech/polkadot", branch = "master" }
polkadot-node-core-chain-api = { git = "https://github.com/paritytech/polkadot", branch = "master" }
polkadot-node-core-chain-selection = { git = "https://github.com/paritytech/polkadot", branch = "master" }
polkadot-node-core-dispute-participation = { git = "https://github.com/paritytech/polkadot", branch = "master" }
polkadot-node-core-parachains-inherent = { git = "https://github.com/paritytech/polkadot", branch = "master" }
polkadot-node-core-provisioner = { git = "https://github.com/paritytech/polkadot", branch = "master" }
polkadot-node-core-pvf = { git = "https://github.com/paritytech/polkadot", branch = "master" }
@@ -14,12 +14,13 @@
// 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/>.
use beefy_primitives::crypto::AuthorityId as BeefyId;
use bp_rialto::derive_account_from_millau_id;
use polkadot_primitives::v1::{AssignmentId, ValidatorId};
use rialto_runtime::{
AccountId, BabeConfig, BalancesConfig, BridgeKovanConfig, BridgeMillauMessagesConfig,
BridgeRialtoPoaConfig, ConfigurationConfig, GenesisConfig, GrandpaConfig, SessionConfig,
SessionKeys, Signature, SudoConfig, SystemConfig, WASM_BINARY,
AccountId, BabeConfig, BalancesConfig, BeefyConfig, BridgeMillauMessagesConfig,
ConfigurationConfig, GenesisConfig, GrandpaConfig, SessionConfig, SessionKeys, Signature,
SudoConfig, SystemConfig, WASM_BINARY,
};
use serde_json::json;
use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId;
@@ -62,10 +63,11 @@ where
/// Helper function to generate authority keys.
pub fn get_authority_keys_from_seed(
s: &str,
) -> (AccountId, BabeId, GrandpaId, ValidatorId, AssignmentId, AuthorityDiscoveryId) {
) -> (AccountId, BabeId, BeefyId, GrandpaId, ValidatorId, AssignmentId, AuthorityDiscoveryId) {
(
get_account_id_from_seed::<sr25519::Public>(s),
get_from_seed::<BabeId>(s),
get_from_seed::<BeefyId>(s),
get_from_seed::<GrandpaId>(s),
get_from_seed::<ValidatorId>(s),
get_from_seed::<AssignmentId>(s),
@@ -183,18 +185,20 @@ impl Alternative {
fn session_keys(
babe: BabeId,
beefy: BeefyId,
grandpa: GrandpaId,
para_validator: ValidatorId,
para_assignment: AssignmentId,
authority_discovery: AuthorityDiscoveryId,
) -> SessionKeys {
SessionKeys { babe, grandpa, para_validator, para_assignment, authority_discovery }
SessionKeys { babe, beefy, grandpa, para_validator, para_assignment, authority_discovery }
}
fn testnet_genesis(
initial_authorities: Vec<(
AccountId,
BabeId,
BeefyId,
GrandpaId,
ValidatorId,
AssignmentId,
@@ -207,7 +211,6 @@ fn testnet_genesis(
GenesisConfig {
system: SystemConfig {
code: WASM_BINARY.expect("Rialto development WASM not available").to_vec(),
changes_trie_config: Default::default(),
},
balances: BalancesConfig {
balances: endowed_accounts.iter().cloned().map(|k| (k, 1 << 50)).collect(),
@@ -216,8 +219,7 @@ fn testnet_genesis(
authorities: Vec::new(),
epoch_config: Some(rialto_runtime::BABE_GENESIS_EPOCH_CONFIG),
},
bridge_rialto_poa: load_rialto_poa_bridge_config(),
bridge_kovan: load_kovan_bridge_config(),
beefy: BeefyConfig { authorities: Vec::new() },
grandpa: GrandpaConfig { authorities: Vec::new() },
sudo: SudoConfig { key: root_key },
session: SessionConfig {
@@ -233,6 +235,7 @@ fn testnet_genesis(
x.3.clone(),
x.4.clone(),
x.5.clone(),
x.6.clone(),
),
)
})
@@ -291,22 +294,6 @@ fn testnet_genesis(
}
}
fn load_rialto_poa_bridge_config() -> BridgeRialtoPoaConfig {
BridgeRialtoPoaConfig {
initial_header: rialto_runtime::rialto_poa::genesis_header(),
initial_difficulty: 0.into(),
initial_validators: rialto_runtime::rialto_poa::genesis_validators(),
}
}
fn load_kovan_bridge_config() -> BridgeKovanConfig {
BridgeKovanConfig {
initial_header: rialto_runtime::kovan::genesis_header(),
initial_difficulty: 0.into(),
initial_validators: rialto_runtime::kovan::genesis_validators(),
}
}
#[test]
fn derived_dave_account_is_as_expected() {
let dave = get_account_id_from_seed::<sr25519::Public>("Dave");
@@ -70,7 +70,7 @@ impl SubstrateCli for Cli {
/// Parse and run command line arguments
pub fn run() -> sc_cli::Result<()> {
let cli = Cli::from_args();
sp_core::crypto::set_default_ss58_version(sp_core::crypto::Ss58AddressFormat::Custom(
sp_core::crypto::set_default_ss58_version(sp_core::crypto::Ss58AddressFormat::custom(
rialto_runtime::SS58Prefix::get() as u16,
));
@@ -63,8 +63,7 @@ pub use polkadot_node_core_candidate_validation::CandidateValidationSubsystem;
pub use polkadot_node_core_chain_api::ChainApiSubsystem;
pub use polkadot_node_core_chain_selection::ChainSelectionSubsystem;
pub use polkadot_node_core_dispute_coordinator::DisputeCoordinatorSubsystem;
pub use polkadot_node_core_dispute_participation::DisputeParticipationSubsystem;
pub use polkadot_node_core_provisioner::ProvisioningSubsystem as ProvisionerSubsystem;
pub use polkadot_node_core_provisioner::ProvisionerSubsystem;
pub use polkadot_node_core_runtime_api::RuntimeApiSubsystem;
pub use polkadot_statement_distribution::StatementDistribution as StatementDistributionSubsystem;
@@ -160,7 +159,6 @@ pub fn prepared_overseer_builder<Spawner, RuntimeClient>(
ApprovalVotingSubsystem,
GossipSupportSubsystem<AuthorityDiscoveryService>,
DisputeCoordinatorSubsystem,
DisputeParticipationSubsystem,
DisputeDistributionSubsystem<AuthorityDiscoveryService>,
ChainSelectionSubsystem,
>,
@@ -249,7 +247,6 @@ where
keystore.clone(),
Metrics::register(registry)?,
))
.dispute_participation(DisputeParticipationSubsystem::new())
.dispute_distribution(DisputeDistributionSubsystem::new(
keystore,
dispute_req_receiver,
+56 -68
View File
@@ -17,16 +17,11 @@
//! Rialto chain node service.
//!
//! The code is mostly copy of `service/src/lib.rs` file from Polkadot repository
//! without optional functions.
// this warning comes from Error enum (sc_cli::Error in particular) && it isn't easy to use box
// there
#![allow(clippy::large_enum_variant)]
// this warning comes from `sc_service::PartialComponents` type
#![allow(clippy::type_complexity)]
//! without optional functions, and with BEEFY added on top.
use crate::overseer::{OverseerGen, OverseerGenArgs};
use polkadot_client::RuntimeApiCollection;
use polkadot_node_core_approval_voting::Config as ApprovalVotingConfig;
use polkadot_node_core_av_store::Config as AvailabilityConfig;
use polkadot_node_core_candidate_validation::Config as CandidateValidationConfig;
@@ -43,7 +38,7 @@ use sc_service::{config::PrometheusConfig, Configuration, TaskManager};
use sc_telemetry::{Telemetry, TelemetryWorker};
use sp_api::{ConstructRuntimeApi, HeaderT};
use sp_consensus::SelectChain;
use sp_runtime::traits::{BlakeTwo256, Block as BlockT};
use sp_runtime::traits::Block as BlockT;
use std::{sync::Arc, time::Duration};
use substrate_prometheus_endpoint::Registry;
@@ -115,52 +110,6 @@ type FullBabeBlockImport =
type FullBabeLink = sc_consensus_babe::BabeLink<Block>;
type FullGrandpaLink = sc_finality_grandpa::LinkHalf<Block, FullClient, FullSelectChain>;
/// A set of APIs that polkadot-like runtimes must implement.
///
/// This is the copy of `polkadot_service::RuntimeApiCollection` with some APIs removed
/// (right now - MMR and BEEFY).
pub trait RequiredApiCollection:
sp_transaction_pool::runtime_api::TaggedTransactionQueue<Block>
+ sp_api::ApiExt<Block>
+ sp_consensus_babe::BabeApi<Block>
+ sp_finality_grandpa::GrandpaApi<Block>
+ polkadot_primitives::v1::ParachainHost<Block>
+ sp_block_builder::BlockBuilder<Block>
+ frame_system_rpc_runtime_api::AccountNonceApi<
Block,
bp_rialto::AccountId,
rialto_runtime::Index,
> + pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi<Block, bp_rialto::Balance>
+ sp_api::Metadata<Block>
+ sp_offchain::OffchainWorkerApi<Block>
+ sp_session::SessionKeys<Block>
+ sp_authority_discovery::AuthorityDiscoveryApi<Block>
where
<Self as sp_api::ApiExt<Block>>::StateBackend: sp_api::StateBackend<BlakeTwo256>,
{
}
impl<Api> RequiredApiCollection for Api
where
Api: sp_transaction_pool::runtime_api::TaggedTransactionQueue<Block>
+ sp_api::ApiExt<Block>
+ sp_consensus_babe::BabeApi<Block>
+ sp_finality_grandpa::GrandpaApi<Block>
+ polkadot_primitives::v1::ParachainHost<Block>
+ sp_block_builder::BlockBuilder<Block>
+ frame_system_rpc_runtime_api::AccountNonceApi<
Block,
bp_rialto::AccountId,
rialto_runtime::Index,
> + pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi<Block, bp_rialto::Balance>
+ sp_api::Metadata<Block>
+ sp_offchain::OffchainWorkerApi<Block>
+ sp_session::SessionKeys<Block>
+ sp_authority_discovery::AuthorityDiscoveryApi<Block>,
<Self as sp_api::ApiExt<Block>>::StateBackend: sp_api::StateBackend<BlakeTwo256>,
{
}
// If we're using prometheus, use a registry with a prefix of `polkadot`.
fn set_prometheus_registry(config: &mut Configuration) -> Result<(), Error> {
if let Some(PrometheusConfig { registry, .. }) = config.prometheus_config.as_mut() {
@@ -170,6 +119,8 @@ fn set_prometheus_registry(config: &mut Configuration) -> Result<(), Error> {
Ok(())
}
// Needed here for complex return type while `impl Trait` in type aliases is unstable.
#[allow(clippy::type_complexity)]
pub fn new_partial(
config: &mut Configuration,
) -> Result<
@@ -184,7 +135,12 @@ pub fn new_partial(
sc_rpc::DenyUnsafe,
sc_rpc::SubscriptionTaskExecutor,
) -> Result<jsonrpc_core::IoHandler<sc_service::RpcMetadata>, sc_service::Error>,
(FullBabeBlockImport, FullGrandpaLink, FullBabeLink),
(
FullBabeBlockImport,
FullGrandpaLink,
FullBabeLink,
beefy_gadget::notification::BeefySignedCommitmentSender<Block>,
),
sc_finality_grandpa::SharedVoterState,
std::time::Duration,
Option<Telemetry>,
@@ -195,7 +151,7 @@ pub fn new_partial(
where
RuntimeApi: ConstructRuntimeApi<Block, FullClient> + Send + Sync + 'static,
<RuntimeApi as ConstructRuntimeApi<Block, FullClient>>::RuntimeApi:
RequiredApiCollection<StateBackend = sc_client_api::StateBackendFor<FullBackend, Block>>,
RuntimeApiCollection<StateBackend = sc_client_api::StateBackendFor<FullBackend, Block>>,
ExecutorDispatch: NativeExecutionDispatch + 'static,
{
set_prometheus_registry(config)?;
@@ -226,7 +182,7 @@ where
let client = Arc::new(client);
let telemetry = telemetry.map(|(worker, telemetry)| {
task_manager.spawn_handle().spawn("telemetry", worker.run());
task_manager.spawn_handle().spawn("telemetry", None, worker.run());
telemetry
});
@@ -282,7 +238,10 @@ where
let shared_authority_set = grandpa_link.shared_authority_set().clone();
let shared_voter_state = sc_finality_grandpa::SharedVoterState::empty();
let import_setup = (block_import, grandpa_link, babe_link);
let (signed_commitment_sender, signed_commitment_stream) =
beefy_gadget::notification::BeefySignedCommitmentStream::channel();
let import_setup = (block_import, grandpa_link, babe_link, signed_commitment_sender);
let rpc_setup = shared_voter_state.clone();
let slot_duration = babe_config.slot_duration();
@@ -316,14 +275,23 @@ where
pool,
deny_unsafe,
)));
io.extend_with(TransactionPaymentApi::to_delegate(TransactionPayment::new(client)));
io.extend_with(TransactionPaymentApi::to_delegate(TransactionPayment::new(
client.clone(),
)));
io.extend_with(GrandpaApi::to_delegate(GrandpaRpcHandler::new(
shared_authority_set.clone(),
shared_voter_state,
justification_stream.clone(),
subscription_executor,
subscription_executor.clone(),
finality_proof_provider,
)));
io.extend_with(beefy_gadget_rpc::BeefyApi::to_delegate(
beefy_gadget_rpc::BeefyRpcHandler::new(
signed_commitment_stream.clone(),
subscription_executor,
),
));
io.extend_with(pallet_mmr_rpc::MmrApi::to_delegate(pallet_mmr_rpc::Mmr::new(client)));
Ok(io)
}
@@ -361,7 +329,7 @@ async fn active_leaves(
where
RuntimeApi: ConstructRuntimeApi<Block, FullClient> + Send + Sync + 'static,
<RuntimeApi as ConstructRuntimeApi<Block, FullClient>>::RuntimeApi:
RequiredApiCollection<StateBackend = sc_client_api::StateBackendFor<FullBackend, Block>>,
RuntimeApiCollection<StateBackend = sc_client_api::StateBackendFor<FullBackend, Block>>,
ExecutorDispatch: NativeExecutionDispatch + 'static,
{
let best_block = select_chain.best_chain().await?;
@@ -406,7 +374,7 @@ pub fn new_full(
where
RuntimeApi: ConstructRuntimeApi<Block, FullClient> + Send + Sync + 'static,
<RuntimeApi as ConstructRuntimeApi<Block, FullClient>>::RuntimeApi:
RequiredApiCollection<StateBackend = sc_client_api::StateBackendFor<FullBackend, Block>>,
RuntimeApiCollection<StateBackend = sc_client_api::StateBackendFor<FullBackend, Block>>,
ExecutorDispatch: NativeExecutionDispatch + 'static,
{
let is_collator = false;
@@ -442,6 +410,8 @@ where
// Substrate nodes.
config.network.extra_sets.push(sc_finality_grandpa::grandpa_peers_set_config());
config.network.extra_sets.push(beefy_gadget::beefy_peers_set_config());
{
use polkadot_network_bridge::{peer_sets_info, IsAuthority};
let is_authority = if role.is_authority() { IsAuthority::Yes } else { IsAuthority::No };
@@ -474,7 +444,6 @@ where
transaction_pool: transaction_pool.clone(),
spawn_handle: task_manager.spawn_handle(),
import_queue,
on_demand: None,
block_announce_validator_builder: None,
warp_sync: Some(warp_sync),
})?;
@@ -533,13 +502,11 @@ where
rpc_extensions_builder: Box::new(rpc_extensions_builder),
transaction_pool: transaction_pool.clone(),
task_manager: &mut task_manager,
on_demand: None,
remote_blockchain: None,
system_rpc_tx,
telemetry: telemetry.as_mut(),
})?;
let (block_import, link_half, babe_link) = import_setup;
let (block_import, link_half, babe_link, signed_commitment_sender) = import_setup;
let overseer_client = client.clone();
let spawner = task_manager.spawn_handle();
@@ -574,7 +541,9 @@ where
prometheus_registry.clone(),
);
task_manager.spawn_handle().spawn("authority-discovery-worker", worker.run());
task_manager
.spawn_handle()
.spawn("authority-discovery-worker", None, worker.run());
Some(service)
} else {
None
@@ -619,6 +588,7 @@ where
let handle = handle.clone();
task_manager.spawn_essential_handle().spawn_blocking(
"overseer",
None,
Box::pin(async move {
use futures::{pin_mut, select, FutureExt};
@@ -705,7 +675,7 @@ where
};
let babe = sc_consensus_babe::start_babe(babe_config)?;
task_manager.spawn_essential_handle().spawn_blocking("babe", babe);
task_manager.spawn_essential_handle().spawn_blocking("babe", None, babe);
}
// if the node isn't actively participating in consensus then it doesn't
@@ -713,6 +683,23 @@ where
let keystore_opt =
if role.is_authority() { Some(keystore_container.sync_keystore()) } else { None };
let beefy_params = beefy_gadget::BeefyParams {
client: client.clone(),
backend: backend.clone(),
key_store: keystore_opt.clone(),
network: network.clone(),
signed_commitment_sender,
min_block_delta: 2,
prometheus_registry: prometheus_registry.clone(),
};
// Start the BEEFY bridge gadget.
task_manager.spawn_essential_handle().spawn_blocking(
"beefy-gadget",
None,
beefy_gadget::start_beefy_gadget::<_, _, _, _>(beefy_params),
);
let config = sc_finality_grandpa::Config {
// FIXME substrate#1578 make this available through chainspec
gossip_duration: Duration::from_millis(1000),
@@ -751,6 +738,7 @@ where
task_manager.spawn_essential_handle().spawn_blocking(
"grandpa-voter",
None,
sc_finality_grandpa::run_grandpa_voter(grandpa_config)?,
);
}
+10 -10
View File
@@ -17,8 +17,6 @@ serde = { version = "1.0", optional = true, features = ["derive"] }
# Bridge dependencies
bp-currency-exchange = { path = "../../../primitives/currency-exchange", default-features = false }
bp-eth-poa = { path = "../../../primitives/ethereum-poa", default-features = false }
bp-header-chain = { path = "../../../primitives/header-chain", default-features = false }
bp-message-dispatch = { path = "../../../primitives/message-dispatch", default-features = false }
bp-messages = { path = "../../../primitives/messages", default-features = false }
@@ -26,15 +24,14 @@ bp-millau = { path = "../../../primitives/chain-millau", default-features = fals
bp-rialto = { path = "../../../primitives/chain-rialto", default-features = false }
bp-runtime = { path = "../../../primitives/runtime", default-features = false }
bridge-runtime-common = { path = "../../runtime-common", default-features = false }
pallet-bridge-currency-exchange = { path = "../../../modules/currency-exchange", default-features = false }
pallet-bridge-dispatch = { path = "../../../modules/dispatch", default-features = false }
pallet-bridge-eth-poa = { path = "../../../modules/ethereum", default-features = false }
pallet-bridge-grandpa = { path = "../../../modules/grandpa", default-features = false }
pallet-bridge-messages = { path = "../../../modules/messages", default-features = false }
pallet-shift-session-manager = { path = "../../../modules/shift-session-manager", default-features = false }
# Substrate Dependencies
beefy-primitives = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false, optional = true }
frame-executive = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
@@ -43,7 +40,11 @@ frame-system-rpc-runtime-api = { git = "https://github.com/paritytech/substrate"
pallet-authority-discovery = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
pallet-babe = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
pallet-beefy = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
pallet-beefy-mmr = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
pallet-grandpa = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
pallet-mmr = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
pallet-mmr-primitives = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
pallet-session = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
pallet-sudo = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
pallet-timestamp = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
@@ -80,8 +81,7 @@ substrate-wasm-builder = { git = "https://github.com/paritytech/substrate", bran
[features]
default = ["std"]
std = [
"bp-currency-exchange/std",
"bp-eth-poa/std",
"beefy-primitives/std",
"bp-header-chain/std",
"bp-message-dispatch/std",
"bp-messages/std",
@@ -99,12 +99,14 @@ std = [
"pallet-authority-discovery/std",
"pallet-babe/std",
"pallet-balances/std",
"pallet-bridge-currency-exchange/std",
"pallet-beefy/std",
"pallet-beefy-mmr/std",
"pallet-bridge-dispatch/std",
"pallet-bridge-eth-poa/std",
"pallet-bridge-grandpa/std",
"pallet-bridge-messages/std",
"pallet-grandpa/std",
"pallet-mmr/std",
"pallet-mmr-primitives/std",
"pallet-shift-session-manager/std",
"pallet-sudo/std",
"pallet-timestamp/std",
@@ -137,8 +139,6 @@ runtime-benchmarks = [
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"libsecp256k1",
"pallet-bridge-currency-exchange/runtime-benchmarks",
"pallet-bridge-eth-poa/runtime-benchmarks",
"pallet-bridge-messages/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
]
@@ -1,38 +0,0 @@
// Copyright 2020-2021 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/>.
//! We want to use a different validator configuration for benchmarking than what's used in Kovan
//! or in our Rialto test network. However, we can't configure a new validator set on the fly which
//! means we need to wire the runtime together like this
use pallet_bridge_eth_poa::{ValidatorsConfiguration, ValidatorsSource};
use sp_std::vec;
pub use crate::kovan::{
genesis_header, genesis_validators, BridgeAuraConfiguration, FinalityVotesCachingInterval,
PruningStrategy,
};
frame_support::parameter_types! {
pub BridgeValidatorsConfiguration: pallet_bridge_eth_poa::ValidatorsConfiguration = bench_validator_config();
}
fn bench_validator_config() -> ValidatorsConfiguration {
ValidatorsConfiguration::Multi(vec![
(0, ValidatorsSource::List(vec![[1; 20].into()])),
(1, ValidatorsSource::Contract([3; 20].into(), vec![[1; 20].into()])),
])
}
@@ -1,259 +0,0 @@
// Copyright 2019-2021 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/>.
//! Support for PoA -> Substrate native tokens exchange.
//!
//! If you want to exchange native PoA tokens for native Substrate
//! chain tokens, you need to:
//! 1) send some PoA tokens to `LOCK_FUNDS_ADDRESS` address on PoA chain. Data field of
//! the transaction must be SCALE-encoded id of Substrate account that will receive
//! funds on Substrate chain;
//! 2) wait until the 'lock funds' transaction is mined on PoA chain;
//! 3) wait until the block containing the 'lock funds' transaction is finalized on PoA chain;
//! 4) wait until the required PoA header and its finality are provided
//! to the PoA -> Substrate bridge module (it can be provided by you);
//! 5) receive tokens by providing proof-of-inclusion of PoA transaction.
use bp_currency_exchange::{
Error as ExchangeError, LockFundsTransaction, MaybeLockFundsTransaction,
Result as ExchangeResult,
};
use bp_eth_poa::{transaction_decode_rlp, RawTransaction, RawTransactionReceipt};
use codec::{Decode, Encode};
use frame_support::RuntimeDebug;
use hex_literal::hex;
use scale_info::TypeInfo;
use sp_std::vec::Vec;
/// Ethereum address where locked PoA funds must be sent to.
pub const LOCK_FUNDS_ADDRESS: [u8; 20] = hex!("DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF");
/// Ethereum transaction inclusion proof.
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo)]
pub struct EthereumTransactionInclusionProof {
/// Hash of the block with transaction.
pub block: sp_core::H256,
/// Index of the transaction within the block.
pub index: u64,
/// The proof itself (right now it is all RLP-encoded transactions of the block +
/// RLP-encoded receipts of all transactions of the block).
pub proof: Vec<(RawTransaction, RawTransactionReceipt)>,
}
/// We uniquely identify transfer by the pair (sender, nonce).
///
/// The assumption is that this pair will never appear more than once in
/// transactions included into finalized blocks. This is obviously true
/// for any existing eth-like chain (that keep current TX format), because
/// otherwise transaction can be replayed over and over.
#[derive(Encode, Decode, PartialEq, RuntimeDebug, TypeInfo)]
pub struct EthereumTransactionTag {
/// Account that has locked funds.
pub account: [u8; 20],
/// Lock transaction nonce.
pub nonce: sp_core::U256,
}
/// Ethereum transaction from runtime perspective.
pub struct EthTransaction;
impl MaybeLockFundsTransaction for EthTransaction {
type Transaction = RawTransaction;
type Id = EthereumTransactionTag;
type Recipient = crate::AccountId;
type Amount = crate::Balance;
fn parse(
raw_tx: &Self::Transaction,
) -> ExchangeResult<LockFundsTransaction<Self::Id, Self::Recipient, Self::Amount>> {
let tx = transaction_decode_rlp(raw_tx).map_err(|_| ExchangeError::InvalidTransaction)?;
// we only accept transactions sending funds directly to the pre-configured address
if tx.unsigned.to != Some(LOCK_FUNDS_ADDRESS.into()) {
log::trace!(
target: "runtime",
"Failed to parse fund locks transaction. Invalid peer recipient: {:?}",
tx.unsigned.to,
);
return Err(ExchangeError::InvalidTransaction)
}
let mut recipient_raw = sp_core::H256::default();
match tx.unsigned.payload.len() {
32 => recipient_raw.as_fixed_bytes_mut().copy_from_slice(&tx.unsigned.payload),
len => {
log::trace!(
target: "runtime",
"Failed to parse fund locks transaction. Invalid recipient length: {}",
len,
);
return Err(ExchangeError::InvalidRecipient)
},
}
let amount = tx.unsigned.value.low_u128();
if tx.unsigned.value != amount.into() {
log::trace!(
target: "runtime",
"Failed to parse fund locks transaction. Invalid amount: {}",
tx.unsigned.value,
);
return Err(ExchangeError::InvalidAmount)
}
Ok(LockFundsTransaction {
id: EthereumTransactionTag {
account: *tx.sender.as_fixed_bytes(),
nonce: tx.unsigned.nonce,
},
recipient: crate::AccountId::from(*recipient_raw.as_fixed_bytes()),
amount,
})
}
}
/// 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::Config<I>, I: 'static>(
transactions: &[(RawTransaction, RawTransactionReceipt)],
) -> bp_eth_poa::H256 {
use bp_eth_poa::compute_merkle_root;
use pallet_bridge_eth_poa::{
test_utils::{insert_dummy_header, validator_utils::validator, HeaderBuilder},
BridgeStorage, Storage,
};
let mut storage = BridgeStorage::<T, I>::new();
let header = HeaderBuilder::with_parent_number_on_runtime::<T, I>(0)
.transactions_root(compute_merkle_root(transactions.iter().map(|(tx, _)| tx)))
.receipts_root(compute_merkle_root(transactions.iter().map(|(_, receipt)| receipt)))
.sign_by(&validator(0));
let header_id = header.compute_id();
insert_dummy_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 bp_eth_poa::UnsignedTransaction),
) -> (RawTransaction, RawTransactionReceipt) {
use bp_eth_poa::{signatures::SignTransaction, Receipt, TransactionOutcome};
// prepare tx for OpenEthereum private dev chain:
// chain id is 0x11
// sender secret is 0x4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7
let chain_id = 0x11;
let signer = libsecp256k1::SecretKey::parse(&hex!(
"4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7"
))
.unwrap();
let recipient_raw: &[u8; 32] = recipient.as_ref();
let mut eth_tx = bp_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, Some(chain_id)),
Receipt {
outcome: TransactionOutcome::StatusCode(1),
gas_used: Default::default(),
log_bloom: Default::default(),
logs: Vec::new(),
}
.rlp(),
)
}
#[cfg(test)]
mod tests {
use super::*;
use hex_literal::hex;
fn ferdie() -> crate::AccountId {
hex!("1cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c").into()
}
#[test]
fn valid_transaction_accepted() {
assert_eq!(
EthTransaction::parse(&prepare_ethereum_transaction(&ferdie(), |_| {}).0),
Ok(LockFundsTransaction {
id: EthereumTransactionTag {
account: hex!("00a329c0648769a73afac7f9381e08fb43dbea72"),
nonce: 0.into(),
},
recipient: ferdie(),
amount: 100,
}),
);
}
#[test]
fn invalid_transaction_rejected() {
assert_eq!(EthTransaction::parse(&Vec::new()), Err(ExchangeError::InvalidTransaction),);
}
#[test]
fn transaction_with_invalid_peer_recipient_rejected() {
assert_eq!(
EthTransaction::parse(
&prepare_ethereum_transaction(&ferdie(), |tx| {
tx.to = None;
})
.0
),
Err(ExchangeError::InvalidTransaction),
);
}
#[test]
fn transaction_with_invalid_recipient_rejected() {
assert_eq!(
EthTransaction::parse(
&prepare_ethereum_transaction(&ferdie(), |tx| {
tx.payload.clear();
})
.0
),
Err(ExchangeError::InvalidRecipient),
);
}
#[test]
fn transaction_with_invalid_amount_rejected() {
assert_eq!(
EthTransaction::parse(
&prepare_ethereum_transaction(&ferdie(), |tx| {
tx.value = sp_core::U256::from(u128::MAX) + sp_core::U256::from(1);
})
.0
),
Err(ExchangeError::InvalidAmount),
);
}
}
@@ -1,201 +0,0 @@
// Copyright 2019-2021 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/>.
use crate::exchange::EthereumTransactionInclusionProof;
use bp_eth_poa::{Address, AuraHeader, RawTransaction, U256};
use bp_header_chain::InclusionProofVerifier;
use frame_support::RuntimeDebug;
use hex_literal::hex;
use pallet_bridge_eth_poa::{
AuraConfiguration, ChainTime as TChainTime, PruningStrategy as BridgePruningStrategy,
ValidatorsConfiguration, ValidatorsSource,
};
use sp_std::prelude::*;
frame_support::parameter_types! {
pub const FinalityVotesCachingInterval: Option<u64> = Some(16);
pub BridgeAuraConfiguration: AuraConfiguration =
kovan_aura_configuration();
pub BridgeValidatorsConfiguration: ValidatorsConfiguration =
kovan_validators_configuration();
}
/// Max number of finalized headers to keep. It is equivalent of around 24 hours of
/// finalized blocks on current Kovan chain.
const FINALIZED_HEADERS_TO_KEEP: u64 = 20_000;
/// Aura engine configuration for Kovan chain.
pub fn kovan_aura_configuration() -> AuraConfiguration {
AuraConfiguration {
empty_steps_transition: u64::MAX,
strict_empty_steps_transition: 0,
validate_step_transition: 0x16e360,
validate_score_transition: 0x41a3c4,
two_thirds_majority_transition: u64::MAX,
min_gas_limit: 0x1388.into(),
max_gas_limit: U256::MAX,
maximum_extra_data_size: 0x20,
}
}
/// Validators configuration for Kovan chain.
pub fn kovan_validators_configuration() -> ValidatorsConfiguration {
ValidatorsConfiguration::Multi(vec![
(0, ValidatorsSource::List(genesis_validators())),
(
10960440,
ValidatorsSource::List(vec![
hex!("00D6Cc1BA9cf89BD2e58009741f4F7325BAdc0ED").into(),
hex!("0010f94b296a852aaac52ea6c5ac72e03afd032d").into(),
hex!("00a0a24b9f0e5ec7aa4c7389b8302fd0123194de").into(),
]),
),
(
10960500,
ValidatorsSource::Contract(
hex!("aE71807C1B0a093cB1547b682DC78316D945c9B8").into(),
vec![
hex!("d05f7478c6aa10781258c5cc8b4f385fc8fa989c").into(),
hex!("03801efb0efe2a25ede5dd3a003ae880c0292e4d").into(),
hex!("a4df255ecf08bbf2c28055c65225c9a9847abd94").into(),
hex!("596e8221a30bfe6e7eff67fee664a01c73ba3c56").into(),
hex!("faadface3fbd81ce37b0e19c0b65ff4234148132").into(),
],
),
),
])
}
/// Genesis validators set of Kovan chain.
pub fn genesis_validators() -> Vec<Address> {
vec![
hex!("00D6Cc1BA9cf89BD2e58009741f4F7325BAdc0ED").into(),
hex!("00427feae2419c15b89d1c21af10d1b6650a4d3d").into(),
hex!("4Ed9B08e6354C70fE6F8CB0411b0d3246b424d6c").into(),
hex!("0020ee4Be0e2027d76603cB751eE069519bA81A1").into(),
hex!("0010f94b296a852aaac52ea6c5ac72e03afd032d").into(),
hex!("007733a1FE69CF3f2CF989F81C7b4cAc1693387A").into(),
hex!("00E6d2b931F55a3f1701c7389d592a7778897879").into(),
hex!("00e4a10650e5a6D6001C38ff8E64F97016a1645c").into(),
hex!("00a0a24b9f0e5ec7aa4c7389b8302fd0123194de").into(),
]
}
/// Genesis header of the Kovan chain.
pub fn genesis_header() -> AuraHeader {
AuraHeader {
parent_hash: Default::default(),
timestamp: 0,
number: 0,
author: Default::default(),
transactions_root: hex!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
.into(),
uncles_hash: hex!("1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347")
.into(),
extra_data: vec![],
state_root: hex!("2480155b48a1cea17d67dbfdfaafe821c1d19cdd478c5358e8ec56dec24502b2").into(),
receipts_root: hex!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
.into(),
log_bloom: Default::default(),
gas_used: Default::default(),
gas_limit: 6000000.into(),
difficulty: 131072.into(),
seal: vec![
vec![128],
vec![
184, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
],
],
}
}
/// Kovan headers pruning strategy.
///
/// We do not prune unfinalized headers because exchange module only accepts
/// claims from finalized headers. And if we're pruning unfinalized headers, then
/// some claims may never be accepted.
#[derive(Default, RuntimeDebug)]
pub struct PruningStrategy;
impl BridgePruningStrategy for PruningStrategy {
fn pruning_upper_bound(&mut self, _best_number: u64, best_finalized_number: u64) -> u64 {
best_finalized_number.saturating_sub(FINALIZED_HEADERS_TO_KEEP)
}
}
/// PoA Header timestamp verification against `Timestamp` pallet.
#[derive(Default, RuntimeDebug)]
pub struct ChainTime;
impl TChainTime for ChainTime {
fn is_timestamp_ahead(&self, timestamp: u64) -> bool {
let now = super::Timestamp::now();
timestamp > now
}
}
/// The Kovan Blockchain as seen by the runtime.
pub struct KovanBlockchain;
impl InclusionProofVerifier for KovanBlockchain {
type Transaction = RawTransaction;
type TransactionInclusionProof = EthereumTransactionInclusionProof;
fn verify_transaction_inclusion_proof(
proof: &Self::TransactionInclusionProof,
) -> Option<Self::Transaction> {
let is_transaction_finalized = crate::BridgeKovan::verify_transaction_finalized(
proof.block,
proof.index,
&proof.proof,
);
if !is_transaction_finalized {
return None
}
proof.proof.get(proof.index as usize).map(|(tx, _)| tx.clone())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pruning_strategy_keeps_enough_headers() {
assert_eq!(
PruningStrategy::default().pruning_upper_bound(100_000, 10_000),
0,
"10_000 <= 20_000 => nothing should be pruned yet",
);
assert_eq!(
PruningStrategy::default().pruning_upper_bound(100_000, 20_000),
0,
"20_000 <= 20_000 => nothing should be pruned yet",
);
assert_eq!(
PruningStrategy::default().pruning_upper_bound(100_000, 30_000),
10_000,
"20_000 <= 30_000 => we're ready to prune first 10_000 headers",
);
}
}
+85 -301
View File
@@ -30,30 +30,28 @@
#[cfg(feature = "std")]
include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs"));
pub mod exchange;
#[cfg(feature = "runtime-benchmarks")]
pub mod benches;
pub mod kovan;
pub mod millau_messages;
pub mod parachains;
pub mod rialto_poa;
use crate::millau_messages::{ToMillauMessagePayload, WithMillauMessageBridge};
use beefy_primitives::{crypto::AuthorityId as BeefyId, mmr::MmrLeafVersion, ValidatorSet};
use bridge_runtime_common::messages::{
source::estimate_message_dispatch_and_delivery_fee, MessageBridge,
};
use pallet_grandpa::{
fg_primitives, AuthorityId as GrandpaId, AuthorityList as GrandpaAuthorityList,
};
use pallet_mmr_primitives::{
DataOrHash, EncodableOpaqueLeaf, Error as MmrError, LeafDataProvider, Proof as MmrProof,
};
use pallet_transaction_payment::{FeeDetails, Multiplier, RuntimeDispatchInfo};
use sp_api::impl_runtime_apis;
use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId;
use sp_core::{crypto::KeyTypeId, OpaqueMetadata};
use sp_runtime::{
create_runtime_str, generic, impl_opaque_keys,
traits::{AccountIdLookup, Block as BlockT, NumberFor, OpaqueKeys},
traits::{AccountIdLookup, Block as BlockT, Keccak256, NumberFor, OpaqueKeys},
transaction_validity::{TransactionSource, TransactionValidity},
ApplyExtrinsicResult, FixedPointNumber, MultiSignature, MultiSigner, Perquintill,
};
@@ -72,8 +70,6 @@ pub use frame_support::{
pub use frame_system::Call as SystemCall;
pub use pallet_balances::Call as BalancesCall;
pub use pallet_bridge_currency_exchange::Call as BridgeCurrencyExchangeCall;
pub use pallet_bridge_eth_poa::Call as BridgeEthPoACall;
pub use pallet_bridge_grandpa::Call as BridgeGrandpaMillauCall;
pub use pallet_bridge_messages::Call as MessagesCall;
pub use pallet_sudo::Call as SudoCall;
@@ -109,9 +105,6 @@ pub type Hash = bp_rialto::Hash;
/// Hashing algorithm used by the chain.
pub type Hashing = bp_rialto::Hasher;
/// Digest item type.
pub type DigestItem = generic::DigestItem<Hash>;
/// Opaque types. These are used by the CLI to instantiate machinery that don't need to know
/// the specifics of the runtime. They can then be made to be agnostic over specific formats
/// of data like extrinsics, allowing for them to continue syncing the network through upgrades
@@ -133,6 +126,7 @@ impl_opaque_keys! {
pub struct SessionKeys {
pub babe: Babe,
pub grandpa: Grandpa,
pub beefy: Beefy,
pub para_validator: Initializer,
pub para_assignment: SessionInfo,
pub authority_discovery: AuthorityDiscovery,
@@ -253,46 +247,8 @@ impl pallet_babe::Config for Runtime {
type WeightInfo = ();
}
type RialtoPoA = pallet_bridge_eth_poa::Instance1;
impl pallet_bridge_eth_poa::Config<RialtoPoA> for Runtime {
type AuraConfiguration = rialto_poa::BridgeAuraConfiguration;
type FinalityVotesCachingInterval = rialto_poa::FinalityVotesCachingInterval;
type ValidatorsConfiguration = rialto_poa::BridgeValidatorsConfiguration;
type PruningStrategy = rialto_poa::PruningStrategy;
type ChainTime = rialto_poa::ChainTime;
type OnHeadersSubmitted = ();
}
type Kovan = pallet_bridge_eth_poa::Instance2;
impl pallet_bridge_eth_poa::Config<Kovan> for Runtime {
type AuraConfiguration = kovan::BridgeAuraConfiguration;
type FinalityVotesCachingInterval = kovan::FinalityVotesCachingInterval;
type ValidatorsConfiguration = kovan::BridgeValidatorsConfiguration;
type PruningStrategy = kovan::PruningStrategy;
type ChainTime = kovan::ChainTime;
type OnHeadersSubmitted = ();
}
type RialtoCurrencyExchange = pallet_bridge_currency_exchange::Instance1;
impl pallet_bridge_currency_exchange::Config<RialtoCurrencyExchange> for Runtime {
type OnTransactionSubmitted = ();
type PeerBlockchain = rialto_poa::RialtoBlockchain;
type PeerMaybeLockFundsTransaction = exchange::EthTransaction;
type RecipientsMap = bp_currency_exchange::IdentityRecipients<AccountId>;
type Amount = Balance;
type CurrencyConverter = bp_currency_exchange::IdentityCurrencyConverter<Balance>;
type DepositInto = DepositInto;
}
type KovanCurrencyExchange = pallet_bridge_currency_exchange::Instance2;
impl pallet_bridge_currency_exchange::Config<KovanCurrencyExchange> for Runtime {
type OnTransactionSubmitted = ();
type PeerBlockchain = kovan::KovanBlockchain;
type PeerMaybeLockFundsTransaction = exchange::EthTransaction;
type RecipientsMap = bp_currency_exchange::IdentityRecipients<AccountId>;
type Amount = Balance;
type CurrencyConverter = bp_currency_exchange::IdentityCurrencyConverter<Balance>;
type DepositInto = DepositInto;
impl pallet_beefy::Config for Runtime {
type BeefyId = BeefyId;
}
impl pallet_bridge_dispatch::Config for Runtime {
@@ -307,68 +263,6 @@ impl pallet_bridge_dispatch::Config for Runtime {
type AccountIdConverter = bp_rialto::AccountIdConverter;
}
pub struct DepositInto;
impl bp_currency_exchange::DepositInto for DepositInto {
type Recipient = AccountId;
type Amount = Balance;
fn deposit_into(
recipient: Self::Recipient,
amount: Self::Amount,
) -> bp_currency_exchange::Result<()> {
// let balances module make all checks for us (it won't allow depositing lower than
// existential deposit, balance overflow, ...)
let deposited = <pallet_balances::Pallet<Runtime> as Currency<AccountId>>::deposit_creating(
&recipient, amount,
);
// I'm dropping deposited here explicitly to illustrate the fact that it'll update
// `TotalIssuance` on drop
let deposited_amount = deposited.peek();
drop(deposited);
// we have 3 cases here:
// - deposited == amount: success
// - deposited == 0: deposit has failed and no changes to storage were made
// - deposited != 0: (should never happen in practice) deposit has been partially completed
match deposited_amount {
_ if deposited_amount == amount => {
log::trace!(
target: "runtime",
"Deposited {} to {:?}",
amount,
recipient,
);
Ok(())
},
_ if deposited_amount == 0 => {
log::error!(
target: "runtime",
"Deposit of {} to {:?} has failed",
amount,
recipient,
);
Err(bp_currency_exchange::Error::DepositFailed)
},
_ => {
log::error!(
target: "runtime",
"Deposit of {} to {:?} has partially competed. {} has been deposited",
amount,
recipient,
deposited_amount,
);
// we can't return DepositFailed error here, because storage changes were made
Err(bp_currency_exchange::Error::DepositPartiallyFailed)
},
}
}
}
impl pallet_grandpa::Config for Runtime {
type Event = Event;
type Call = Call;
@@ -386,6 +280,38 @@ impl pallet_grandpa::Config for Runtime {
type MaxAuthorities = MaxAuthorities;
}
impl pallet_mmr::Config for Runtime {
const INDEXING_PREFIX: &'static [u8] = b"mmr";
type Hashing = Keccak256;
type Hash = <Keccak256 as sp_runtime::traits::Hash>::Output;
type OnNewRoot = pallet_beefy_mmr::DepositBeefyDigest<Runtime>;
type WeightInfo = ();
type LeafData = pallet_beefy_mmr::Pallet<Runtime>;
}
parameter_types! {
/// Version of the produced MMR leaf.
///
/// The version consists of two parts;
/// - `major` (3 bits)
/// - `minor` (5 bits)
///
/// `major` should be updated only if decoding the previous MMR Leaf format from the payload
/// is not possible (i.e. backward incompatible change).
/// `minor` should be updated if fields are added to the previous MMR Leaf, which given SCALE
/// encoding does not prevent old leafs from being decoded.
///
/// Hence we expect `major` to be changed really rarely (think never).
/// See [`MmrLeafVersion`] type documentation for more details.
pub LeafVersion: MmrLeafVersion = MmrLeafVersion::new(0, 0);
}
impl pallet_beefy_mmr::Config for Runtime {
type LeafVersion = LeafVersion;
type BeefyAuthorityToMerkleLeaf = pallet_beefy_mmr::BeefyEcdsaToEthereum;
type ParachainHeads = ();
}
parameter_types! {
pub const MinimumPeriod: u64 = bp_rialto::SLOT_DURATION / 2;
}
@@ -579,11 +505,10 @@ construct_runtime!(
Grandpa: pallet_grandpa::{Pallet, Call, Storage, Config, Event},
ShiftSessionManager: pallet_shift_session_manager::{Pallet},
// Eth-PoA chains bridge modules.
BridgeRialtoPoa: pallet_bridge_eth_poa::<Instance1>::{Pallet, Call, Config, Storage, ValidateUnsigned},
BridgeKovan: pallet_bridge_eth_poa::<Instance2>::{Pallet, Call, Config, Storage, ValidateUnsigned},
BridgeRialtoCurrencyExchange: pallet_bridge_currency_exchange::<Instance1>::{Pallet, Call},
BridgeKovanCurrencyExchange: pallet_bridge_currency_exchange::<Instance2>::{Pallet, Call},
// BEEFY Bridges support.
Beefy: pallet_beefy::{Pallet, Storage, Config<T>},
Mmr: pallet_mmr::{Pallet, Storage},
MmrLeaf: pallet_beefy_mmr::{Pallet, Storage},
// Millau bridge modules.
BridgeMillauGrandpa: pallet_bridge_grandpa::{Pallet, Call, Storage},
@@ -694,43 +619,42 @@ impl_runtime_apis! {
}
}
impl bp_eth_poa::RialtoPoAHeaderApi<Block> for Runtime {
fn best_block() -> (u64, bp_eth_poa::H256) {
let best_block = BridgeRialtoPoa::best_block();
(best_block.number, best_block.hash)
}
fn finalized_block() -> (u64, bp_eth_poa::H256) {
let finalized_block = BridgeRialtoPoa::finalized_block();
(finalized_block.number, finalized_block.hash)
}
fn is_import_requires_receipts(header: bp_eth_poa::AuraHeader) -> bool {
BridgeRialtoPoa::is_import_requires_receipts(header)
}
fn is_known_block(hash: bp_eth_poa::H256) -> bool {
BridgeRialtoPoa::is_known_block(hash)
impl beefy_primitives::BeefyApi<Block> for Runtime {
fn validator_set() -> ValidatorSet<BeefyId> {
Beefy::validator_set()
}
}
impl bp_eth_poa::KovanHeaderApi<Block> for Runtime {
fn best_block() -> (u64, bp_eth_poa::H256) {
let best_block = BridgeKovan::best_block();
(best_block.number, best_block.hash)
impl pallet_mmr_primitives::MmrApi<Block, Hash> for Runtime {
fn generate_proof(leaf_index: u64)
-> Result<(EncodableOpaqueLeaf, MmrProof<Hash>), MmrError>
{
Mmr::generate_proof(leaf_index)
.map(|(leaf, proof)| (EncodableOpaqueLeaf::from_leaf(&leaf), proof))
}
fn finalized_block() -> (u64, bp_eth_poa::H256) {
let finalized_block = BridgeKovan::finalized_block();
(finalized_block.number, finalized_block.hash)
fn verify_proof(leaf: EncodableOpaqueLeaf, proof: MmrProof<Hash>)
-> Result<(), MmrError>
{
pub type Leaf = <
<Runtime as pallet_mmr::Config>::LeafData as LeafDataProvider
>::LeafData;
let leaf: Leaf = leaf
.into_opaque_leaf()
.try_decode()
.ok_or(MmrError::Verify)?;
Mmr::verify_leaf(leaf, proof)
}
fn is_import_requires_receipts(header: bp_eth_poa::AuraHeader) -> bool {
BridgeKovan::is_import_requires_receipts(header)
}
fn is_known_block(hash: bp_eth_poa::H256) -> bool {
BridgeKovan::is_known_block(hash)
fn verify_proof_stateless(
root: Hash,
leaf: EncodableOpaqueLeaf,
proof: MmrProof<Hash>
) -> Result<(), MmrError> {
type MmrHashing = <Runtime as pallet_mmr::Config>::Hashing;
let node = DataOrHash::Data(leaf.into_opaque_leaf());
pallet_mmr::verify_leaf_proof::<MmrHashing, _>(root, node, proof)
}
}
@@ -745,18 +669,6 @@ impl_runtime_apis! {
}
}
impl bp_currency_exchange::RialtoCurrencyExchangeApi<Block, exchange::EthereumTransactionInclusionProof> for Runtime {
fn filter_transaction_proof(proof: exchange::EthereumTransactionInclusionProof) -> bool {
BridgeRialtoCurrencyExchange::filter_transaction_proof(&proof)
}
}
impl bp_currency_exchange::KovanCurrencyExchangeApi<Block, exchange::EthereumTransactionInclusionProof> for Runtime {
fn filter_transaction_proof(proof: exchange::EthereumTransactionInclusionProof) -> bool {
BridgeKovanCurrencyExchange::filter_transaction_proof(&proof)
}
}
impl sp_transaction_pool::runtime_api::TaggedTransactionQueue<Block> for Runtime {
fn validate_transaction(
source: TransactionSource,
@@ -846,6 +758,13 @@ impl_runtime_apis! {
polkadot_runtime_parachains::runtime_api_impl::v1::persisted_validation_data::<Runtime>(para_id, assumption)
}
fn assumed_validation_data(
para_id: polkadot_primitives::v1::Id,
expected_persisted_validation_data_hash: Hash,
) -> Option<(polkadot_primitives::v1::PersistedValidationData<Hash, BlockNumber>, polkadot_primitives::v1::ValidationCodeHash)> {
polkadot_runtime_parachains::runtime_api_impl::v1::assumed_validation_data::<Runtime>(para_id, expected_persisted_validation_data_hash)
}
fn check_validation_outputs(
para_id: polkadot_primitives::v1::Id,
outputs: polkadot_primitives::v1::CandidateCommitments,
@@ -1029,17 +948,10 @@ impl_runtime_apis! {
use frame_benchmarking::{list_benchmark, Benchmarking, BenchmarkList};
use frame_support::traits::StorageInfoTrait;
use pallet_bridge_currency_exchange::benchmarking::Pallet as BridgeCurrencyExchangeBench;
use pallet_bridge_messages::benchmarking::Pallet as MessagesBench;
let mut list = Vec::<BenchmarkList>::new();
list_benchmark!(list, extra, pallet_bridge_eth_poa, BridgeRialtoPoa);
list_benchmark!(
list,
extra,
pallet_bridge_currency_exchange, BridgeCurrencyExchangeBench::<Runtime, KovanCurrencyExchange>
);
list_benchmark!(list, extra, pallet_bridge_messages, MessagesBench::<Runtime, WithMillauMessagesInstance>);
list_benchmark!(list, extra, pallet_bridge_grandpa, BridgeMillauGrandpa);
@@ -1073,46 +985,6 @@ impl_runtime_apis! {
let mut batches = Vec::<BenchmarkBatch>::new();
let params = (&config, &whitelist);
use pallet_bridge_currency_exchange::benchmarking::{
Pallet as BridgeCurrencyExchangeBench,
Config as BridgeCurrencyExchangeConfig,
ProofParams as BridgeCurrencyExchangeProofParams,
};
impl BridgeCurrencyExchangeConfig<KovanCurrencyExchange> for Runtime {
fn make_proof(
proof_params: BridgeCurrencyExchangeProofParams<AccountId>,
) -> crate::exchange::EthereumTransactionInclusionProof {
use bp_currency_exchange::DepositInto;
if proof_params.recipient_exists {
<Runtime as pallet_bridge_currency_exchange::Config<KovanCurrencyExchange>>::DepositInto::deposit_into(
proof_params.recipient.clone(),
ExistentialDeposit::get(),
).unwrap();
}
let (transaction, receipt) = 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, receipt))
.take(1 + proof_params.proof_size_factor as usize)
.collect::<Vec<_>>();
let block_hash = crate::exchange::prepare_environment_for_claim::<Runtime, Kovan>(&transactions);
crate::exchange::EthereumTransactionInclusionProof {
block: block_hash,
index: 0,
proof: transactions,
}
}
}
use crate::millau_messages::{ToMillauMessagePayload, WithMillauMessageBridge};
use bp_runtime::messages::DispatchFeePayment;
use bridge_runtime_common::messages;
@@ -1279,13 +1151,6 @@ impl_runtime_apis! {
}
}
add_benchmark!(params, batches, pallet_bridge_eth_poa, BridgeRialtoPoa);
add_benchmark!(
params,
batches,
pallet_bridge_currency_exchange,
BridgeCurrencyExchangeBench::<Runtime, KovanCurrencyExchange>
);
add_benchmark!(
params,
batches,
@@ -1327,48 +1192,8 @@ where
#[cfg(test)]
mod tests {
use super::*;
use bp_currency_exchange::DepositInto;
use bridge_runtime_common::messages;
fn run_deposit_into_test(test: impl Fn(AccountId) -> Balance) {
let mut ext: sp_io::TestExternalities =
SystemConfig::default().build_storage::<Runtime>().unwrap().into();
ext.execute_with(|| {
// initially issuance is zero
assert_eq!(
<pallet_balances::Pallet<Runtime> as Currency<AccountId>>::total_issuance(),
0,
);
// create account
let account: AccountId = [1u8; 32].into();
let initial_amount = ExistentialDeposit::get();
let deposited =
<pallet_balances::Pallet<Runtime> as Currency<AccountId>>::deposit_creating(
&account,
initial_amount,
);
drop(deposited);
assert_eq!(
<pallet_balances::Pallet<Runtime> as Currency<AccountId>>::total_issuance(),
initial_amount,
);
assert_eq!(
<pallet_balances::Pallet<Runtime> as Currency<AccountId>>::free_balance(&account),
initial_amount,
);
// run test
let total_issuance_change = test(account);
// check that total issuance has changed by `run_deposit_into_test`
assert_eq!(
<pallet_balances::Pallet<Runtime> as Currency<AccountId>>::total_issuance(),
initial_amount + total_issuance_change,
);
});
}
#[test]
fn ensure_rialto_message_lane_weights_are_correct() {
type Weights = pallet_bridge_messages::weights::RialtoWeight<Runtime>;
@@ -1410,53 +1235,12 @@ mod tests {
);
}
#[test]
fn deposit_into_existing_account_works() {
run_deposit_into_test(|existing_account| {
let initial_amount =
<pallet_balances::Pallet<Runtime> as Currency<AccountId>>::free_balance(
&existing_account,
);
let additional_amount = 10_000;
<Runtime as pallet_bridge_currency_exchange::Config<KovanCurrencyExchange>>::DepositInto::deposit_into(
existing_account.clone(),
additional_amount,
)
.unwrap();
assert_eq!(
<pallet_balances::Pallet<Runtime> as Currency<AccountId>>::free_balance(
&existing_account
),
initial_amount + additional_amount,
);
additional_amount
});
}
#[test]
fn deposit_into_new_account_works() {
run_deposit_into_test(|_| {
let initial_amount = 0;
let additional_amount = ExistentialDeposit::get() + 10_000;
let new_account: AccountId = [42u8; 32].into();
<Runtime as pallet_bridge_currency_exchange::Config<KovanCurrencyExchange>>::DepositInto::deposit_into(
new_account.clone(),
additional_amount,
)
.unwrap();
assert_eq!(
<pallet_balances::Pallet<Runtime> as Currency<AccountId>>::free_balance(
&new_account
),
initial_amount + additional_amount,
);
additional_amount
});
}
#[test]
fn call_size() {
const MAX_CALL_SIZE: usize = 230; // value from polkadot-runtime tests
assert!(core::mem::size_of::<Call>() <= MAX_CALL_SIZE);
const DOT_MAX_CALL_SZ: usize = 230;
assert!(core::mem::size_of::<pallet_bridge_grandpa::Call<Runtime>>() <= DOT_MAX_CALL_SZ);
// FIXME: get this down to 230. https://github.com/paritytech/grandpa-bridge-gadget/issues/359
const BEEFY_MAX_CALL_SZ: usize = 232;
assert!(core::mem::size_of::<pallet_bridge_messages::Call<Runtime>>() <= BEEFY_MAX_CALL_SZ);
}
}
@@ -71,7 +71,9 @@ impl parachains_paras::Config for Runtime {
type WeightInfo = parachains_paras::TestWeightInfo;
}
impl parachains_paras_inherent::Config for Runtime {}
impl parachains_paras_inherent::Config for Runtime {
type WeightInfo = parachains_paras_inherent::TestWeightInfo;
}
impl parachains_scheduler::Config for Runtime {}
@@ -1,183 +0,0 @@
// Copyright 2020-2021 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/>.
//! Configuration parameters for the Rialto PoA chain.
use crate::exchange::EthereumTransactionInclusionProof;
use bp_eth_poa::{Address, AuraHeader, RawTransaction, U256};
use bp_header_chain::InclusionProofVerifier;
use frame_support::RuntimeDebug;
use hex_literal::hex;
use pallet_bridge_eth_poa::{
AuraConfiguration, ChainTime as TChainTime, PruningStrategy as TPruningStrategy,
ValidatorsConfiguration, ValidatorsSource,
};
use sp_std::prelude::*;
frame_support::parameter_types! {
pub const FinalityVotesCachingInterval: Option<u64> = Some(8);
pub BridgeAuraConfiguration: AuraConfiguration =
aura_configuration();
pub BridgeValidatorsConfiguration: ValidatorsConfiguration =
validators_configuration();
}
/// Max number of finalized headers to keep.
const FINALIZED_HEADERS_TO_KEEP: u64 = 5_000;
/// Aura engine configuration for Rialto chain.
pub fn aura_configuration() -> AuraConfiguration {
AuraConfiguration {
empty_steps_transition: 0xfffffffff,
strict_empty_steps_transition: 0,
validate_step_transition: 0,
validate_score_transition: 0,
two_thirds_majority_transition: u64::MAX,
min_gas_limit: 0x1388.into(),
max_gas_limit: U256::MAX,
maximum_extra_data_size: 0x20,
}
}
/// Validators configuration for Rialto PoA chain.
pub fn validators_configuration() -> ValidatorsConfiguration {
ValidatorsConfiguration::Single(ValidatorsSource::List(genesis_validators()))
}
/// Genesis validators set of Rialto PoA chain.
pub fn genesis_validators() -> Vec<Address> {
vec![
hex!("005e714f896a8b7cede9d38688c1a81de72a58e4").into(),
hex!("007594304039c2937a12220338aab821d819f5a4").into(),
hex!("004e7a39907f090e19b0b80a277e77b72b22e269").into(),
]
}
/// Genesis header of the Rialto PoA chain.
///
/// To obtain genesis header from a running node, invoke:
/// ```bash
/// $ http localhost:8545 jsonrpc=2.0 id=1 method=eth_getBlockByNumber params:='["earliest", false]' -v
/// ```
pub fn genesis_header() -> AuraHeader {
AuraHeader {
parent_hash: Default::default(),
timestamp: 0,
number: 0,
author: Default::default(),
transactions_root: hex!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
.into(),
uncles_hash: hex!("1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347")
.into(),
extra_data: vec![],
state_root: hex!("a992d04c791620ed7ed96555a80cf0568355bb4bee2656f46899a4372f25f248").into(),
receipts_root: hex!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
.into(),
log_bloom: Default::default(),
gas_used: Default::default(),
gas_limit: 0x222222.into(),
difficulty: 0x20000.into(),
seal: vec![vec![0x80], {
let mut vec = vec![0xb8, 0x41];
vec.resize(67, 0);
vec
}],
}
}
/// Rialto PoA headers pruning strategy.
///
/// We do not prune unfinalized headers because exchange module only accepts
/// claims from finalized headers. And if we're pruning unfinalized headers, then
/// some claims may never be accepted.
#[derive(Default, RuntimeDebug)]
pub struct PruningStrategy;
impl TPruningStrategy for PruningStrategy {
fn pruning_upper_bound(&mut self, _best_number: u64, best_finalized_number: u64) -> u64 {
best_finalized_number.saturating_sub(FINALIZED_HEADERS_TO_KEEP)
}
}
/// `ChainTime` provider
#[derive(Default)]
pub struct ChainTime;
impl TChainTime for ChainTime {
fn is_timestamp_ahead(&self, timestamp: u64) -> bool {
let now = super::Timestamp::now();
timestamp > now
}
}
/// The Rialto PoA Blockchain as seen by the runtime.
pub struct RialtoBlockchain;
impl InclusionProofVerifier for RialtoBlockchain {
type Transaction = RawTransaction;
type TransactionInclusionProof = EthereumTransactionInclusionProof;
fn verify_transaction_inclusion_proof(
proof: &Self::TransactionInclusionProof,
) -> Option<Self::Transaction> {
let is_transaction_finalized = crate::BridgeRialtoPoa::verify_transaction_finalized(
proof.block,
proof.index,
&proof.proof,
);
if !is_transaction_finalized {
return None
}
proof.proof.get(proof.index as usize).map(|(tx, _)| tx.clone())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn genesis_hash_matches() {
assert_eq!(
genesis_header().compute_hash(),
hex!("1468e1a0fa20d30025a5a0f87e1cced4fdc393b84b7d2850b11ca5863db482cb").into(),
);
}
#[test]
fn pruning_strategy_keeps_enough_headers() {
assert_eq!(
PruningStrategy::default().pruning_upper_bound(100_000, 1_000),
0,
"1_000 <= 5_000 => nothing should be pruned yet",
);
assert_eq!(
PruningStrategy::default().pruning_upper_bound(100_000, 5_000),
0,
"5_000 <= 5_000 => nothing should be pruned yet",
);
assert_eq!(
PruningStrategy::default().pruning_upper_bound(100_000, 10_000),
5_000,
"5_000 <= 10_000 => we're ready to prune first 5_000 headers",
);
}
}
+1 -1
View File
@@ -24,7 +24,7 @@ USER user
WORKDIR /home/user
ARG PROJECT=ethereum-poa-relay
ARG PROJECT=substrate-relay
COPY --chown=user:user ./${PROJECT} ./
COPY --chown=user:user ./bridge-entrypoint.sh ./
+8 -17
View File
@@ -44,16 +44,16 @@ the monitoring Compose file is _not_ optional, and must be included for bridge d
### Running and Updating Deployments
We currently support two bridge deployments
1. Ethereum PoA to Rialto Substrate
2. Rialto Substrate to Millau Substrate
1. Rialto Substrate to Millau Substrate
2. Westend Substrate to Millau Substrate
These bridges can be deployed using our [`./run.sh`](./run.sh) script.
The first argument it takes is the name of the bridge you want to run. Right now we only support two
bridges: `poa-rialto` and `rialto-millau`.
bridges: `rialto-millau` and `westend-millau`.
```bash
./run.sh poa-rialto
./run.sh rialto-millau
```
If you add a second `update` argument to the script it will pull the latest images from Docker Hub
@@ -66,7 +66,7 @@ and restart the deployment.
You can also bring down a deployment using the script with the `stop` argument.
```bash
./run.sh poa-rialto stop
./run.sh rialto-millau stop
```
### Adding Deployments
@@ -80,7 +80,6 @@ not strictly required.
## General Notes
Rialto authorities are named: `Alice`, `Bob`, `Charlie`, `Dave`, `Eve`.
Rialto-PoA authorities are named: `Arthur`, `Bertha`, `Carlos`.
Millau authorities are named: `Alice`, `Bob`, `Charlie`, `Dave`, `Eve`.
Both authorities and following accounts have enough funds (for test purposes) on corresponding Substrate chains:
@@ -89,8 +88,8 @@ Both authorities and following accounts have enough funds (for test purposes) on
- on Millau: `Ferdie`, `George`, `Harry`.
Names of accounts on Substrate (Rialto and Millau) chains may be prefixed with `//` and used as
seeds for the `sr25519` keys. This seed may also be used in the signer argument in Substrate
and PoA relays. Example:
seeds for the `sr25519` keys. This seed may also be used in the signer argument in Substrate relays.
Example:
```bash
./substrate-relay relay-headers rialto-to-millau \
@@ -105,13 +104,6 @@ and PoA relays. Example:
Some accounts are used by bridge components. Using these accounts to sign other transactions
is not recommended, because this may lead to nonces conflict.
Following accounts are used when `poa-rialto` bridge is running:
- Rialto's `Alice` signs relay transactions with new Rialto-PoA headers;
- Rialto's `Bob` signs relay transactions with Rialto-PoA -> Rialto currency exchange proofs.
- Rialto-PoA's `Arthur`: signs relay transactions with new Rialto headers;
- Rialto-PoA's `Bertha`: signs currency exchange transactions.
Following accounts are used when `rialto-millau` bridge is running:
- Millau's `Charlie` signs complex headers+messages relay transactions on Millau chain;
@@ -133,7 +125,7 @@ Following accounts are used when `westend-millau` bridge is running:
When the network is running you can query logs from individual nodes using:
```bash
docker logs rialto_poa-node-bertha_1 -f
docker logs rialto_millau-node-charlie_1 -f
```
To kill all leftover containers and start the network from scratch next time:
@@ -190,7 +182,6 @@ Here are the arguments currently supported:
- `PROJECT`: Project to build withing bridges repo. Can be one of:
- `rialto-bridge-node`
- `millau-bridge-node`
- `ethereum-poa-relay`
- `substrate-relay`
### GitHub Actions
@@ -1,26 +0,0 @@
FROM docker.io/library/node:12 as build-deps
# install tools and dependencies
RUN set -eux; \
apt-get install -y git
# clone UI repo
RUN cd /usr/src/ && git clone https://github.com/paritytech/bridge-ui.git
WORKDIR /usr/src/bridge-ui
RUN yarn
ARG SUBSTRATE_PROVIDER
ARG ETHEREUM_PROVIDER
ARG EXPECTED_ETHEREUM_NETWORK_ID
ENV SUBSTRATE_PROVIDER $SUBSTRATE_PROVIDER
ENV ETHEREUM_PROVIDER $ETHEREUM_PROVIDER
ENV EXPECTED_ETHEREUM_NETWORK_ID $EXPECTED_ETHEREUM_NETWORK_ID
RUN yarn build:docker
# Stage 2 - the production environment
FROM docker.io/library/nginx:1.12
COPY --from=build-deps /usr/src/bridge-ui/nginx/*.conf /etc/nginx/conf.d/
COPY --from=build-deps /usr/src/bridge-ui/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
@@ -1,474 +0,0 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": "-- Grafana --",
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"gnetId": null,
"graphTooltip": 0,
"links": [],
"panels": [
{
"datasource": "Prometheus",
"description": "",
"fieldConfig": {
"defaults": {
"custom": {},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 7,
"w": 7,
"x": 0,
"y": 0
},
"id": 2,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"mean"
],
"fields": "",
"values": false
},
"textMode": "auto"
},
"pluginVersion": "7.1.3",
"targets": [
{
"expr": "Ethereum_to_Substrate_Exchange_best_block_numbers",
"instant": true,
"interval": "",
"legendFormat": "Best {{type}} block",
"refId": "A"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Best finalized blocks",
"type": "stat"
},
{
"datasource": "Prometheus",
"fieldConfig": {
"defaults": {
"custom": {},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 7,
"w": 5,
"x": 7,
"y": 0
},
"id": 12,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"mean"
],
"fields": "",
"values": false
},
"textMode": "auto"
},
"pluginVersion": "7.1.3",
"targets": [
{
"expr": "Ethereum_to_Substrate_Exchange_processed_blocks",
"instant": true,
"interval": "",
"legendFormat": "",
"refId": "A"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Number of processed blocks since last restart",
"type": "stat"
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "Prometheus",
"fieldConfig": {
"defaults": {
"custom": {},
"links": []
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 9,
"w": 6,
"x": 12,
"y": 0
},
"hiddenSeries": false,
"id": 6,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"nullPointMode": "null",
"percentage": false,
"pluginVersion": "7.1.3",
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": "Ethereum_to_Substrate_Exchange_system_average_load",
"interval": "",
"legendFormat": "Average system load in last {{over}}",
"refId": "A"
}
],
"thresholds": [
{
"colorMode": "critical",
"fill": true,
"line": true,
"op": "gt",
"value": null
}
],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Average System Load",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
},
{
"datasource": "Prometheus",
"description": "",
"fieldConfig": {
"defaults": {
"custom": {},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 9,
"w": 6,
"x": 18,
"y": 0
},
"id": 8,
"options": {
"orientation": "auto",
"reduceOptions": {
"calcs": [
"mean"
],
"fields": "",
"values": false
},
"showThresholdLabels": false,
"showThresholdMarkers": true
},
"pluginVersion": "7.1.3",
"targets": [
{
"expr": "Ethereum_to_Substrate_Exchange_process_cpu_usage_percentage",
"interval": "",
"legendFormat": "1 CPU = 100",
"refId": "A"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Relay Process CPU Usage",
"type": "gauge"
},
{
"datasource": "Prometheus",
"fieldConfig": {
"defaults": {
"custom": {},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 10,
"w": 12,
"x": 0,
"y": 7
},
"id": 14,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"mean"
],
"fields": "",
"values": false
},
"textMode": "auto"
},
"pluginVersion": "7.1.3",
"targets": [
{
"expr": "Ethereum_to_Substrate_Exchange_processed_transactions",
"instant": true,
"interval": "",
"legendFormat": "{{type}} transactions",
"refId": "A"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Number of processed transactions since last restart",
"type": "stat"
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "Prometheus",
"fieldConfig": {
"defaults": {
"custom": {},
"links": []
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 9
},
"hiddenSeries": false,
"id": 10,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"nullPointMode": "null",
"percentage": false,
"pluginVersion": "7.1.3",
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": "Ethereum_to_Substrate_Exchange_process_memory_usage_bytes / 1024 / 1024",
"interval": "",
"legendFormat": "Process memory, MB",
"refId": "A"
}
],
"thresholds": [],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Memory Usage for Relay Process",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
}
],
"refresh": "5s",
"schemaVersion": 26,
"style": "dark",
"tags": [],
"templating": {
"list": []
},
"time": {
"from": "now-5m",
"to": "now"
},
"timepicker": {
"refresh_intervals": [
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
]
},
"timezone": "",
"title": "Ethereum PoA to Rialto Exchange Dashboard",
"uid": "relay-poa-to-rialto-exchange",
"version": 1
}
@@ -1,694 +0,0 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": "-- Grafana --",
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"gnetId": null,
"graphTooltip": 0,
"links": [],
"panels": [
{
"alert": {
"alertRuleTags": {},
"conditions": [
{
"evaluator": {
"params": [
5
],
"type": "gt"
},
"operator": {
"type": "and"
},
"query": {
"params": [
"A",
"5m",
"now"
]
},
"reducer": {
"params": [],
"type": "min"
},
"type": "query"
}
],
"executionErrorState": "alerting",
"for": "5m",
"frequency": "5m",
"handler": 1,
"message": "",
"name": "Synced Header Difference is Over 5 (Ethereum PoA to Rialto)",
"noDataState": "no_data",
"notifications": []
},
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "Prometheus",
"description": "Shows how many headers behind the target chain is from the source chain.",
"fieldConfig": {
"defaults": {
"custom": {}
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 0
},
"hiddenSeries": false,
"id": 14,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"nullPointMode": "null",
"percentage": false,
"pluginVersion": "7.1.3",
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": "max(Ethereum_to_Substrate_Sync_best_block_numbers{node=\"source\"}) - max(Ethereum_to_Substrate_Sync_best_block_numbers{node=\"target\"})",
"format": "table",
"instant": false,
"interval": "",
"legendFormat": "",
"refId": "A"
}
],
"thresholds": [
{
"colorMode": "critical",
"fill": true,
"line": true,
"op": "gt",
"value": 5
}
],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Difference Between Source and Target Headers",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
},
{
"alert": {
"alertRuleTags": {},
"conditions": [
{
"evaluator": {
"params": [
5
],
"type": "lt"
},
"operator": {
"type": "and"
},
"query": {
"params": [
"A",
"2m",
"now"
]
},
"reducer": {
"params": [],
"type": "min"
},
"type": "query"
}
],
"executionErrorState": "alerting",
"for": "3m",
"frequency": "5m",
"handler": 1,
"name": "No New Headers (Ethereum PoA to Rialto)",
"noDataState": "no_data",
"notifications": []
},
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "Prometheus",
"description": "How many headers has the relay synced from the source node in the last 2 mins?",
"fieldConfig": {
"defaults": {
"custom": {}
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 0
},
"hiddenSeries": false,
"id": 16,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"nullPointMode": "null",
"percentage": false,
"pluginVersion": "7.1.3",
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": "max_over_time(Ethereum_to_Substrate_Sync_best_block_numbers{node=\"source\"}[2m])-min_over_time(Ethereum_to_Substrate_Sync_best_block_numbers{node=\"source\"}[2m])",
"interval": "",
"legendFormat": "Number of new Headers on Ethereum PoA (Last 2 Mins)",
"refId": "A"
}
],
"thresholds": [
{
"colorMode": "critical",
"fill": true,
"line": true,
"op": "lt",
"value": 5
}
],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Headers Synced on Rialto (Last 2 Mins)",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
},
{
"datasource": "Prometheus",
"fieldConfig": {
"defaults": {
"custom": {
"align": null
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 6,
"w": 12,
"x": 0,
"y": 8
},
"id": 2,
"interval": "5s",
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"mean"
],
"fields": "",
"values": false
},
"textMode": "auto"
},
"pluginVersion": "7.1.3",
"targets": [
{
"expr": "Ethereum_to_Substrate_Sync_best_block_numbers",
"format": "time_series",
"instant": true,
"interval": "",
"intervalFactor": 1,
"legendFormat": "Best Known Header on {{node}} Node",
"refId": "A"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Best Blocks according to Relay",
"type": "stat"
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "Prometheus",
"description": "",
"fieldConfig": {
"defaults": {
"custom": {}
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 8,
"w": 6,
"x": 12,
"y": 8
},
"hiddenSeries": false,
"id": 6,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"nullPointMode": "null",
"percentage": false,
"pluginVersion": "7.1.3",
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": "Ethereum_to_Substrate_Sync_system_average_load",
"interval": "",
"legendFormat": "Average system load in last {{over}}",
"refId": "A"
}
],
"thresholds": [
{
"colorMode": "critical",
"fill": true,
"line": true,
"op": "gt",
"value": null
}
],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Average System Load",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
},
{
"datasource": "Prometheus",
"fieldConfig": {
"defaults": {
"custom": {},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 6,
"x": 18,
"y": 8
},
"id": 12,
"options": {
"orientation": "auto",
"reduceOptions": {
"calcs": [
"mean"
],
"fields": "",
"values": false
},
"showThresholdLabels": false,
"showThresholdMarkers": true
},
"pluginVersion": "7.1.3",
"targets": [
{
"expr": "avg_over_time(Ethereum_to_Substrate_Sync_process_cpu_usage_percentage[1m])",
"instant": true,
"interval": "",
"legendFormat": "1 CPU = 100",
"refId": "A"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Relay Process CPU Usage ",
"type": "gauge"
},
{
"datasource": "Prometheus",
"description": "",
"fieldConfig": {
"defaults": {
"custom": {},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 10,
"w": 12,
"x": 0,
"y": 14
},
"id": 4,
"options": {
"displayMode": "gradient",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"mean"
],
"fields": "",
"values": false
},
"showUnfilled": true
},
"pluginVersion": "7.1.3",
"targets": [
{
"expr": "Ethereum_to_Substrate_Sync_blocks_in_state",
"instant": true,
"interval": "",
"legendFormat": "{{state}}",
"refId": "A"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Queued Headers in Relay",
"type": "bargauge"
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "Prometheus",
"description": "",
"fieldConfig": {
"defaults": {
"custom": {}
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 16
},
"hiddenSeries": false,
"id": 10,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"nullPointMode": "null",
"percentage": false,
"pluginVersion": "7.1.3",
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": "Ethereum_to_Substrate_Sync_process_memory_usage_bytes / 1024 / 1024",
"interval": "",
"legendFormat": "Process memory, MB",
"refId": "A"
}
],
"thresholds": [],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Memory Usage for Relay Process",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
}
],
"refresh": "5s",
"schemaVersion": 26,
"style": "dark",
"tags": [],
"templating": {
"list": []
},
"time": {
"from": "now-5m",
"to": "now"
},
"timepicker": {
"refresh_intervals": [
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
]
},
"timezone": "",
"title": "Ethereum PoA to Rialto Header Sync Dashboard",
"uid": "relay-poa-to-rialto-headers",
"version": 1
}
@@ -1,694 +0,0 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": "-- Grafana --",
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"gnetId": null,
"graphTooltip": 0,
"links": [],
"panels": [
{
"alert": {
"alertRuleTags": {},
"conditions": [
{
"evaluator": {
"params": [
5
],
"type": "gt"
},
"operator": {
"type": "and"
},
"query": {
"params": [
"A",
"5m",
"now"
]
},
"reducer": {
"params": [],
"type": "min"
},
"type": "query"
}
],
"executionErrorState": "alerting",
"for": "5m",
"frequency": "5m",
"handler": 1,
"message": "",
"name": "Synced Header Difference is Over 5 (Rialto to Ethereum PoA)",
"noDataState": "no_data",
"notifications": []
},
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "Prometheus",
"description": "Shows how many headers behind the target chain is from the source chain.",
"fieldConfig": {
"defaults": {
"custom": {}
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 0
},
"hiddenSeries": false,
"id": 14,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"nullPointMode": "null",
"percentage": false,
"pluginVersion": "7.1.3",
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": "max(Substrate_to_Ethereum_Sync_best_block_numbers{node=\"source\"}) - max(Substrate_to_Ethereum_Sync_best_block_numbers{node=\"target\"})",
"format": "table",
"instant": false,
"interval": "",
"legendFormat": "",
"refId": "A"
}
],
"thresholds": [
{
"colorMode": "critical",
"fill": true,
"line": true,
"op": "gt",
"value": 5
}
],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Difference Between Source and Target Headers",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
},
{
"alert": {
"alertRuleTags": {},
"conditions": [
{
"evaluator": {
"params": [
5
],
"type": "lt"
},
"operator": {
"type": "and"
},
"query": {
"params": [
"A",
"2m",
"now"
]
},
"reducer": {
"params": [],
"type": "min"
},
"type": "query"
}
],
"executionErrorState": "alerting",
"for": "3m",
"frequency": "5m",
"handler": 1,
"name": "No New Headers (Rialto to Ethereum PoA)",
"noDataState": "no_data",
"notifications": []
},
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "Prometheus",
"description": "How many headers has the relay synced from the source node in the last 2 mins?",
"fieldConfig": {
"defaults": {
"custom": {}
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 0
},
"hiddenSeries": false,
"id": 16,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"nullPointMode": "null",
"percentage": false,
"pluginVersion": "7.1.3",
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": "max_over_time(Substrate_to_Ethereum_Sync_best_block_numbers{node=\"source\"}[2m])-min_over_time(Substrate_to_Ethereum_Sync_best_block_numbers{node=\"source\"}[2m])",
"interval": "",
"legendFormat": "Number of new Headers on Rialto (Last 2 Mins)",
"refId": "A"
}
],
"thresholds": [
{
"colorMode": "critical",
"fill": true,
"line": true,
"op": "lt",
"value": 5
}
],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Headers Synced on Ethereum PoA (Last 2 Mins)",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
},
{
"datasource": "Prometheus",
"fieldConfig": {
"defaults": {
"custom": {
"align": null
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 6,
"w": 12,
"x": 0,
"y": 8
},
"id": 2,
"interval": "5s",
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"mean"
],
"fields": "",
"values": false
},
"textMode": "auto"
},
"pluginVersion": "7.1.3",
"targets": [
{
"expr": "Substrate_to_Ethereum_Sync_best_block_numbers",
"format": "time_series",
"instant": true,
"interval": "",
"intervalFactor": 1,
"legendFormat": "Best Known Header on {{node}} Node",
"refId": "A"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Best Blocks according to Relay",
"type": "stat"
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "Prometheus",
"description": "",
"fieldConfig": {
"defaults": {
"custom": {}
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 8,
"w": 6,
"x": 12,
"y": 8
},
"hiddenSeries": false,
"id": 6,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"nullPointMode": "null",
"percentage": false,
"pluginVersion": "7.1.3",
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": "Substrate_to_Ethereum_Sync_system_average_load",
"interval": "",
"legendFormat": "Average system load in last {{over}}",
"refId": "A"
}
],
"thresholds": [
{
"colorMode": "critical",
"fill": true,
"line": true,
"op": "gt",
"value": null
}
],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Average System Load",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
},
{
"datasource": "Prometheus",
"fieldConfig": {
"defaults": {
"custom": {},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 6,
"x": 18,
"y": 8
},
"id": 12,
"options": {
"orientation": "auto",
"reduceOptions": {
"calcs": [
"mean"
],
"fields": "",
"values": false
},
"showThresholdLabels": false,
"showThresholdMarkers": true
},
"pluginVersion": "7.1.3",
"targets": [
{
"expr": "avg_over_time(Substrate_to_Ethereum_Sync_process_cpu_usage_percentage[1m])",
"instant": true,
"interval": "",
"legendFormat": "1 CPU = 100",
"refId": "A"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Relay Process CPU Usage ",
"type": "gauge"
},
{
"datasource": "Prometheus",
"description": "",
"fieldConfig": {
"defaults": {
"custom": {},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 10,
"w": 12,
"x": 0,
"y": 14
},
"id": 4,
"options": {
"displayMode": "gradient",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"mean"
],
"fields": "",
"values": false
},
"showUnfilled": true
},
"pluginVersion": "7.1.3",
"targets": [
{
"expr": "Substrate_to_Ethereum_Sync_blocks_in_state",
"instant": true,
"interval": "",
"legendFormat": "{{state}}",
"refId": "A"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Queued Headers in Relay",
"type": "bargauge"
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "Prometheus",
"description": "",
"fieldConfig": {
"defaults": {
"custom": {}
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 16
},
"hiddenSeries": false,
"id": 10,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"nullPointMode": "null",
"percentage": false,
"pluginVersion": "7.1.3",
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": "Substrate_to_Ethereum_Sync_process_memory_usage_bytes / 1024 / 1024",
"interval": "",
"legendFormat": "Process memory, MB",
"refId": "A"
}
],
"thresholds": [],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Memory Usage for Relay Process",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
}
],
"refresh": "5s",
"schemaVersion": 26,
"style": "dark",
"tags": [],
"templating": {
"list": []
},
"time": {
"from": "now-5m",
"to": "now"
},
"timepicker": {
"refresh_intervals": [
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
]
},
"timezone": "",
"title": "Rialto to Ethereum PoA Header Sync Dashboard",
"uid": "relay-rialto-to-poa-headers",
"version": 1
}
@@ -1,4 +0,0 @@
- targets:
- relay-headers-poa-to-rialto:9616
- relay-poa-exchange-rialto:9616
- relay-headers-rialto-to-poa:9616
@@ -1,94 +0,0 @@
# This Compose file should be built using the Rialto and Eth-PoA node
# compose files. Otherwise it won't work.
#
# Exposed ports: 9616, 9716, 9816, 9916, 8080
version: '3.5'
services:
# We override these nodes to make sure we have the correct chain config for this network.
poa-node-arthur: &poa-node
volumes:
- ./bridges/poa-rialto/poa-config:/config
poa-node-bertha:
<<: *poa-node
poa-node-carlos:
<<: *poa-node
# We provide an override for this particular node since this is a public facing
# node which we use to connect from things like Polkadot JS Apps.
rialto-node-charlie:
environment:
VIRTUAL_HOST: rialto.bridges.test-installations.parity.io,wss.rialto.brucke.link
VIRTUAL_PORT: 9944
LETSENCRYPT_HOST: rialto.bridges.test-installations.parity.io,wss.rialto.brucke.link
LETSENCRYPT_EMAIL: admin@parity.io
relay-headers-poa-to-rialto: &eth-poa-relay
image: paritytech/ethereum-poa-relay
entrypoint: /entrypoints/relay-headers-poa-to-rialto-entrypoint.sh
volumes:
- ./bridges/poa-rialto/entrypoints:/entrypoints
environment:
RUST_LOG: rpc=trace,bridge=trace
ports:
- "9616:9616"
depends_on: &all-nodes
- poa-node-arthur
- poa-node-bertha
- poa-node-carlos
- rialto-node-alice
- rialto-node-bob
- rialto-node-charlie
- rialto-node-dave
- rialto-node-eve
relay-poa-exchange-rialto:
<<: *eth-poa-relay
entrypoint: /entrypoints/relay-poa-exchange-rialto-entrypoint.sh
ports:
- "9716:9616"
relay-headers-rialto-to-poa:
<<: *eth-poa-relay
entrypoint: /entrypoints/relay-headers-rialto-to-poa-entrypoint.sh
ports:
- "9816:9616"
poa-exchange-tx-generator:
<<: *eth-poa-relay
entrypoint: /entrypoints/poa-exchange-tx-generator-entrypoint.sh
environment:
EXCHANGE_GEN_MIN_AMOUNT_FINNEY: ${EXCHANGE_GEN_MIN_AMOUNT_FINNEY:-1}
EXCHANGE_GEN_MAX_AMOUNT_FINNEY: ${EXCHANGE_GEN_MAX_AMOUNT_FINNEY:-100000}
EXCHANGE_GEN_MAX_SUBMIT_DELAY_S: ${EXCHANGE_GEN_MAX_SUBMIT_DELAY_S:-60}
ports:
- "9916:9616"
depends_on:
- relay-headers-poa-to-rialto
- relay-headers-rialto-to-poa
front-end:
build:
context: .
dockerfile: ./bridges/poa-rialto/Front-end.Dockerfile
args:
SUBSTRATE_PROVIDER: ${UI_SUBSTRATE_PROVIDER:-ws://localhost:9944}
ETHEREUM_PROVIDER: ${UI_ETHEREUM_PROVIDER:-http://localhost:8545}
EXPECTED_ETHEREUM_NETWORK_ID: ${UI_EXPECTED_ETHEREUM_NETWORK_ID:-105}
ports:
- "8080:80"
# Note: These are being overridden from the top level `monitoring` compose file.
prometheus-metrics:
volumes:
- ./bridges/poa-rialto/dashboard/prometheus/targets.yml:/etc/prometheus/targets-poa-rialto.yml
depends_on: *all-nodes
grafana-dashboard:
volumes:
- ./bridges/poa-rialto/dashboard/grafana:/etc/grafana/dashboards/poa-rialto:ro
environment:
VIRTUAL_HOST: dashboard.rialto.bridges.test-installations.parity.io,grafana.rialto.brucke.link
VIRTUAL_PORT: 3000
LETSENCRYPT_HOST: dashboard.rialto.bridges.test-installations.parity.io,grafana.rialto.brucke.link
LETSENCRYPT_EMAIL: admin@parity.io
@@ -1,99 +0,0 @@
#!/bin/bash
# THIS SCRIPT IS NOT INTENDED FOR USE IN PRODUCTION ENVIRONMENT
#
# This scripts periodically calls relay binary to generate PoA -> Substrate
# exchange transaction from hardcoded PoA senders (assuming they have
# enough funds) to hardcoded Substrate recipients.
set -eu
# Path to relay binary
RELAY_BINARY_PATH=${RELAY_BINARY_PATH:-./ethereum-poa-relay}
# Ethereum node host
ETH_HOST=${ETH_HOST:-poa-node-arthur}
# Ethereum node websocket port
ETH_PORT=${ETH_PORT:-8546}
# Ethereum chain id
ETH_CHAIN_ID=${ETH_CHAIN_ID:-105}
# All possible Substrate recipients (hex-encoded public keys)
SUB_RECIPIENTS=(
# Alice (5GrwvaEF...)
"d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d"\
# Bob (5FHneW46...)
"8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48"\
# Charlie (5FLSigC9...)
"90b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22"\
# Dave (5DAAnrj7...)
"306721211d5404bd9da88e0204360a1a9ab8b87c66c1bc2fcdd37f3c2222cc20"\
# Eve (5HGjWAeF...)
"e659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df4e"\
# Ferdie (5CiPPseX...)
"1cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c"
)
# All possible Ethereum signers (hex-encoded private keys)
# (note that we're tracking nonce here => sender must not send concurrent transactions)
ETH_SIGNERS=(
# Bertha account (0x007594304039c2937a12220338aab821d819f5a4) and its current nonce (unknown by default)
"bc10e0f21e33456ade82182dd1ebdbdd89bca923d4e4adbd90fb5b44d7098cbe" ""
)
# Minimal exchange amount (in finney)
MIN_EXCHANGE_AMOUNT_FINNEY=${EXCHANGE_GEN_MIN_AMOUNT_FINNEY:-1} # 0.1 ETH
# Maximal exchange amount (in finney)
MAX_EXCHANGE_AMOUNT_FINNEY=${EXCHANGE_GEN_MAX_AMOUNT_FINNEY:-100000} # 100 ETH
# Max delay before submitting transactions (s)
MAX_SUBMIT_DELAY_S=${EXCHANGE_GEN_MAX_SUBMIT_DELAY_S:-60}
while true
do
# sleep some time
SUBMIT_DELAY_S=`shuf -i 0-$MAX_SUBMIT_DELAY_S -n 1`
echo "Sleeping $SUBMIT_DELAY_S seconds..."
sleep $SUBMIT_DELAY_S
# select recipient
SUB_RECIPIENTS_MAX_INDEX=$((${#SUB_RECIPIENTS[@]} - 1))
SUB_RECIPIENT_INDEX=`shuf -i 0-$SUB_RECIPIENTS_MAX_INDEX -n 1`
SUB_RECIPIENT=${SUB_RECIPIENTS[$SUB_RECIPIENT_INDEX]}
# select signer
ETH_SIGNERS_MAX_INDEX=$(((${#ETH_SIGNERS[@]} - 1) / 2))
ETH_SIGNERS_INDEX=`shuf -i 0-$ETH_SIGNERS_MAX_INDEX -n 1`
ETH_SIGNER_INDEX=$(($ETH_SIGNERS_INDEX * 2))
ETH_SIGNER_NONCE_INDEX=$(($ETH_SIGNER_INDEX + 1))
ETH_SIGNER=${ETH_SIGNERS[$ETH_SIGNER_INDEX]}
ETH_SIGNER_NONCE=${ETH_SIGNERS[$ETH_SIGNER_NONCE_INDEX]}
if [ -z $ETH_SIGNER_NONCE ]; then
ETH_SIGNER_NONCE_ARG=
else
ETH_SIGNER_NONCE_ARG=`printf -- "--eth-nonce=%s" $ETH_SIGNER_NONCE`
fi
# select amount
EXCHANGE_AMOUNT_FINNEY=`shuf -i $MIN_EXCHANGE_AMOUNT_FINNEY-$MAX_EXCHANGE_AMOUNT_FINNEY -n 1`
EXCHANGE_AMOUNT_ETH=`printf "%s000" $EXCHANGE_AMOUNT_FINNEY`
# submit transaction
echo "Sending $EXCHANGE_AMOUNT_ETH from PoA:$ETH_SIGNER to Substrate:$SUB_RECIPIENT. Nonce: $ETH_SIGNER_NONCE"
set -x
SUBMIT_OUTPUT=`$RELAY_BINARY_PATH 2>&1 eth-submit-exchange-tx \
--sub-recipient=$SUB_RECIPIENT \
--eth-host=$ETH_HOST \
--eth-port=$ETH_PORT \
--eth-chain-id=$ETH_CHAIN_ID \
--eth-signer=$ETH_SIGNER \
--eth-amount=$EXCHANGE_AMOUNT_ETH \
$ETH_SIGNER_NONCE_ARG`
set +x
# update sender nonce
SUBMIT_OUTPUT_RE='nonce: ([0-9]+)'
if [[ $SUBMIT_OUTPUT =~ $SUBMIT_OUTPUT_RE ]]; then
ETH_SIGNER_NONCE=${BASH_REMATCH[1]}
ETH_SIGNERS[$ETH_SIGNER_NONCE_INDEX]=$(($ETH_SIGNER_NONCE + 1))
else
echo "Missing nonce in relay response: $SUBMIT_OUTPUT"
exit 1
fi
done
@@ -1,15 +0,0 @@
#!/bin/bash
set -xeu
sleep 60
curl -v http://poa-node-arthur:8545/api/health
curl -v http://poa-node-bertha:8545/api/health
curl -v http://poa-node-carlos:8545/api/health
curl -v http://rialto-node-alice:9933/health
curl -v http://rialto-node-bob:9933/health
curl -v http://rialto-node-charlie:9933/health
/home/user/ethereum-poa-relay eth-to-sub \
--sub-host rialto-node-alice \
--eth-host poa-node-arthur \
--prometheus-host=0.0.0.0
@@ -1,26 +0,0 @@
#!/bin/bash
set -xeu
sleep 60
curl -v http://rialto-node-bob:9933/health
curl -v http://poa-node-bertha:8545/api/health
# Try to deploy contracts first
# networkID = 0x69
# Arthur's key.
/home/user/ethereum-poa-relay eth-deploy-contract \
--eth-chain-id 105 \
--eth-signer 0399dbd15cf6ee8250895a1f3873eb1e10e23ca18e8ed0726c63c4aea356e87d \
--sub-host rialto-node-bob \
--eth-host poa-node-bertha || echo "Failed to deploy contracts."
sleep 10
echo "Starting SUB -> ETH relay"
/home/user/ethereum-poa-relay sub-to-eth \
--eth-contract c9a61fb29e971d1dabfd98657969882ef5d0beee \
--eth-chain-id 105 \
--eth-signer 0399dbd15cf6ee8250895a1f3873eb1e10e23ca18e8ed0726c63c4aea356e87d \
--sub-host rialto-node-bob \
--eth-host poa-node-bertha \
--prometheus-host=0.0.0.0
@@ -1,16 +0,0 @@
#!/bin/bash
set -xeu
sleep 60
curl -v http://poa-node-arthur:8545/api/health
curl -v http://poa-node-bertha:8545/api/health
curl -v http://poa-node-carlos:8545/api/health
curl -v http://rialto-node-alice:9933/health
curl -v http://rialto-node-bob:9933/health
curl -v http://rialto-node-charlie:9933/health
/home/user/ethereum-poa-relay eth-exchange-sub \
--sub-host rialto-node-alice \
--sub-signer //Bob \
--eth-host poa-node-arthur \
--prometheus-host=0.0.0.0
@@ -1 +0,0 @@
{"id":"dd04f316-bc9d-2deb-4a34-51014cd5f34f","version":3,"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"aa91e6f0e6cf48208be4a1bcf15c6f30"},"ciphertext":"6e057599b13a87e8181bb39a40e14848fdc97958d493ddfa6bb1260350f69328","kdf":"pbkdf2","kdfparams":{"c":10240,"dklen":32,"prf":"hmac-sha256","salt":"79dd8c09c5c066b830179a2558a51efca6d97c0db2c4128090a01835786823c5"},"mac":"8f8b8e2c9de29ec8eefc54a60055e30ae7ff4dd4a367eaf38880edb887da771e"},"address":"005e714f896a8b7cede9d38688c1a81de72a58e4","name":"","meta":"{}"}
@@ -1 +0,0 @@
{"id":"6d1e690f-0b52-35f7-989b-46100e7c65ed","version":3,"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"a5b4d0466834e75c9fd29c6cbbac57ad"},"ciphertext":"102ac328cbe66d8cb8515c42e3268776a9be4419a5cb7b79852860b1e691c15b","kdf":"pbkdf2","kdfparams":{"c":10240,"dklen":32,"prf":"hmac-sha256","salt":"e8daf2e70086b0cacf925d368fd3f60cada1285e39a42c4cc73c135368cfdbef"},"mac":"1bc3b750900a1143c64ba9e677d69e1093aab47cb003ba09f3cd595a3b422db5"},"address":"007594304039c2937a12220338aab821d819f5a4","name":"","meta":"{}"}
@@ -1 +0,0 @@
{"id":"ffaebba1-f1b9-8758-7034-0314040b1396","version":3,"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"97f124bc8a7bf55d00eb2755c2b50364"},"ciphertext":"b87827816f33d2bef2dc3102a8a7744b86912f8ace10e45cb282a13487769ed2","kdf":"pbkdf2","kdfparams":{"c":10240,"dklen":32,"prf":"hmac-sha256","salt":"3114c67a05bff7831d112083f566b176bfc874aea160eebadbe5564e406ee85c"},"mac":"e9bfe8fd6f612bc036bb57659297fc03db022264f5086a1b5726972d3ab6f64a"},"address":"004e7a39907f090e19b0b80a277e77b72b22e269","name":"","meta":"{}"}
@@ -1 +0,0 @@
{"id":"ef9eb431-dc73-cf31-357e-736f64febe68","version":3,"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"7077f1c4170d9fc2e05c5956be32fb51"},"ciphertext":"a053be448768d984257aeb8f9c7913e3f54c6e6e741accad9f09dd70c2d9828c","kdf":"pbkdf2","kdfparams":{"c":10240,"dklen":32,"prf":"hmac-sha256","salt":"12580aa4624040970301e7474d3f9b2a93552bfe9ea2517f7119ccf8e91ebd0d"},"mac":"796dbb48adcfc09041fe39121632801d9f950d3c73dd47105180d8097d4f4491"},"address":"00eed42bf93b498f28acd21d207427a14074defe","name":"","meta":"{}"}
@@ -1 +0,0 @@
password
@@ -1,20 +0,0 @@
[parity]
chain = "/config/poa.json"
keys_path = "/config/keys"
no_persistent_txqueue = true
[account]
password = ["/config/pass"]
[network]
reserved_peers = "/config/reserved"
[rpc]
apis = ["all"]
cors = ["moz-extension://*", "chrome-extension://*"]
[mining]
force_sealing = true
[misc]
unsafe_expose = true
@@ -1,184 +0,0 @@
{
"name": "BridgePoa",
"engine": {
"authorityRound": {
"params": {
"stepDuration": 10,
"validators": {
"list": [
"0x005e714f896a8b7cede9d38688c1a81de72a58e4",
"0x007594304039c2937a12220338aab821d819f5a4",
"0x004e7a39907f090e19b0b80a277e77b72b22e269"
]
},
"validateScoreTransition": 0,
"validateStepTransition": 0,
"maximumUncleCountTransition": 0,
"maximumUncleCount": 0,
"emptyStepsTransition": "0xfffffffff",
"maximumEmptySteps": 1
}
}
},
"params": {
"accountStartNonce": "0x0",
"eip1014Transition": "0x0",
"eip1052Transition": "0x0",
"eip140Transition": "0x0",
"eip145Transition": "0x0",
"eip150Transition": "0x0",
"eip155Transition": "0x0",
"eip160Transition": "0x0",
"eip161abcTransition": "0x0",
"eip161dTransition": "0x0",
"eip211Transition": "0x0",
"eip214Transition": "0x0",
"eip658Transition": "0x0",
"eip98Transition": "0x7fffffffffffff",
"gasLimitBoundDivisor": "0x0400",
"maxCodeSize": 24576,
"maxCodeSizeTransition": "0x0",
"maximumExtraDataSize": "0x20",
"minGasLimit": "0x1388",
"networkID" : "0x69",
"validateChainIdTransition": "0x0",
"validateReceiptsTransition": "0x0"
},
"genesis": {
"seal": {
"authorityRound": {
"step": "0x0",
"signature": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
}
},
"difficulty": "0x20000",
"author": "0x0000000000000000000000000000000000000000",
"timestamp": "0x00",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"extraData": "0x",
"gasLimit": "0x222222"
},
"accounts": {
"0000000000000000000000000000000000000001": { "balance": "1", "nonce": "1048576", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } },
"0000000000000000000000000000000000000002": { "balance": "1", "nonce": "1048576", "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } },
"0000000000000000000000000000000000000003": { "balance": "1", "nonce": "1048576", "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } },
"0000000000000000000000000000000000000004": { "balance": "1", "nonce": "1048576", "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } },
"0000000000000000000000000000000000000005": { "balance": "1", "builtin": { "name": "modexp", "activate_at": 0, "pricing": { "modexp": { "divisor": 20 } } } },
"0000000000000000000000000000000000000006": {
"balance": "1",
"builtin": {
"name": "alt_bn128_add",
"pricing": {
"0": {
"price": { "alt_bn128_const_operations": { "price": 500 }}
},
"0x7fffffffffffff": {
"info": "EIP 1108 transition",
"price": { "alt_bn128_const_operations": { "price": 150 }}
}
}
}
},
"0000000000000000000000000000000000000007": {
"balance": "1",
"builtin": {
"name": "alt_bn128_mul",
"pricing": {
"0": {
"price": { "alt_bn128_const_operations": { "price": 40000 }}
},
"0x7fffffffffffff": {
"info": "EIP 1108 transition",
"price": { "alt_bn128_const_operations": { "price": 6000 }}
}
}
}
},
"0000000000000000000000000000000000000008": {
"balance": "1",
"builtin": {
"name": "alt_bn128_pairing",
"pricing": {
"0": {
"price": { "alt_bn128_pairing": { "base": 100000, "pair": 80000 }}
},
"0x7fffffffffffff": {
"info": "EIP 1108 transition",
"price": { "alt_bn128_pairing": { "base": 45000, "pair": 34000 }}
}
}
}
},
"0x0000000000000000000000000000000000000009": {
"builtin": {
"name": "blake2_f",
"activate_at": "0xd751a5",
"pricing": {
"blake2_f": {
"gas_per_round": 1
}
}
}
},
"0x0000000000000000000000000000000000000010": {
"builtin": {
"name": "parse_substrate_header",
"pricing": {
"linear": {
"base": 3000,
"word": 0
}
}
}
},
"0x0000000000000000000000000000000000000011": {
"builtin": {
"name": "get_substrate_header_signal",
"pricing": {
"linear": {
"base": 3000,
"word": 0
}
}
}
},
"0x0000000000000000000000000000000000000012": {
"builtin": {
"name": "verify_substrate_finality_proof",
"pricing": {
"linear": {
"base": 3000,
"word": 0
}
}
}
},
"0x0000000000000000000000000000000000000013": {
"builtin": {
"name": "my_test",
"pricing": {
"linear": {
"base": 3000,
"word": 0
}
}
}
},
"0x005e714f896a8b7cede9d38688c1a81de72a58e4": {
"balance": "1606938044258990275541962092341162602522202993782792835301376",
"nonce": "0x1"
},
"0x007594304039c2937a12220338aab821d819f5a4": {
"balance": "1606938044258990275541962092341162602522202993782792835301376",
"nonce": "0x1"
},
"0x004e7a39907f090e19b0b80a277e77b72b22e269": {
"balance": "1606938044258990275541962092341162602522202993782792835301376",
"nonce": "0x1"
},
"0x00eed42bf93b498f28acd21d207427a14074defe": {
"balance": "1606938044258990275541962092341162602522202993782792835301376",
"nonce": "0x1"
}
}
}
@@ -1,3 +0,0 @@
enode://543d0874df46dff238d62547160f9d11e3d21897d7041bbbe46a04d2ee56d9eaf108f2133c0403159624f7647198e224d0755d23ad0e1a50c0912973af6e8a8a@poa-node-arthur:30303
enode://710de70733e88a24032e53054985f7239e37351f5f3335a468a1a78a3026e9f090356973b00262c346a6608403df2c7107fc4def2cfe4995ea18a41292b9384f@poa-node-bertha:30303
enode://943525f415b9482f1c49bd39eb979e4e2b406f4137450b0553bffa5cba2928e25ff89ef70f7325aad8a75dbb5955eaecc1aee7ac55d66bcaaa07c8ea58adb23a@poa-node-carlos:30303
@@ -1441,7 +1441,7 @@
"pluginVersion": "7.1.3",
"targets": [
{
"expr": "avg_over_time(Millau_to_Rialto_MessageLane_00000000_process_cpu_usage_percentage[1m])",
"expr": "avg_over_time(process_cpu_usage_percentage{instance='relay-millau-rialto:9616'}[1m])",
"instant": true,
"interval": "",
"legendFormat": "1 CPU = 100",
@@ -1499,7 +1499,7 @@
"steppedLine": false,
"targets": [
{
"expr": "Millau_to_Rialto_MessageLane_00000000_system_average_load",
"expr": "system_average_load{instance='relay-millau-rialto:9616'}",
"interval": "",
"legendFormat": "Average system load in last {{over}}",
"refId": "A"
@@ -1592,7 +1592,7 @@
"steppedLine": false,
"targets": [
{
"expr": "Millau_to_Rialto_MessageLane_00000000_process_memory_usage_bytes / 1024 / 1024",
"expr": "process_memory_usage_bytes{instance='relay-millau-rialto:9616'} / 1024 / 1024",
"interval": "",
"legendFormat": "Process memory, MB",
"refId": "A"
@@ -1190,7 +1190,7 @@
"pluginVersion": "7.1.3",
"targets": [
{
"expr": "avg_over_time(Rialto_to_Millau_MessageLane_00000000_process_cpu_usage_percentage[1m])",
"expr": "avg_over_time(process_cpu_usage_percentage{instance='relay-millau-rialto:9616'}[1m])",
"instant": true,
"interval": "",
"legendFormat": "1 CPU = 100",
@@ -1248,7 +1248,7 @@
"steppedLine": false,
"targets": [
{
"expr": "Rialto_to_Millau_MessageLane_00000000_system_average_load",
"expr": "system_average_load{instance='relay-millau-rialto:9616'}",
"interval": "",
"legendFormat": "Average system load in last {{over}}",
"refId": "A"
@@ -1341,7 +1341,7 @@
"steppedLine": false,
"targets": [
{
"expr": "Rialto_to_Millau_MessageLane_00000000_process_memory_usage_bytes / 1024 / 1024",
"expr": "process_memory_usage_bytes{instance='relay-millau-rialto:9616'} / 1024 / 1024",
"interval": "",
"legendFormat": "Process memory, MB",
"refId": "A"
@@ -65,7 +65,7 @@
"targets": [
{
"exemplar": true,
"expr": "Rialto_to_Millau_MessageLane_00000000_rialto_storage_proof_overhead",
"expr": "rialto_storage_proof_overhead{instance='relay-millau-rialto:9616'}",
"interval": "",
"legendFormat": "Actual overhead",
"refId": "A"
@@ -169,14 +169,14 @@
"targets": [
{
"exemplar": true,
"expr": "Westend_to_Millau_Sync_kusama_to_base_conversion_rate / Westend_to_Millau_Sync_polkadot_to_base_conversion_rate",
"expr": "kusama_to_base_conversion_rate{instance='relay-millau-rialto:9616'} / polkadot_to_base_conversion_rate{instance='relay-millau-rialto:9616'}",
"interval": "",
"legendFormat": "Outside of runtime (actually Polkadot -> Kusama)",
"refId": "A"
},
{
"exemplar": true,
"expr": "Rialto_to_Millau_MessageLane_00000000_rialto_millau_to_rialto_conversion_rate",
"expr": "Millau_Rialto_to_Millau_conversion_rate{instance='relay-millau-rialto:9616'}",
"hide": false,
"interval": "",
"legendFormat": "At runtime",
@@ -187,7 +187,7 @@
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Rialto: Millau -> Rialto conversion rate",
"title": "Millau: Rialto -> Millau conversion rate",
"tooltip": {
"shared": true,
"sort": 0,
@@ -273,7 +273,7 @@
"targets": [
{
"exemplar": true,
"expr": "Millau_to_Rialto_MessageLane_00000000_millau_storage_proof_overhead",
"expr": "millau_storage_proof_overhead{instance='relay-millau-rialto:9616'}",
"interval": "",
"legendFormat": "Actual overhead",
"refId": "A"
@@ -377,14 +377,14 @@
"targets": [
{
"exemplar": true,
"expr": "Westend_to_Millau_Sync_polkadot_to_base_conversion_rate / Westend_to_Millau_Sync_kusama_to_base_conversion_rate",
"expr": "polkadot_to_base_conversion_rate{instance='relay-millau-rialto:9616'} / kusama_to_base_conversion_rate{instance='relay-millau-rialto:9616'}",
"interval": "",
"legendFormat": "Outside of runtime (actually Kusama -> Polkadot)",
"refId": "A"
},
{
"exemplar": true,
"expr": "Millau_to_Rialto_MessageLane_00000000_millau_rialto_to_millau_conversion_rate",
"expr": "Rialto_Millau_to_Rialto_conversion_rate{instance='relay-millau-rialto:9616'}",
"hide": false,
"interval": "",
"legendFormat": "At runtime",
@@ -395,7 +395,7 @@
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Millau: Rialto -> Millau conversion rate",
"title": "Rialto: Millau -> Rialto conversion rate",
"tooltip": {
"shared": true,
"sort": 0,
@@ -401,7 +401,7 @@
"steppedLine": false,
"targets": [
{
"expr": "Westend_to_Millau_Sync_system_average_load",
"expr": "system_average_load{instance='relay-headers-westend-to-millau:9616'}",
"interval": "",
"legendFormat": "Average system load in last {{over}}",
"refId": "A"
@@ -500,7 +500,7 @@
"pluginVersion": "7.1.3",
"targets": [
{
"expr": "avg_over_time(Westend_to_Millau_Sync_process_cpu_usage_percentage[1m])",
"expr": "avg_over_time(process_cpu_usage_percentage{instance='relay-headers-westend-to-millau:9616'}[1m])",
"instant": true,
"interval": "",
"legendFormat": "1 CPU = 100",
@@ -615,7 +615,7 @@
"steppedLine": false,
"targets": [
{
"expr": "Westend_to_Millau_Sync_process_memory_usage_bytes / 1024 / 1024",
"expr": "process_memory_usage_bytes{instance='relay-headers-westend-to-millau:9616'} / 1024 / 1024",
"interval": "",
"legendFormat": "Process memory, MB",
"refId": "A"
@@ -1 +0,0 @@
{"id":"dd04f316-bc9d-2deb-4a34-51014cd5f34f","version":3,"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"aa91e6f0e6cf48208be4a1bcf15c6f30"},"ciphertext":"6e057599b13a87e8181bb39a40e14848fdc97958d493ddfa6bb1260350f69328","kdf":"pbkdf2","kdfparams":{"c":10240,"dklen":32,"prf":"hmac-sha256","salt":"79dd8c09c5c066b830179a2558a51efca6d97c0db2c4128090a01835786823c5"},"mac":"8f8b8e2c9de29ec8eefc54a60055e30ae7ff4dd4a367eaf38880edb887da771e"},"address":"005e714f896a8b7cede9d38688c1a81de72a58e4","name":"","meta":"{}"}
@@ -1 +0,0 @@
password
@@ -1,17 +0,0 @@
[parity]
chain = "./deployments/dev/poa-config/poa.json"
keys_path = "./deployments/dev/poa-config/keys"
no_persistent_txqueue = true
[account]
password = ["./deployments/dev/poa-config/pass"]
[rpc]
apis = ["all"]
cors = ["moz-extension://*", "chrome-extension://*"]
[mining]
force_sealing = true
[misc]
unsafe_expose = true
@@ -1,178 +0,0 @@
{
"name": "BridgePoa",
"engine": {
"authorityRound": {
"params": {
"stepDuration": 10,
"validators": {
"list": [
"0x005e714f896a8b7cede9d38688c1a81de72a58e4"
]
},
"validateScoreTransition": 0,
"validateStepTransition": 0,
"maximumUncleCountTransition": 0,
"maximumUncleCount": 0,
"emptyStepsTransition": "0xfffffffff",
"maximumEmptySteps": 1
}
}
},
"params": {
"accountStartNonce": "0x0",
"eip1014Transition": "0x0",
"eip1052Transition": "0x0",
"eip140Transition": "0x0",
"eip145Transition": "0x0",
"eip150Transition": "0x0",
"eip155Transition": "0x0",
"eip160Transition": "0x0",
"eip161abcTransition": "0x0",
"eip161dTransition": "0x0",
"eip211Transition": "0x0",
"eip214Transition": "0x0",
"eip658Transition": "0x0",
"eip98Transition": "0x7fffffffffffff",
"gasLimitBoundDivisor": "0x0400",
"maxCodeSize": 24576,
"maxCodeSizeTransition": "0x0",
"maximumExtraDataSize": "0x20",
"minGasLimit": "0x1388",
"networkID" : "0x69",
"validateChainIdTransition": "0x0",
"validateReceiptsTransition": "0x0"
},
"genesis": {
"seal": {
"authorityRound": {
"step": "0x0",
"signature": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
}
},
"difficulty": "0x20000",
"author": "0x0000000000000000000000000000000000000000",
"timestamp": "0x00",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"extraData": "0x",
"gasLimit": "0x222222"
},
"accounts": {
"0000000000000000000000000000000000000001": { "balance": "1", "nonce": "1048576", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } },
"0000000000000000000000000000000000000002": { "balance": "1", "nonce": "1048576", "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } },
"0000000000000000000000000000000000000003": { "balance": "1", "nonce": "1048576", "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } },
"0000000000000000000000000000000000000004": { "balance": "1", "nonce": "1048576", "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } },
"0000000000000000000000000000000000000005": { "balance": "1", "builtin": { "name": "modexp", "activate_at": 0, "pricing": { "modexp": { "divisor": 20 } } } },
"0000000000000000000000000000000000000006": {
"balance": "1",
"builtin": {
"name": "alt_bn128_add",
"pricing": {
"0": {
"price": { "alt_bn128_const_operations": { "price": 500 }}
},
"0x7fffffffffffff": {
"info": "EIP 1108 transition",
"price": { "alt_bn128_const_operations": { "price": 150 }}
}
}
}
},
"0000000000000000000000000000000000000007": {
"balance": "1",
"builtin": {
"name": "alt_bn128_mul",
"pricing": {
"0": {
"price": { "alt_bn128_const_operations": { "price": 40000 }}
},
"0x7fffffffffffff": {
"info": "EIP 1108 transition",
"price": { "alt_bn128_const_operations": { "price": 6000 }}
}
}
}
},
"0000000000000000000000000000000000000008": {
"balance": "1",
"builtin": {
"name": "alt_bn128_pairing",
"pricing": {
"0": {
"price": { "alt_bn128_pairing": { "base": 100000, "pair": 80000 }}
},
"0x7fffffffffffff": {
"info": "EIP 1108 transition",
"price": { "alt_bn128_pairing": { "base": 45000, "pair": 34000 }}
}
}
}
},
"0x0000000000000000000000000000000000000009": {
"builtin": {
"name": "blake2_f",
"activate_at": "0xd751a5",
"pricing": {
"blake2_f": {
"gas_per_round": 1
}
}
}
},
"0x0000000000000000000000000000000000000010": {
"builtin": {
"name": "parse_substrate_header",
"pricing": {
"linear": {
"base": 3000,
"word": 0
}
}
}
},
"0x0000000000000000000000000000000000000011": {
"builtin": {
"name": "get_substrate_header_signal",
"pricing": {
"linear": {
"base": 3000,
"word": 0
}
}
}
},
"0x0000000000000000000000000000000000000012": {
"builtin": {
"name": "verify_substrate_finality_proof",
"pricing": {
"linear": {
"base": 3000,
"word": 0
}
}
}
},
"0x0000000000000000000000000000000000000013": {
"builtin": {
"name": "my_test",
"pricing": {
"linear": {
"base": 3000,
"word": 0
}
}
}
},
"0x005e714f896a8b7cede9d38688c1a81de72a58e4": {
"balance": "1606938044258990275541962092341162602522202993782792835301376",
"nonce": "0x1"
},
"0x007594304039c2937a12220338aab821d819f5a4": {
"balance": "1606938044258990275541962092341162602522202993782792835301376",
"nonce": "0x1"
},
"0x004e7a39907f090e19b0b80a277e77b72b22e269": {
"balance": "1606938044258990275541962092341162602522202993782792835301376",
"nonce": "0x1"
}
}
}
@@ -1,91 +0,0 @@
FROM docker.io/library/ubuntu:xenial AS builder
# show backtraces
ENV RUST_BACKTRACE 1
ENV LAST_DEPS_UPDATE 2020-06-19
# install tools and dependencies
RUN set -eux; \
apt-get update && \
apt-get install -y file curl jq ca-certificates && \
apt-get install -y cmake pkg-config libssl-dev git clang libclang-dev
ENV LAST_CERTS_UPDATE 2020-06-19
RUN update-ca-certificates && \
curl https://sh.rustup.rs -sSf | sh -s -- -y
ENV PATH="/root/.cargo/bin:${PATH}"
ENV LAST_RUST_UPDATE="2020-09-09"
RUN rustup update stable && \
rustup install nightly && \
rustup target add wasm32-unknown-unknown --toolchain nightly
RUN rustc -vV && \
cargo -V && \
gcc -v && \
g++ -v && \
cmake --version
WORKDIR /openethereum
### Build from the repo
ARG ETHEREUM_REPO=https://github.com/paritytech/openethereum.git
ARG ETHEREUM_HASH=344991dbba2bc8657b00916f0e4b029c66f159e8
RUN git clone $ETHEREUM_REPO /openethereum && git checkout $ETHEREUM_HASH
### Build locally. Make sure to set the CONTEXT to main directory of the repo.
# ADD openethereum /openethereum
WORKDIR /parity-bridges-common
### Build from the repo
# Build using `master` initially.
ARG BRIDGE_REPO=https://github.com/paritytech/parity-bridges-common
RUN git clone $BRIDGE_REPO /parity-bridges-common && git checkout master
WORKDIR /openethereum
RUN cargo build --release --verbose || true
# Then rebuild by switching to a different branch to only incrementally
# build the changes.
WORKDIR /parity-bridges-common
ARG BRIDGE_HASH=master
RUN git checkout . && git fetch && git checkout $BRIDGE_HASH
### Build locally. Make sure to set the CONTEXT to main directory of the repo.
# ADD . /parity-bridges-common
WORKDIR /openethereum
RUN cargo build --release --verbose
RUN strip ./target/release/openethereum
FROM docker.io/library/ubuntu:xenial
# show backtraces
ENV RUST_BACKTRACE 1
RUN set -eux; \
apt-get update && \
apt-get install -y curl
RUN groupadd -g 1000 openethereum \
&& useradd -u 1000 -g openethereum -s /bin/sh -m openethereum
# switch to user openethereum here
USER openethereum
WORKDIR /home/openethereum
COPY --chown=openethereum:openethereum --from=builder /openethereum/target/release/openethereum ./
# Solve issues with custom --keys-path
RUN mkdir -p ~/.local/share/io.parity.ethereum/keys/
# check if executable works in this container
RUN ./openethereum --version
EXPOSE 8545 8546 30303/tcp 30303/udp
HEALTHCHECK --interval=2m --timeout=5s \
CMD curl -f http://localhost:8545/api/health || exit 1
ENTRYPOINT ["/home/openethereum/openethereum"]
@@ -1,46 +0,0 @@
# Compose file for quickly spinning up a local instance of an Ethereum PoA network.
#
# Note that this PoA network is only used for testing, so the configuration settings you see here
# are *not* recommended for a production environment.
#
# For example, do *not* keep your account key in version control, and unless you're _really_ sure
# you want to provide public access to your nodes do *not* publicly expose RPC methods.
version: '3.5'
services:
poa-node-arthur: &poa-node
image: hcastano/openethereum-bridge-builtins
entrypoint:
- /home/openethereum/openethereum
- --config=/config/poa-node-config
- --node-key=arthur
- --engine-signer=0x005e714f896a8b7cede9d38688c1a81de72a58e4
environment:
RUST_LOG: rpc=trace,txqueue=trace,bridge-builtin=trace
ports:
- "8545:8545"
- "8546:8546"
- "30303:30303"
poa-node-bertha:
<<: *poa-node
entrypoint:
- /home/openethereum/openethereum
- --config=/config/poa-node-config
- --node-key=bertha
- --engine-signer=0x007594304039c2937a12220338aab821d819f5a4
ports:
- "8645:8545"
- "8646:8546"
- "31303:30303"
poa-node-carlos:
<<: *poa-node
entrypoint:
- /home/openethereum/openethereum
- --config=/config/poa-node-config
- --node-key=carlos
- --engine-signer=0x004e7a39907f090e19b0b80a277e77b72b22e269
ports:
- "8745:8545"
- "8746:8546"
- "32303:30303"
@@ -20,7 +20,7 @@ services:
- --unsafe-rpc-external
- --unsafe-ws-external
environment:
RUST_LOG: runtime=trace,rpc=debug,txpool=trace,runtime::bridge=trace,sc_basic_authorship=trace
RUST_LOG: runtime=trace,rpc=debug,txpool=trace,runtime::bridge=trace,sc_basic_authorship=trace,beefy=debug
ports:
- "19933:9933"
- "19944:9944"
@@ -20,7 +20,7 @@ services:
- --unsafe-rpc-external
- --unsafe-ws-external
environment:
RUST_LOG: runtime=trace,rpc=debug,txpool=trace,runtime::bridge=trace
RUST_LOG: runtime=trace,rpc=debug,txpool=trace,runtime::bridge=trace,beefy=debug
ports:
- "9933:9933"
- "9944:9944"
+2 -12
View File
@@ -4,7 +4,7 @@
#
# To deploy a network you can run this script with the name of the bridge (or multiple bridges) you want to run.
#
# `./run.sh poa-rialto rialto-millau`
# `./run.sh westend-millau rialto-millau`
#
# To update a deployment to use the latest images available from the Docker Hub add the `update`
# argument after the bridge name.
@@ -30,7 +30,6 @@ function show_help () {
echo Error: $1
echo " "
echo "Usage:"
echo " ./run.sh poa-rialto [stop|update] Run PoA <> Rialto Networks & Bridge"
echo " ./run.sh rialto-millau [stop|update] Run Rialto <> Millau Networks & Bridge"
echo " ./run.sh westend-millau [stop|update] Run Westend -> Millau Networks & Bridge"
echo " "
@@ -39,13 +38,12 @@ function show_help () {
echo " --no-ui Disable UI"
echo " "
echo "You can start multiple bridges at once by passing several bridge names:"
echo " ./run.sh poa-rialto rialto-millau westend-millau [stop|update]"
echo " ./run.sh rialto-millau westend-millau [stop|update]"
exit 1
}
RIALTO=' -f ./networks/rialto.yml -f ./networks/rialto-parachain.yml'
MILLAU=' -f ./networks/millau.yml'
ETH_POA=' -f ./networks/eth-poa.yml'
MONITORING=' -f ./monitoring/docker-compose.yml'
UI=' -f ./ui/docker-compose.yml'
@@ -65,14 +63,6 @@ do
shift
continue
;;
poa-rialto)
BRIDGES+=($i)
NETWORKS+=${RIALTO}
RIALTO=''
NETWORKS+=${ETH_POA}
ETH_POA=''
shift
;;
rialto-millau)
BRIDGES+=($i)
NETWORKS+=${RIALTO}
@@ -7,7 +7,7 @@ services:
VIRTUAL_PORT: 80
LETSENCRYPT_HOST: ui.brucke.link
LETSENCRYPT_EMAIL: admin@parity.io
CHAIN_1_SUBSTRATE_PROVIDER: ws://localhost:9944
CHAIN_2_SUBSTRATE_PROVIDER: ws://localhost:19944
CHAIN_1_SUBSTRATE_PROVIDER: ${UI_CHAIN_1:-ws://localhost:9944}
CHAIN_2_SUBSTRATE_PROVIDER: ${UI_CHAIN_2:-ws://localhost:19944}
ports:
- "8080:80"
-13
View File
@@ -1,13 +0,0 @@
# Bridge Architecture Diagrams
## Bridge Relay
![General Overview](general-overview.svg)
![Bridge Relay Node](bridge-relay.svg)
## Runtime Modules
![Ethereum Pallet](ethereum-pallet.svg)
![Currency Exchange Pallet](currency-exchange-pallet.svg)
## Usage
![Cross Chain Fund Transfer](cross-chain-fund-transfer.svg)
![Parachain](parachain.svg)
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 27 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 21 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 25 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 40 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 20 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 22 KiB

-71
View File
@@ -1,71 +0,0 @@
# PoA Ethereum High Level Documentation
NOTE: This is from the old README
### Ethereum Bridge Runtime Module
The main job of this runtime module is to keep track of useful information an Ethereum PoA chain
which has been submitted by a bridge relayer. This includes:
- Ethereum headers and their status (e.g are they the best header, are they finalized, etc.)
- Current validator set, and upcoming validator sets
This runtime module has more responsibilties than simply storing headers and validator sets. It is
able to perform checks on the incoming headers to verify their general integrity, as well as whether
or not they've been finalized by the authorities on the PoA chain.
This module is laid out as so:
```
├── ethereum
│ └── src
│ ├── error.rs // Runtime error handling
│ ├── finality.rs // Manage finality operations
│ ├── import.rs // Import new Ethereum headers
│ ├── lib.rs // Store headers and validator set info
│ ├── validators.rs // Track current and future PoA validator sets
│ └── verification.rs // Verify validity of incoming Ethereum headers
```
### Currency Exchange Runtime Module
The currency exchange module is used to faciliate cross-chain funds transfers. It works by accepting
a transaction which proves that funds were locked on one chain, and releases a corresponding amount
of funds on the recieving chain.
For example: Alice would like to send funds from chain A to chain B. What she would do is send a
transaction to chain A indicating that she would like to send funds to an address on chain B. This
transaction would contain the amount of funds she would like to send, as well as the address of the
recipient on chain B. These funds would now be locked on chain A. Once the block containing this
"locked-funds" transaction is finalized it can be relayed to chain B. Chain B will verify that this
transaction was included in a finalized block on chain A, and if successful deposit funds into the
recipient account on chain B.
Chain B would need a way to convert from a foreign currency to its local currency. How this is done
is left to the runtime developer for chain B.
This module is one example of how an on-chain light client can be used to prove a particular action
was taken on a foreign chain. In particular it enables transfers of the foreign chain's native
currency, but more sophisticated modules such as ERC20 token transfers or arbitrary message transfers
are being worked on as well.
## Ethereum Node
On the Ethereum side of things, we require two things. First, a Solidity smart contract to track the
Substrate headers which have been submitted to the bridge (by the relay), and a built-in contract to
be able to verify that headers have been finalized by the GRANDPA finality gadget. Together this
allows the Ethereum PoA chain to verify the integrity and finality of incoming Substrate headers.
The Solidity smart contract is not part of this repo, but can be found
[here](https://github.com/svyatonik/substrate-bridge-sol/blob/master/substrate-bridge.sol) if you're
curious. We have the contract ABI in the `ethereum/relays/res` directory.
## Rialto Runtime
The node runtime consists of several runtime modules, however not all of them are used at the same
time. When running an Ethereum PoA to Substrate bridge the modules required are the Ethereum module
and the currency exchange module. When running a Substrate to Substrate bridge the Substrate and
currency exchange modules are required.
Below is a brief description of each of the runtime modules.
## Bridge Relay
The bridge relay is responsible for syncing the chains which are being bridged, and passing messages
between them. The current implementation of the relay supportings syncing and interacting with
Ethereum PoA and Substrate chains.
@@ -1,50 +0,0 @@
[package]
name = "pallet-bridge-currency-exchange"
description = "A Substrate Runtime module that accepts 'lock funds' transactions from a peer chain and grants an equivalent amount to a the appropriate Substrate account."
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies]
codec = { package = "parity-scale-codec", version = "2.2.0", default-features = false }
log = { version = "0.4.14", default-features = false }
scale-info = { version = "1.0", default-features = false, features = ["derive"] }
serde = { version = "1.0", optional = true }
# Bridge dependencies
bp-currency-exchange = { path = "../../primitives/currency-exchange", default-features = false }
bp-header-chain = { path = "../../primitives/header-chain", default-features = false }
# Substrate Dependencies
frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false, optional = true }
frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
frame-system = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false, optional = true }
[dev-dependencies]
sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-io = { git = "https://github.com/paritytech/substrate", branch = "master" }
[features]
default = ["std"]
std = [
"bp-currency-exchange/std",
"bp-header-chain/std",
"codec/std",
"frame-benchmarking/std",
"frame-support/std",
"frame-system/std",
"log/std",
"scale-info/std",
"serde",
"sp-runtime/std",
"sp-std/std",
]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
"sp-std",
]
@@ -1,135 +0,0 @@
// Copyright 2019-2021 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::{
Call, Config as CurrencyExchangeConfig, InclusionProofVerifier,
Pallet as CurrencyExchangePallet,
};
use sp_std::prelude::*;
use frame_benchmarking::{account, benchmarks_instance_pallet};
use frame_system::RawOrigin;
const SEED: u32 = 0;
const WORST_TX_SIZE_FACTOR: u32 = 1000;
const WORST_PROOF_SIZE_FACTOR: u32 = 1000;
/// Pallet we're benchmarking here.
pub struct Pallet<T: Config<I>, I: 'static>(CurrencyExchangePallet<T, I>);
/// 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,
}
/// Config that must be implemented by runtime.
pub trait Config<I: 'static>: CurrencyExchangeConfig<I> {
/// Prepare proof for importing exchange transaction.
fn make_proof(
proof_params: ProofParams<Self::AccountId>,
) -> <<Self as CurrencyExchangeConfig<I>>::PeerBlockchain as InclusionProofVerifier>::TransactionInclusionProof;
}
benchmarks_instance_pallet! {
// 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)
}
@@ -1,514 +0,0 @@
// Copyright 2019-2021 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/>.
//! Runtime module that allows tokens exchange between two bridged chains.
#![cfg_attr(not(feature = "std"), no_std)]
use bp_currency_exchange::{
CurrencyConverter, DepositInto, Error as ExchangeError, MaybeLockFundsTransaction,
RecipientsMap,
};
use bp_header_chain::InclusionProofVerifier;
use frame_support::ensure;
#[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.
fn on_valid_transaction_submitted(submitter: AccountId);
}
pub use pallet::*;
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
#[pallet::config]
pub trait Config<I: 'static = ()>: frame_system::Config {
/// Handler for transaction submission result.
type OnTransactionSubmitted: OnTransactionSubmitted<Self::AccountId>;
/// Represents the blockchain that we'll be exchanging currency with.
type PeerBlockchain: InclusionProofVerifier;
/// Peer blockchain transaction parser.
type PeerMaybeLockFundsTransaction: MaybeLockFundsTransaction<
Transaction = <Self::PeerBlockchain as InclusionProofVerifier>::Transaction,
>;
/// Map between blockchains recipients.
type RecipientsMap: RecipientsMap<
PeerRecipient = <Self::PeerMaybeLockFundsTransaction as MaybeLockFundsTransaction>::Recipient,
Recipient = Self::AccountId,
>;
/// This blockchain currency amount type.
type Amount;
/// Converter from peer blockchain currency type into current blockchain currency type.
type CurrencyConverter: CurrencyConverter<
SourceAmount = <Self::PeerMaybeLockFundsTransaction as MaybeLockFundsTransaction>::Amount,
TargetAmount = Self::Amount,
>;
/// Something that could grant money.
type DepositInto: DepositInto<Recipient = Self::AccountId, Amount = Self::Amount>;
}
#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
pub struct Pallet<T, I = ()>(PhantomData<(T, I)>);
#[pallet::hooks]
impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pallet<T, I> {}
#[pallet::call]
impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// Imports lock fund transaction of the peer blockchain.
#[pallet::weight(0)] // TODO: update me (https://github.com/paritytech/parity-bridges-common/issues/78)
pub fn import_peer_transaction(
origin: OriginFor<T>,
proof: <<T as Config<I>>::PeerBlockchain as InclusionProofVerifier>::TransactionInclusionProof,
) -> DispatchResult {
let submitter = frame_system::ensure_signed(origin)?;
// verify and parse transaction proof
let deposit = prepare_deposit_details::<T, I>(&proof)?;
// make sure to update the mapping if we deposit successfully to avoid double spending,
// i.e. whenever `deposit_into` is successful we MUST update `Transfers`.
{
// if any changes were made to the storage, we can't just return error here, because
// otherwise the same proof may be imported again
let deposit_result =
T::DepositInto::deposit_into(deposit.recipient, deposit.amount);
match deposit_result {
Ok(_) => (),
Err(ExchangeError::DepositPartiallyFailed) => (),
Err(error) => return Err(Error::<T, I>::from(error).into()),
}
Transfers::<T, I>::insert(&deposit.transfer_id, ())
}
// reward submitter for providing valid message
T::OnTransactionSubmitted::on_valid_transaction_submitted(submitter);
log::trace!(
target: "runtime",
"Completed currency exchange: {:?}",
deposit.transfer_id,
);
Ok(())
}
}
#[pallet::error]
pub enum Error<T, I = ()> {
/// Invalid peer blockchain transaction provided.
InvalidTransaction,
/// Peer transaction has invalid amount.
InvalidAmount,
/// Peer transaction has invalid recipient.
InvalidRecipient,
/// Cannot map from peer recipient to this blockchain recipient.
FailedToMapRecipients,
/// Failed to convert from peer blockchain currency to this blockchain currency.
FailedToConvertCurrency,
/// Deposit has failed.
DepositFailed,
/// Deposit has partially failed (changes to recipient account were made).
DepositPartiallyFailed,
/// Transaction is not finalized.
UnfinalizedTransaction,
/// Transaction funds are already claimed.
AlreadyClaimed,
}
/// All transfers that have already been claimed.
#[pallet::storage]
pub(super) type Transfers<T: Config<I>, I: 'static = ()> = StorageMap<
_,
Blake2_128Concat,
<T::PeerMaybeLockFundsTransaction as MaybeLockFundsTransaction>::Id,
(),
ValueQuery,
>;
}
impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// Returns true if currency exchange module is able to import given transaction proof in
/// its current state.
pub fn filter_transaction_proof(
proof: &<T::PeerBlockchain as InclusionProofVerifier>::TransactionInclusionProof,
) -> bool {
if let Err(err) = prepare_deposit_details::<T, I>(proof) {
log::trace!(
target: "runtime",
"Can't accept exchange transaction: {:?}",
err,
);
return false
}
true
}
}
impl<T: Config<I>, I: 'static> From<ExchangeError> for Error<T, I> {
fn from(error: ExchangeError) -> Self {
match error {
ExchangeError::InvalidTransaction => Error::InvalidTransaction,
ExchangeError::InvalidAmount => Error::InvalidAmount,
ExchangeError::InvalidRecipient => Error::InvalidRecipient,
ExchangeError::FailedToMapRecipients => Error::FailedToMapRecipients,
ExchangeError::FailedToConvertCurrency => Error::FailedToConvertCurrency,
ExchangeError::DepositFailed => Error::DepositFailed,
ExchangeError::DepositPartiallyFailed => Error::DepositPartiallyFailed,
}
}
}
impl<AccountId> OnTransactionSubmitted<AccountId> for () {
fn on_valid_transaction_submitted(_: AccountId) {}
}
/// Exchange deposit details.
struct DepositDetails<T: Config<I>, I: 'static> {
/// Transfer id.
pub transfer_id: <T::PeerMaybeLockFundsTransaction as MaybeLockFundsTransaction>::Id,
/// Transfer recipient.
pub recipient: <T::RecipientsMap as RecipientsMap>::Recipient,
/// Transfer amount.
pub amount: <T::CurrencyConverter as CurrencyConverter>::TargetAmount,
}
/// Verify and parse transaction proof, preparing everything required for importing
/// this transaction proof.
fn prepare_deposit_details<T: Config<I>, I: 'static>(
proof: &<<T as Config<I>>::PeerBlockchain as InclusionProofVerifier>::TransactionInclusionProof,
) -> Result<DepositDetails<T, I>, Error<T, I>> {
// ensure that transaction is included in finalized block that we know of
let transaction = <T as Config<I>>::PeerBlockchain::verify_transaction_inclusion_proof(proof)
.ok_or(Error::<T, I>::UnfinalizedTransaction)?;
// parse transaction
let transaction = <T as Config<I>>::PeerMaybeLockFundsTransaction::parse(&transaction)
.map_err(Error::<T, I>::from)?;
let transfer_id = transaction.id;
ensure!(!Transfers::<T, I>::contains_key(&transfer_id), Error::<T, I>::AlreadyClaimed);
// grant recipient
let recipient = T::RecipientsMap::map(transaction.recipient).map_err(Error::<T, I>::from)?;
let amount = T::CurrencyConverter::convert(transaction.amount).map_err(Error::<T, I>::from)?;
Ok(DepositDetails { transfer_id, recipient, amount })
}
#[cfg(test)]
mod tests {
// From construct_runtime macro
#![allow(clippy::from_over_into)]
use super::*;
use bp_currency_exchange::LockFundsTransaction;
use frame_support::{
assert_noop, assert_ok, construct_runtime, parameter_types, weights::Weight,
};
use sp_core::H256;
use sp_runtime::{
testing::Header,
traits::{BlakeTwo256, IdentityLookup},
Perbill,
};
type AccountId = u64;
const INVALID_TRANSACTION_ID: u64 = 100;
const ALREADY_CLAIMED_TRANSACTION_ID: u64 = 101;
const UNKNOWN_RECIPIENT_ID: u64 = 0;
const INVALID_AMOUNT: u64 = 0;
const MAX_DEPOSIT_AMOUNT: u64 = 1000;
const SUBMITTER: u64 = 2000;
type RawTransaction = LockFundsTransaction<u64, u64, u64>;
pub struct DummyTransactionSubmissionHandler;
impl OnTransactionSubmitted<AccountId> for DummyTransactionSubmissionHandler {
fn on_valid_transaction_submitted(submitter: AccountId) {
Transfers::<TestRuntime, ()>::insert(submitter, ());
}
}
pub struct DummyBlockchain;
impl InclusionProofVerifier for DummyBlockchain {
type Transaction = RawTransaction;
type TransactionInclusionProof = (bool, RawTransaction);
fn verify_transaction_inclusion_proof(
proof: &Self::TransactionInclusionProof,
) -> Option<RawTransaction> {
if proof.0 {
Some(proof.1.clone())
} else {
None
}
}
}
pub struct DummyTransaction;
impl MaybeLockFundsTransaction for DummyTransaction {
type Transaction = RawTransaction;
type Id = u64;
type Recipient = AccountId;
type Amount = u64;
fn parse(tx: &Self::Transaction) -> bp_currency_exchange::Result<RawTransaction> {
match tx.id {
INVALID_TRANSACTION_ID => Err(ExchangeError::InvalidTransaction),
_ => Ok(tx.clone()),
}
}
}
pub struct DummyRecipientsMap;
impl RecipientsMap for DummyRecipientsMap {
type PeerRecipient = AccountId;
type Recipient = AccountId;
fn map(
peer_recipient: Self::PeerRecipient,
) -> bp_currency_exchange::Result<Self::Recipient> {
match peer_recipient {
UNKNOWN_RECIPIENT_ID => Err(ExchangeError::FailedToMapRecipients),
_ => Ok(peer_recipient * 10),
}
}
}
pub struct DummyCurrencyConverter;
impl CurrencyConverter for DummyCurrencyConverter {
type SourceAmount = u64;
type TargetAmount = u64;
fn convert(amount: Self::SourceAmount) -> bp_currency_exchange::Result<Self::TargetAmount> {
match amount {
INVALID_AMOUNT => Err(ExchangeError::FailedToConvertCurrency),
_ => Ok(amount * 10),
}
}
}
pub struct DummyDepositInto;
impl DepositInto for DummyDepositInto {
type Recipient = AccountId;
type Amount = u64;
fn deposit_into(
_recipient: Self::Recipient,
amount: Self::Amount,
) -> bp_currency_exchange::Result<()> {
match amount {
amount if amount < MAX_DEPOSIT_AMOUNT * 10 => Ok(()),
amount if amount == MAX_DEPOSIT_AMOUNT * 10 =>
Err(ExchangeError::DepositPartiallyFailed),
_ => Err(ExchangeError::DepositFailed),
}
}
}
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<TestRuntime>;
type Block = frame_system::mocking::MockBlock<TestRuntime>;
use crate as pallet_bridge_currency_exchange;
construct_runtime! {
pub enum TestRuntime where
Block = Block,
NodeBlock = Block,
UncheckedExtrinsic = UncheckedExtrinsic,
{
System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
Exchange: pallet_bridge_currency_exchange::{Pallet},
}
}
parameter_types! {
pub const BlockHashCount: u64 = 250;
pub const MaximumBlockWeight: Weight = 1024;
pub const MaximumBlockLength: u32 = 2 * 1024;
pub const AvailableBlockRatio: Perbill = Perbill::one();
}
impl frame_system::Config for TestRuntime {
type Origin = Origin;
type Index = u64;
type Call = Call;
type BlockNumber = u64;
type Hash = H256;
type Hashing = BlakeTwo256;
type AccountId = AccountId;
type Lookup = IdentityLookup<Self::AccountId>;
type Header = Header;
type Event = ();
type BlockHashCount = BlockHashCount;
type Version = ();
type PalletInfo = PalletInfo;
type AccountData = ();
type OnNewAccount = ();
type OnKilledAccount = ();
type BaseCallFilter = frame_support::traits::Everything;
type SystemWeightInfo = ();
type BlockWeights = ();
type BlockLength = ();
type DbWeight = ();
type SS58Prefix = ();
type OnSetCode = ();
}
impl Config for TestRuntime {
type OnTransactionSubmitted = DummyTransactionSubmissionHandler;
type PeerBlockchain = DummyBlockchain;
type PeerMaybeLockFundsTransaction = DummyTransaction;
type RecipientsMap = DummyRecipientsMap;
type Amount = u64;
type CurrencyConverter = DummyCurrencyConverter;
type DepositInto = DummyDepositInto;
}
fn new_test_ext() -> sp_io::TestExternalities {
let t = frame_system::GenesisConfig::default().build_storage::<TestRuntime>().unwrap();
sp_io::TestExternalities::new(t)
}
fn transaction(id: u64) -> RawTransaction {
RawTransaction { id, recipient: 1, amount: 2 }
}
#[test]
fn unfinalized_transaction_rejected() {
new_test_ext().execute_with(|| {
assert_noop!(
Exchange::import_peer_transaction(
Origin::signed(SUBMITTER),
(false, transaction(0))
),
Error::<TestRuntime, ()>::UnfinalizedTransaction,
);
});
}
#[test]
fn invalid_transaction_rejected() {
new_test_ext().execute_with(|| {
assert_noop!(
Exchange::import_peer_transaction(
Origin::signed(SUBMITTER),
(true, transaction(INVALID_TRANSACTION_ID)),
),
Error::<TestRuntime, ()>::InvalidTransaction,
);
});
}
#[test]
fn claimed_transaction_rejected() {
new_test_ext().execute_with(|| {
<Exchange as crate::Store>::Transfers::insert(ALREADY_CLAIMED_TRANSACTION_ID, ());
assert_noop!(
Exchange::import_peer_transaction(
Origin::signed(SUBMITTER),
(true, transaction(ALREADY_CLAIMED_TRANSACTION_ID)),
),
Error::<TestRuntime, ()>::AlreadyClaimed,
);
});
}
#[test]
fn transaction_with_unknown_recipient_rejected() {
new_test_ext().execute_with(|| {
let mut transaction = transaction(0);
transaction.recipient = UNKNOWN_RECIPIENT_ID;
assert_noop!(
Exchange::import_peer_transaction(Origin::signed(SUBMITTER), (true, transaction)),
Error::<TestRuntime, ()>::FailedToMapRecipients,
);
});
}
#[test]
fn transaction_with_invalid_amount_rejected() {
new_test_ext().execute_with(|| {
let mut transaction = transaction(0);
transaction.amount = INVALID_AMOUNT;
assert_noop!(
Exchange::import_peer_transaction(Origin::signed(SUBMITTER), (true, transaction)),
Error::<TestRuntime, ()>::FailedToConvertCurrency,
);
});
}
#[test]
fn transaction_with_invalid_deposit_rejected() {
new_test_ext().execute_with(|| {
let mut transaction = transaction(0);
transaction.amount = MAX_DEPOSIT_AMOUNT + 1;
assert_noop!(
Exchange::import_peer_transaction(Origin::signed(SUBMITTER), (true, transaction)),
Error::<TestRuntime, ()>::DepositFailed,
);
});
}
#[test]
fn valid_transaction_accepted_even_if_deposit_partially_fails() {
new_test_ext().execute_with(|| {
let mut transaction = transaction(0);
transaction.amount = MAX_DEPOSIT_AMOUNT;
assert_ok!(Exchange::import_peer_transaction(
Origin::signed(SUBMITTER),
(true, transaction),
));
// ensure that the transfer has been marked as completed
assert!(<Exchange as crate::Store>::Transfers::contains_key(0u64));
// ensure that submitter has been rewarded
assert!(<Exchange as crate::Store>::Transfers::contains_key(SUBMITTER));
});
}
#[test]
fn valid_transaction_accepted() {
new_test_ext().execute_with(|| {
assert_ok!(Exchange::import_peer_transaction(
Origin::signed(SUBMITTER),
(true, transaction(0)),
));
// ensure that the transfer has been marked as completed
assert!(<Exchange as crate::Store>::Transfers::contains_key(0u64));
// ensure that submitter has been rewarded
assert!(<Exchange as crate::Store>::Transfers::contains_key(SUBMITTER));
});
}
}
@@ -1,28 +0,0 @@
[package]
name = "ethereum-contract-builtin"
description = "Small crate that helps Solidity contract to verify finality proof."
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies]
codec = { package = "parity-scale-codec", version = "2.2.0" }
ethereum-types = "0.12.0"
finality-grandpa = "0.14.0"
hex = "0.4"
log = "0.4.14"
# Runtime/chain specific dependencies
rialto-runtime = { path = "../../bin/rialto/runtime" }
# Substrate Dependencies
sc-finality-grandpa = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-blockchain = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-finality-grandpa = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" }
[dev-dependencies]
sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" }
@@ -1,372 +0,0 @@
// Copyright 2019-2021 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/>.
use codec::{Decode, Encode};
use ethereum_types::U256;
use finality_grandpa::voter_set::VoterSet;
use rialto_runtime::{Block, BlockNumber, Hash, Header as RuntimeHeader};
use sp_blockchain::Error as ClientError;
use sp_finality_grandpa::{AuthorityList, ConsensusLog, GRANDPA_ENGINE_ID};
/// Builtin errors.
#[derive(Debug)]
pub enum Error {
/// Failed to decode block number.
BlockNumberDecode,
/// Failed to decode Substrate header.
HeaderDecode(codec::Error),
/// Failed to decode the best voters set.
BestSetDecode(codec::Error),
/// The best voters set is invalid.
InvalidBestSet,
/// Failed to decode finality proof.
FinalityProofDecode(codec::Error),
/// Failed to verify justification.
JustificationVerify(Box<ClientError>),
}
/// Substrate header.
#[derive(Debug, PartialEq)]
pub struct Header {
/// Header hash.
pub hash: Hash,
/// Parent header hash.
pub parent_hash: Hash,
/// Header number.
pub number: BlockNumber,
/// GRANDPA validators change signal.
pub signal: Option<ValidatorsSetSignal>,
}
/// GRANDPA validators set change signal.
#[derive(Debug, PartialEq)]
pub struct ValidatorsSetSignal {
/// Signal delay.
pub delay: BlockNumber,
/// New validators set.
pub validators: Vec<u8>,
}
/// Convert from U256 to BlockNumber. Fails if `U256` value isn't fitting within `BlockNumber`
/// limits (the runtime referenced by this module uses u32 as `BlockNumber`).
pub fn to_substrate_block_number(number: U256) -> Result<BlockNumber, Error> {
let substrate_block_number = match number == number.low_u32().into() {
true => Ok(number.low_u32()),
false => Err(Error::BlockNumberDecode),
};
log::trace!(
target: "bridge-builtin",
"Parsed Substrate block number from {}: {:?}",
number,
substrate_block_number,
);
substrate_block_number
}
/// Convert from BlockNumber to U256.
pub fn from_substrate_block_number(number: BlockNumber) -> Result<U256, Error> {
Ok(U256::from(number as u64))
}
/// Parse Substrate header.
pub fn parse_substrate_header(raw_header: &[u8]) -> Result<Header, Error> {
let substrate_header = RuntimeHeader::decode(&mut &*raw_header)
.map(|header| Header {
hash: header.hash(),
parent_hash: header.parent_hash,
number: header.number,
signal: sp_runtime::traits::Header::digest(&header)
.log(|log| {
log.as_consensus().and_then(|(engine_id, log)| {
if engine_id == GRANDPA_ENGINE_ID {
Some(log)
} else {
None
}
})
})
.and_then(|log| ConsensusLog::decode(&mut &*log).ok())
.and_then(|log| match log {
ConsensusLog::ScheduledChange(scheduled_change) => Some(ValidatorsSetSignal {
delay: scheduled_change.delay,
validators: scheduled_change.next_authorities.encode(),
}),
_ => None,
}),
})
.map_err(Error::HeaderDecode);
log::debug!(
target: "bridge-builtin",
"Parsed Substrate header {}: {:?}",
if substrate_header.is_ok() {
format!("<{}-bytes-blob>", raw_header.len())
} else {
hex::encode(raw_header)
},
substrate_header,
);
substrate_header
}
/// Verify GRANDPA finality proof.
pub fn verify_substrate_finality_proof(
finality_target_number: BlockNumber,
finality_target_hash: Hash,
best_set_id: u64,
raw_best_set: &[u8],
raw_finality_proof: &[u8],
) -> Result<(), Error> {
let best_set = AuthorityList::decode(&mut &*raw_best_set)
.map_err(Error::BestSetDecode)
.and_then(|authorities| {
VoterSet::new(authorities.into_iter()).ok_or(Error::InvalidBestSet)
});
log::debug!(
target: "bridge-builtin",
"Parsed Substrate authorities set {}: {:?}",
if best_set.is_ok() {
format!("<{}-bytes-blob>", raw_best_set.len())
} else {
hex::encode(raw_best_set)
},
best_set,
);
let best_set = best_set?;
let verify_result =
sc_finality_grandpa::GrandpaJustification::<Block>::decode_and_verify_finalizes(
raw_finality_proof,
(finality_target_hash, finality_target_number),
best_set_id,
&best_set,
)
.map_err(Box::new)
.map_err(Error::JustificationVerify)
.map(|_| ());
log::debug!(
target: "bridge-builtin",
"Verified Substrate finality proof {}: {:?}",
if verify_result.is_ok() {
format!("<{}-bytes-blob>", raw_finality_proof.len())
} else {
hex::encode(raw_finality_proof)
},
verify_result,
);
verify_result
}
#[cfg(test)]
mod tests {
use super::*;
use rialto_runtime::DigestItem;
use sp_core::crypto::Public;
use sp_finality_grandpa::{AuthorityId, ScheduledChange};
use sp_runtime::generic::Digest;
#[test]
fn to_substrate_block_number_succeeds() {
assert_eq!(to_substrate_block_number(U256::zero()).unwrap(), 0);
assert_eq!(
to_substrate_block_number(U256::from(std::u32::MAX as u64)).unwrap(),
0xFFFFFFFF
);
}
#[test]
fn to_substrate_block_number_fails() {
assert!(matches!(
to_substrate_block_number(U256::from(std::u32::MAX as u64 + 1)),
Err(Error::BlockNumberDecode)
));
}
#[test]
fn from_substrate_block_number_succeeds() {
assert_eq!(from_substrate_block_number(0).unwrap(), U256::zero());
assert_eq!(from_substrate_block_number(std::u32::MAX).unwrap(), U256::from(std::u32::MAX));
}
#[test]
fn substrate_header_without_signal_parsed() {
let raw_header = RuntimeHeader {
parent_hash: [0u8; 32].into(),
number: 0,
state_root: "b2fc47904df5e355c6ab476d89fbc0733aeddbe302f0b94ba4eea9283f7e89e7"
.parse()
.unwrap(),
extrinsics_root: "03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314"
.parse()
.unwrap(),
digest: Default::default(),
}
.encode();
assert_eq!(
raw_header,
hex::decode("000000000000000000000000000000000000000000000000000000000000000000b2fc47904df5e355c6ab476d89fbc0733aeddbe302f0b94ba4eea9283f7e89e703170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c11131400").unwrap(),
);
assert_eq!(
parse_substrate_header(&raw_header).unwrap(),
Header {
hash: "afbbeb92bf6ff14f60bdef0aa89f043dd403659ae82665238810ace0d761f6d0"
.parse()
.unwrap(),
parent_hash: Default::default(),
number: 0,
signal: None,
},
);
}
#[test]
fn substrate_header_with_signal_parsed() {
let authorities = vec![
(AuthorityId::from_slice(&[1; 32]), 101),
(AuthorityId::from_slice(&[3; 32]), 103),
];
let mut digest = Digest::default();
digest.push(DigestItem::Consensus(
GRANDPA_ENGINE_ID,
ConsensusLog::ScheduledChange(ScheduledChange {
next_authorities: authorities.clone(),
delay: 8,
})
.encode(),
));
let raw_header = RuntimeHeader {
parent_hash: "c0ac300d4005141ea690f3df593e049739c227316eb7f05052f3ee077388b68b"
.parse()
.unwrap(),
number: 8,
state_root: "822d6b412033aa9ac8e1722918eec5f25633529225754b3d4149982f5cacd4aa"
.parse()
.unwrap(),
extrinsics_root: "e7b07c0ce2799416ce7877b9cefc7f596bea5e8813bb2a0abf760414073ca928"
.parse()
.unwrap(),
digest,
}
.encode();
assert_eq!(
raw_header,
hex::decode("c0ac300d4005141ea690f3df593e049739c227316eb7f05052f3ee077388b68b20822d6b412033aa9ac8e1722918eec5f25633529225754b3d4149982f5cacd4aae7b07c0ce2799416ce7877b9cefc7f596bea5e8813bb2a0abf760414073ca928040446524e4b59010108010101010101010101010101010101010101010101010101010101010101010165000000000000000303030303030303030303030303030303030303030303030303030303030303670000000000000008000000").unwrap(),
);
assert_eq!(
parse_substrate_header(&raw_header).unwrap(),
Header {
hash: "3dfebb280bd87a4640f89d7f2adecd62b88148747bff5b63af6e1634ee37a56e"
.parse()
.unwrap(),
parent_hash: "c0ac300d4005141ea690f3df593e049739c227316eb7f05052f3ee077388b68b"
.parse()
.unwrap(),
number: 8,
signal: Some(ValidatorsSetSignal { delay: 8, validators: authorities.encode() }),
},
);
}
/// Number of the example block with justification.
const EXAMPLE_JUSTIFIED_BLOCK_NUMBER: u32 = 8;
/// Hash of the example block with justification.
const EXAMPLE_JUSTIFIED_BLOCK_HASH: &str =
"a2f45892db86b2ad133ce57d81b7e4375bb7035ce9883e6b68c358164f343775";
/// Id of authorities set that have generated example justification. Could be computed by
/// tracking every set change in canonized headers.
const EXAMPLE_AUTHORITIES_SET_ID: u64 = 0;
/// Encoded authorities set that has generated example justification. Could be fetched from
/// `ScheduledChange` digest of the block that has scheduled this set OR by calling
/// `GrandpaApi::grandpa_authorities()` at appropriate block.
const EXAMPLE_AUTHORITIES_SET: &str = "1488dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee0100000000000000d17c2d7823ebf260fd138f2d7e27d114c0145d968b5ff5006125f2414fadae690100000000000000439660b36c6c03afafca027b910b4fecf99801834c62a5e6006f27d978de234f01000000000000005e639b43e0052c47447dac87d6fd2b6ec50bdd4d0f614e4299c665249bbd09d901000000000000001dfe3e22cc0d45c70779c1095f7489a8ef3cf52d62fbd8c2fa38c9f1723502b50100000000000000";
/// Example justification. Could be fetched by calling 'chain_getBlock' RPC.
const EXAMPLE_JUSTIFICATION: &str = "2600000000000000a2f45892db86b2ad133ce57d81b7e4375bb7035ce9883e6b68c358164f3437750800000010a2f45892db86b2ad133ce57d81b7e4375bb7035ce9883e6b68c358164f34377508000000d66b4ceb57ef8bcbc955071b597c8c5d2adcfdbb009c73f8438d342670fdeca9ac60686cbd58105b10f51d0a64a8e73b2e5829b2eab3248a008c472852130b00439660b36c6c03afafca027b910b4fecf99801834c62a5e6006f27d978de234fa2f45892db86b2ad133ce57d81b7e4375bb7035ce9883e6b68c358164f34377508000000f5730c14d3cd22b7661e2f5fcb3139dd5fef37f946314a441d01b40ce1200ef70d810525f23fd278b588cd67473c200bda83c338c407b479386aa83798e5970b5e639b43e0052c47447dac87d6fd2b6ec50bdd4d0f614e4299c665249bbd09d9a2f45892db86b2ad133ce57d81b7e4375bb7035ce9883e6b68c358164f34377508000000c78d6ec463f476461a695b4791d30e7626d16fdf72d7c252c2cad387495a97e8c2827ed4d5af853d6e05d31cb6fb7438c9481a7e9c6990d60a9bfaf6a6e1930988dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0eea2f45892db86b2ad133ce57d81b7e4375bb7035ce9883e6b68c358164f3437750800000052b4fc52d430286b3e2d650aa6e01b6ff4fae8b968893a62be789209eb97ee6e23780d3f5af7042d85bb48f1b202890b22724dfebce138826f66a5e00324320fd17c2d7823ebf260fd138f2d7e27d114c0145d968b5ff5006125f2414fadae6900";
#[test]
fn substrate_header_parse_fails() {
assert!(matches!(parse_substrate_header(&[]), Err(_)));
}
#[test]
fn verify_substrate_finality_proof_succeeds() {
verify_substrate_finality_proof(
EXAMPLE_JUSTIFIED_BLOCK_NUMBER,
EXAMPLE_JUSTIFIED_BLOCK_HASH.parse().unwrap(),
EXAMPLE_AUTHORITIES_SET_ID,
&hex::decode(EXAMPLE_AUTHORITIES_SET).unwrap(),
&hex::decode(EXAMPLE_JUSTIFICATION).unwrap(),
)
.unwrap();
}
#[test]
fn verify_substrate_finality_proof_fails_when_wrong_block_is_finalized() {
verify_substrate_finality_proof(
4,
Default::default(),
EXAMPLE_AUTHORITIES_SET_ID,
&hex::decode(EXAMPLE_AUTHORITIES_SET).unwrap(),
&hex::decode(EXAMPLE_JUSTIFICATION).unwrap(),
)
.unwrap_err();
}
#[test]
fn verify_substrate_finality_proof_fails_when_wrong_set_is_provided() {
verify_substrate_finality_proof(
EXAMPLE_JUSTIFIED_BLOCK_NUMBER,
EXAMPLE_JUSTIFIED_BLOCK_HASH.parse().unwrap(),
EXAMPLE_AUTHORITIES_SET_ID,
&hex::decode("deadbeef").unwrap(),
&hex::decode(EXAMPLE_JUSTIFICATION).unwrap(),
)
.unwrap_err();
}
#[test]
fn verify_substrate_finality_proof_fails_when_wrong_set_id_is_provided() {
verify_substrate_finality_proof(
EXAMPLE_JUSTIFIED_BLOCK_NUMBER,
EXAMPLE_JUSTIFIED_BLOCK_HASH.parse().unwrap(),
42,
&hex::decode(EXAMPLE_AUTHORITIES_SET).unwrap(),
&hex::decode(EXAMPLE_JUSTIFICATION).unwrap(),
)
.unwrap_err();
}
#[test]
fn verify_substrate_finality_proof_fails_when_wrong_proof_is_provided() {
verify_substrate_finality_proof(
EXAMPLE_JUSTIFIED_BLOCK_NUMBER,
EXAMPLE_JUSTIFIED_BLOCK_HASH.parse().unwrap(),
0,
&hex::decode(EXAMPLE_AUTHORITIES_SET).unwrap(),
&hex::decode("deadbeef").unwrap(),
)
.unwrap_err();
}
}
@@ -1,51 +0,0 @@
[package]
name = "pallet-bridge-eth-poa"
description = "A Substrate Runtime module that is able to verify PoA headers and their finality."
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies]
codec = { package = "parity-scale-codec", version = "2.2.0", default-features = false }
libsecp256k1 = { version = "0.7", default-features = false, features = ["hmac"], optional = true }
log = { version = "0.4.14", default-features = false }
scale-info = { version = "1.0", default-features = false, features = ["derive"] }
serde = { version = "1.0", optional = true }
# Bridge dependencies
bp-eth-poa = { path = "../../primitives/ethereum-poa", default-features = false }
# Substrate Dependencies
frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false, optional = true }
frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
frame-system = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-io = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
[dev-dependencies]
libsecp256k1 = { version = "0.7", features = ["hmac"] }
hex-literal = "0.3"
[features]
default = ["std"]
std = [
"bp-eth-poa/std",
"codec/std",
"frame-benchmarking/std",
"frame-support/std",
"frame-system/std",
"log/std",
"scale-info/std",
"serde",
"sp-io/std",
"sp-runtime/std",
"sp-std/std",
]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
"libsecp256k1",
]
@@ -1,270 +0,0 @@
// Copyright 2019-2021 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/>.
use super::*;
use crate::test_utils::{
build_custom_header, build_genesis_header, insert_header, validator_utils::*,
validators_change_receipt, HeaderBuilder,
};
use bp_eth_poa::{compute_merkle_root, U256};
use frame_benchmarking::benchmarks_instance_pallet;
use frame_system::RawOrigin;
benchmarks_instance_pallet! {
// Benchmark `import_unsigned_header` extrinsic with the best possible conditions:
// * Parent header is finalized.
// * New header doesn't require receipts.
// * Nothing is finalized by new header.
// * Nothing is pruned by new header.
import_unsigned_header_best_case {
let n in 1..1000;
let num_validators = 2;
let initial_header = initialize_bench::<T, I>(num_validators);
// prepare header to be inserted
let header = build_custom_header(
&validator(1),
&initial_header,
|mut header| {
header.gas_limit = header.gas_limit + U256::from(n);
header
},
);
}: import_unsigned_header(RawOrigin::None, Box::new(header), None)
verify {
let storage = BridgeStorage::<T, I>::new();
assert_eq!(storage.best_block().0.number, 1);
assert_eq!(storage.finalized_block().number, 0);
}
// Our goal with this bench is to try and see the effect that finalizing difference ranges of
// blocks has on our import time. As such we need to make sure that we keep the number of
// validators fixed while changing the number blocks finalized (the complexity parameter) by
// importing the last header.
//
// One important thing to keep in mind is that the runtime provides a finality cache in order to
// reduce the overhead of header finalization. However, this is only triggered every 16 blocks.
import_unsigned_finality {
// Our complexity parameter, n, will represent the number of blocks imported before
// finalization.
let n in 1..7;
let mut storage = BridgeStorage::<T, I>::new();
let num_validators: u32 = 2;
let initial_header = initialize_bench::<T, I>(num_validators as usize);
// Since we only have two validators we need to make sure the number of blocks is even to
// make sure the right validator signs the final block
let num_blocks = 2 * n;
let mut headers = Vec::new();
let mut parent = initial_header.clone();
// Import a bunch of headers without any verification, will ensure that they're not
// finalized prematurely
for i in 1..=num_blocks {
let header = HeaderBuilder::with_parent(&parent).sign_by(&validator(0));
let id = header.compute_id();
insert_header(&mut storage, header.clone());
headers.push(header.clone());
parent = header;
}
let last_header = headers.last().unwrap().clone();
let last_authority = validator(1);
// Need to make sure that the header we're going to import hasn't been inserted
// into storage already
let header = HeaderBuilder::with_parent(&last_header).sign_by(&last_authority);
}: import_unsigned_header(RawOrigin::None, Box::new(header), None)
verify {
let storage = BridgeStorage::<T, I>::new();
assert_eq!(storage.best_block().0.number, (num_blocks + 1) as u64);
assert_eq!(storage.finalized_block().number, num_blocks as u64);
}
// Basically the exact same as `import_unsigned_finality` but with a different range for the
// complexity parameter. In this bench we use a larger range of blocks to see how performance
// changes when the finality cache kicks in (>16 blocks).
import_unsigned_finality_with_cache {
// Our complexity parameter, n, will represent the number of blocks imported before
// finalization.
let n in 7..100;
let mut storage = BridgeStorage::<T, I>::new();
let num_validators: u32 = 2;
let initial_header = initialize_bench::<T, I>(num_validators as usize);
// Since we only have two validators we need to make sure the number of blocks is even to
// make sure the right validator signs the final block
let num_blocks = 2 * n;
let mut headers = Vec::new();
let mut parent = initial_header.clone();
// Import a bunch of headers without any verification, will ensure that they're not
// finalized prematurely
for i in 1..=num_blocks {
let header = HeaderBuilder::with_parent(&parent).sign_by(&validator(0));
let id = header.compute_id();
insert_header(&mut storage, header.clone());
headers.push(header.clone());
parent = header;
}
let last_header = headers.last().unwrap().clone();
let last_authority = validator(1);
// Need to make sure that the header we're going to import hasn't been inserted
// into storage already
let header = HeaderBuilder::with_parent(&last_header).sign_by(&last_authority);
}: import_unsigned_header(RawOrigin::None, Box::new(header), None)
verify {
let storage = BridgeStorage::<T, I>::new();
assert_eq!(storage.best_block().0.number, (num_blocks + 1) as u64);
assert_eq!(storage.finalized_block().number, num_blocks as u64);
}
// A block import may trigger a pruning event, which adds extra work to the import progress.
// In this bench we trigger a pruning event in order to see how much extra time is spent by the
// runtime dealing with it. In the Ethereum Pallet, we're limited pruning to eight blocks in a
// single import, as dictated by MAX_BLOCKS_TO_PRUNE_IN_SINGLE_IMPORT.
import_unsigned_pruning {
let n in 1..MAX_BLOCKS_TO_PRUNE_IN_SINGLE_IMPORT as u32;
let mut storage = BridgeStorage::<T, I>::new();
let num_validators = 3;
let initial_header = initialize_bench::<T, I>(num_validators as usize);
let validators = validators(num_validators);
// Want to prune eligible blocks between [0, n)
BlocksToPrune::<T, I>::put(PruningRange {
oldest_unpruned_block: 0,
oldest_block_to_keep: n as u64,
});
let mut parent = initial_header;
for i in 1..=n {
let header = HeaderBuilder::with_parent(&parent).sign_by_set(&validators);
let id = header.compute_id();
insert_header(&mut storage, header.clone());
parent = header;
}
let header = HeaderBuilder::with_parent(&parent).sign_by_set(&validators);
}: import_unsigned_header(RawOrigin::None, Box::new(header), None)
verify {
let storage = BridgeStorage::<T, I>::new();
let max_pruned: u64 = (n - 1) as _;
assert_eq!(storage.best_block().0.number, (n + 1) as u64);
assert!(HeadersByNumber::<T, I>::get(&0).is_none());
assert!(HeadersByNumber::<T, I>::get(&max_pruned).is_none());
}
// The goal of this bench is to import a block which contains a transaction receipt. The receipt
// will contain a validator set change. Verifying the receipt root is an expensive operation to
// do, which is why we're interested in benchmarking it.
import_unsigned_with_receipts {
let n in 1..100;
let mut storage = BridgeStorage::<T, I>::new();
let num_validators = 1;
let initial_header = initialize_bench::<T, I>(num_validators as usize);
let mut receipts = vec![];
for i in 1..=n {
let receipt = validators_change_receipt(Default::default());
receipts.push(receipt)
}
let encoded_receipts = receipts.iter().map(|r| r.rlp());
// We need this extra header since this is what signals a validator set transition. This
// will ensure that the next header is within the "Contract" window
let header1 = HeaderBuilder::with_parent(&initial_header).sign_by(&validator(0));
insert_header(&mut storage, header1.clone());
let header = build_custom_header(
&validator(0),
&header1,
|mut header| {
// Logs Bloom signals a change in validator set
header.log_bloom = (&[0xff; 256]).into();
header.receipts_root = compute_merkle_root(encoded_receipts);
header
},
);
}: import_unsigned_header(RawOrigin::None, Box::new(header), Some(receipts))
verify {
let storage = BridgeStorage::<T, I>::new();
assert_eq!(storage.best_block().0.number, 2);
}
}
fn initialize_bench<T: Config<I>, I: 'static>(num_validators: usize) -> AuraHeader {
// Initialize storage with some initial header
let initial_header = build_genesis_header(&validator(0));
let initial_difficulty = initial_header.difficulty;
let initial_validators = validators_addresses(num_validators as usize);
initialize_storage::<T, I>(&initial_header, initial_difficulty, &initial_validators);
initial_header
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mock::{run_test, TestRuntime};
use frame_support::assert_ok;
#[test]
fn insert_unsigned_header_best_case() {
run_test(1, |_| {
assert_ok!(test_benchmark_import_unsigned_header_best_case::<TestRuntime>());
});
}
#[test]
fn insert_unsigned_header_finality() {
run_test(1, |_| {
assert_ok!(test_benchmark_import_unsigned_finality::<TestRuntime>());
});
}
#[test]
fn insert_unsigned_header_finality_with_cache() {
run_test(1, |_| {
assert_ok!(test_benchmark_import_unsigned_finality_with_cache::<TestRuntime>());
});
}
#[test]
fn insert_unsigned_header_pruning() {
run_test(1, |_| {
assert_ok!(test_benchmark_import_unsigned_pruning::<TestRuntime>());
});
}
#[test]
fn insert_unsigned_header_receipts() {
run_test(1, |_| {
assert_ok!(test_benchmark_import_unsigned_with_receipts::<TestRuntime>());
});
}
}
@@ -1,102 +0,0 @@
// Copyright 2019-2021 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/>.
use sp_runtime::RuntimeDebug;
/// Header import error.
#[derive(Clone, Copy, RuntimeDebug)]
#[cfg_attr(feature = "std", derive(PartialEq))]
pub enum Error {
/// The header is beyond last finalized and can not be imported.
AncientHeader = 0,
/// The header is already imported.
KnownHeader = 1,
/// Seal has an incorrect format.
InvalidSealArity = 2,
/// Block number isn't sensible.
RidiculousNumber = 3,
/// Block has too much gas used.
TooMuchGasUsed = 4,
/// Gas limit header field is invalid.
InvalidGasLimit = 5,
/// Extra data is of an invalid length.
ExtraDataOutOfBounds = 6,
/// Timestamp header overflowed.
TimestampOverflow = 7,
/// The parent header is missing from the blockchain.
MissingParentBlock = 8,
/// The header step is missing from the header.
MissingStep = 9,
/// The header signature is missing from the header.
MissingSignature = 10,
/// Empty steps are missing from the header.
MissingEmptySteps = 11,
/// The same author issued different votes at the same step.
DoubleVote = 12,
/// Validation proof insufficient.
InsufficientProof = 13,
/// Difficulty header field is invalid.
InvalidDifficulty = 14,
/// The received block is from an incorrect proposer.
NotValidator = 15,
/// Missing transaction receipts for the operation.
MissingTransactionsReceipts = 16,
/// Redundant transaction receipts are provided.
RedundantTransactionsReceipts = 17,
/// Provided transactions receipts are not matching the header.
TransactionsReceiptsMismatch = 18,
/// Can't accept unsigned header from the far future.
UnsignedTooFarInTheFuture = 19,
/// Trying to finalize sibling of finalized block.
TryingToFinalizeSibling = 20,
/// Header timestamp is ahead of on-chain timestamp
HeaderTimestampIsAhead = 21,
}
impl Error {
pub fn msg(&self) -> &'static str {
match *self {
Error::AncientHeader => "Header is beyound last finalized and can not be imported",
Error::KnownHeader => "Header is already imported",
Error::InvalidSealArity => "Header has an incorrect seal",
Error::RidiculousNumber => "Header has too large number",
Error::TooMuchGasUsed => "Header has too much gas used",
Error::InvalidGasLimit => "Header has invalid gas limit",
Error::ExtraDataOutOfBounds => "Header has too large extra data",
Error::TimestampOverflow => "Header has too large timestamp",
Error::MissingParentBlock => "Header has unknown parent hash",
Error::MissingStep => "Header is missing step seal",
Error::MissingSignature => "Header is missing signature seal",
Error::MissingEmptySteps => "Header is missing empty steps seal",
Error::DoubleVote => "Header has invalid step in seal",
Error::InsufficientProof => "Header has insufficient proof",
Error::InvalidDifficulty => "Header has invalid difficulty",
Error::NotValidator => "Header is sealed by unexpected validator",
Error::MissingTransactionsReceipts =>
"The import operation requires transactions receipts",
Error::RedundantTransactionsReceipts => "Redundant transactions receipts are provided",
Error::TransactionsReceiptsMismatch => "Invalid transactions receipts provided",
Error::UnsignedTooFarInTheFuture => "The unsigned header is too far in future",
Error::TryingToFinalizeSibling => "Trying to finalize sibling of finalized block",
Error::HeaderTimestampIsAhead => "Header timestamp is ahead of on-chain timestamp",
}
}
/// Return unique error code.
pub fn code(&self) -> u8 {
*self as u8
}
}
@@ -1,557 +0,0 @@
// Copyright 2019-2021 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/>.
use crate::{error::Error, Storage};
use bp_eth_poa::{public_to_address, Address, AuraHeader, HeaderId, SealedEmptyStep, H256};
use codec::{Decode, Encode};
use scale_info::TypeInfo;
use sp_io::crypto::secp256k1_ecdsa_recover;
use sp_runtime::RuntimeDebug;
use sp_std::{
collections::{
btree_map::{BTreeMap, Entry},
btree_set::BTreeSet,
vec_deque::VecDeque,
},
prelude::*,
};
/// Cached finality votes for given block.
#[derive(RuntimeDebug)]
#[cfg_attr(test, derive(PartialEq))]
pub struct CachedFinalityVotes<Submitter> {
/// True if we have stopped at best finalized block' sibling. This means
/// that we are trying to finalize block from fork that has forked before
/// best finalized.
pub stopped_at_finalized_sibling: bool,
/// Header ancestors that were read while we have been searching for
/// cached votes entry. The newest header has index 0.
pub unaccounted_ancestry: VecDeque<(HeaderId, Option<Submitter>, AuraHeader)>,
/// Cached finality votes, if they have been found. The associated
/// header is not included into `unaccounted_ancestry`.
pub votes: Option<FinalityVotes<Submitter>>,
}
/// Finality effects.
#[derive(RuntimeDebug)]
#[cfg_attr(test, derive(PartialEq))]
pub struct FinalityEffects<Submitter> {
/// Finalized headers.
pub finalized_headers: Vec<(HeaderId, Option<Submitter>)>,
/// Finality votes used in computation.
pub votes: FinalityVotes<Submitter>,
}
/// Finality votes for given block.
#[derive(RuntimeDebug, Decode, Encode, TypeInfo)]
#[cfg_attr(test, derive(Clone, PartialEq))]
pub struct FinalityVotes<Submitter> {
/// Number of votes per each validator.
pub votes: BTreeMap<Address, u64>,
/// Ancestry blocks with the oldest ancestors at the beginning and newest at the
/// end of the queue.
pub ancestry: VecDeque<FinalityAncestor<Submitter>>,
}
/// Information about block ancestor that is used in computations.
#[derive(RuntimeDebug, Decode, Encode, TypeInfo)]
#[cfg_attr(test, derive(Clone, Default, PartialEq))]
pub struct FinalityAncestor<Submitter> {
/// Bock id.
pub id: HeaderId,
/// Block submitter.
pub submitter: Option<Submitter>,
/// Validators that have signed this block and empty steps on top
/// of this block.
pub signers: BTreeSet<Address>,
}
/// Tries to finalize blocks when given block is imported.
///
/// Returns numbers and hashes of finalized blocks in ascending order.
pub fn finalize_blocks<S: Storage>(
storage: &S,
best_finalized: HeaderId,
header_validators: (HeaderId, &[Address]),
id: HeaderId,
submitter: Option<&S::Submitter>,
header: &AuraHeader,
two_thirds_majority_transition: u64,
) -> Result<FinalityEffects<S::Submitter>, Error> {
// compute count of voters for every unfinalized block in ancestry
let validators = header_validators.1.iter().collect();
let votes = prepare_votes(
header
.parent_id()
.map(|parent_id| {
storage.cached_finality_votes(&parent_id, &best_finalized, |hash| {
*hash == header_validators.0.hash || *hash == best_finalized.hash
})
})
.unwrap_or_default(),
best_finalized,
&validators,
id,
header,
submitter.cloned(),
)?;
// now let's iterate in reverse order && find just finalized blocks
let mut finalized_headers = Vec::new();
let mut current_votes = votes.votes.clone();
for ancestor in &votes.ancestry {
if !is_finalized(
&validators,
&current_votes,
ancestor.id.number >= two_thirds_majority_transition,
) {
break
}
remove_signers_votes(&ancestor.signers, &mut current_votes);
finalized_headers.push((ancestor.id, ancestor.submitter.clone()));
}
Ok(FinalityEffects { finalized_headers, votes })
}
/// Returns true if there are enough votes to treat this header as finalized.
fn is_finalized(
validators: &BTreeSet<&Address>,
votes: &BTreeMap<Address, u64>,
requires_two_thirds_majority: bool,
) -> bool {
(!requires_two_thirds_majority && votes.len() * 2 > validators.len()) ||
(requires_two_thirds_majority && votes.len() * 3 > validators.len() * 2)
}
/// Prepare 'votes' of header and its ancestors' signers.
pub(crate) fn prepare_votes<Submitter>(
mut cached_votes: CachedFinalityVotes<Submitter>,
best_finalized: HeaderId,
validators: &BTreeSet<&Address>,
id: HeaderId,
header: &AuraHeader,
submitter: Option<Submitter>,
) -> Result<FinalityVotes<Submitter>, Error> {
// if we have reached finalized block sibling, then we're trying
// to switch finalized blocks
if cached_votes.stopped_at_finalized_sibling {
return Err(Error::TryingToFinalizeSibling)
}
// this fn can only work with single validators set
if !validators.contains(&header.author) {
return Err(Error::NotValidator)
}
// now we have votes that were valid when some block B has been inserted
// things may have changed a bit, but we do not need to read anything else
// from the db, because we have ancestry
// so the only thing we need to do is:
// 1) remove votes from blocks that have been finalized after B has been inserted;
// 2) add votes from B descendants
let mut votes = cached_votes.votes.unwrap_or_default();
// remove votes from finalized blocks
while let Some(old_ancestor) = votes.ancestry.pop_front() {
if old_ancestor.id.number > best_finalized.number {
votes.ancestry.push_front(old_ancestor);
break
}
remove_signers_votes(&old_ancestor.signers, &mut votes.votes);
}
// add votes from new blocks
let mut parent_empty_step_signers = empty_steps_signers(header);
let mut unaccounted_ancestry = VecDeque::new();
while let Some((ancestor_id, ancestor_submitter, ancestor)) =
cached_votes.unaccounted_ancestry.pop_front()
{
let mut signers = empty_steps_signers(&ancestor);
sp_std::mem::swap(&mut signers, &mut parent_empty_step_signers);
signers.insert(ancestor.author);
add_signers_votes(validators, &signers, &mut votes.votes)?;
unaccounted_ancestry.push_front(FinalityAncestor {
id: ancestor_id,
submitter: ancestor_submitter,
signers,
});
}
votes.ancestry.extend(unaccounted_ancestry);
// add votes from block itself
let mut header_signers = BTreeSet::new();
header_signers.insert(header.author);
*votes.votes.entry(header.author).or_insert(0) += 1;
votes
.ancestry
.push_back(FinalityAncestor { id, submitter, signers: header_signers });
Ok(votes)
}
/// Increase count of 'votes' for every passed signer.
/// Fails if at least one of signers is not in the `validators` set.
fn add_signers_votes(
validators: &BTreeSet<&Address>,
signers_to_add: &BTreeSet<Address>,
votes: &mut BTreeMap<Address, u64>,
) -> Result<(), Error> {
for signer in signers_to_add {
if !validators.contains(signer) {
return Err(Error::NotValidator)
}
*votes.entry(*signer).or_insert(0) += 1;
}
Ok(())
}
/// Decrease 'votes' count for every passed signer.
fn remove_signers_votes(signers_to_remove: &BTreeSet<Address>, votes: &mut BTreeMap<Address, u64>) {
for signer in signers_to_remove {
match votes.entry(*signer) {
Entry::Occupied(mut entry) =>
if *entry.get() <= 1 {
entry.remove();
} else {
*entry.get_mut() -= 1;
},
Entry::Vacant(_) => unreachable!("we only remove signers that have been added; qed"),
}
}
}
/// Returns unique set of empty steps signers.
fn empty_steps_signers(header: &AuraHeader) -> BTreeSet<Address> {
header
.empty_steps()
.into_iter()
.flatten()
.filter_map(|step| empty_step_signer(&step, &header.parent_hash))
.collect::<BTreeSet<_>>()
}
/// Returns author of empty step signature.
fn empty_step_signer(empty_step: &SealedEmptyStep, parent_hash: &H256) -> Option<Address> {
let message = empty_step.message(parent_hash);
secp256k1_ecdsa_recover(empty_step.signature.as_fixed_bytes(), message.as_fixed_bytes())
.ok()
.map(|public| public_to_address(&public))
}
impl<Submitter> Default for CachedFinalityVotes<Submitter> {
fn default() -> Self {
CachedFinalityVotes {
stopped_at_finalized_sibling: false,
unaccounted_ancestry: VecDeque::new(),
votes: None,
}
}
}
impl<Submitter> Default for FinalityVotes<Submitter> {
fn default() -> Self {
FinalityVotes { votes: BTreeMap::new(), ancestry: VecDeque::new() }
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
mock::{
insert_header, run_test, validator, validators_addresses, HeaderBuilder, TestRuntime,
},
BridgeStorage, FinalityCache, HeaderToImport,
};
const TOTAL_VALIDATORS: usize = 5;
#[test]
fn verifies_header_author() {
run_test(TOTAL_VALIDATORS, |_| {
assert_eq!(
finalize_blocks(
&BridgeStorage::<TestRuntime>::new(),
Default::default(),
(Default::default(), &[]),
Default::default(),
None,
&AuraHeader::default(),
0,
),
Err(Error::NotValidator),
);
});
}
#[test]
fn finalize_blocks_works() {
run_test(TOTAL_VALIDATORS, |ctx| {
// let's say we have 5 validators (we need 'votes' from 3 validators to achieve
// finality)
let mut storage = BridgeStorage::<TestRuntime>::new();
// when header#1 is inserted, nothing is finalized (1 vote)
let header1 = HeaderBuilder::with_parent(&ctx.genesis).sign_by(&validator(0));
let id1 = header1.compute_id();
let mut header_to_import = HeaderToImport {
context: storage.import_context(None, &header1.parent_hash).unwrap(),
is_best: true,
id: id1,
header: header1,
total_difficulty: 0.into(),
enacted_change: None,
scheduled_change: None,
finality_votes: Default::default(),
};
assert_eq!(
finalize_blocks(
&storage,
ctx.genesis.compute_id(),
(Default::default(), &ctx.addresses),
id1,
None,
&header_to_import.header,
u64::MAX,
)
.map(|eff| eff.finalized_headers),
Ok(Vec::new()),
);
storage.insert_header(header_to_import.clone());
// when header#2 is inserted, nothing is finalized (2 votes)
header_to_import.header =
HeaderBuilder::with_parent_hash(id1.hash).sign_by(&validator(1));
header_to_import.id = header_to_import.header.compute_id();
let id2 = header_to_import.header.compute_id();
assert_eq!(
finalize_blocks(
&storage,
ctx.genesis.compute_id(),
(Default::default(), &ctx.addresses),
id2,
None,
&header_to_import.header,
u64::MAX,
)
.map(|eff| eff.finalized_headers),
Ok(Vec::new()),
);
storage.insert_header(header_to_import.clone());
// when header#3 is inserted, header#1 is finalized (3 votes)
header_to_import.header =
HeaderBuilder::with_parent_hash(id2.hash).sign_by(&validator(2));
header_to_import.id = header_to_import.header.compute_id();
let id3 = header_to_import.header.compute_id();
assert_eq!(
finalize_blocks(
&storage,
ctx.genesis.compute_id(),
(Default::default(), &ctx.addresses),
id3,
None,
&header_to_import.header,
u64::MAX,
)
.map(|eff| eff.finalized_headers),
Ok(vec![(id1, None)]),
);
storage.insert_header(header_to_import);
});
}
#[test]
fn cached_votes_are_updated_with_ancestry() {
// we're inserting header#5
// cached votes are from header#3
// header#4 has finalized header#1 and header#2
// => when inserting header#5, we need to:
// 1) remove votes from header#1 and header#2
// 2) add votes from header#4 and header#5
let validators = validators_addresses(5);
let headers = (1..6)
.map(|number| {
HeaderBuilder::with_number(number).sign_by(&validator(number as usize - 1))
})
.collect::<Vec<_>>();
let ancestry = headers
.iter()
.map(|header| FinalityAncestor {
id: header.compute_id(),
signers: vec![header.author].into_iter().collect(),
..Default::default()
})
.collect::<Vec<_>>();
let header5 = headers[4].clone();
assert_eq!(
prepare_votes::<()>(
CachedFinalityVotes {
stopped_at_finalized_sibling: false,
unaccounted_ancestry:
vec![(headers[3].compute_id(), None, headers[3].clone()),]
.into_iter()
.collect(),
votes: Some(FinalityVotes {
votes: vec![(validators[0], 1), (validators[1], 1), (validators[2], 1),]
.into_iter()
.collect(),
ancestry: ancestry[..3].iter().cloned().collect(),
}),
},
headers[1].compute_id(),
&validators.iter().collect(),
header5.compute_id(),
&header5,
None,
)
.unwrap(),
FinalityVotes {
votes: vec![(validators[2], 1), (validators[3], 1), (validators[4], 1),]
.into_iter()
.collect(),
ancestry: ancestry[2..].iter().cloned().collect(),
},
);
}
#[test]
fn prepare_votes_respects_finality_cache() {
run_test(TOTAL_VALIDATORS, |ctx| {
// we need signatures of 3 validators to finalize block
let mut storage = BridgeStorage::<TestRuntime>::new();
// headers 1..3 are signed by validator#0
// headers 4..6 are signed by validator#1
// headers 7..9 are signed by validator#2
let mut hashes = Vec::new();
let mut headers = Vec::new();
let mut ancestry = Vec::new();
let mut parent_hash = ctx.genesis.compute_hash();
for i in 1..10 {
let header =
HeaderBuilder::with_parent_hash(parent_hash).sign_by(&validator((i - 1) / 3));
let id = header.compute_id();
insert_header(&mut storage, header.clone());
hashes.push(id.hash);
ancestry.push(FinalityAncestor {
id: header.compute_id(),
submitter: None,
signers: vec![header.author].into_iter().collect(),
});
headers.push(header);
parent_hash = id.hash;
}
// when we're inserting header#7 and last finalized header is 0:
// check that votes at #7 are computed correctly without cache
let expected_votes_at_7 = FinalityVotes {
votes: vec![(ctx.addresses[0], 3), (ctx.addresses[1], 3), (ctx.addresses[2], 1)]
.into_iter()
.collect(),
ancestry: ancestry[..7].iter().cloned().collect(),
};
let id7 = headers[6].compute_id();
assert_eq!(
prepare_votes(
storage.cached_finality_votes(
&headers.get(5).unwrap().compute_id(),
&ctx.genesis.compute_id(),
|_| false,
),
Default::default(),
&ctx.addresses.iter().collect(),
id7,
headers.get(6).unwrap(),
None,
)
.unwrap(),
expected_votes_at_7,
);
// cached votes at #5
let expected_votes_at_5 = FinalityVotes {
votes: vec![(ctx.addresses[0], 3), (ctx.addresses[1], 2)].into_iter().collect(),
ancestry: ancestry[..5].iter().cloned().collect(),
};
FinalityCache::<TestRuntime>::insert(hashes[4], expected_votes_at_5);
// when we're inserting header#7 and last finalized header is 0:
// check that votes at #7 are computed correctly with cache
assert_eq!(
prepare_votes(
storage.cached_finality_votes(
&headers.get(5).unwrap().compute_id(),
&ctx.genesis.compute_id(),
|_| false,
),
Default::default(),
&ctx.addresses.iter().collect(),
id7,
headers.get(6).unwrap(),
None,
)
.unwrap(),
expected_votes_at_7,
);
// when we're inserting header#7 and last finalized header is 3:
// check that votes at #7 are computed correctly with cache
let expected_votes_at_7 = FinalityVotes {
votes: vec![(ctx.addresses[1], 3), (ctx.addresses[2], 1)].into_iter().collect(),
ancestry: ancestry[3..7].iter().cloned().collect(),
};
assert_eq!(
prepare_votes(
storage.cached_finality_votes(
&headers.get(5).unwrap().compute_id(),
&headers.get(2).unwrap().compute_id(),
|hash| *hash == hashes[2],
),
headers[2].compute_id(),
&ctx.addresses.iter().collect(),
id7,
headers.get(6).unwrap(),
None,
)
.unwrap(),
expected_votes_at_7,
);
});
}
#[test]
fn prepare_votes_fails_when_finalized_sibling_is_in_ancestry() {
assert_eq!(
prepare_votes::<()>(
CachedFinalityVotes { stopped_at_finalized_sibling: true, ..Default::default() },
Default::default(),
&validators_addresses(3).iter().collect(),
Default::default(),
&Default::default(),
None,
),
Err(Error::TryingToFinalizeSibling),
);
}
}
@@ -1,600 +0,0 @@
// Copyright 2019-2021 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/>.
use crate::{
error::Error,
finality::finalize_blocks,
validators::{Validators, ValidatorsConfiguration},
verification::{is_importable_header, verify_aura_header},
AuraConfiguration, ChainTime, ChangeToEnact, PruningStrategy, Storage,
};
use bp_eth_poa::{AuraHeader, HeaderId, Receipt};
use sp_std::{collections::btree_map::BTreeMap, prelude::*};
/// Imports a bunch of headers and updates blocks finality.
///
/// Transactions receipts must be provided if `header_import_requires_receipts()`
/// has returned true.
/// If successful, returns tuple where first element is the number of useful headers
/// we have imported and the second element is the number of useless headers (duplicate)
/// we have NOT imported.
/// Returns error if fatal error has occurred during import. Some valid headers may be
/// imported in this case.
/// TODO: update me (https://github.com/paritytech/parity-bridges-common/issues/415)
#[allow(clippy::too_many_arguments)]
pub fn import_headers<S: Storage, PS: PruningStrategy, CT: ChainTime>(
storage: &mut S,
pruning_strategy: &mut PS,
aura_config: &AuraConfiguration,
validators_config: &ValidatorsConfiguration,
submitter: Option<S::Submitter>,
headers: Vec<(AuraHeader, Option<Vec<Receipt>>)>,
chain_time: &CT,
finalized_headers: &mut BTreeMap<S::Submitter, u64>,
) -> Result<(u64, u64), Error> {
let mut useful = 0;
let mut useless = 0;
for (header, receipts) in headers {
let import_result = import_header(
storage,
pruning_strategy,
aura_config,
validators_config,
submitter.clone(),
header,
chain_time,
receipts,
);
match import_result {
Ok((_, finalized)) => {
for (_, submitter) in finalized {
if let Some(submitter) = submitter {
*finalized_headers.entry(submitter).or_default() += 1;
}
}
useful += 1;
},
Err(Error::AncientHeader) | Err(Error::KnownHeader) => useless += 1,
Err(error) => return Err(error),
}
}
Ok((useful, useless))
}
/// A vector of finalized headers and their submitters.
pub type FinalizedHeaders<S> = Vec<(HeaderId, Option<<S as Storage>::Submitter>)>;
/// Imports given header and updates blocks finality (if required).
///
/// Transactions receipts must be provided if `header_import_requires_receipts()`
/// has returned true.
///
/// Returns imported block id and list of all finalized headers.
/// TODO: update me (https://github.com/paritytech/parity-bridges-common/issues/415)
#[allow(clippy::too_many_arguments)]
pub fn import_header<S: Storage, PS: PruningStrategy, CT: ChainTime>(
storage: &mut S,
pruning_strategy: &mut PS,
aura_config: &AuraConfiguration,
validators_config: &ValidatorsConfiguration,
submitter: Option<S::Submitter>,
header: AuraHeader,
chain_time: &CT,
receipts: Option<Vec<Receipt>>,
) -> Result<(HeaderId, FinalizedHeaders<S>), Error> {
// first check that we are able to import this header at all
let (header_id, finalized_id) = is_importable_header(storage, &header)?;
// verify header
let import_context = verify_aura_header(storage, aura_config, submitter, &header, chain_time)?;
// check if block schedules new validators
let validators = Validators::new(validators_config);
let (scheduled_change, enacted_change) =
validators.extract_validators_change(&header, receipts)?;
// check if block finalizes some other blocks and corresponding scheduled validators
let validators_set = import_context.validators_set();
let finalized_blocks = finalize_blocks(
storage,
finalized_id,
(validators_set.enact_block, &validators_set.validators),
header_id,
import_context.submitter(),
&header,
aura_config.two_thirds_majority_transition,
)?;
let enacted_change = enacted_change
.map(|validators| ChangeToEnact { signal_block: None, validators })
.or_else(|| {
validators.finalize_validators_change(storage, &finalized_blocks.finalized_headers)
});
// NOTE: we can't return Err() from anywhere below this line
// (because otherwise we'll have inconsistent storage if transaction will fail)
// and finally insert the block
let (best_id, best_total_difficulty) = storage.best_block();
let total_difficulty = import_context.total_difficulty() + header.difficulty;
let is_best = total_difficulty > best_total_difficulty;
storage.insert_header(import_context.into_import_header(
is_best,
header_id,
header,
total_difficulty,
enacted_change,
scheduled_change,
finalized_blocks.votes,
));
// compute upper border of updated pruning range
let new_best_block_id = if is_best { header_id } else { best_id };
let new_best_finalized_block_id = finalized_blocks.finalized_headers.last().map(|(id, _)| *id);
let pruning_upper_bound = pruning_strategy.pruning_upper_bound(
new_best_block_id.number,
new_best_finalized_block_id.map(|id| id.number).unwrap_or(finalized_id.number),
);
// now mark finalized headers && prune old headers
storage.finalize_and_prune_headers(new_best_finalized_block_id, pruning_upper_bound);
Ok((header_id, finalized_blocks.finalized_headers))
}
/// Returns true if transactions receipts are required to import given header.
pub fn header_import_requires_receipts<S: Storage>(
storage: &S,
validators_config: &ValidatorsConfiguration,
header: &AuraHeader,
) -> bool {
is_importable_header(storage, header)
.map(|_| Validators::new(validators_config))
.map(|validators| validators.maybe_signals_validators_change(header))
.unwrap_or(false)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
mock::{
run_test, secret_to_address, test_aura_config, test_validators_config, validator,
validators_addresses, validators_change_receipt, HeaderBuilder,
KeepSomeHeadersBehindBest, TestRuntime, GAS_LIMIT,
},
validators::ValidatorsSource,
BlocksToPrune, BridgeStorage, Headers, PruningRange,
};
use libsecp256k1::SecretKey;
const TOTAL_VALIDATORS: usize = 3;
#[test]
fn rejects_finalized_block_competitors() {
run_test(TOTAL_VALIDATORS, |_| {
let mut storage = BridgeStorage::<TestRuntime>::new();
storage.finalize_and_prune_headers(
Some(HeaderId { number: 100, ..Default::default() }),
0,
);
assert_eq!(
import_header(
&mut storage,
&mut KeepSomeHeadersBehindBest::default(),
&test_aura_config(),
&test_validators_config(),
None,
Default::default(),
&(),
None,
),
Err(Error::AncientHeader),
);
});
}
#[test]
fn rejects_known_header() {
run_test(TOTAL_VALIDATORS, |ctx| {
let mut storage = BridgeStorage::<TestRuntime>::new();
let header = HeaderBuilder::with_parent(&ctx.genesis).sign_by(&validator(1));
assert_eq!(
import_header(
&mut storage,
&mut KeepSomeHeadersBehindBest::default(),
&test_aura_config(),
&test_validators_config(),
None,
header.clone(),
&(),
None,
)
.map(|_| ()),
Ok(()),
);
assert_eq!(
import_header(
&mut storage,
&mut KeepSomeHeadersBehindBest::default(),
&test_aura_config(),
&test_validators_config(),
None,
header,
&(),
None,
)
.map(|_| ()),
Err(Error::KnownHeader),
);
});
}
#[test]
fn import_header_works() {
run_test(TOTAL_VALIDATORS, |ctx| {
let validators_config = ValidatorsConfiguration::Multi(vec![
(0, ValidatorsSource::List(ctx.addresses.clone())),
(1, ValidatorsSource::List(validators_addresses(2))),
]);
let mut storage = BridgeStorage::<TestRuntime>::new();
let header = HeaderBuilder::with_parent(&ctx.genesis).sign_by(&validator(1));
let hash = header.compute_hash();
assert_eq!(
import_header(
&mut storage,
&mut KeepSomeHeadersBehindBest::default(),
&test_aura_config(),
&validators_config,
None,
header,
&(),
None
)
.map(|_| ()),
Ok(()),
);
// check that new validators will be used for next header
let imported_header = Headers::<TestRuntime>::get(&hash).unwrap();
assert_eq!(
imported_header.next_validators_set_id,
1, // new set is enacted from config
);
});
}
#[test]
fn headers_are_pruned_during_import() {
run_test(TOTAL_VALIDATORS, |ctx| {
let validators_config = ValidatorsConfiguration::Single(ValidatorsSource::Contract(
[3; 20].into(),
ctx.addresses.clone(),
));
let validators = vec![validator(0), validator(1), validator(2)];
let mut storage = BridgeStorage::<TestRuntime>::new();
// header [0..11] are finalizing blocks [0; 9]
// => since we want to keep 10 finalized blocks, we aren't pruning anything
let mut latest_block_id = Default::default();
for i in 1..11 {
let header = HeaderBuilder::with_parent_number(i - 1).sign_by_set(&validators);
let parent_id = header.parent_id().unwrap();
let (rolling_last_block_id, finalized_blocks) = import_header(
&mut storage,
&mut KeepSomeHeadersBehindBest::default(),
&test_aura_config(),
&validators_config,
Some(100),
header,
&(),
None,
)
.unwrap();
match i {
2..=10 => {
assert_eq!(finalized_blocks, vec![(parent_id, Some(100))], "At {}", i,)
},
_ => assert_eq!(finalized_blocks, vec![], "At {}", i),
}
latest_block_id = rolling_last_block_id;
}
assert!(storage.header(&ctx.genesis.compute_hash()).is_some());
// header 11 finalizes headers [10] AND schedules change
// => we prune header#0
let header11 = HeaderBuilder::with_parent_number(10)
.log_bloom((&[0xff; 256]).into())
.receipts_root(
"ead6c772ba0083bbff497ba0f4efe47c199a2655401096c21ab7450b6c466d97"
.parse()
.unwrap(),
)
.sign_by_set(&validators);
let parent_id = header11.parent_id().unwrap();
let (rolling_last_block_id, finalized_blocks) = import_header(
&mut storage,
&mut KeepSomeHeadersBehindBest::default(),
&test_aura_config(),
&validators_config,
Some(101),
header11.clone(),
&(),
Some(vec![validators_change_receipt(latest_block_id.hash)]),
)
.unwrap();
assert_eq!(finalized_blocks, vec![(parent_id, Some(100))]);
assert!(storage.header(&ctx.genesis.compute_hash()).is_none());
latest_block_id = rolling_last_block_id;
// and now let's say validators 1 && 2 went offline
// => in the range 12-25 no blocks are finalized, but we still continue to prune old
// headers until header#11 is met. we can't prune #11, because it schedules change
let mut step = 56u64;
let mut expected_blocks = vec![(header11.compute_id(), Some(101))];
for i in 12..25 {
let header = HeaderBuilder::with_parent_hash(latest_block_id.hash)
.difficulty(i.into())
.step(step)
.sign_by_set(&validators);
expected_blocks.push((header.compute_id(), Some(102)));
let (rolling_last_block_id, finalized_blocks) = import_header(
&mut storage,
&mut KeepSomeHeadersBehindBest::default(),
&test_aura_config(),
&validators_config,
Some(102),
header,
&(),
None,
)
.unwrap();
assert_eq!(finalized_blocks, vec![]);
latest_block_id = rolling_last_block_id;
step += 3;
}
assert_eq!(
BlocksToPrune::<TestRuntime, ()>::get(),
PruningRange { oldest_unpruned_block: 11, oldest_block_to_keep: 14 },
);
// now let's insert block signed by validator 1
// => blocks 11..24 are finalized and blocks 11..14 are pruned
step -= 2;
let header = HeaderBuilder::with_parent_hash(latest_block_id.hash)
.difficulty(25.into())
.step(step)
.sign_by_set(&validators);
let (_, finalized_blocks) = import_header(
&mut storage,
&mut KeepSomeHeadersBehindBest::default(),
&test_aura_config(),
&validators_config,
Some(103),
header,
&(),
None,
)
.unwrap();
assert_eq!(finalized_blocks, expected_blocks);
assert_eq!(
BlocksToPrune::<TestRuntime, ()>::get(),
PruningRange { oldest_unpruned_block: 15, oldest_block_to_keep: 15 },
);
});
}
fn import_custom_block<S: Storage>(
storage: &mut S,
validators: &[SecretKey],
header: AuraHeader,
) -> Result<HeaderId, Error> {
let id = header.compute_id();
import_header(
storage,
&mut KeepSomeHeadersBehindBest::default(),
&test_aura_config(),
&ValidatorsConfiguration::Single(ValidatorsSource::Contract(
[0; 20].into(),
validators.iter().map(secret_to_address).collect(),
)),
None,
header,
&(),
None,
)
.map(|_| id)
}
#[test]
fn import_of_non_best_block_may_finalize_blocks() {
run_test(TOTAL_VALIDATORS, |ctx| {
let mut storage = BridgeStorage::<TestRuntime>::new();
// insert headers (H1, validator1), (H2, validator1), (H3, validator1)
// making H3 the best header, without finalizing anything (we need 2 signatures)
let mut expected_best_block = Default::default();
for i in 1..4 {
let step = 1 + i * TOTAL_VALIDATORS as u64;
expected_best_block = import_custom_block(
&mut storage,
&ctx.validators,
HeaderBuilder::with_parent_number(i - 1)
.step(step)
.sign_by_set(&ctx.validators),
)
.unwrap();
}
let (best_block, best_difficulty) = storage.best_block();
assert_eq!(best_block, expected_best_block);
assert_eq!(storage.finalized_block(), ctx.genesis.compute_id());
// insert headers (H1', validator1), (H2', validator2), finalizing H2, even though H3
// has better difficulty than H2' (because there are more steps involved)
let mut expected_finalized_block = Default::default();
let mut parent_hash = ctx.genesis.compute_hash();
for i in 1..3 {
let step = i;
let id = import_custom_block(
&mut storage,
&ctx.validators,
HeaderBuilder::with_parent_hash(parent_hash)
.step(step)
.gas_limit((GAS_LIMIT + 1).into())
.sign_by_set(&ctx.validators),
)
.unwrap();
parent_hash = id.hash;
if i == 1 {
expected_finalized_block = id;
}
}
let (new_best_block, new_best_difficulty) = storage.best_block();
assert_eq!(new_best_block, expected_best_block);
assert_eq!(new_best_difficulty, best_difficulty);
assert_eq!(storage.finalized_block(), expected_finalized_block);
});
}
#[test]
fn append_to_unfinalized_fork_fails() {
const VALIDATORS: u64 = 5;
run_test(VALIDATORS as usize, |ctx| {
let mut storage = BridgeStorage::<TestRuntime>::new();
// header1, authored by validator[2] is best common block between two competing forks
let header1 = import_custom_block(
&mut storage,
&ctx.validators,
HeaderBuilder::with_parent_number(0).step(2).sign_by_set(&ctx.validators),
)
.unwrap();
assert_eq!(storage.best_block().0, header1);
assert_eq!(storage.finalized_block().number, 0);
// validator[3] has authored header2 (nothing is finalized yet)
let header2 = import_custom_block(
&mut storage,
&ctx.validators,
HeaderBuilder::with_parent_number(1).step(3).sign_by_set(&ctx.validators),
)
.unwrap();
assert_eq!(storage.best_block().0, header2);
assert_eq!(storage.finalized_block().number, 0);
// validator[4] has authored header3 (header1 is finalized)
let header3 = import_custom_block(
&mut storage,
&ctx.validators,
HeaderBuilder::with_parent_number(2).step(4).sign_by_set(&ctx.validators),
)
.unwrap();
assert_eq!(storage.best_block().0, header3);
assert_eq!(storage.finalized_block(), header1);
// validator[4] has authored 4 blocks: header2'...header5' (header1 is still finalized)
let header2_1 = import_custom_block(
&mut storage,
&ctx.validators,
HeaderBuilder::with_parent_number(1)
.gas_limit((GAS_LIMIT + 1).into())
.step(4)
.sign_by_set(&ctx.validators),
)
.unwrap();
let header3_1 = import_custom_block(
&mut storage,
&ctx.validators,
HeaderBuilder::with_parent_hash(header2_1.hash)
.step(4 + VALIDATORS)
.sign_by_set(&ctx.validators),
)
.unwrap();
let header4_1 = import_custom_block(
&mut storage,
&ctx.validators,
HeaderBuilder::with_parent_hash(header3_1.hash)
.step(4 + VALIDATORS * 2)
.sign_by_set(&ctx.validators),
)
.unwrap();
let header5_1 = import_custom_block(
&mut storage,
&ctx.validators,
HeaderBuilder::with_parent_hash(header4_1.hash)
.step(4 + VALIDATORS * 3)
.sign_by_set(&ctx.validators),
)
.unwrap();
assert_eq!(storage.best_block().0, header5_1);
assert_eq!(storage.finalized_block(), header1);
// when we import header4 { parent = header3 }, authored by validator[0], header2 is
// finalized
let header4 = import_custom_block(
&mut storage,
&ctx.validators,
HeaderBuilder::with_parent_number(3).step(5).sign_by_set(&ctx.validators),
)
.unwrap();
assert_eq!(storage.best_block().0, header5_1);
assert_eq!(storage.finalized_block(), header2);
// when we import header5 { parent = header4 }, authored by validator[1], header3 is
// finalized
let header5 = import_custom_block(
&mut storage,
&ctx.validators,
HeaderBuilder::with_parent_hash(header4.hash)
.step(6)
.sign_by_set(&ctx.validators),
)
.unwrap();
assert_eq!(storage.best_block().0, header5);
assert_eq!(storage.finalized_block(), header3);
// import of header2'' { parent = header1 } fails, because it has number <
// best_finalized
assert_eq!(
import_custom_block(
&mut storage,
&ctx.validators,
HeaderBuilder::with_parent_number(1)
.gas_limit((GAS_LIMIT + 1).into())
.step(3)
.sign_by_set(&ctx.validators)
),
Err(Error::AncientHeader),
);
// import of header6' should also fail because we're trying to append to fork thas
// has forked before finalized block
assert_eq!(
import_custom_block(
&mut storage,
&ctx.validators,
HeaderBuilder::with_parent_number(5)
.gas_limit((GAS_LIMIT + 1).into())
.step(5 + VALIDATORS * 4)
.sign_by_set(&ctx.validators),
),
Err(Error::TryingToFinalizeSibling),
);
});
}
}
File diff suppressed because it is too large Load Diff
@@ -1,188 +0,0 @@
// Copyright 2019-2021 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/>.
// From construct_runtime macro
#![allow(clippy::from_over_into)]
pub use crate::test_utils::{
insert_header, validator_utils::*, validators_change_receipt, HeaderBuilder, GAS_LIMIT,
};
pub use bp_eth_poa::signatures::secret_to_address;
use crate::{
validators::{ValidatorsConfiguration, ValidatorsSource},
AuraConfiguration, ChainTime, Config, GenesisConfig as CrateGenesisConfig, PruningStrategy,
};
use bp_eth_poa::{Address, AuraHeader, H256, U256};
use frame_support::{parameter_types, traits::GenesisBuild, weights::Weight};
use libsecp256k1::SecretKey;
use sp_runtime::{
testing::Header as SubstrateHeader,
traits::{BlakeTwo256, IdentityLookup},
Perbill,
};
pub type AccountId = u64;
type Block = frame_system::mocking::MockBlock<TestRuntime>;
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<TestRuntime>;
use crate as pallet_ethereum;
frame_support::construct_runtime! {
pub enum TestRuntime where
Block = Block,
NodeBlock = Block,
UncheckedExtrinsic = UncheckedExtrinsic,
{
System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
Ethereum: pallet_ethereum::{Pallet, Call},
}
}
parameter_types! {
pub const BlockHashCount: u64 = 250;
pub const MaximumBlockWeight: Weight = 1024;
pub const MaximumBlockLength: u32 = 2 * 1024;
pub const AvailableBlockRatio: Perbill = Perbill::one();
}
impl frame_system::Config for TestRuntime {
type Origin = Origin;
type Index = u64;
type Call = Call;
type BlockNumber = u64;
type Hash = H256;
type Hashing = BlakeTwo256;
type AccountId = AccountId;
type Lookup = IdentityLookup<Self::AccountId>;
type Header = SubstrateHeader;
type Event = Event;
type BlockHashCount = BlockHashCount;
type Version = ();
type PalletInfo = PalletInfo;
type AccountData = ();
type OnNewAccount = ();
type OnKilledAccount = ();
type BaseCallFilter = frame_support::traits::Everything;
type SystemWeightInfo = ();
type BlockWeights = ();
type BlockLength = ();
type DbWeight = ();
type SS58Prefix = ();
type OnSetCode = ();
}
parameter_types! {
pub const TestFinalityVotesCachingInterval: Option<u64> = Some(16);
pub TestAuraConfiguration: AuraConfiguration = test_aura_config();
pub TestValidatorsConfiguration: ValidatorsConfiguration = test_validators_config();
}
impl Config for TestRuntime {
type AuraConfiguration = TestAuraConfiguration;
type ValidatorsConfiguration = TestValidatorsConfiguration;
type FinalityVotesCachingInterval = TestFinalityVotesCachingInterval;
type PruningStrategy = KeepSomeHeadersBehindBest;
type ChainTime = ConstChainTime;
type OnHeadersSubmitted = ();
}
/// Test context.
pub struct TestContext {
/// Initial (genesis) header.
pub genesis: AuraHeader,
/// Number of initial validators.
pub total_validators: usize,
/// Secret keys of validators, ordered by validator index.
pub validators: Vec<SecretKey>,
/// Addresses of validators, ordered by validator index.
pub addresses: Vec<Address>,
}
/// Aura configuration that is used in tests by default.
pub fn test_aura_config() -> AuraConfiguration {
AuraConfiguration {
empty_steps_transition: u64::MAX,
strict_empty_steps_transition: 0,
validate_step_transition: 0x16e360,
validate_score_transition: 0x41a3c4,
two_thirds_majority_transition: u64::MAX,
min_gas_limit: 0x1388.into(),
max_gas_limit: U256::MAX,
maximum_extra_data_size: 0x20,
}
}
/// Validators configuration that is used in tests by default.
pub fn test_validators_config() -> ValidatorsConfiguration {
ValidatorsConfiguration::Single(ValidatorsSource::List(validators_addresses(3)))
}
/// Genesis header that is used in tests by default.
pub fn genesis() -> AuraHeader {
HeaderBuilder::genesis().sign_by(&validator(0))
}
/// Run test with default genesis header.
pub fn run_test<T>(total_validators: usize, test: impl FnOnce(TestContext) -> T) -> T {
run_test_with_genesis(genesis(), total_validators, test)
}
/// Run test with default genesis header.
pub fn run_test_with_genesis<T>(
genesis: AuraHeader,
total_validators: usize,
test: impl FnOnce(TestContext) -> T,
) -> T {
let validators = validators(total_validators);
let addresses = validators_addresses(total_validators);
sp_io::TestExternalities::from(
GenesisBuild::<TestRuntime>::build_storage(&CrateGenesisConfig {
initial_header: genesis.clone(),
initial_difficulty: 0.into(),
initial_validators: addresses.clone(),
})
.unwrap(),
)
.execute_with(|| test(TestContext { genesis, total_validators, validators, addresses }))
}
/// Pruning strategy that keeps 10 headers behind best block.
pub struct KeepSomeHeadersBehindBest(pub u64);
impl Default for KeepSomeHeadersBehindBest {
fn default() -> KeepSomeHeadersBehindBest {
KeepSomeHeadersBehindBest(10)
}
}
impl PruningStrategy for KeepSomeHeadersBehindBest {
fn pruning_upper_bound(&mut self, best_number: u64, _: u64) -> u64 {
best_number.saturating_sub(self.0)
}
}
/// Constant chain time
#[derive(Default)]
pub struct ConstChainTime;
impl ChainTime for ConstChainTime {
fn is_timestamp_ahead(&self, timestamp: u64) -> bool {
let now = i32::MAX as u64 / 2;
timestamp > now
}
}
@@ -1,322 +0,0 @@
// Copyright 2019-2021 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/>.
//! Utilities for testing and benchmarking the Ethereum Bridge Pallet.
//!
//! Although the name implies that it is used by tests, it shouldn't be be used _directly_ by tests.
//! Instead these utilities should be used by the Mock runtime, which in turn is used by tests.
//!
//! On the other hand, they may be used directly by the benchmark module.
// Since this is test code it's fine that not everything is used
#![allow(dead_code)]
use crate::{
finality::FinalityVotes, validators::CHANGE_EVENT_HASH, verification::calculate_score, Config,
HeaderToImport, Storage,
};
use bp_eth_poa::{
rlp_encode,
signatures::{secret_to_address, sign, SignHeader},
Address, AuraHeader, Bloom, Receipt, SealedEmptyStep, H256, U256,
};
use libsecp256k1::SecretKey;
use sp_std::prelude::*;
/// Gas limit valid in test environment.
pub const GAS_LIMIT: u64 = 0x2000;
/// Test header builder.
pub struct HeaderBuilder {
header: AuraHeader,
parent_header: AuraHeader,
}
impl HeaderBuilder {
/// Creates default genesis header.
pub fn genesis() -> Self {
let current_step = 0u64;
Self {
header: AuraHeader {
gas_limit: GAS_LIMIT.into(),
seal: vec![bp_eth_poa::rlp_encode(&current_step).to_vec(), vec![]],
..Default::default()
},
parent_header: Default::default(),
}
}
/// Creates default header on top of test parent with given hash.
#[cfg(test)]
pub fn with_parent_hash(parent_hash: H256) -> Self {
Self::with_parent_hash_on_runtime::<crate::mock::TestRuntime, ()>(parent_hash)
}
/// Creates default header on top of test parent with given number. First parent is selected.
#[cfg(test)]
pub fn with_parent_number(parent_number: u64) -> Self {
Self::with_parent_number_on_runtime::<crate::mock::TestRuntime, ()>(parent_number)
}
/// Creates default header on top of parent with given hash.
pub fn with_parent_hash_on_runtime<T: Config<I>, I: 'static>(parent_hash: H256) -> Self {
use crate::Headers;
let parent_header = Headers::<T, I>::get(&parent_hash).unwrap().header;
Self::with_parent(&parent_header)
}
/// Creates default header on top of parent with given number. First parent is selected.
pub fn with_parent_number_on_runtime<T: Config<I>, I: 'static>(parent_number: u64) -> Self {
use crate::HeadersByNumber;
let parent_hash = HeadersByNumber::<T, I>::get(parent_number).unwrap()[0];
Self::with_parent_hash_on_runtime::<T, I>(parent_hash)
}
/// Creates default header on top of non-existent parent.
#[cfg(test)]
pub fn with_number(number: u64) -> Self {
Self::with_parent(&AuraHeader {
number: number - 1,
seal: vec![bp_eth_poa::rlp_encode(&(number - 1)).to_vec(), vec![]],
..Default::default()
})
}
/// Creates default header on top of given parent.
pub fn with_parent(parent_header: &AuraHeader) -> Self {
let parent_step = parent_header.step().unwrap();
let current_step = parent_step + 1;
Self {
header: AuraHeader {
parent_hash: parent_header.compute_hash(),
number: parent_header.number + 1,
gas_limit: GAS_LIMIT.into(),
seal: vec![bp_eth_poa::rlp_encode(&current_step).to_vec(), vec![]],
difficulty: calculate_score(parent_step, current_step, 0),
..Default::default()
},
parent_header: parent_header.clone(),
}
}
/// Update step of this header.
pub fn step(mut self, step: u64) -> Self {
let parent_step = self.parent_header.step();
self.header.seal[0] = rlp_encode(&step).to_vec();
self.header.difficulty = parent_step
.map(|parent_step| calculate_score(parent_step, step, 0))
.unwrap_or_default();
self
}
/// Adds empty steps to this header.
pub fn empty_steps(mut self, empty_steps: &[(&SecretKey, u64)]) -> Self {
let sealed_empty_steps = empty_steps
.iter()
.map(|(author, step)| {
let mut empty_step = SealedEmptyStep { step: *step, signature: Default::default() };
let message = empty_step.message(&self.header.parent_hash);
let signature: [u8; 65] = sign(author, message).into();
empty_step.signature = signature.into();
empty_step
})
.collect::<Vec<_>>();
// by default in test configuration headers are generated without empty steps seal
if self.header.seal.len() < 3 {
self.header.seal.push(Vec::new());
}
self.header.seal[2] = SealedEmptyStep::rlp_of(&sealed_empty_steps);
self
}
/// Update difficulty field of this header.
pub fn difficulty(mut self, difficulty: U256) -> Self {
self.header.difficulty = difficulty;
self
}
/// Update extra data field of this header.
pub fn extra_data(mut self, extra_data: Vec<u8>) -> Self {
self.header.extra_data = extra_data;
self
}
/// Update gas limit field of this header.
pub fn gas_limit(mut self, gas_limit: U256) -> Self {
self.header.gas_limit = gas_limit;
self
}
/// Update gas used field of this header.
pub fn gas_used(mut self, gas_used: U256) -> Self {
self.header.gas_used = gas_used;
self
}
/// Update log bloom field of this header.
pub fn log_bloom(mut self, log_bloom: Bloom) -> Self {
self.header.log_bloom = log_bloom;
self
}
/// Update receipts root field of this header.
pub fn receipts_root(mut self, receipts_root: H256) -> Self {
self.header.receipts_root = receipts_root;
self
}
/// Update timestamp field of this header.
pub fn timestamp(mut self, timestamp: u64) -> Self {
self.header.timestamp = timestamp;
self
}
/// Update transactions root field of this header.
pub fn transactions_root(mut self, transactions_root: H256) -> Self {
self.header.transactions_root = transactions_root;
self
}
/// Signs header by given author.
pub fn sign_by(self, author: &SecretKey) -> AuraHeader {
self.header.sign_by(author)
}
/// Signs header by given authors set.
pub fn sign_by_set(self, authors: &[SecretKey]) -> AuraHeader {
self.header.sign_by_set(authors)
}
}
/// Helper function for getting a genesis header which has been signed by an authority.
pub fn build_genesis_header(author: &SecretKey) -> AuraHeader {
let genesis = HeaderBuilder::genesis();
genesis.header.sign_by(author)
}
/// Helper function for building a custom child header which has been signed by an authority.
pub fn build_custom_header<F>(
author: &SecretKey,
previous: &AuraHeader,
customize_header: F,
) -> AuraHeader
where
F: FnOnce(AuraHeader) -> AuraHeader,
{
let new_header = HeaderBuilder::with_parent(previous);
let custom_header = customize_header(new_header.header);
custom_header.sign_by(author)
}
/// Insert unverified header into storage.
///
/// This function assumes that the header is signed by validator from the current set.
pub fn insert_header<S: Storage>(storage: &mut S, header: AuraHeader) {
let id = header.compute_id();
let best_finalized = storage.finalized_block();
let import_context = storage.import_context(None, &header.parent_hash).unwrap();
let parent_finality_votes =
storage.cached_finality_votes(&header.parent_id().unwrap(), &best_finalized, |_| false);
let finality_votes = crate::finality::prepare_votes(
parent_finality_votes,
best_finalized,
&import_context.validators_set().validators.iter().collect(),
id,
&header,
None,
)
.unwrap();
storage.insert_header(HeaderToImport {
context: storage.import_context(None, &header.parent_hash).unwrap(),
is_best: true,
id,
header,
total_difficulty: 0.into(),
enacted_change: None,
scheduled_change: None,
finality_votes,
});
}
/// Insert unverified header into storage.
///
/// No assumptions about header author are made. The cost is that finality votes cache
/// is filled incorrectly, so this function shall not be used if you're going to insert
/// (or import) header descendants.
pub fn insert_dummy_header<S: Storage>(storage: &mut S, header: AuraHeader) {
storage.insert_header(HeaderToImport {
context: storage.import_context(None, &header.parent_hash).unwrap(),
is_best: true,
id: header.compute_id(),
header,
total_difficulty: 0.into(),
enacted_change: None,
scheduled_change: None,
finality_votes: FinalityVotes::default(),
});
}
pub fn validators_change_receipt(parent_hash: H256) -> Receipt {
use bp_eth_poa::{LogEntry, TransactionOutcome};
Receipt {
gas_used: 0.into(),
log_bloom: (&[0xff; 256]).into(),
outcome: TransactionOutcome::Unknown,
logs: vec![LogEntry {
address: [3; 20].into(),
topics: vec![CHANGE_EVENT_HASH.into(), parent_hash],
data: vec![
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
],
}],
}
}
pub mod validator_utils {
use super::*;
/// Return key pair of given test validator.
pub fn validator(index: usize) -> SecretKey {
let mut raw_secret = [0u8; 32];
raw_secret[..8].copy_from_slice(&(index + 1).to_le_bytes());
SecretKey::parse(&raw_secret).unwrap()
}
/// Return key pairs of all test validators.
pub fn validators(count: usize) -> Vec<SecretKey> {
(0..count).map(validator).collect()
}
/// Return address of test validator.
pub fn validator_address(index: usize) -> Address {
secret_to_address(&validator(index))
}
/// Return addresses of all test validators.
pub fn validators_addresses(count: usize) -> Vec<Address> {
(0..count).map(validator_address).collect()
}
}
@@ -1,458 +0,0 @@
// Copyright 2019-2021 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/>.
use crate::{error::Error, ChangeToEnact, Storage};
use bp_eth_poa::{Address, AuraHeader, HeaderId, LogEntry, Receipt, U256};
use sp_std::prelude::*;
/// The hash of InitiateChange event of the validators set contract.
pub(crate) const CHANGE_EVENT_HASH: &[u8; 32] = &[
0x55, 0x25, 0x2f, 0xa6, 0xee, 0xe4, 0x74, 0x1b, 0x4e, 0x24, 0xa7, 0x4a, 0x70, 0xe9, 0xc1, 0x1f,
0xd2, 0xc2, 0x28, 0x1d, 0xf8, 0xd6, 0xea, 0x13, 0x12, 0x6f, 0xf8, 0x45, 0xf7, 0x82, 0x5c, 0x89,
];
/// Where source of validators addresses come from. This covers the chain lifetime.
pub enum ValidatorsConfiguration {
/// There's a single source for the whole chain lifetime.
Single(ValidatorsSource),
/// Validators source changes at given blocks. The blocks are ordered
/// by the block number.
Multi(Vec<(u64, ValidatorsSource)>),
}
/// Where validators addresses come from.
///
/// This source is valid within some blocks range. The blocks range could
/// cover multiple epochs - i.e. the validators that are authoring blocks
/// within this range could change, but the source itself can not.
#[cfg_attr(any(test, feature = "runtime-benchmarks"), derive(Debug, PartialEq))]
pub enum ValidatorsSource {
/// The validators addresses are hardcoded and never change.
List(Vec<Address>),
/// The validators addresses are determined by the validators set contract
/// deployed at given address. The contract must implement the `ValidatorSet`
/// interface. Additionally, the initial validators set must be provided.
Contract(Address, Vec<Address>),
}
/// A short hand for optional validators change.
pub type ValidatorsChange = Option<Vec<Address>>;
/// Validators manager.
pub struct Validators<'a> {
config: &'a ValidatorsConfiguration,
}
impl<'a> Validators<'a> {
/// Creates new validators manager using given configuration.
pub fn new(config: &'a ValidatorsConfiguration) -> Self {
Self { config }
}
/// Returns true if header (probabilistically) signals validators change and
/// the caller needs to provide transactions receipts to import the header.
pub fn maybe_signals_validators_change(&self, header: &AuraHeader) -> bool {
let (_, _, source) = self.source_at(header.number);
// if we are taking validators set from the fixed list, there's always
// single epoch
// => we never require transactions receipts
let contract_address = match source {
ValidatorsSource::List(_) => return false,
ValidatorsSource::Contract(contract_address, _) => contract_address,
};
// else we need to check logs bloom and if it has required bits set, it means
// that the contract has (probably) emitted epoch change event
let expected_bloom = LogEntry {
address: *contract_address,
topics: vec![CHANGE_EVENT_HASH.into(), header.parent_hash],
data: Vec::new(), // irrelevant for bloom.
}
.bloom();
header.log_bloom.contains(&expected_bloom)
}
/// Extracts validators change signal from the header.
///
/// Returns tuple where first element is the change scheduled by this header
/// (i.e. this change is only applied starting from the block that has finalized
/// current block). The second element is the immediately applied change.
pub fn extract_validators_change(
&self,
header: &AuraHeader,
receipts: Option<Vec<Receipt>>,
) -> Result<(ValidatorsChange, ValidatorsChange), Error> {
// let's first check if new source is starting from this header
let (source_index, _, source) = self.source_at(header.number);
let (next_starts_at, next_source) = self.source_at_next_header(source_index, header.number);
if next_starts_at == header.number {
match *next_source {
ValidatorsSource::List(ref new_list) => return Ok((None, Some(new_list.clone()))),
ValidatorsSource::Contract(_, ref new_list) =>
return Ok((Some(new_list.clone()), None)),
}
}
// else deal with previous source
//
// if we are taking validators set from the fixed list, there's always
// single epoch
// => we never require transactions receipts
let contract_address = match source {
ValidatorsSource::List(_) => return Ok((None, None)),
ValidatorsSource::Contract(contract_address, _) => contract_address,
};
// else we need to check logs bloom and if it has required bits set, it means
// that the contract has (probably) emitted epoch change event
let expected_bloom = LogEntry {
address: *contract_address,
topics: vec![CHANGE_EVENT_HASH.into(), header.parent_hash],
data: Vec::new(), // irrelevant for bloom.
}
.bloom();
if !header.log_bloom.contains(&expected_bloom) {
return Ok((None, None))
}
let receipts = receipts.ok_or(Error::MissingTransactionsReceipts)?;
#[allow(clippy::question_mark)]
if header.check_receipts_root(&receipts).is_err() {
return Err(Error::TransactionsReceiptsMismatch)
}
// iterate in reverse because only the _last_ change in a given
// block actually has any effect
Ok((
receipts
.iter()
.rev()
.filter(|r| r.log_bloom.contains(&expected_bloom))
.flat_map(|r| r.logs.iter())
.filter(|l| {
l.address == *contract_address &&
l.topics.len() == 2 && l.topics[0].as_fixed_bytes() == CHANGE_EVENT_HASH &&
l.topics[1] == header.parent_hash
})
.filter_map(|l| {
let data_len = l.data.len();
if data_len < 64 {
return None
}
let new_validators_len_u256 = U256::from_big_endian(&l.data[32..64]);
let new_validators_len = new_validators_len_u256.low_u64();
if new_validators_len_u256 != new_validators_len.into() {
return None
}
if (data_len - 64) as u64 != new_validators_len.saturating_mul(32) {
return None
}
Some(
l.data[64..]
.chunks(32)
.map(|chunk| {
let mut new_validator = Address::default();
new_validator.as_mut().copy_from_slice(&chunk[12..32]);
new_validator
})
.collect(),
)
})
.next(),
None,
))
}
/// Finalize changes when blocks are finalized.
pub fn finalize_validators_change<S: Storage>(
&self,
storage: &S,
finalized_blocks: &[(HeaderId, Option<S::Submitter>)],
) -> Option<ChangeToEnact> {
// if we haven't finalized any blocks, no changes may be finalized
let newest_finalized_id = match finalized_blocks.last().map(|(id, _)| id) {
Some(last_finalized_id) => last_finalized_id,
None => return None,
};
let oldest_finalized_id = finalized_blocks
.first()
.map(|(id, _)| id)
.expect("finalized_blocks is not empty; qed");
// try to directly go to the header that has scheduled last change
//
// if we're unable to create import context for some block, it means
// that the header has already been pruned => it and its ancestors had
// no scheduled changes
//
// if we're unable to find scheduled changes for some block, it means
// that these changes have been finalized already
storage
.import_context(None, &newest_finalized_id.hash)
.and_then(|context| context.last_signal_block())
.and_then(|signal_block| {
if signal_block.number >= oldest_finalized_id.number {
Some(signal_block)
} else {
None
}
})
.and_then(|signal_block| {
storage.scheduled_change(&signal_block.hash).map(|change| ChangeToEnact {
signal_block: Some(signal_block),
validators: change.validators,
})
})
}
/// Returns source of validators that should author the header.
fn source_at(&self, header_number: u64) -> (usize, u64, &ValidatorsSource) {
match self.config {
ValidatorsConfiguration::Single(ref source) => (0, 0, source),
ValidatorsConfiguration::Multi(ref sources) => sources
.iter()
.rev()
.enumerate()
.find(|(_, &(begin, _))| begin < header_number)
.map(|(i, (begin, source))| (sources.len() - 1 - i, *begin, source))
.expect(
"there's always entry for the initial block;\
we do not touch any headers with number < initial block number; qed",
),
}
}
/// Returns source of validators that should author the next header.
fn source_at_next_header(
&self,
header_source_index: usize,
header_number: u64,
) -> (u64, &ValidatorsSource) {
match self.config {
ValidatorsConfiguration::Single(ref source) => (0, source),
ValidatorsConfiguration::Multi(ref sources) => {
let next_source_index = header_source_index + 1;
if next_source_index < sources.len() {
let next_source = &sources[next_source_index];
if next_source.0 < header_number + 1 {
return (next_source.0, &next_source.1)
}
}
let source = &sources[header_source_index];
(source.0, &source.1)
},
}
}
}
impl ValidatorsSource {
/// Returns initial validators set.
pub fn initial_epoch_validators(&self) -> Vec<Address> {
match self {
ValidatorsSource::List(ref list) => list.clone(),
ValidatorsSource::Contract(_, ref list) => list.clone(),
}
}
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::{
mock::{run_test, validators_addresses, validators_change_receipt, TestRuntime},
AuraScheduledChange, BridgeStorage, Headers, ScheduledChanges, StoredHeader,
};
use bp_eth_poa::compute_merkle_root;
const TOTAL_VALIDATORS: usize = 3;
#[test]
fn source_at_works() {
let config = ValidatorsConfiguration::Multi(vec![
(0, ValidatorsSource::List(vec![[1; 20].into()])),
(100, ValidatorsSource::List(vec![[2; 20].into()])),
(200, ValidatorsSource::Contract([3; 20].into(), vec![[3; 20].into()])),
]);
let validators = Validators::new(&config);
assert_eq!(validators.source_at(99), (0, 0, &ValidatorsSource::List(vec![[1; 20].into()])),);
assert_eq!(
validators.source_at_next_header(0, 99),
(0, &ValidatorsSource::List(vec![[1; 20].into()])),
);
assert_eq!(
validators.source_at(100),
(0, 0, &ValidatorsSource::List(vec![[1; 20].into()])),
);
assert_eq!(
validators.source_at_next_header(0, 100),
(100, &ValidatorsSource::List(vec![[2; 20].into()])),
);
assert_eq!(
validators.source_at(200),
(1, 100, &ValidatorsSource::List(vec![[2; 20].into()])),
);
assert_eq!(
validators.source_at_next_header(1, 200),
(200, &ValidatorsSource::Contract([3; 20].into(), vec![[3; 20].into()])),
);
}
#[test]
fn maybe_signals_validators_change_works() {
// when contract is active, but bloom has no required bits set
let config = ValidatorsConfiguration::Single(ValidatorsSource::Contract(
Default::default(),
Vec::new(),
));
let validators = Validators::new(&config);
let mut header = AuraHeader { number: u64::max_value(), ..Default::default() };
assert!(!validators.maybe_signals_validators_change(&header));
// when contract is active and bloom has required bits set
header.log_bloom = (&[0xff; 256]).into();
assert!(validators.maybe_signals_validators_change(&header));
// when list is active and bloom has required bits set
let config = ValidatorsConfiguration::Single(ValidatorsSource::List(vec![[42; 20].into()]));
let validators = Validators::new(&config);
assert!(!validators.maybe_signals_validators_change(&header));
}
#[test]
fn extract_validators_change_works() {
let config = ValidatorsConfiguration::Multi(vec![
(0, ValidatorsSource::List(vec![[1; 20].into()])),
(100, ValidatorsSource::List(vec![[2; 20].into()])),
(200, ValidatorsSource::Contract([3; 20].into(), vec![[3; 20].into()])),
]);
let validators = Validators::new(&config);
let mut header = AuraHeader { number: 100, ..Default::default() };
// when we're at the block that switches to list source
assert_eq!(
validators.extract_validators_change(&header, None),
Ok((None, Some(vec![[2; 20].into()]))),
);
// when we're inside list range
header.number = 150;
assert_eq!(validators.extract_validators_change(&header, None), Ok((None, None)));
// when we're at the block that switches to contract source
header.number = 200;
assert_eq!(
validators.extract_validators_change(&header, None),
Ok((Some(vec![[3; 20].into()]), None)),
);
// when we're inside contract range and logs bloom signals change
// but we have no receipts
header.number = 250;
header.log_bloom = (&[0xff; 256]).into();
assert_eq!(
validators.extract_validators_change(&header, None),
Err(Error::MissingTransactionsReceipts),
);
// when we're inside contract range and logs bloom signals change
// but there's no change in receipts
header.receipts_root = "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"
.parse()
.unwrap();
assert_eq!(
validators.extract_validators_change(&header, Some(Vec::new())),
Ok((None, None)),
);
// when we're inside contract range and logs bloom signals change
// and there's change in receipts
let receipts = vec![validators_change_receipt(Default::default())];
header.receipts_root = compute_merkle_root(receipts.iter().map(|r| r.rlp()));
assert_eq!(
validators.extract_validators_change(&header, Some(receipts)),
Ok((Some(vec![[7; 20].into()]), None)),
);
// when incorrect receipts root passed
assert_eq!(
validators.extract_validators_change(&header, Some(Vec::new())),
Err(Error::TransactionsReceiptsMismatch),
);
}
fn try_finalize_with_scheduled_change(scheduled_at: Option<HeaderId>) -> Option<ChangeToEnact> {
run_test(TOTAL_VALIDATORS, |_| {
let config = ValidatorsConfiguration::Single(ValidatorsSource::Contract(
Default::default(),
Vec::new(),
));
let validators = Validators::new(&config);
let storage = BridgeStorage::<TestRuntime>::new();
// when we're finailizing blocks 10...100
let id10 = HeaderId { number: 10, hash: [10; 32].into() };
let id100 = HeaderId { number: 100, hash: [100; 32].into() };
let finalized_blocks = vec![(id10, None), (id100, None)];
let header100 = StoredHeader::<u64> {
submitter: None,
header: AuraHeader { number: 100, ..Default::default() },
total_difficulty: 0.into(),
next_validators_set_id: 0,
last_signal_block: scheduled_at,
};
let scheduled_change = AuraScheduledChange {
validators: validators_addresses(1),
prev_signal_block: None,
};
Headers::<TestRuntime>::insert(id100.hash, header100);
if let Some(scheduled_at) = scheduled_at {
ScheduledChanges::<TestRuntime, ()>::insert(scheduled_at.hash, scheduled_change);
}
validators.finalize_validators_change(&storage, &finalized_blocks)
})
}
#[test]
fn finalize_validators_change_finalizes_scheduled_change() {
let id50 = HeaderId { number: 50, ..Default::default() };
assert_eq!(
try_finalize_with_scheduled_change(Some(id50)),
Some(ChangeToEnact { signal_block: Some(id50), validators: validators_addresses(1) }),
);
}
#[test]
fn finalize_validators_change_does_not_finalize_when_changes_are_not_scheduled() {
assert_eq!(try_finalize_with_scheduled_change(None), None);
}
#[test]
fn finalize_validators_change_does_not_finalize_changes_when_they_are_outside_of_range() {
let id5 = HeaderId { number: 5, ..Default::default() };
assert_eq!(try_finalize_with_scheduled_change(Some(id5)), None,);
}
}
@@ -1,971 +0,0 @@
// Copyright 2019-2021 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/>.
use crate::{
error::Error,
validators::{Validators, ValidatorsConfiguration},
AuraConfiguration, AuraScheduledChange, ChainTime, ImportContext, PoolConfiguration, Storage,
};
use bp_eth_poa::{
public_to_address, step_validator, Address, AuraHeader, HeaderId, Receipt, SealedEmptyStep,
H256, H520, U128, U256,
};
use codec::Encode;
use sp_io::crypto::secp256k1_ecdsa_recover;
use sp_runtime::transaction_validity::TransactionTag;
use sp_std::{vec, vec::Vec};
/// Pre-check to see if should try and import this header.
/// Returns error if we should not try to import this block.
/// Returns ID of passed header and best finalized header.
pub fn is_importable_header<S: Storage>(
storage: &S,
header: &AuraHeader,
) -> Result<(HeaderId, HeaderId), Error> {
// we never import any header that competes with finalized header
let finalized_id = storage.finalized_block();
if header.number <= finalized_id.number {
return Err(Error::AncientHeader)
}
// we never import any header with known hash
let id = header.compute_id();
if storage.header(&id.hash).is_some() {
return Err(Error::KnownHeader)
}
Ok((id, finalized_id))
}
/// Try to accept unsigned aura header into transaction pool.
///
/// Returns required and provided tags.
pub fn accept_aura_header_into_pool<S: Storage, CT: ChainTime>(
storage: &S,
config: &AuraConfiguration,
validators_config: &ValidatorsConfiguration,
pool_config: &PoolConfiguration,
header: &AuraHeader,
chain_time: &CT,
receipts: Option<&Vec<Receipt>>,
) -> Result<(Vec<TransactionTag>, Vec<TransactionTag>), Error> {
// check if we can verify further
let (header_id, _) = is_importable_header(storage, header)?;
// we can always do contextless checks
contextless_checks(config, header, chain_time)?;
// we want to avoid having same headers twice in the pool
// => we're strict about receipts here - if we need them, we require receipts to be Some,
// otherwise we require receipts to be None
let receipts_required =
Validators::new(validators_config).maybe_signals_validators_change(header);
match (receipts_required, receipts.is_some()) {
(true, false) => return Err(Error::MissingTransactionsReceipts),
(false, true) => return Err(Error::RedundantTransactionsReceipts),
_ => (),
}
// we do not want to have all future headers in the pool at once
// => if we see header with number > maximal ever seen header number + LIMIT,
// => we consider this transaction invalid, but only at this moment (we do not want to ban it)
// => let's mark it as Unknown transaction
let (best_id, _) = storage.best_block();
let difference = header.number.saturating_sub(best_id.number);
if difference > pool_config.max_future_number_difference {
return Err(Error::UnsignedTooFarInTheFuture)
}
// TODO: only accept new headers when we're at the tip of PoA chain
// https://github.com/paritytech/parity-bridges-common/issues/38
// we want to see at most one header with given number from single authority
// => every header is providing tag (block_number + authority)
// => since only one tx in the pool can provide the same tag, they're auto-deduplicated
let provides_number_and_authority_tag = (header.number, header.author).encode();
// we want to see several 'future' headers in the pool at once, but we may not have access to
// previous headers here
// => we can at least 'verify' that headers comprise a chain by providing and requiring
// tag (header.number, header.hash)
let provides_header_number_and_hash_tag = header_id.encode();
// depending on whether parent header is available, we either perform full or 'shortened' check
let context = storage.import_context(None, &header.parent_hash);
let tags = match context {
Some(context) => {
let header_step = contextual_checks(config, &context, None, header)?;
validator_checks(config, &context.validators_set().validators, header, header_step)?;
// since our parent is already in the storage, we do not require it
// to be in the transaction pool
(vec![], vec![provides_number_and_authority_tag, provides_header_number_and_hash_tag])
},
None => {
// we know nothing about parent header
// => the best thing we can do is to believe that there are no forks in
// PoA chain AND that the header is produced either by previous, or next
// scheduled validators set change
let header_step = header.step().ok_or(Error::MissingStep)?;
let best_context = storage.import_context(None, &best_id.hash).expect(
"import context is None only when header is missing from the storage;\
best header is always in the storage; qed",
);
let validators_check_result = validator_checks(
config,
&best_context.validators_set().validators,
header,
header_step,
);
if let Err(error) = validators_check_result {
find_next_validators_signal(storage, &best_context).ok_or(error).and_then(
|next_validators| {
validator_checks(config, &next_validators, header, header_step)
},
)?;
}
// since our parent is missing from the storage, we **DO** require it
// to be in the transaction pool
// (- 1 can't underflow because there's always best block in the header)
let requires_header_number_and_hash_tag =
HeaderId { number: header.number - 1, hash: header.parent_hash }.encode();
(
vec![requires_header_number_and_hash_tag],
vec![provides_number_and_authority_tag, provides_header_number_and_hash_tag],
)
},
};
// the heaviest, but rare operation - we do not want invalid receipts in the pool
if let Some(receipts) = receipts {
log::trace!(target: "runtime", "Got receipts! {:?}", receipts);
#[allow(clippy::question_mark)]
if header.check_receipts_root(receipts).is_err() {
return Err(Error::TransactionsReceiptsMismatch)
}
}
Ok(tags)
}
/// Verify header by Aura rules.
pub fn verify_aura_header<S: Storage, CT: ChainTime>(
storage: &S,
config: &AuraConfiguration,
submitter: Option<S::Submitter>,
header: &AuraHeader,
chain_time: &CT,
) -> Result<ImportContext<S::Submitter>, Error> {
// let's do the lightest check first
contextless_checks(config, header, chain_time)?;
// the rest of checks requires access to the parent header
let context = storage.import_context(submitter, &header.parent_hash).ok_or_else(|| {
log::warn!(
target: "runtime",
"Missing parent PoA block: ({:?}, {})",
header.number.checked_sub(1),
header.parent_hash,
);
Error::MissingParentBlock
})?;
let header_step = contextual_checks(config, &context, None, header)?;
validator_checks(config, &context.validators_set().validators, header, header_step)?;
Ok(context)
}
/// Perform basic checks that only require header itself.
fn contextless_checks<CT: ChainTime>(
config: &AuraConfiguration,
header: &AuraHeader,
chain_time: &CT,
) -> Result<(), Error> {
let expected_seal_fields = expected_header_seal_fields(config, header);
if header.seal.len() != expected_seal_fields {
return Err(Error::InvalidSealArity)
}
if header.number >= u64::max_value() {
return Err(Error::RidiculousNumber)
}
if header.gas_used > header.gas_limit {
return Err(Error::TooMuchGasUsed)
}
if header.gas_limit < config.min_gas_limit {
return Err(Error::InvalidGasLimit)
}
if header.gas_limit > config.max_gas_limit {
return Err(Error::InvalidGasLimit)
}
if header.number != 0 && header.extra_data.len() as u64 > config.maximum_extra_data_size {
return Err(Error::ExtraDataOutOfBounds)
}
// we can't detect if block is from future in runtime
// => let's only do an overflow check
if header.timestamp > i32::max_value() as u64 {
return Err(Error::TimestampOverflow)
}
if chain_time.is_timestamp_ahead(header.timestamp) {
return Err(Error::HeaderTimestampIsAhead)
}
Ok(())
}
/// Perform checks that require access to parent header.
fn contextual_checks<Submitter>(
config: &AuraConfiguration,
context: &ImportContext<Submitter>,
validators_override: Option<&[Address]>,
header: &AuraHeader,
) -> Result<u64, Error> {
let validators = validators_override.unwrap_or_else(|| &context.validators_set().validators);
let header_step = header.step().ok_or(Error::MissingStep)?;
let parent_step = context.parent_header().step().ok_or(Error::MissingStep)?;
// Ensure header is from the step after context.
if header_step == parent_step {
return Err(Error::DoubleVote)
}
#[allow(clippy::suspicious_operation_groupings)]
if header.number >= config.validate_step_transition && header_step < parent_step {
return Err(Error::DoubleVote)
}
// If empty step messages are enabled we will validate the messages in the seal, missing
// messages are not reported as there's no way to tell whether the empty step message was never
// sent or simply not included.
let empty_steps_len = match header.number >= config.empty_steps_transition {
true => {
let strict_empty_steps = header.number >= config.strict_empty_steps_transition;
let empty_steps = header.empty_steps().ok_or(Error::MissingEmptySteps)?;
let empty_steps_len = empty_steps.len();
let mut prev_empty_step = 0;
for empty_step in empty_steps {
if empty_step.step <= parent_step || empty_step.step >= header_step {
return Err(Error::InsufficientProof)
}
if !verify_empty_step(&header.parent_hash, &empty_step, validators) {
return Err(Error::InsufficientProof)
}
if strict_empty_steps {
if empty_step.step <= prev_empty_step {
return Err(Error::InsufficientProof)
}
prev_empty_step = empty_step.step;
}
}
empty_steps_len
},
false => 0,
};
// Validate chain score.
if header.number >= config.validate_score_transition {
let expected_difficulty = calculate_score(parent_step, header_step, empty_steps_len as _);
if header.difficulty != expected_difficulty {
return Err(Error::InvalidDifficulty)
}
}
Ok(header_step)
}
/// Check that block is produced by expected validator.
fn validator_checks(
config: &AuraConfiguration,
validators: &[Address],
header: &AuraHeader,
header_step: u64,
) -> Result<(), Error> {
let expected_validator = *step_validator(validators, header_step);
if header.author != expected_validator {
return Err(Error::NotValidator)
}
let validator_signature = header.signature().ok_or(Error::MissingSignature)?;
let header_seal_hash = header
.seal_hash(header.number >= config.empty_steps_transition)
.ok_or(Error::MissingEmptySteps)?;
let is_invalid_proposer =
!verify_signature(&expected_validator, &validator_signature, &header_seal_hash);
if is_invalid_proposer {
return Err(Error::NotValidator)
}
Ok(())
}
/// Returns expected number of seal fields in the header.
fn expected_header_seal_fields(config: &AuraConfiguration, header: &AuraHeader) -> usize {
if header.number != u64::MAX && header.number >= config.empty_steps_transition {
3
} else {
2
}
}
/// Verify single sealed empty step.
fn verify_empty_step(parent_hash: &H256, step: &SealedEmptyStep, validators: &[Address]) -> bool {
let expected_validator = *step_validator(validators, step.step);
let message = step.message(parent_hash);
verify_signature(&expected_validator, &step.signature, &message)
}
/// Chain scoring: total `weight is sqrt(U256::max_value())*height - step`.
pub(crate) fn calculate_score(
parent_step: u64,
current_step: u64,
current_empty_steps: usize,
) -> U256 {
U256::from(U128::max_value()) + U256::from(parent_step) - U256::from(current_step) +
U256::from(current_empty_steps)
}
/// Verify that the signature over message has been produced by given validator.
fn verify_signature(expected_validator: &Address, signature: &H520, message: &H256) -> bool {
secp256k1_ecdsa_recover(signature.as_fixed_bytes(), message.as_fixed_bytes())
.map(|public| public_to_address(&public))
.map(|address| *expected_validator == address)
.unwrap_or(false)
}
/// Find next unfinalized validators set change after finalized set.
fn find_next_validators_signal<S: Storage>(
storage: &S,
context: &ImportContext<S::Submitter>,
) -> Option<Vec<Address>> {
// that's the earliest block number we may met in following loop
// it may be None if that's the first set
let best_set_signal_block = context.validators_set().signal_block;
// if parent schedules validators set change, then it may be our set
// else we'll start with last known change
let mut current_set_signal_block = context.last_signal_block();
let mut next_scheduled_set: Option<AuraScheduledChange> = None;
loop {
// if we have reached block that signals finalized change, then
// next_current_block_hash points to the block that schedules next
// change
let current_scheduled_set = match current_set_signal_block {
Some(current_set_signal_block)
if Some(&current_set_signal_block) == best_set_signal_block.as_ref() =>
return next_scheduled_set.map(|scheduled_set| scheduled_set.validators),
None => return next_scheduled_set.map(|scheduled_set| scheduled_set.validators),
Some(current_set_signal_block) =>
storage.scheduled_change(&current_set_signal_block.hash).expect(
"header that is associated with this change is not pruned;\
scheduled changes are only removed when header is pruned; qed",
),
};
current_set_signal_block = current_scheduled_set.prev_signal_block;
next_scheduled_set = Some(current_scheduled_set);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
mock::{
insert_header, run_test_with_genesis, test_aura_config, validator, validator_address,
validators_addresses, validators_change_receipt, AccountId, ConstChainTime,
HeaderBuilder, TestRuntime, GAS_LIMIT,
},
pool_configuration,
validators::ValidatorsSource,
BridgeStorage, FinalizedBlock, Headers, HeadersByNumber, NextValidatorsSetId,
ScheduledChanges, ValidatorsSet, ValidatorsSets,
};
use bp_eth_poa::{compute_merkle_root, rlp_encode, TransactionOutcome, H520, U256};
use hex_literal::hex;
use libsecp256k1::SecretKey;
use sp_runtime::transaction_validity::TransactionTag;
const GENESIS_STEP: u64 = 42;
const TOTAL_VALIDATORS: usize = 3;
fn genesis() -> AuraHeader {
HeaderBuilder::genesis().step(GENESIS_STEP).sign_by(&validator(0))
}
fn verify_with_config(
config: &AuraConfiguration,
header: &AuraHeader,
) -> Result<ImportContext<AccountId>, Error> {
run_test_with_genesis(genesis(), TOTAL_VALIDATORS, |_| {
let storage = BridgeStorage::<TestRuntime>::new();
verify_aura_header(&storage, config, None, header, &ConstChainTime::default())
})
}
fn default_verify(header: &AuraHeader) -> Result<ImportContext<AccountId>, Error> {
verify_with_config(&test_aura_config(), header)
}
fn default_accept_into_pool(
mut make_header: impl FnMut(&[SecretKey]) -> (AuraHeader, Option<Vec<Receipt>>),
) -> Result<(Vec<TransactionTag>, Vec<TransactionTag>), Error> {
run_test_with_genesis(genesis(), TOTAL_VALIDATORS, |_| {
let validators = vec![validator(0), validator(1), validator(2)];
let mut storage = BridgeStorage::<TestRuntime>::new();
let block1 = HeaderBuilder::with_parent_number(0).sign_by_set(&validators);
insert_header(&mut storage, block1);
let block2 = HeaderBuilder::with_parent_number(1).sign_by_set(&validators);
let block2_id = block2.compute_id();
insert_header(&mut storage, block2);
let block3 = HeaderBuilder::with_parent_number(2).sign_by_set(&validators);
insert_header(&mut storage, block3);
FinalizedBlock::<TestRuntime, ()>::put(block2_id);
let validators_config = ValidatorsConfiguration::Single(ValidatorsSource::Contract(
Default::default(),
Vec::new(),
));
let (header, receipts) = make_header(&validators);
accept_aura_header_into_pool(
&storage,
&test_aura_config(),
&validators_config,
&pool_configuration(),
&header,
&(),
receipts.as_ref(),
)
})
}
fn change_validators_set_at(
number: u64,
finalized_set: Vec<Address>,
signalled_set: Option<Vec<Address>>,
) {
let set_id = NextValidatorsSetId::<TestRuntime, ()>::get();
NextValidatorsSetId::<TestRuntime, ()>::put(set_id + 1);
ValidatorsSets::<TestRuntime, ()>::insert(
set_id,
ValidatorsSet {
validators: finalized_set,
signal_block: None,
enact_block: HeaderId {
number: 0,
hash: HeadersByNumber::<TestRuntime, ()>::get(&0).unwrap()[0],
},
},
);
let header_hash = HeadersByNumber::<TestRuntime, ()>::get(&number).unwrap()[0];
let mut header = Headers::<TestRuntime>::get(&header_hash).unwrap();
header.next_validators_set_id = set_id;
if let Some(signalled_set) = signalled_set {
header.last_signal_block = Some(HeaderId {
number: header.header.number - 1,
hash: header.header.parent_hash,
});
ScheduledChanges::<TestRuntime, ()>::insert(
header.header.parent_hash,
AuraScheduledChange { validators: signalled_set, prev_signal_block: None },
);
}
Headers::<TestRuntime>::insert(header_hash, header);
}
#[test]
fn verifies_seal_count() {
// when there are no seals at all
let mut header = AuraHeader::default();
assert_eq!(default_verify(&header), Err(Error::InvalidSealArity));
// when there's single seal (we expect 2 or 3 seals)
header.seal = vec![vec![]];
assert_eq!(default_verify(&header), Err(Error::InvalidSealArity));
// when there's 3 seals (we expect 2 by default)
header.seal = vec![vec![], vec![], vec![]];
assert_eq!(default_verify(&header), Err(Error::InvalidSealArity));
// when there's 2 seals
header.seal = vec![vec![], vec![]];
assert_ne!(default_verify(&header), Err(Error::InvalidSealArity));
}
#[test]
fn verifies_header_number() {
// when number is u64::MAX
let header = HeaderBuilder::with_number(u64::MAX).sign_by(&validator(0));
assert_eq!(default_verify(&header), Err(Error::RidiculousNumber));
// when header is < u64::MAX
let header = HeaderBuilder::with_number(u64::MAX - 1).sign_by(&validator(0));
assert_ne!(default_verify(&header), Err(Error::RidiculousNumber));
}
#[test]
fn verifies_gas_used() {
// when gas used is larger than gas limit
let header = HeaderBuilder::with_number(1)
.gas_used((GAS_LIMIT + 1).into())
.sign_by(&validator(0));
assert_eq!(default_verify(&header), Err(Error::TooMuchGasUsed));
// when gas used is less than gas limit
let header = HeaderBuilder::with_number(1)
.gas_used((GAS_LIMIT - 1).into())
.sign_by(&validator(0));
assert_ne!(default_verify(&header), Err(Error::TooMuchGasUsed));
}
#[test]
fn verifies_gas_limit() {
let mut config = test_aura_config();
config.min_gas_limit = 100.into();
config.max_gas_limit = 200.into();
// when limit is lower than expected
let header = HeaderBuilder::with_number(1).gas_limit(50.into()).sign_by(&validator(0));
assert_eq!(verify_with_config(&config, &header), Err(Error::InvalidGasLimit));
// when limit is larger than expected
let header = HeaderBuilder::with_number(1).gas_limit(250.into()).sign_by(&validator(0));
assert_eq!(verify_with_config(&config, &header), Err(Error::InvalidGasLimit));
// when limit is within expected range
let header = HeaderBuilder::with_number(1).gas_limit(150.into()).sign_by(&validator(0));
assert_ne!(verify_with_config(&config, &header), Err(Error::InvalidGasLimit));
}
#[test]
fn verifies_extra_data_len() {
// when extra data is too large
let header = HeaderBuilder::with_number(1)
.extra_data(std::iter::repeat(42).take(1000).collect::<Vec<_>>())
.sign_by(&validator(0));
assert_eq!(default_verify(&header), Err(Error::ExtraDataOutOfBounds));
// when extra data size is OK
let header = HeaderBuilder::with_number(1)
.extra_data(std::iter::repeat(42).take(10).collect::<Vec<_>>())
.sign_by(&validator(0));
assert_ne!(default_verify(&header), Err(Error::ExtraDataOutOfBounds));
}
#[test]
fn verifies_timestamp() {
// when timestamp overflows i32
let header = HeaderBuilder::with_number(1)
.timestamp(i32::MAX as u64 + 1)
.sign_by(&validator(0));
assert_eq!(default_verify(&header), Err(Error::TimestampOverflow));
// when timestamp doesn't overflow i32
let header =
HeaderBuilder::with_number(1).timestamp(i32::MAX as u64).sign_by(&validator(0));
assert_ne!(default_verify(&header), Err(Error::TimestampOverflow));
}
#[test]
fn verifies_chain_time() {
// expected import context after verification
let expect = ImportContext::<AccountId> {
submitter: None,
parent_hash: hex!("6e41bff05578fc1db17f6816117969b07d2217f1f9039d8116a82764335991d3")
.into(),
parent_header: genesis(),
parent_total_difficulty: U256::zero(),
parent_scheduled_change: None,
validators_set_id: 0,
validators_set: ValidatorsSet {
validators: vec![
hex!("dc5b20847f43d67928f49cd4f85d696b5a7617b5").into(),
hex!("897df33a7b3c62ade01e22c13d48f98124b4480f").into(),
hex!("05c987b34c6ef74e0c7e69c6e641120c24164c2d").into(),
],
signal_block: None,
enact_block: HeaderId {
number: 0,
hash: hex!("6e41bff05578fc1db17f6816117969b07d2217f1f9039d8116a82764335991d3")
.into(),
},
},
last_signal_block: None,
};
// header is behind
let header = HeaderBuilder::with_parent(&genesis())
.timestamp(i32::MAX as u64 / 2 - 100)
.sign_by(&validator(1));
assert_eq!(default_verify(&header).unwrap(), expect);
// header is ahead
let header = HeaderBuilder::with_parent(&genesis())
.timestamp(i32::MAX as u64 / 2 + 100)
.sign_by(&validator(1));
assert_eq!(default_verify(&header), Err(Error::HeaderTimestampIsAhead));
// header has same timestamp as ConstChainTime
let header = HeaderBuilder::with_parent(&genesis())
.timestamp(i32::MAX as u64 / 2)
.sign_by(&validator(1));
assert_eq!(default_verify(&header).unwrap(), expect);
}
#[test]
fn verifies_parent_existence() {
// when there's no parent in the storage
let header = HeaderBuilder::with_number(1).sign_by(&validator(0));
assert_eq!(default_verify(&header), Err(Error::MissingParentBlock));
// when parent is in the storage
let header = HeaderBuilder::with_parent(&genesis()).sign_by(&validator(0));
assert_ne!(default_verify(&header), Err(Error::MissingParentBlock));
}
#[test]
fn verifies_step() {
// when step is missing from seals
let mut header = AuraHeader {
seal: vec![vec![], vec![]],
gas_limit: test_aura_config().min_gas_limit,
parent_hash: genesis().compute_hash(),
..Default::default()
};
assert_eq!(default_verify(&header), Err(Error::MissingStep));
// when step is the same as for the parent block
header.seal[0] = rlp_encode(&42u64).to_vec();
assert_eq!(default_verify(&header), Err(Error::DoubleVote));
// when step is OK
header.seal[0] = rlp_encode(&43u64).to_vec();
assert_ne!(default_verify(&header), Err(Error::DoubleVote));
// now check with validate_step check enabled
let mut config = test_aura_config();
config.validate_step_transition = 0;
// when step is lesser that for the parent block
header.seal[0] = rlp_encode(&40u64).to_vec();
header.seal = vec![vec![40], vec![]];
assert_eq!(verify_with_config(&config, &header), Err(Error::DoubleVote));
// when step is OK
header.seal[0] = rlp_encode(&44u64).to_vec();
assert_ne!(verify_with_config(&config, &header), Err(Error::DoubleVote));
}
#[test]
fn verifies_empty_step() {
let mut config = test_aura_config();
config.empty_steps_transition = 0;
// when empty step duplicates parent step
let header = HeaderBuilder::with_parent(&genesis())
.empty_steps(&[(&validator(0), GENESIS_STEP)])
.step(GENESIS_STEP + 3)
.sign_by(&validator(3));
assert_eq!(verify_with_config(&config, &header), Err(Error::InsufficientProof));
// when empty step signature check fails
let header = HeaderBuilder::with_parent(&genesis())
.empty_steps(&[(&validator(100), GENESIS_STEP + 1)])
.step(GENESIS_STEP + 3)
.sign_by(&validator(3));
assert_eq!(verify_with_config(&config, &header), Err(Error::InsufficientProof));
// when we are accepting strict empty steps and they come not in order
config.strict_empty_steps_transition = 0;
let header = HeaderBuilder::with_parent(&genesis())
.empty_steps(&[(&validator(2), GENESIS_STEP + 2), (&validator(1), GENESIS_STEP + 1)])
.step(GENESIS_STEP + 3)
.sign_by(&validator(3));
assert_eq!(verify_with_config(&config, &header), Err(Error::InsufficientProof));
// when empty steps are OK
let header = HeaderBuilder::with_parent(&genesis())
.empty_steps(&[(&validator(1), GENESIS_STEP + 1), (&validator(2), GENESIS_STEP + 2)])
.step(GENESIS_STEP + 3)
.sign_by(&validator(3));
assert_ne!(verify_with_config(&config, &header), Err(Error::InsufficientProof));
}
#[test]
fn verifies_chain_score() {
let mut config = test_aura_config();
config.validate_score_transition = 0;
// when chain score is invalid
let header = HeaderBuilder::with_parent(&genesis())
.difficulty(100.into())
.sign_by(&validator(0));
assert_eq!(verify_with_config(&config, &header), Err(Error::InvalidDifficulty));
// when chain score is accepted
let header = HeaderBuilder::with_parent(&genesis()).sign_by(&validator(0));
assert_ne!(verify_with_config(&config, &header), Err(Error::InvalidDifficulty));
}
#[test]
fn verifies_validator() {
let good_header = HeaderBuilder::with_parent(&genesis()).sign_by(&validator(1));
// when header author is invalid
let mut header = good_header.clone();
header.author = Default::default();
assert_eq!(default_verify(&header), Err(Error::NotValidator));
// when header signature is invalid
let mut header = good_header.clone();
header.seal[1] = rlp_encode(&H520::default()).to_vec();
assert_eq!(default_verify(&header), Err(Error::NotValidator));
// when everything is OK
assert_eq!(default_verify(&good_header).map(|_| ()), Ok(()));
}
#[test]
fn pool_verifies_known_blocks() {
// when header is known
assert_eq!(
default_accept_into_pool(|validators| (
HeaderBuilder::with_parent_number(2).sign_by_set(validators),
None
)),
Err(Error::KnownHeader),
);
}
#[test]
fn pool_verifies_ancient_blocks() {
// when header number is less than finalized
assert_eq!(
default_accept_into_pool(|validators| (
HeaderBuilder::with_parent_number(1)
.gas_limit((GAS_LIMIT + 1).into())
.sign_by_set(validators),
None,
),),
Err(Error::AncientHeader),
);
}
#[test]
fn pool_rejects_headers_without_required_receipts() {
assert_eq!(
default_accept_into_pool(|_| (
AuraHeader {
number: 20_000_000,
seal: vec![vec![], vec![]],
gas_limit: test_aura_config().min_gas_limit,
log_bloom: (&[0xff; 256]).into(),
..Default::default()
},
None,
),),
Err(Error::MissingTransactionsReceipts),
);
}
#[test]
fn pool_rejects_headers_with_redundant_receipts() {
assert_eq!(
default_accept_into_pool(|validators| (
HeaderBuilder::with_parent_number(3).sign_by_set(validators),
Some(vec![Receipt {
gas_used: 1.into(),
log_bloom: (&[0xff; 256]).into(),
logs: vec![],
outcome: TransactionOutcome::Unknown,
}]),
),),
Err(Error::RedundantTransactionsReceipts),
);
}
#[test]
fn pool_verifies_future_block_number() {
// when header is too far from the future
assert_eq!(
default_accept_into_pool(|validators| (
HeaderBuilder::with_number(100).sign_by_set(validators),
None
),),
Err(Error::UnsignedTooFarInTheFuture),
);
}
#[test]
fn pool_performs_full_verification_when_parent_is_known() {
// if parent is known, then we'll execute contextual_checks, which
// checks for DoubleVote
assert_eq!(
default_accept_into_pool(|validators| (
HeaderBuilder::with_parent_number(3)
.step(GENESIS_STEP + 3)
.sign_by_set(validators),
None,
),),
Err(Error::DoubleVote),
);
}
#[test]
fn pool_performs_validators_checks_when_parent_is_unknown() {
// if parent is unknown, then we still need to check if header has required signature
// (even if header will be considered invalid/duplicate later, we can use this signature
// as a proof of malicious action by this validator)
assert_eq!(
default_accept_into_pool(|_| (
HeaderBuilder::with_number(8).step(8).sign_by(&validator(1)),
None,
)),
Err(Error::NotValidator),
);
}
#[test]
fn pool_verifies_header_with_known_parent() {
let mut hash = None;
assert_eq!(
default_accept_into_pool(|validators| {
let header = HeaderBuilder::with_parent_number(3).sign_by_set(validators);
hash = Some(header.compute_hash());
(header, None)
}),
Ok((
// no tags are required
vec![],
// header provides two tags
vec![(4u64, validators_addresses(3)[1]).encode(), (4u64, hash.unwrap()).encode(),],
)),
);
}
#[test]
fn pool_verifies_header_with_unknown_parent() {
let mut id = None;
let mut parent_id = None;
assert_eq!(
default_accept_into_pool(|validators| {
let header =
HeaderBuilder::with_number(5).step(GENESIS_STEP + 5).sign_by_set(validators);
id = Some(header.compute_id());
parent_id = header.parent_id();
(header, None)
}),
Ok((
// parent tag required
vec![parent_id.unwrap().encode()],
// header provides two tags
vec![(5u64, validator_address(2)).encode(), id.unwrap().encode(),],
)),
);
}
#[test]
fn pool_uses_next_validators_set_when_finalized_fails() {
assert_eq!(
default_accept_into_pool(|actual_validators| {
// change finalized set at parent header
change_validators_set_at(3, validators_addresses(1), None);
// header is signed using wrong set
let header = HeaderBuilder::with_number(5)
.step(GENESIS_STEP + 2)
.sign_by_set(actual_validators);
(header, None)
}),
Err(Error::NotValidator),
);
let mut id = None;
let mut parent_id = None;
assert_eq!(
default_accept_into_pool(|actual_validators| {
// change finalized set at parent header + signal valid set at parent block
change_validators_set_at(
3,
validators_addresses(10),
Some(validators_addresses(3)),
);
// header is signed using wrong set
let header = HeaderBuilder::with_number(5)
.step(GENESIS_STEP + 2)
.sign_by_set(actual_validators);
id = Some(header.compute_id());
parent_id = header.parent_id();
(header, None)
}),
Ok((
// parent tag required
vec![parent_id.unwrap().encode(),],
// header provides two tags
vec![(5u64, validator_address(2)).encode(), id.unwrap().encode(),],
)),
);
}
#[test]
fn pool_rejects_headers_with_invalid_receipts() {
assert_eq!(
default_accept_into_pool(|validators| {
let header = HeaderBuilder::with_parent_number(3)
.log_bloom((&[0xff; 256]).into())
.sign_by_set(validators);
(header, Some(vec![validators_change_receipt(Default::default())]))
}),
Err(Error::TransactionsReceiptsMismatch),
);
}
#[test]
fn pool_accepts_headers_with_valid_receipts() {
let mut hash = None;
let receipts = vec![validators_change_receipt(Default::default())];
let receipts_root = compute_merkle_root(receipts.iter().map(|r| r.rlp()));
assert_eq!(
default_accept_into_pool(|validators| {
let header = HeaderBuilder::with_parent_number(3)
.log_bloom((&[0xff; 256]).into())
.receipts_root(receipts_root)
.sign_by_set(validators);
hash = Some(header.compute_hash());
(header, Some(receipts.clone()))
}),
Ok((
// no tags are required
vec![],
// header provides two tags
vec![(4u64, validators_addresses(3)[1]).encode(), (4u64, hash.unwrap()).encode(),],
)),
);
}
}
+6 -2
View File
@@ -239,7 +239,7 @@ pub mod pallet {
operational: bool,
) -> DispatchResultWithPostInfo {
ensure_owner_or_root::<T, I>(origin)?;
<IsHalted<T, I>>::put(operational);
<IsHalted<T, I>>::put(!operational);
if operational {
log::info!(target: "runtime::bridge-grandpa", "Resuming pallet operations.");
@@ -798,9 +798,13 @@ mod tests {
#[test]
fn pallet_rejects_transactions_if_halted() {
run_test(|| {
<IsHalted<TestRuntime>>::put(true);
initialize_substrate_bridge();
assert_ok!(Pallet::<TestRuntime>::set_operational(Origin::root(), false));
assert_noop!(submit_finality_proof(1), Error::<TestRuntime>::Halted);
assert_ok!(Pallet::<TestRuntime>::set_operational(Origin::root(), true));
assert_ok!(submit_finality_proof(1));
})
}
@@ -1,27 +0,0 @@
[package]
name = "bp-currency-exchange"
description = "Primitives of currency exchange module."
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies]
codec = { package = "parity-scale-codec", version = "2.2.0", default-features = false }
scale-info = { version = "1.0", default-features = false, features = ["derive"] }
# Substrate Dependencies
frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-api = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
[features]
default = ["std"]
std = [
"codec/std",
"frame-support/std",
"scale-info/std",
"sp-api/std",
"sp-std/std",
]
@@ -1,153 +0,0 @@
// Copyright 2019-2021 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/>.
#![cfg_attr(not(feature = "std"), no_std)]
// RuntimeApi generated functions
#![allow(clippy::too_many_arguments)]
// Generated by `DecodeLimit::decode_with_depth_limit`
#![allow(clippy::unnecessary_mut_passed)]
use codec::{Decode, Encode, EncodeLike};
use frame_support::{Parameter, RuntimeDebug};
use scale_info::TypeInfo;
use sp_api::decl_runtime_apis;
use sp_std::marker::PhantomData;
/// All errors that may happen during exchange.
#[derive(RuntimeDebug, PartialEq)]
pub enum Error {
/// Invalid peer blockchain transaction provided.
InvalidTransaction,
/// Peer transaction has invalid amount.
InvalidAmount,
/// Peer transaction has invalid recipient.
InvalidRecipient,
/// Cannot map from peer recipient to this blockchain recipient.
FailedToMapRecipients,
/// Failed to convert from peer blockchain currency to this blockchain currency.
FailedToConvertCurrency,
/// Deposit has failed.
DepositFailed,
/// Deposit has partially failed (changes to recipient account were made).
DepositPartiallyFailed,
}
/// Result of all exchange operations.
pub type Result<T> = sp_std::result::Result<T, Error>;
/// Peer blockchain lock funds transaction.
#[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq, TypeInfo)]
pub struct LockFundsTransaction<TransferId, Recipient, Amount> {
/// Something that uniquely identifies this transfer.
pub id: TransferId,
/// Funds recipient on the peer chain.
pub recipient: Recipient,
/// Amount of the locked funds.
pub amount: Amount,
}
/// Peer blockchain transaction that may represent lock funds transaction.
pub trait MaybeLockFundsTransaction {
/// Transaction type.
type Transaction;
/// Identifier that uniquely identifies this transfer.
type Id: Decode + Encode + TypeInfo + EncodeLike + sp_std::fmt::Debug;
/// Peer recipient type.
type Recipient;
/// Peer currency amount type.
type Amount;
/// Parse lock funds transaction of the peer blockchain. Returns None if
/// transaction format is unknown, or it isn't a lock funds transaction.
fn parse(
tx: &Self::Transaction,
) -> Result<LockFundsTransaction<Self::Id, Self::Recipient, Self::Amount>>;
}
/// Map that maps recipients from peer blockchain to this blockchain recipients.
pub trait RecipientsMap {
/// Peer blockchain recipient type.
type PeerRecipient;
/// Current blockchain recipient type.
type Recipient;
/// Lookup current blockchain recipient by peer blockchain recipient.
fn map(peer_recipient: Self::PeerRecipient) -> Result<Self::Recipient>;
}
/// Conversion between two currencies.
pub trait CurrencyConverter {
/// Type of the source currency amount.
type SourceAmount;
/// Type of the target currency amount.
type TargetAmount;
/// Covert from source to target currency.
fn convert(amount: Self::SourceAmount) -> Result<Self::TargetAmount>;
}
/// Currency deposit.
pub trait DepositInto {
/// Recipient type.
type Recipient;
/// Currency amount type.
type Amount;
/// Grant some money to given account.
fn deposit_into(recipient: Self::Recipient, amount: Self::Amount) -> Result<()>;
}
/// Recipients map which is used when accounts ids are the same on both chains.
#[derive(Debug)]
pub struct IdentityRecipients<AccountId>(PhantomData<AccountId>);
impl<AccountId> RecipientsMap for IdentityRecipients<AccountId> {
type PeerRecipient = AccountId;
type Recipient = AccountId;
fn map(peer_recipient: Self::PeerRecipient) -> Result<Self::Recipient> {
Ok(peer_recipient)
}
}
/// Currency converter which is used when currency is the same on both chains.
#[derive(Debug)]
pub struct IdentityCurrencyConverter<Amount>(PhantomData<Amount>);
impl<Amount> CurrencyConverter for IdentityCurrencyConverter<Amount> {
type SourceAmount = Amount;
type TargetAmount = Amount;
fn convert(currency: Self::SourceAmount) -> Result<Self::TargetAmount> {
Ok(currency)
}
}
decl_runtime_apis! {
/// API for Rialto exchange transactions submitters.
pub trait RialtoCurrencyExchangeApi<Proof: Parameter> {
/// Returns true if currency exchange module is able to import transaction proof in
/// its current state.
fn filter_transaction_proof(proof: Proof) -> bool;
}
/// API for Kovan exchange transactions submitters.
pub trait KovanCurrencyExchangeApi<Proof: Parameter> {
/// Returns true if currency exchange module is able to import transaction proof in
/// its current state.
fn filter_transaction_proof(proof: Proof) -> bool;
}
}
@@ -1,59 +0,0 @@
[package]
name = "bp-eth-poa"
description = "Primitives of Ethereum PoA Bridge module."
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies]
codec = { package = "parity-scale-codec", version = "2.2.0", default-features = false }
ethbloom = { version = "0.10.0", default-features = false, features = ["rlp"] }
fixed-hash = { version = "0.7", default-features = false }
hash-db = { version = "0.15.2", default-features = false }
impl-rlp = { version = "0.3", default-features = false }
impl-serde = { version = "0.3.1", optional = true }
libsecp256k1 = { version = "0.7", default-features = false, features = ["hmac", "static-context"] }
parity-bytes = { version = "0.1", default-features = false }
plain_hasher = { version = "0.2.2", default-features = false }
primitive-types = { version = "0.10", default-features = false, features = ["codec", "rlp"] }
rlp = { version = "0.5", default-features = false }
scale-info = { version = "1.0", default-features = false, features = ["derive"] }
serde = { version = "1.0", optional = true }
serde-big-array = { version = "0.2", optional = true }
triehash = { version = "0.8.2", default-features = false }
# Substrate Dependencies
sp-api = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-io = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
[dev-dependencies]
hex-literal = "0.2"
[features]
default = ["std"]
std = [
"codec/std",
"ethbloom/std",
"fixed-hash/std",
"hash-db/std",
"impl-rlp/std",
"impl-serde",
"libsecp256k1/std",
"parity-bytes/std",
"plain_hasher/std",
"primitive-types/std",
"primitive-types/serde",
"rlp/std",
"scale-info/std",
"serde/std",
"serde-big-array",
"sp-api/std",
"sp-io/std",
"sp-runtime/std",
"sp-std/std",
"triehash/std",
]
@@ -1,732 +0,0 @@
// Copyright 2019-2021 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/>.
#![cfg_attr(not(feature = "std"), no_std)]
// RuntimeApi generated functions
#![allow(clippy::too_many_arguments)]
// Generated by `DecodeLimit::decode_with_depth_limit`
#![allow(clippy::unnecessary_mut_passed)]
pub use parity_bytes::Bytes;
pub use primitive_types::{H160, H256, H512, U128, U256};
pub use rlp::encode as rlp_encode;
use codec::{Decode, Encode};
use ethbloom::{Bloom as EthBloom, Input as BloomInput};
use fixed_hash::construct_fixed_hash;
use rlp::{Decodable, DecoderError, Rlp, RlpStream};
use scale_info::TypeInfo;
use sp_io::hashing::keccak_256;
use sp_runtime::RuntimeDebug;
use sp_std::prelude::*;
use impl_rlp::impl_fixed_hash_rlp;
#[cfg(feature = "std")]
use impl_serde::impl_fixed_hash_serde;
#[cfg(feature = "std")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "std")]
use serde_big_array::big_array;
construct_fixed_hash! { pub struct H520(65); }
impl_fixed_hash_rlp!(H520, 65);
#[cfg(feature = "std")]
impl_fixed_hash_serde!(H520, 65);
/// Raw (RLP-encoded) ethereum transaction.
pub type RawTransaction = Vec<u8>;
/// Raw (RLP-encoded) ethereum transaction receipt.
pub type RawTransactionReceipt = Vec<u8>;
/// An ethereum address.
pub type Address = H160;
pub mod signatures;
/// Complete header id.
#[derive(Encode, Decode, Default, RuntimeDebug, PartialEq, Clone, Copy, TypeInfo)]
pub struct HeaderId {
/// Header number.
pub number: u64,
/// Header hash.
pub hash: H256,
}
/// An Aura header.
#[derive(Clone, Default, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
pub struct AuraHeader {
/// Parent block hash.
pub parent_hash: H256,
/// Block timestamp.
pub timestamp: u64,
/// Block number.
pub number: u64,
/// Block author.
pub author: Address,
/// Transactions root.
pub transactions_root: H256,
/// Block uncles hash.
pub uncles_hash: H256,
/// Block extra data.
pub extra_data: Bytes,
/// State root.
pub state_root: H256,
/// Block receipts root.
pub receipts_root: H256,
/// Block bloom.
pub log_bloom: Bloom,
/// Gas used for contracts execution.
pub gas_used: U256,
/// Block gas limit.
pub gas_limit: U256,
/// Block difficulty.
pub difficulty: U256,
/// Vector of post-RLP-encoded fields.
pub seal: Vec<Bytes>,
}
/// Parsed ethereum transaction.
#[derive(PartialEq, RuntimeDebug)]
pub struct Transaction {
/// Sender address.
pub sender: Address,
/// Unsigned portion of ethereum transaction.
pub unsigned: UnsignedTransaction,
}
/// Unsigned portion of ethereum transaction.
#[derive(Clone, PartialEq, RuntimeDebug)]
pub struct UnsignedTransaction {
/// Sender nonce.
pub nonce: U256,
/// Gas price.
pub gas_price: U256,
/// Gas limit.
pub gas: U256,
/// Transaction destination address. None if it is contract creation transaction.
pub to: Option<Address>,
/// Value.
pub value: U256,
/// Associated data.
pub payload: Bytes,
}
/// Information describing execution of a transaction.
#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo)]
pub struct Receipt {
/// The total gas used in the block following execution of the transaction.
pub gas_used: U256,
/// The OR-wide combination of all logs' blooms for this transaction.
pub log_bloom: Bloom,
/// The logs stemming from this transaction.
pub logs: Vec<LogEntry>,
/// Transaction outcome.
pub outcome: TransactionOutcome,
}
/// Transaction outcome store in the receipt.
#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo)]
pub enum TransactionOutcome {
/// Status and state root are unknown under EIP-98 rules.
Unknown,
/// State root is known. Pre EIP-98 and EIP-658 rules.
StateRoot(H256),
/// Status code is known. EIP-658 rules.
StatusCode(u8),
}
/// A record of execution for a `LOG` operation.
#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo)]
pub struct LogEntry {
/// The address of the contract executing at the point of the `LOG` operation.
pub address: Address,
/// The topics associated with the `LOG` operation.
pub topics: Vec<H256>,
/// The data associated with the `LOG` operation.
pub data: Bytes,
}
/// Logs bloom.
#[derive(Clone, Encode, Decode, TypeInfo)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
pub struct Bloom(#[cfg_attr(feature = "std", serde(with = "BigArray"))] [u8; 256]);
#[cfg(feature = "std")]
big_array! { BigArray; }
/// An empty step message that is included in a seal, the only difference is that it doesn't include
/// the `parent_hash` in order to save space. The included signature is of the original empty step
/// message, which can be reconstructed by using the parent hash of the block in which this sealed
/// empty message is included.
pub struct SealedEmptyStep {
/// Signature of the original message author.
pub signature: H520,
/// The step this message is generated for.
pub step: u64,
}
impl AuraHeader {
/// Compute id of this header.
pub fn compute_id(&self) -> HeaderId {
HeaderId { number: self.number, hash: self.compute_hash() }
}
/// Compute hash of this header (keccak of the RLP with seal).
pub fn compute_hash(&self) -> H256 {
keccak_256(&self.rlp(true)).into()
}
/// Get id of this header' parent. Returns None if this is genesis header.
pub fn parent_id(&self) -> Option<HeaderId> {
self.number
.checked_sub(1)
.map(|parent_number| HeaderId { number: parent_number, hash: self.parent_hash })
}
/// Check if passed transactions receipts are matching receipts root in this header.
/// Returns Ok(computed-root) if check succeeds.
/// Returns Err(computed-root) if check fails.
pub fn check_receipts_root(&self, receipts: &[Receipt]) -> Result<H256, H256> {
check_merkle_proof(self.receipts_root, receipts.iter().map(|r| r.rlp()))
}
/// Check if passed raw transactions receipts are matching receipts root in this header.
/// Returns Ok(computed-root) if check succeeds.
/// Returns Err(computed-root) if check fails.
pub fn check_raw_receipts_root<'a>(
&self,
receipts: impl IntoIterator<Item = &'a RawTransactionReceipt>,
) -> Result<H256, H256> {
check_merkle_proof(self.receipts_root, receipts.into_iter())
}
/// Check if passed transactions are matching transactions root in this header.
/// Returns Ok(computed-root) if check succeeds.
/// Returns Err(computed-root) if check fails.
pub fn check_transactions_root<'a>(
&self,
transactions: impl IntoIterator<Item = &'a RawTransaction>,
) -> Result<H256, H256> {
check_merkle_proof(self.transactions_root, transactions.into_iter())
}
/// Gets the seal hash of this header.
pub fn seal_hash(&self, include_empty_steps: bool) -> Option<H256> {
Some(match include_empty_steps {
true => {
let mut message = self.compute_hash().as_bytes().to_vec();
message.extend_from_slice(self.seal.get(2)?);
keccak_256(&message).into()
},
false => keccak_256(&self.rlp(false)).into(),
})
}
/// Get step this header is generated for.
pub fn step(&self) -> Option<u64> {
self.seal.get(0).map(|x| Rlp::new(x)).and_then(|x| x.as_val().ok())
}
/// Get header author' signature.
pub fn signature(&self) -> Option<H520> {
self.seal.get(1).and_then(|x| Rlp::new(x).as_val().ok())
}
/// Extracts the empty steps from the header seal.
pub fn empty_steps(&self) -> Option<Vec<SealedEmptyStep>> {
self.seal.get(2).and_then(|x| Rlp::new(x).as_list::<SealedEmptyStep>().ok())
}
/// Returns header RLP with or without seals.
fn rlp(&self, with_seal: bool) -> Bytes {
let mut s = RlpStream::new();
if with_seal {
s.begin_list(13 + self.seal.len());
} else {
s.begin_list(13);
}
s.append(&self.parent_hash);
s.append(&self.uncles_hash);
s.append(&self.author);
s.append(&self.state_root);
s.append(&self.transactions_root);
s.append(&self.receipts_root);
s.append(&EthBloom::from(self.log_bloom.0));
s.append(&self.difficulty);
s.append(&self.number);
s.append(&self.gas_limit);
s.append(&self.gas_used);
s.append(&self.timestamp);
s.append(&self.extra_data);
if with_seal {
for b in &self.seal {
s.append_raw(b, 1);
}
}
s.out().to_vec()
}
}
impl UnsignedTransaction {
/// Decode unsigned portion of raw transaction RLP.
pub fn decode_rlp(raw_tx: &[u8]) -> Result<Self, DecoderError> {
let tx_rlp = Rlp::new(raw_tx);
let to = tx_rlp.at(3)?;
Ok(UnsignedTransaction {
nonce: tx_rlp.val_at(0)?,
gas_price: tx_rlp.val_at(1)?,
gas: tx_rlp.val_at(2)?,
to: match to.is_empty() {
false => Some(to.as_val()?),
true => None,
},
value: tx_rlp.val_at(4)?,
payload: tx_rlp.val_at(5)?,
})
}
/// Returns message that has to be signed to sign this transaction.
pub fn message(&self, chain_id: Option<u64>) -> H256 {
keccak_256(&self.rlp(chain_id)).into()
}
/// Returns unsigned transaction RLP.
pub fn rlp(&self, chain_id: Option<u64>) -> Bytes {
let mut stream = RlpStream::new_list(if chain_id.is_some() { 9 } else { 6 });
self.rlp_to(chain_id, &mut stream);
stream.out().to_vec()
}
/// Encode to given RLP stream.
pub fn rlp_to(&self, chain_id: Option<u64>, stream: &mut RlpStream) {
stream.append(&self.nonce);
stream.append(&self.gas_price);
stream.append(&self.gas);
match self.to {
Some(to) => stream.append(&to),
None => stream.append(&""),
};
stream.append(&self.value);
stream.append(&self.payload);
if let Some(chain_id) = chain_id {
stream.append(&chain_id);
stream.append(&0u8);
stream.append(&0u8);
}
}
}
impl Receipt {
/// Decode status from raw transaction receipt RLP.
pub fn is_successful_raw_receipt(raw_receipt: &[u8]) -> Result<bool, DecoderError> {
let rlp = Rlp::new(raw_receipt);
if rlp.item_count()? == 3 {
// no outcome - invalid tx?
Ok(false)
} else {
let first = rlp.at(0)?;
if first.is_data() && first.data()?.len() <= 1 {
// EIP-658 transaction - status of successful transaction is 1
let status: u8 = first.as_val()?;
Ok(status == 1)
} else {
// pre-EIP-658 transaction - we do not support this kind of transactions
Ok(false)
}
}
}
/// Returns receipt RLP.
pub fn rlp(&self) -> Bytes {
let mut s = RlpStream::new();
match self.outcome {
TransactionOutcome::Unknown => {
s.begin_list(3);
},
TransactionOutcome::StateRoot(ref root) => {
s.begin_list(4);
s.append(root);
},
TransactionOutcome::StatusCode(ref status_code) => {
s.begin_list(4);
s.append(status_code);
},
}
s.append(&self.gas_used);
s.append(&EthBloom::from(self.log_bloom.0));
s.begin_list(self.logs.len());
for log in &self.logs {
s.begin_list(3);
s.append(&log.address);
s.begin_list(log.topics.len());
for topic in &log.topics {
s.append(topic);
}
s.append(&log.data);
}
s.out().to_vec()
}
}
impl SealedEmptyStep {
/// Returns message that has to be signed by the validator.
pub fn message(&self, parent_hash: &H256) -> H256 {
let mut message = RlpStream::new_list(2);
message.append(&self.step);
message.append(parent_hash);
keccak_256(&message.out()).into()
}
/// Returns RLP for the vector of empty steps (we only do encoding in tests).
pub fn rlp_of(empty_steps: &[SealedEmptyStep]) -> Bytes {
let mut s = RlpStream::new();
s.begin_list(empty_steps.len());
for empty_step in empty_steps {
s.begin_list(2).append(&empty_step.signature).append(&empty_step.step);
}
s.out().to_vec()
}
}
impl Decodable for SealedEmptyStep {
fn decode(rlp: &Rlp) -> Result<Self, DecoderError> {
let signature: H520 = rlp.val_at(0)?;
let step = rlp.val_at(1)?;
Ok(SealedEmptyStep { signature, step })
}
}
impl LogEntry {
/// Calculates the bloom of this log entry.
pub fn bloom(&self) -> Bloom {
let eth_bloom = self.topics.iter().fold(
EthBloom::from(BloomInput::Raw(self.address.as_bytes())),
|mut b, t| {
b.accrue(BloomInput::Raw(t.as_bytes()));
b
},
);
Bloom(*eth_bloom.data())
}
}
impl Bloom {
/// Returns true if this bloom has all bits from the other set.
pub fn contains(&self, other: &Bloom) -> bool {
self.0.iter().zip(other.0.iter()).all(|(l, r)| (l & r) == *r)
}
}
impl<'a> From<&'a [u8; 256]> for Bloom {
fn from(buffer: &'a [u8; 256]) -> Bloom {
Bloom(*buffer)
}
}
impl PartialEq<Bloom> for Bloom {
fn eq(&self, other: &Bloom) -> bool {
self.0.iter().zip(other.0.iter()).all(|(l, r)| l == r)
}
}
// there's no default for [_; 256], but clippy still complains
#[allow(clippy::derivable_impls)]
impl Default for Bloom {
fn default() -> Self {
Bloom([0; 256])
}
}
#[cfg(feature = "std")]
impl std::fmt::Debug for Bloom {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
fmt.debug_struct("Bloom").finish()
}
}
/// Decode Ethereum transaction.
pub fn transaction_decode_rlp(raw_tx: &[u8]) -> Result<Transaction, DecoderError> {
// parse transaction fields
let unsigned = UnsignedTransaction::decode_rlp(raw_tx)?;
let tx_rlp = Rlp::new(raw_tx);
let v: u64 = tx_rlp.val_at(6)?;
let r: U256 = tx_rlp.val_at(7)?;
let s: U256 = tx_rlp.val_at(8)?;
// reconstruct signature
let mut signature = [0u8; 65];
let (chain_id, v) = match v {
v if v == 27u64 => (None, 0),
v if v == 28u64 => (None, 1),
v if v >= 35u64 => (Some((v - 35) / 2), ((v - 1) % 2) as u8),
_ => (None, 4),
};
r.to_big_endian(&mut signature[0..32]);
s.to_big_endian(&mut signature[32..64]);
signature[64] = v;
// reconstruct message that has been signed
let message = unsigned.message(chain_id);
// recover tx sender
let sender_public =
sp_io::crypto::secp256k1_ecdsa_recover(&signature, message.as_fixed_bytes())
.map_err(|_| rlp::DecoderError::Custom("Failed to recover transaction sender"))?;
let sender_address = public_to_address(&sender_public);
Ok(Transaction { sender: sender_address, unsigned })
}
/// Convert public key into corresponding ethereum address.
pub fn public_to_address(public: &[u8; 64]) -> Address {
let hash = keccak_256(public);
let mut result = Address::zero();
result.as_bytes_mut().copy_from_slice(&hash[12..]);
result
}
/// Check ethereum merkle proof.
/// Returns Ok(computed-root) if check succeeds.
/// Returns Err(computed-root) if check fails.
fn check_merkle_proof<T: AsRef<[u8]>>(
expected_root: H256,
items: impl Iterator<Item = T>,
) -> Result<H256, H256> {
let computed_root = compute_merkle_root(items);
if computed_root == expected_root {
Ok(computed_root)
} else {
Err(computed_root)
}
}
/// Compute ethereum merkle root.
pub fn compute_merkle_root<T: AsRef<[u8]>>(items: impl Iterator<Item = T>) -> H256 {
struct Keccak256Hasher;
impl hash_db::Hasher for Keccak256Hasher {
type Out = H256;
type StdHasher = plain_hasher::PlainHasher;
const LENGTH: usize = 32;
fn hash(x: &[u8]) -> Self::Out {
keccak_256(x).into()
}
}
triehash::ordered_trie_root::<Keccak256Hasher, _>(items)
}
/// Get validator that should author the block at given step.
pub fn step_validator<T>(header_validators: &[T], header_step: u64) -> &T {
&header_validators[(header_step % header_validators.len() as u64) as usize]
}
sp_api::decl_runtime_apis! {
/// API for querying information about headers from the Rialto Bridge Pallet
pub trait RialtoPoAHeaderApi {
/// Returns number and hash of the best block known to the bridge module.
///
/// The caller should only submit an `import_header` transaction that makes
/// (or leads to making) other header the best one.
fn best_block() -> (u64, H256);
/// Returns number and hash of the best finalized block known to the bridge module.
fn finalized_block() -> (u64, H256);
/// Returns true if the import of given block requires transactions receipts.
fn is_import_requires_receipts(header: AuraHeader) -> bool;
/// Returns true if header is known to the runtime.
fn is_known_block(hash: H256) -> bool;
}
/// API for querying information about headers from the Kovan Bridge Pallet
pub trait KovanHeaderApi {
/// Returns number and hash of the best block known to the bridge module.
///
/// The caller should only submit an `import_header` transaction that makes
/// (or leads to making) other header the best one.
fn best_block() -> (u64, H256);
/// Returns number and hash of the best finalized block known to the bridge module.
fn finalized_block() -> (u64, H256);
/// Returns true if the import of given block requires transactions receipts.
fn is_import_requires_receipts(header: AuraHeader) -> bool;
/// Returns true if header is known to the runtime.
fn is_known_block(hash: H256) -> bool;
}
}
#[cfg(test)]
mod tests {
use super::*;
use hex_literal::hex;
#[test]
fn transfer_transaction_decode_works() {
// value transfer transaction
// https://etherscan.io/tx/0xb9d4ad5408f53eac8627f9ccd840ba8fb3469d55cd9cc2a11c6e049f1eef4edd
// https://etherscan.io/getRawTx?tx=0xb9d4ad5408f53eac8627f9ccd840ba8fb3469d55cd9cc2a11c6e049f1eef4edd
let raw_tx = hex!("f86c0a85046c7cfe0083016dea94d1310c1e038bc12865d3d3997275b3e4737c6302880b503be34d9fe80080269fc7eaaa9c21f59adf8ad43ed66cf5ef9ee1c317bd4d32cd65401e7aaca47cfaa0387d79c65b90be6260d09dcfb780f29dd8133b9b1ceb20b83b7e442b4bfc30cb");
assert_eq!(
transaction_decode_rlp(&raw_tx),
Ok(Transaction {
sender: hex!("67835910d32600471f388a137bbff3eb07993c04").into(),
unsigned: UnsignedTransaction {
nonce: 10.into(),
gas_price: 19000000000u64.into(),
gas: 93674.into(),
to: Some(hex!("d1310c1e038bc12865d3d3997275b3e4737c6302").into()),
value: 815217380000000000_u64.into(),
payload: Default::default(),
}
}),
);
// Kovan value transfer transaction
// https://kovan.etherscan.io/tx/0x3b4b7bd41c1178045ccb4753aa84c1ef9864b4d712fa308b228917cd837915da
// https://kovan.etherscan.io/getRawTx?tx=0x3b4b7bd41c1178045ccb4753aa84c1ef9864b4d712fa308b228917cd837915da
let raw_tx = hex!("f86a822816808252089470c1ccde719d6f477084f07e4137ab0e55f8369f8930cf46e92063afd8008078a00e4d1f4d8aa992bda3c105ff3d6e9b9acbfd99facea00985e2131029290adbdca028ea29a46a4b66ec65b454f0706228e3768cb0ecf755f67c50ddd472f11d5994");
assert_eq!(
transaction_decode_rlp(&raw_tx),
Ok(Transaction {
sender: hex!("faadface3fbd81ce37b0e19c0b65ff4234148132").into(),
unsigned: UnsignedTransaction {
nonce: 10262.into(),
gas_price: 0.into(),
gas: 21000.into(),
to: Some(hex!("70c1ccde719d6f477084f07e4137ab0e55f8369f").into()),
value: 900379597077600000000_u128.into(),
payload: Default::default(),
},
}),
);
}
#[test]
fn payload_transaction_decode_works() {
// contract call transaction
// https://etherscan.io/tx/0xdc2b996b4d1d6922bf6dba063bfd70913279cb6170967c9bb80252aeb061cf65
// https://etherscan.io/getRawTx?tx=0xdc2b996b4d1d6922bf6dba063bfd70913279cb6170967c9bb80252aeb061cf65
let raw_tx = hex!("f8aa76850430e234008301500094dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb000000000000000000000000e08f35f66867a454835b25118f1e490e7f9e9a7400000000000000000000000000000000000000000000000000000000004c4b4025a0964e023999621dc3d4d831c43c71f7555beb6d1192dee81a3674b3f57e310f21a00f229edd86f841d1ee4dc48cc16667e2283817b1d39bae16ced10cd206ae4fd4");
assert_eq!(
transaction_decode_rlp(&raw_tx),
Ok(Transaction {
sender: hex!("2b9a4d37bdeecdf994c4c9ad7f3cf8dc632f7d70").into(),
unsigned: UnsignedTransaction {
nonce: 118.into(),
gas_price: 18000000000u64.into(),
gas: 86016.into(),
to: Some(hex!("dac17f958d2ee523a2206206994597c13d831ec7").into()),
value: 0.into(),
payload: hex!("a9059cbb000000000000000000000000e08f35f66867a454835b25118f1e490e7f9e9a7400000000000000000000000000000000000000000000000000000000004c4b40").to_vec(),
},
}),
);
// Kovan contract call transaction
// https://kovan.etherscan.io/tx/0x2904b4451d23665492239016b78da052d40d55fdebc7304b38e53cf6a37322cf
// https://kovan.etherscan.io/getRawTx?tx=0x2904b4451d23665492239016b78da052d40d55fdebc7304b38e53cf6a37322cf
let raw_tx = hex!("f8ac8302200b843b9aca00830271009484dd11eb2a29615303d18149c0dbfa24167f896680b844a9059cbb00000000000000000000000001503dfc5ad81bf630d83697e98601871bb211b600000000000000000000000000000000000000000000000000000000000027101ba0ce126d2cca81f5e245f292ff84a0d915c0a4ac52af5c51219db1e5d36aa8da35a0045298b79dac631907403888f9b04c2ab5509fe0cc31785276d30a40b915fcf9");
assert_eq!(
transaction_decode_rlp(&raw_tx),
Ok(Transaction {
sender: hex!("617da121abf03d4c1af572f5a4e313e26bef7bdc").into(),
unsigned: UnsignedTransaction {
nonce: 139275.into(),
gas_price: 1000000000.into(),
gas: 160000.into(),
to: Some(hex!("84dd11eb2a29615303d18149c0dbfa24167f8966").into()),
value: 0.into(),
payload: hex!("a9059cbb00000000000000000000000001503dfc5ad81bf630d83697e98601871bb211b60000000000000000000000000000000000000000000000000000000000002710").to_vec(),
},
}),
);
}
#[test]
fn is_successful_raw_receipt_works() {
assert!(Receipt::is_successful_raw_receipt(&[]).is_err());
assert_eq!(
Receipt::is_successful_raw_receipt(
&Receipt {
outcome: TransactionOutcome::Unknown,
gas_used: Default::default(),
log_bloom: Default::default(),
logs: Vec::new(),
}
.rlp()
),
Ok(false),
);
assert_eq!(
Receipt::is_successful_raw_receipt(
&Receipt {
outcome: TransactionOutcome::StateRoot(Default::default()),
gas_used: Default::default(),
log_bloom: Default::default(),
logs: Vec::new(),
}
.rlp()
),
Ok(false),
);
assert_eq!(
Receipt::is_successful_raw_receipt(
&Receipt {
outcome: TransactionOutcome::StatusCode(0),
gas_used: Default::default(),
log_bloom: Default::default(),
logs: Vec::new(),
}
.rlp()
),
Ok(false),
);
assert_eq!(
Receipt::is_successful_raw_receipt(
&Receipt {
outcome: TransactionOutcome::StatusCode(1),
gas_used: Default::default(),
log_bloom: Default::default(),
logs: Vec::new(),
}
.rlp()
),
Ok(true),
);
}
#[test]
fn is_successful_raw_receipt_with_empty_data() {
let mut stream = RlpStream::new();
stream.begin_list(4);
stream.append_empty_data();
stream.append(&1u64);
stream.append(&2u64);
stream.append(&3u64);
assert_eq!(Receipt::is_successful_raw_receipt(&stream.out()), Ok(false));
}
}
@@ -1,138 +0,0 @@
// Copyright 2020-2021 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/>.
//
//! Helpers related to signatures.
//!
//! Used for testing and benchmarking.
// reexport to avoid direct secp256k1 deps by other crates
pub use libsecp256k1::SecretKey;
use crate::{
public_to_address, rlp_encode, step_validator, Address, AuraHeader, RawTransaction,
UnsignedTransaction, H256, H520, U256,
};
use libsecp256k1::{Message, PublicKey};
/// Utilities for signing headers.
pub trait SignHeader {
/// Signs header by given author.
fn sign_by(self, author: &SecretKey) -> AuraHeader;
/// Signs header by given authors set.
fn sign_by_set(self, authors: &[SecretKey]) -> AuraHeader;
}
/// Utilities for signing transactions.
pub trait SignTransaction {
/// Sign transaction by given author.
fn sign_by(self, author: &SecretKey, chain_id: Option<u64>) -> RawTransaction;
}
impl SignHeader for AuraHeader {
fn sign_by(mut self, author: &SecretKey) -> Self {
self.author = secret_to_address(author);
let message = self.seal_hash(false).unwrap();
let signature = sign(author, message);
self.seal[1] = rlp_encode(&signature).to_vec();
self
}
fn sign_by_set(self, authors: &[SecretKey]) -> Self {
let step = self.step().unwrap();
let author = step_validator(authors, step);
self.sign_by(author)
}
}
impl SignTransaction for UnsignedTransaction {
fn sign_by(self, author: &SecretKey, chain_id: Option<u64>) -> RawTransaction {
let message = self.message(chain_id);
let signature = sign(author, message);
let signature_r = U256::from_big_endian(&signature.as_fixed_bytes()[..32][..]);
let signature_s = U256::from_big_endian(&signature.as_fixed_bytes()[32..64][..]);
let signature_v = signature.as_fixed_bytes()[64] as u64;
let signature_v = signature_v + if let Some(n) = chain_id { 35 + n * 2 } else { 27 };
let mut stream = rlp::RlpStream::new_list(9);
self.rlp_to(None, &mut stream);
stream.append(&signature_v);
stream.append(&signature_r);
stream.append(&signature_s);
stream.out().to_vec()
}
}
/// Return author's signature over given message.
pub fn sign(author: &SecretKey, message: H256) -> H520 {
let (signature, recovery_id) =
libsecp256k1::sign(&Message::parse(message.as_fixed_bytes()), author);
let mut raw_signature = [0u8; 65];
raw_signature[..64].copy_from_slice(&signature.serialize());
raw_signature[64] = recovery_id.serialize();
raw_signature.into()
}
/// Returns address corresponding to given secret key.
pub fn secret_to_address(secret: &SecretKey) -> Address {
let public = PublicKey::from_secret_key(secret);
let mut raw_public = [0u8; 64];
raw_public.copy_from_slice(&public.serialize()[1..]);
public_to_address(&raw_public)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{transaction_decode_rlp, Transaction};
#[test]
fn transaction_signed_properly() {
// case1: with chain_id replay protection + to
let signer = SecretKey::parse(&[1u8; 32]).unwrap();
let signer_address = secret_to_address(&signer);
let unsigned = UnsignedTransaction {
nonce: 100.into(),
gas_price: 200.into(),
gas: 300.into(),
to: Some([42u8; 20].into()),
value: 400.into(),
payload: vec![1, 2, 3],
};
let raw_tx = unsigned.clone().sign_by(&signer, Some(42));
assert_eq!(
transaction_decode_rlp(&raw_tx),
Ok(Transaction { sender: signer_address, unsigned }),
);
// case2: without chain_id replay protection + contract creation
let unsigned = UnsignedTransaction {
nonce: 100.into(),
gas_price: 200.into(),
gas: 300.into(),
to: None,
value: 400.into(),
payload: vec![1, 2, 3],
};
let raw_tx = unsigned.clone().sign_by(&signer, None);
assert_eq!(
transaction_decode_rlp(&raw_tx),
Ok(Transaction { sender: signer_address, unsigned }),
);
}
}
@@ -22,6 +22,9 @@ sp-state-machine = { git = "https://github.com/paritytech/substrate", branch = "
sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-trie = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
[dev-dependencies]
hex-literal = "0.3"
[features]
default = ["std"]
std = [
+14 -2
View File
@@ -256,10 +256,22 @@ pub fn storage_map_final_key_identity(
///
/// Copied from `frame_support::parameter_types` macro
pub fn storage_parameter_key(parameter_name: &str) -> StorageKey {
let mut buffer = Vec::with_capacity(1 + parameter_name.len() + 1 + 1);
let mut buffer = Vec::with_capacity(1 + parameter_name.len() + 1);
buffer.push(b':');
buffer.extend_from_slice(parameter_name.as_bytes());
buffer.push(b':');
buffer.push(0);
StorageKey(sp_io::hashing::twox_128(&buffer).to_vec())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn storage_parameter_key_works() {
assert_eq!(
storage_parameter_key("MillauToRialtoConversionRate"),
StorageKey(hex_literal::hex!("58942375551bb0af1682f72786b59d04").to_vec()),
);
}
}
@@ -1,42 +0,0 @@
[package]
name = "ethereum-poa-relay"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies]
anyhow = "1.0"
async-std = "1.9.0"
async-trait = "0.1.42"
clap = { version = "2.33.3", features = ["yaml"] }
codec = { package = "parity-scale-codec", version = "2.2.0" }
ethabi = { git = "https://github.com/svyatonik/ethabi.git", branch = "bump-deps" }
ethabi-contract = { git = "https://github.com/svyatonik/ethabi.git", branch = "bump-deps" }
ethabi-derive = { git = "https://github.com/svyatonik/ethabi.git", branch = "bump-deps" }
futures = "0.3.12"
hex = "0.4"
hex-literal = "0.3"
libsecp256k1 = { version = "0.7", default-features = false, features = ["hmac"] }
log = "0.4.14"
num-traits = "0.2"
serde_json = "1.0.64"
thiserror = "1.0.26"
# Bridge dependencies
bp-currency-exchange = { path = "../../primitives/currency-exchange" }
bp-eth-poa = { path = "../../primitives/ethereum-poa" }
exchange-relay = { path = "../exchange" }
headers-relay = { path = "../headers" }
relay-ethereum-client = { path = "../client-ethereum" }
relay-rialto-client = { path = "../client-rialto" }
relay-substrate-client = { path = "../client-substrate" }
relay-utils = { path = "../utils" }
rialto-runtime = { path = "../../bin/rialto/runtime" }
# Substrate Dependencies
sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-keyring = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" }
@@ -1,7 +0,0 @@
# PoA <> Substrate Bridge
**DISCLAIMER:** *we recommend not using the bridge in "production" (to bridge significant amounts) just yet.
it's missing a code audit and should still be considered alpha. we can't rule out that there are bugs that might result in loss of the bridged amounts.
we'll update this disclaimer once that changes*
These docs are very incomplete yet. Describe high-level goals here in the (near) future.
@@ -1,167 +0,0 @@
[
{
"inputs": [
{
"internalType": "bytes",
"name": "rawInitialHeader",
"type": "bytes"
},
{
"internalType": "uint64",
"name": "initialValidatorsSetId",
"type": "uint64"
},
{
"internalType": "bytes",
"name": "initialValidatorsSet",
"type": "bytes"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"stateMutability": "nonpayable",
"type": "fallback"
},
{
"inputs": [],
"name": "bestKnownHeader",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
},
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "finalityTargetNumber",
"type": "uint256"
},
{
"internalType": "bytes32",
"name": "finalityTargetHash",
"type": "bytes32"
},
{
"internalType": "bytes",
"name": "rawFinalityProof",
"type": "bytes"
}
],
"name": "importFinalityProof",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes",
"name": "rawHeader1",
"type": "bytes"
},
{
"internalType": "bytes",
"name": "rawHeader2",
"type": "bytes"
},
{
"internalType": "bytes",
"name": "rawHeader3",
"type": "bytes"
},
{
"internalType": "bytes",
"name": "rawHeader4",
"type": "bytes"
}
],
"name": "importHeaders",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "incompleteHeaders",
"outputs": [
{
"internalType": "uint256[]",
"name": "",
"type": "uint256[]"
},
{
"internalType": "bytes32[]",
"name": "",
"type": "bytes32[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes",
"name": "rawHeader1",
"type": "bytes"
},
{
"internalType": "bytes",
"name": "rawHeader2",
"type": "bytes"
},
{
"internalType": "bytes",
"name": "rawHeader3",
"type": "bytes"
},
{
"internalType": "bytes",
"name": "rawHeader4",
"type": "bytes"
}
],
"name": "isIncompleteHeaders",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "headerHash",
"type": "bytes32"
}
],
"name": "isKnownHeader",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
}
]
File diff suppressed because one or more lines are too long
@@ -1,5 +0,0 @@
Last Change Date: 2020-07-30
Solc version: 0.6.6+commit.6c089d02.Linux.g++
Source hash (keccak256): 0xea5d6d744f69157adc2857166792aca139c0b5b186ba89c1011358fbcad90d7e
Source gist: https://github.com/svyatonik/substrate-bridge-sol/blob/6456d3e016c95cd5e6d5e817c23e9e69e739aa78/substrate-bridge.sol
Compiler flags used (command to produce the file): `docker run -i ethereum/solc:0.6.6 --optimize --bin - < substrate-bridge.sol`
@@ -1,166 +0,0 @@
name: ethsub-bridge
version: "0.1.0"
author: Parity Technologies <admin@parity.io>
about: Parity Ethereum (PoA) <-> Substrate bridge
subcommands:
- eth-to-sub:
about: Synchronize headers from Ethereum node to Substrate node.
args:
- eth-host: &eth-host
long: eth-host
value_name: ETH_HOST
help: Connect to Ethereum node websocket server at given host.
takes_value: true
- eth-port: &eth-port
long: eth-port
value_name: ETH_PORT
help: Connect to Ethereum node websocket server at given port.
takes_value: true
- sub-host: &sub-host
long: sub-host
value_name: SUB_HOST
help: Connect to Substrate node websocket server at given host.
takes_value: true
- sub-port: &sub-port
long: sub-port
value_name: SUB_PORT
help: Connect to Substrate node websocket server at given port.
takes_value: true
- sub-tx-mode:
long: sub-tx-mode
value_name: MODE
help: Submit headers using signed (default) or unsigned transactions. Third mode - backup - submits signed transactions only when we believe that sync has stalled.
takes_value: true
possible_values:
- signed
- unsigned
- backup
- sub-signer: &sub-signer
long: sub-signer
value_name: SUB_SIGNER
help: The SURI of secret key to use when transactions are submitted to the Substrate node.
- sub-signer-password: &sub-signer-password
long: sub-signer-password
value_name: SUB_SIGNER_PASSWORD
help: The password for the SURI of secret key to use when transactions are submitted to the Substrate node.
- sub-pallet-instance: &sub-pallet-instance
long: instance
short: i
value_name: PALLET_INSTANCE
help: The instance of the bridge pallet the relay should follow.
takes_value: true
case_insensitive: true
possible_values:
- Rialto
- Kovan
default_value: Rialto
- no-prometheus: &no-prometheus
long: no-prometheus
help: Do not expose a Prometheus metric endpoint.
- prometheus-host: &prometheus-host
long: prometheus-host
value_name: PROMETHEUS_HOST
help: Expose Prometheus endpoint at given interface.
- prometheus-port: &prometheus-port
long: prometheus-port
value_name: PROMETHEUS_PORT
help: Expose Prometheus endpoint at given port.
- sub-to-eth:
about: Synchronize headers from Substrate node to Ethereum node.
args:
- eth-host: *eth-host
- eth-port: *eth-port
- eth-contract:
long: eth-contract
value_name: ETH_CONTRACT
help: Address of deployed bridge contract.
takes_value: true
- eth-chain-id: &eth-chain-id
long: eth-chain-id
value_name: ETH_CHAIN_ID
help: Chain ID to use for signing.
- eth-signer: &eth-signer
long: eth-signer
value_name: ETH_SIGNER
help: Hex-encoded secret to use when transactions are submitted to the Ethereum node.
- sub-host: *sub-host
- sub-port: *sub-port
- no-prometheus: *no-prometheus
- prometheus-host: *prometheus-host
- prometheus-port: *prometheus-port
- eth-deploy-contract:
about: Deploy Bridge contract on Ethereum node.
args:
- eth-host: *eth-host
- eth-port: *eth-port
- eth-signer: *eth-signer
- eth-chain-id: *eth-chain-id
- eth-contract-code:
long: eth-contract-code
value_name: ETH_CONTRACT_CODE
help: Bytecode of bridge contract.
takes_value: true
- sub-host: *sub-host
- sub-port: *sub-port
- sub-authorities-set-id:
long: sub-authorities-set-id
value_name: SUB_AUTHORITIES_SET_ID
help: ID of initial GRANDPA authorities set.
takes_value: true
- sub-authorities-set:
long: sub-authorities-set
value_name: SUB_AUTHORITIES_SET
help: Encoded initial GRANDPA authorities set.
takes_value: true
- sub-initial-header:
long: sub-initial-header
value_name: SUB_INITIAL_HEADER
help: Encoded initial Substrate header.
takes_value: true
- eth-submit-exchange-tx:
about: Submit lock funds transaction to Ethereum node.
args:
- eth-host: *eth-host
- eth-port: *eth-port
- eth-nonce:
long: eth-nonce
value_name: ETH_NONCE
help: Nonce that have to be used when building transaction. If not specified, read from PoA node.
takes_value: true
- eth-signer: *eth-signer
- eth-chain-id: *eth-chain-id
- eth-amount:
long: eth-amount
value_name: ETH_AMOUNT
help: Amount of ETH to lock (in wei).
takes_value: true
- sub-recipient:
long: sub-recipient
value_name: SUB_RECIPIENT
help: Hex-encoded Public key of funds recipient in Substrate chain.
takes_value: true
- eth-exchange-sub:
about: Submit proof of PoA lock funds transaction to Substrate node.
args:
- eth-host: *eth-host
- eth-port: *eth-port
- eth-start-with-block:
long: eth-start-with-block
value_name: ETH_START_WITH_BLOCK
help: Auto-relay transactions starting with given block number. If not specified, starts with best finalized Ethereum block (known to Substrate node) transactions.
takes_value: true
conflicts_with:
- eth-tx-hash
- eth-tx-hash:
long: eth-tx-hash
value_name: ETH_TX_HASH
help: Hash of the lock funds transaction.
takes_value: true
- sub-host: *sub-host
- sub-port: *sub-port
- sub-signer: *sub-signer
- sub-signer-password: *sub-signer-password
- sub-pallet-instance: *sub-pallet-instance
- no-prometheus: *no-prometheus
- prometheus-host: *prometheus-host
- prometheus-port: *prometheus-port

Some files were not shown because too many files have changed in this diff Show More