mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 14:01:02 +00:00
Merge commit '114f487fd9daef4b4cd791446372a9a690c137ac' into update-bridges-subtree-r/w
This commit is contained in:
@@ -204,8 +204,6 @@ build:
|
|||||||
- mv -v ./target/release/rialto-parachain-collator ./artifacts/
|
- mv -v ./target/release/rialto-parachain-collator ./artifacts/
|
||||||
- strip ./target/release/millau-bridge-node
|
- strip ./target/release/millau-bridge-node
|
||||||
- mv -v ./target/release/millau-bridge-node ./artifacts/
|
- 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
|
- strip ./target/release/substrate-relay
|
||||||
- mv -v ./target/release/substrate-relay ./artifacts/
|
- mv -v ./target/release/substrate-relay ./artifacts/
|
||||||
- mv -v ./deployments/local-scripts/bridge-entrypoint.sh ./artifacts/
|
- mv -v ./deployments/local-scripts/bridge-entrypoint.sh ./artifacts/
|
||||||
@@ -286,10 +284,6 @@ millau-bridge-node:
|
|||||||
stage: publish
|
stage: publish
|
||||||
<<: *build-push-image
|
<<: *build-push-image
|
||||||
|
|
||||||
ethereum-poa-relay:
|
|
||||||
stage: publish
|
|
||||||
<<: *build-push-image
|
|
||||||
|
|
||||||
substrate-relay:
|
substrate-relay:
|
||||||
stage: publish
|
stage: publish
|
||||||
<<: *build-push-image
|
<<: *build-push-image
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -13,7 +13,7 @@ WORKDIR /parity-bridges-common
|
|||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
ARG PROJECT=ethereum-poa-relay
|
ARG PROJECT=substrate-relay
|
||||||
RUN cargo build --release --verbose -p ${PROJECT} && \
|
RUN cargo build --release --verbose -p ${PROJECT} && \
|
||||||
strip ./target/release/${PROJECT}
|
strip ./target/release/${PROJECT}
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ USER user
|
|||||||
|
|
||||||
WORKDIR /home/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/target/release/${PROJECT} ./
|
||||||
COPY --chown=user:user --from=builder /parity-bridges-common/deployments/local-scripts/bridge-entrypoint.sh ./
|
COPY --chown=user:user --from=builder /parity-bridges-common/deployments/local-scripts/bridge-entrypoint.sh ./
|
||||||
|
|||||||
@@ -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.
|
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
|
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 🚧
|
🚧 The bridges are currently under construction - a hardhat is recommended beyond this point 🚧
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ cargo build --all
|
|||||||
cargo test --all
|
cargo test --all
|
||||||
```
|
```
|
||||||
|
|
||||||
Also you can build the repo with
|
Also you can build the repo with
|
||||||
[Parity CI Docker image](https://github.com/paritytech/scripts/tree/master/dockerfiles/bridges-ci):
|
[Parity CI Docker image](https://github.com/paritytech/scripts/tree/master/dockerfiles/bridges-ci):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -54,7 +54,7 @@ docker run --rm -it -w /shellhere/parity-bridges-common \
|
|||||||
#artifacts can be found in ~/cache/target
|
#artifacts can be found in ~/cache/target
|
||||||
```
|
```
|
||||||
|
|
||||||
If you want to reproduce other steps of CI process you can use the following
|
If you want to reproduce other steps of CI process you can use the following
|
||||||
[guide](https://github.com/paritytech/scripts#reproduce-ci-locally).
|
[guide](https://github.com/paritytech/scripts#reproduce-ci-locally).
|
||||||
|
|
||||||
If you need more information about setting up your development environment Substrate's
|
If you need more information about setting up your development environment Substrate's
|
||||||
@@ -104,7 +104,6 @@ the `relays` which are used to pass messages between chains.
|
|||||||
├── diagrams // Pretty pictures of the project architecture
|
├── diagrams // Pretty pictures of the project architecture
|
||||||
│ └── ...
|
│ └── ...
|
||||||
├── modules // Substrate Runtime Modules (a.k.a Pallets)
|
├── modules // Substrate Runtime Modules (a.k.a Pallets)
|
||||||
│ ├── ethereum // Ethereum PoA Header Sync Module
|
|
||||||
│ ├── grandpa // On-Chain GRANDPA Light Client
|
│ ├── grandpa // On-Chain GRANDPA Light Client
|
||||||
│ ├── messages // Cross Chain Message Passing
|
│ ├── messages // Cross Chain Message Passing
|
||||||
│ ├── dispatch // Target Chain Message Execution
|
│ ├── dispatch // Target Chain Message Execution
|
||||||
|
|||||||
@@ -23,9 +23,13 @@ pallet-bridge-messages = { path = "../../../modules/messages" }
|
|||||||
|
|
||||||
# Substrate Dependencies
|
# 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 = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||||
frame-benchmarking-cli = { 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" }
|
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" }
|
pallet-transaction-payment-rpc = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||||
sc-basic-authorship = { 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"] }
|
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
|
// 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/>.
|
// 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 bp_millau::derive_account_from_rialto_id;
|
||||||
use millau_runtime::{
|
use millau_runtime::{
|
||||||
AccountId, AuraConfig, BalancesConfig, BridgeRialtoMessagesConfig, BridgeWestendGrandpaConfig,
|
AccountId, AuraConfig, BalancesConfig, BeefyConfig, BridgeRialtoMessagesConfig,
|
||||||
GenesisConfig, GrandpaConfig, SessionConfig, SessionKeys, Signature, SudoConfig, SystemConfig,
|
BridgeWestendGrandpaConfig, GenesisConfig, GrandpaConfig, SessionConfig, SessionKeys,
|
||||||
WASM_BINARY,
|
Signature, SudoConfig, SystemConfig, WASM_BINARY,
|
||||||
};
|
};
|
||||||
use sp_consensus_aura::sr25519::AuthorityId as AuraId;
|
use sp_consensus_aura::sr25519::AuthorityId as AuraId;
|
||||||
use sp_core::{sr25519, Pair, Public};
|
use sp_core::{sr25519, Pair, Public};
|
||||||
@@ -57,10 +58,11 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Helper function to generate an authority key for Aura
|
/// 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_account_id_from_seed::<sr25519::Public>(s),
|
||||||
get_from_seed::<AuraId>(s),
|
get_from_seed::<AuraId>(s),
|
||||||
|
get_from_seed::<BeefyId>(s),
|
||||||
get_from_seed::<GrandpaId>(s),
|
get_from_seed::<GrandpaId>(s),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -173,12 +175,12 @@ impl Alternative {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn session_keys(aura: AuraId, grandpa: GrandpaId) -> SessionKeys {
|
fn session_keys(aura: AuraId, beefy: BeefyId, grandpa: GrandpaId) -> SessionKeys {
|
||||||
SessionKeys { aura, grandpa }
|
SessionKeys { aura, beefy, grandpa }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn testnet_genesis(
|
fn testnet_genesis(
|
||||||
initial_authorities: Vec<(AccountId, AuraId, GrandpaId)>,
|
initial_authorities: Vec<(AccountId, AuraId, BeefyId, GrandpaId)>,
|
||||||
root_key: AccountId,
|
root_key: AccountId,
|
||||||
endowed_accounts: Vec<AccountId>,
|
endowed_accounts: Vec<AccountId>,
|
||||||
_enable_println: bool,
|
_enable_println: bool,
|
||||||
@@ -186,18 +188,20 @@ fn testnet_genesis(
|
|||||||
GenesisConfig {
|
GenesisConfig {
|
||||||
system: SystemConfig {
|
system: SystemConfig {
|
||||||
code: WASM_BINARY.expect("Millau development WASM not available").to_vec(),
|
code: WASM_BINARY.expect("Millau development WASM not available").to_vec(),
|
||||||
changes_trie_config: Default::default(),
|
|
||||||
},
|
},
|
||||||
balances: BalancesConfig {
|
balances: BalancesConfig {
|
||||||
balances: endowed_accounts.iter().cloned().map(|k| (k, 1 << 50)).collect(),
|
balances: endowed_accounts.iter().cloned().map(|k| (k, 1 << 50)).collect(),
|
||||||
},
|
},
|
||||||
aura: AuraConfig { authorities: Vec::new() },
|
aura: AuraConfig { authorities: Vec::new() },
|
||||||
|
beefy: BeefyConfig { authorities: Vec::new() },
|
||||||
grandpa: GrandpaConfig { authorities: Vec::new() },
|
grandpa: GrandpaConfig { authorities: Vec::new() },
|
||||||
sudo: SudoConfig { key: root_key },
|
sudo: SudoConfig { key: root_key },
|
||||||
session: SessionConfig {
|
session: SessionConfig {
|
||||||
keys: initial_authorities
|
keys: initial_authorities
|
||||||
.iter()
|
.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<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
},
|
},
|
||||||
bridge_westend_grandpa: BridgeWestendGrandpaConfig {
|
bridge_westend_grandpa: BridgeWestendGrandpaConfig {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ use crate::{
|
|||||||
service::new_partial,
|
service::new_partial,
|
||||||
};
|
};
|
||||||
use millau_runtime::{Block, RuntimeApi};
|
use millau_runtime::{Block, RuntimeApi};
|
||||||
use sc_cli::{ChainSpec, Role, RuntimeVersion, SubstrateCli};
|
use sc_cli::{ChainSpec, RuntimeVersion, SubstrateCli};
|
||||||
use sc_service::PartialComponents;
|
use sc_service::PartialComponents;
|
||||||
|
|
||||||
impl SubstrateCli for Cli {
|
impl SubstrateCli for Cli {
|
||||||
@@ -72,7 +72,7 @@ impl SubstrateCli for Cli {
|
|||||||
pub fn run() -> sc_cli::Result<()> {
|
pub fn run() -> sc_cli::Result<()> {
|
||||||
let cli = Cli::from_args();
|
let cli = Cli::from_args();
|
||||||
// make sure to set correct crypto version.
|
// 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,
|
millau_runtime::SS58Prefix::get() as u16,
|
||||||
));
|
));
|
||||||
|
|
||||||
@@ -146,11 +146,7 @@ pub fn run() -> sc_cli::Result<()> {
|
|||||||
None => {
|
None => {
|
||||||
let runner = cli.create_runner(&cli.run)?;
|
let runner = cli.create_runner(&cli.run)?;
|
||||||
runner.run_node_until_exit(|config| async move {
|
runner.run_node_until_exit(|config| async move {
|
||||||
match config.role {
|
service::new_full(config).map_err(sc_cli::Error::Service)
|
||||||
Role::Light => service::new_light(config),
|
|
||||||
_ => service::new_full(config),
|
|
||||||
}
|
|
||||||
.map_err(sc_cli::Error::Service)
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,18 +21,19 @@
|
|||||||
// =====================================================================================
|
// =====================================================================================
|
||||||
// UPDATE GUIDE:
|
// UPDATE GUIDE:
|
||||||
// 1) replace everything with node-template/src/service.rs contents (found in main Substrate repo);
|
// 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
|
// 2) from old code keep `rpc_extensions_builder` - we use our own custom RPCs;
|
||||||
// RPCs; 3) fix compilation errors;
|
// 3) from old code keep the Beefy gadget;
|
||||||
// 4) test :)
|
// 4) fix compilation errors;
|
||||||
|
// 5) test :)
|
||||||
// =====================================================================================
|
// =====================================================================================
|
||||||
// =====================================================================================
|
// =====================================================================================
|
||||||
// =====================================================================================
|
// =====================================================================================
|
||||||
|
|
||||||
use millau_runtime::{self, opaque::Block, RuntimeApi};
|
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};
|
use sc_consensus_aura::{ImportQueueParams, SlotProportion, StartAuraParams};
|
||||||
pub use sc_executor::NativeElseWasmExecutor;
|
pub use sc_executor::NativeElseWasmExecutor;
|
||||||
|
use sc_finality_grandpa::SharedVoterState;
|
||||||
use sc_keystore::LocalKeystore;
|
use sc_keystore::LocalKeystore;
|
||||||
use sc_service::{error::Error as ServiceError, Configuration, TaskManager};
|
use sc_service::{error::Error as ServiceError, Configuration, TaskManager};
|
||||||
use sc_telemetry::{Telemetry, TelemetryWorker};
|
use sc_telemetry::{Telemetry, TelemetryWorker};
|
||||||
@@ -40,13 +41,16 @@ use sp_consensus::SlotData;
|
|||||||
use sp_consensus_aura::sr25519::AuthorityPair as AuraPair;
|
use sp_consensus_aura::sr25519::AuthorityPair as AuraPair;
|
||||||
use std::{sync::Arc, time::Duration};
|
use std::{sync::Arc, time::Duration};
|
||||||
|
|
||||||
type Executor = NativeElseWasmExecutor<ExecutorDispatch>;
|
|
||||||
|
|
||||||
// Our native executor instance.
|
// Our native executor instance.
|
||||||
pub struct ExecutorDispatch;
|
pub struct ExecutorDispatch;
|
||||||
|
|
||||||
impl sc_executor::NativeExecutionDispatch for 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;
|
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>> {
|
fn dispatch(method: &str, data: &[u8]) -> Option<Vec<u8>> {
|
||||||
millau_runtime::api::dispatch(method, data)
|
millau_runtime::api::dispatch(method, data)
|
||||||
@@ -62,7 +66,6 @@ type FullClient =
|
|||||||
type FullBackend = sc_service::TFullBackend<Block>;
|
type FullBackend = sc_service::TFullBackend<Block>;
|
||||||
type FullSelectChain = sc_consensus::LongestChain<FullBackend, Block>;
|
type FullSelectChain = sc_consensus::LongestChain<FullBackend, Block>;
|
||||||
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
pub fn new_partial(
|
pub fn new_partial(
|
||||||
config: &Configuration,
|
config: &Configuration,
|
||||||
) -> Result<
|
) -> Result<
|
||||||
@@ -86,7 +89,7 @@ pub fn new_partial(
|
|||||||
ServiceError,
|
ServiceError,
|
||||||
> {
|
> {
|
||||||
if config.keystore_remote.is_some() {
|
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
|
let telemetry = config
|
||||||
@@ -107,15 +110,15 @@ pub fn new_partial(
|
|||||||
);
|
);
|
||||||
|
|
||||||
let (client, backend, keystore_container, task_manager) =
|
let (client, backend, keystore_container, task_manager) =
|
||||||
sc_service::new_full_parts::<Block, RuntimeApi, Executor>(
|
sc_service::new_full_parts::<Block, RuntimeApi, _>(
|
||||||
config,
|
&config,
|
||||||
telemetry.as_ref().map(|(_, telemetry)| telemetry.handle()),
|
telemetry.as_ref().map(|(_, telemetry)| telemetry.handle()),
|
||||||
executor,
|
executor,
|
||||||
)?;
|
)?;
|
||||||
let client = Arc::new(client);
|
let client = Arc::new(client);
|
||||||
|
|
||||||
let telemetry = telemetry.map(|(worker, telemetry)| {
|
let telemetry = telemetry.map(|(worker, telemetry)| {
|
||||||
task_manager.spawn_handle().spawn("telemetry", worker.run());
|
task_manager.spawn_handle().spawn("telemetry", None, worker.run());
|
||||||
telemetry
|
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,
|
// FIXME: here would the concrete keystore be built,
|
||||||
// must return a concrete type (NOT `LocalKeystore`) that
|
// must return a concrete type (NOT `LocalKeystore`) that
|
||||||
// implements `CryptoStore` and `SyncCryptoStore`
|
// 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(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(
|
let warp_sync = Arc::new(sc_finality_grandpa::warp_proof::NetworkProvider::new(
|
||||||
backend.clone(),
|
backend.clone(),
|
||||||
grandpa_link.shared_authority_set().clone(),
|
grandpa_link.shared_authority_set().clone(),
|
||||||
vec![],
|
Vec::default(),
|
||||||
));
|
));
|
||||||
|
|
||||||
let (network, system_rpc_tx, network_starter) =
|
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(),
|
transaction_pool: transaction_pool.clone(),
|
||||||
spawn_handle: task_manager.spawn_handle(),
|
spawn_handle: task_manager.spawn_handle(),
|
||||||
import_queue,
|
import_queue,
|
||||||
on_demand: None,
|
|
||||||
block_announce_validator_builder: None,
|
block_announce_validator_builder: None,
|
||||||
warp_sync: Some(warp_sync),
|
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 name = config.network.node_name.clone();
|
||||||
let enable_grandpa = !config.disable_grandpa;
|
let enable_grandpa = !config.disable_grandpa;
|
||||||
let prometheus_registry = config.prometheus_registry().cloned();
|
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 = {
|
let rpc_extensions_builder = {
|
||||||
use sc_finality_grandpa::FinalityProofProvider as GrandpaFinalityProofProvider;
|
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()),
|
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();
|
let mut io = jsonrpc_core::IoHandler::default();
|
||||||
io.extend_with(SystemApi::to_delegate(FullSystem::new(
|
io.extend_with(SystemApi::to_delegate(FullSystem::new(
|
||||||
client.clone(),
|
client.clone(),
|
||||||
@@ -277,9 +282,18 @@ pub fn new_full(mut config: Configuration) -> Result<TaskManager, ServiceError>
|
|||||||
shared_authority_set.clone(),
|
shared_authority_set.clone(),
|
||||||
shared_voter_state.clone(),
|
shared_voter_state.clone(),
|
||||||
justification_stream.clone(),
|
justification_stream.clone(),
|
||||||
subscription_executor,
|
subscription_executor.clone(),
|
||||||
finality_proof_provider.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)
|
Ok(io)
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
@@ -291,9 +305,7 @@ pub fn new_full(mut config: Configuration) -> Result<TaskManager, ServiceError>
|
|||||||
task_manager: &mut task_manager,
|
task_manager: &mut task_manager,
|
||||||
transaction_pool: transaction_pool.clone(),
|
transaction_pool: transaction_pool.clone(),
|
||||||
rpc_extensions_builder,
|
rpc_extensions_builder,
|
||||||
on_demand: None,
|
backend: backend.clone(),
|
||||||
remote_blockchain: None,
|
|
||||||
backend,
|
|
||||||
system_rpc_tx,
|
system_rpc_tx,
|
||||||
config,
|
config,
|
||||||
telemetry: telemetry.as_mut(),
|
telemetry: telemetry.as_mut(),
|
||||||
@@ -317,17 +329,18 @@ pub fn new_full(mut config: Configuration) -> Result<TaskManager, ServiceError>
|
|||||||
let aura = sc_consensus_aura::start_aura::<AuraPair, _, _, _, _, _, _, _, _, _, _, _>(
|
let aura = sc_consensus_aura::start_aura::<AuraPair, _, _, _, _, _, _, _, _, _, _, _>(
|
||||||
StartAuraParams {
|
StartAuraParams {
|
||||||
slot_duration,
|
slot_duration,
|
||||||
client,
|
client: client.clone(),
|
||||||
select_chain,
|
select_chain,
|
||||||
block_import,
|
block_import,
|
||||||
proposer_factory,
|
proposer_factory,
|
||||||
create_inherent_data_providers: move |_, ()| async move {
|
create_inherent_data_providers: move |_, ()| async move {
|
||||||
let timestamp = sp_timestamp::InherentDataProvider::from_system_time();
|
let timestamp = sp_timestamp::InherentDataProvider::from_system_time();
|
||||||
|
|
||||||
let slot = sp_consensus_aura::inherents::InherentDataProvider::from_timestamp_and_duration(
|
let slot =
|
||||||
*timestamp,
|
sp_consensus_aura::inherents::InherentDataProvider::from_timestamp_and_duration(
|
||||||
raw_slot_duration,
|
*timestamp,
|
||||||
);
|
raw_slot_duration,
|
||||||
|
);
|
||||||
|
|
||||||
Ok((timestamp, slot))
|
Ok((timestamp, slot))
|
||||||
},
|
},
|
||||||
@@ -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
|
// the AURA authoring task is considered essential, i.e. if it
|
||||||
// fails we take down the service with 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
|
// 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 =
|
let keystore =
|
||||||
if role.is_authority() { Some(keystore_container.sync_keystore()) } else { None };
|
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 {
|
let grandpa_config = sc_finality_grandpa::Config {
|
||||||
// FIXME #1578 make this available through chainspec
|
// FIXME #1578 make this available through chainspec
|
||||||
gossip_duration: Duration::from_millis(333),
|
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.
|
// if it fails we take down the service with it.
|
||||||
task_manager.spawn_essential_handle().spawn_blocking(
|
task_manager.spawn_essential_handle().spawn_blocking(
|
||||||
"grandpa-voter",
|
"grandpa-voter",
|
||||||
|
None,
|
||||||
sc_finality_grandpa::run_grandpa_voter(grandpa_config)?,
|
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();
|
network_starter.start_network();
|
||||||
Ok(task_manager)
|
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
|
# 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-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-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 }
|
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 }
|
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-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-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-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-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-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-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]
|
[features]
|
||||||
default = ["std"]
|
default = ["std"]
|
||||||
std = [
|
std = [
|
||||||
|
"beefy-primitives/std",
|
||||||
"bp-header-chain/std",
|
"bp-header-chain/std",
|
||||||
"bp-messages/std",
|
"bp-messages/std",
|
||||||
"bp-millau/std",
|
"bp-millau/std",
|
||||||
@@ -78,11 +84,14 @@ std = [
|
|||||||
"frame-system/std",
|
"frame-system/std",
|
||||||
"pallet-aura/std",
|
"pallet-aura/std",
|
||||||
"pallet-balances/std",
|
"pallet-balances/std",
|
||||||
|
"pallet-beefy/std",
|
||||||
|
"pallet-beefy-mmr/std",
|
||||||
"pallet-bridge-dispatch/std",
|
"pallet-bridge-dispatch/std",
|
||||||
"pallet-bridge-grandpa/std",
|
"pallet-bridge-grandpa/std",
|
||||||
"pallet-bridge-messages/std",
|
"pallet-bridge-messages/std",
|
||||||
"pallet-bridge-token-swap/std",
|
"pallet-bridge-token-swap/std",
|
||||||
"pallet-grandpa/std",
|
"pallet-grandpa/std",
|
||||||
|
"pallet-mmr/std",
|
||||||
"pallet-randomness-collective-flip/std",
|
"pallet-randomness-collective-flip/std",
|
||||||
"pallet-session/std",
|
"pallet-session/std",
|
||||||
"pallet-shift-session-manager/std",
|
"pallet-shift-session-manager/std",
|
||||||
|
|||||||
@@ -34,19 +34,23 @@ pub mod rialto_messages;
|
|||||||
|
|
||||||
use crate::rialto_messages::{ToRialtoMessagePayload, WithRialtoMessageBridge};
|
use crate::rialto_messages::{ToRialtoMessagePayload, WithRialtoMessageBridge};
|
||||||
|
|
||||||
|
use beefy_primitives::{crypto::AuthorityId as BeefyId, mmr::MmrLeafVersion, ValidatorSet};
|
||||||
use bridge_runtime_common::messages::{
|
use bridge_runtime_common::messages::{
|
||||||
source::estimate_message_dispatch_and_delivery_fee, MessageBridge,
|
source::estimate_message_dispatch_and_delivery_fee, MessageBridge,
|
||||||
};
|
};
|
||||||
use pallet_grandpa::{
|
use pallet_grandpa::{
|
||||||
fg_primitives, AuthorityId as GrandpaId, AuthorityList as GrandpaAuthorityList,
|
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 pallet_transaction_payment::{FeeDetails, Multiplier, RuntimeDispatchInfo};
|
||||||
use sp_api::impl_runtime_apis;
|
use sp_api::impl_runtime_apis;
|
||||||
use sp_consensus_aura::sr25519::AuthorityId as AuraId;
|
use sp_consensus_aura::sr25519::AuthorityId as AuraId;
|
||||||
use sp_core::{crypto::KeyTypeId, OpaqueMetadata};
|
use sp_core::{crypto::KeyTypeId, OpaqueMetadata};
|
||||||
use sp_runtime::{
|
use sp_runtime::{
|
||||||
create_runtime_str, generic, impl_opaque_keys,
|
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},
|
transaction_validity::{TransactionSource, TransactionValidity},
|
||||||
ApplyExtrinsicResult, FixedPointNumber, MultiSignature, MultiSigner, Perquintill,
|
ApplyExtrinsicResult, FixedPointNumber, MultiSignature, MultiSigner, Perquintill,
|
||||||
};
|
};
|
||||||
@@ -100,9 +104,6 @@ pub type Hash = bp_millau::Hash;
|
|||||||
/// Hashing algorithm used by the chain.
|
/// Hashing algorithm used by the chain.
|
||||||
pub type Hashing = bp_millau::Hasher;
|
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
|
/// 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
|
/// 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
|
/// of data like extrinsics, allowing for them to continue syncing the network through upgrades
|
||||||
@@ -123,6 +124,7 @@ pub mod opaque {
|
|||||||
impl_opaque_keys! {
|
impl_opaque_keys! {
|
||||||
pub struct SessionKeys {
|
pub struct SessionKeys {
|
||||||
pub aura: Aura,
|
pub aura: Aura,
|
||||||
|
pub beefy: Beefy,
|
||||||
pub grandpa: Grandpa,
|
pub grandpa: Grandpa,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -215,6 +217,11 @@ impl pallet_aura::Config for Runtime {
|
|||||||
type MaxAuthorities = MaxAuthorities;
|
type MaxAuthorities = MaxAuthorities;
|
||||||
type DisabledValidators = ();
|
type DisabledValidators = ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl pallet_beefy::Config for Runtime {
|
||||||
|
type BeefyId = BeefyId;
|
||||||
|
}
|
||||||
|
|
||||||
impl pallet_bridge_dispatch::Config for Runtime {
|
impl pallet_bridge_dispatch::Config for Runtime {
|
||||||
type Event = Event;
|
type Event = Event;
|
||||||
type BridgeMessageId = (bp_messages::LaneId, bp_messages::MessageNonce);
|
type BridgeMessageId = (bp_messages::LaneId, bp_messages::MessageNonce);
|
||||||
@@ -243,6 +250,40 @@ impl pallet_grandpa::Config for Runtime {
|
|||||||
type MaxAuthorities = MaxAuthorities;
|
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! {
|
parameter_types! {
|
||||||
pub const MinimumPeriod: u64 = bp_millau::SLOT_DURATION / 2;
|
pub const MinimumPeriod: u64 = bp_millau::SLOT_DURATION / 2;
|
||||||
}
|
}
|
||||||
@@ -462,6 +503,11 @@ construct_runtime!(
|
|||||||
ShiftSessionManager: pallet_shift_session_manager::{Pallet},
|
ShiftSessionManager: pallet_shift_session_manager::{Pallet},
|
||||||
RandomnessCollectiveFlip: pallet_randomness_collective_flip::{Pallet, Storage},
|
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.
|
// Rialto bridge modules.
|
||||||
BridgeRialtoGrandpa: pallet_bridge_grandpa::{Pallet, Call, Storage},
|
BridgeRialtoGrandpa: pallet_bridge_grandpa::{Pallet, Call, Storage},
|
||||||
BridgeDispatch: pallet_bridge_dispatch::{Pallet, Event<T>},
|
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 {
|
impl fg_primitives::GrandpaApi<Block> for Runtime {
|
||||||
fn current_set_id() -> fg_primitives::SetId {
|
fn current_set_id() -> fg_primitives::SetId {
|
||||||
Grandpa::current_set_id()
|
Grandpa::current_set_id()
|
||||||
|
|||||||
@@ -151,7 +151,6 @@ fn testnet_genesis(
|
|||||||
code: rialto_parachain_runtime::WASM_BINARY
|
code: rialto_parachain_runtime::WASM_BINARY
|
||||||
.expect("WASM binary was not build, please build it!")
|
.expect("WASM binary was not build, please build it!")
|
||||||
.to_vec(),
|
.to_vec(),
|
||||||
changes_trie_config: Default::default(),
|
|
||||||
},
|
},
|
||||||
balances: rialto_parachain_runtime::BalancesConfig {
|
balances: rialto_parachain_runtime::BalancesConfig {
|
||||||
balances: endowed_accounts.iter().cloned().map(|k| (k, 1 << 60)).collect(),
|
balances: endowed_accounts.iter().cloned().map(|k| (k, 1 << 60)).collect(),
|
||||||
|
|||||||
@@ -103,6 +103,9 @@ pub struct Cli {
|
|||||||
#[structopt(subcommand)]
|
#[structopt(subcommand)]
|
||||||
pub subcommand: Option<Subcommand>,
|
pub subcommand: Option<Subcommand>,
|
||||||
|
|
||||||
|
#[structopt(long)]
|
||||||
|
pub parachain_id: Option<u32>,
|
||||||
|
|
||||||
#[structopt(flatten)]
|
#[structopt(flatten)]
|
||||||
pub run: cumulus_client_cli::RunCmd,
|
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> {
|
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 {
|
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.
|
/// Parse command line arguments into service configuration.
|
||||||
pub fn run() -> Result<()> {
|
pub fn run() -> Result<()> {
|
||||||
let cli = Cli::from_args();
|
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,
|
rialto_parachain_runtime::SS58Prefix::get() as u16,
|
||||||
));
|
));
|
||||||
|
|
||||||
@@ -273,7 +273,7 @@ pub fn run() -> Result<()> {
|
|||||||
[RelayChainCli::executable_name()].iter().chain(cli.relaychain_args.iter()),
|
[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 =
|
let parachain_account =
|
||||||
AccountIdConversion::<polkadot_primitives::v0::AccountId>::into_account(&id);
|
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_worker_handle = telemetry.as_ref().map(|(worker, _)| worker.handle());
|
||||||
|
|
||||||
let telemetry = telemetry.map(|(worker, telemetry)| {
|
let telemetry = telemetry.map(|(worker, telemetry)| {
|
||||||
task_manager.spawn_handle().spawn("telemetry", worker.run());
|
task_manager.spawn_handle().spawn("telemetry", None, worker.run());
|
||||||
telemetry
|
telemetry
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -283,7 +283,6 @@ where
|
|||||||
transaction_pool: transaction_pool.clone(),
|
transaction_pool: transaction_pool.clone(),
|
||||||
spawn_handle: task_manager.spawn_handle(),
|
spawn_handle: task_manager.spawn_handle(),
|
||||||
import_queue: import_queue.clone(),
|
import_queue: import_queue.clone(),
|
||||||
on_demand: None,
|
|
||||||
block_announce_validator_builder: Some(Box::new(|_| block_announce_validator)),
|
block_announce_validator_builder: Some(Box::new(|_| block_announce_validator)),
|
||||||
warp_sync: None,
|
warp_sync: None,
|
||||||
})?;
|
})?;
|
||||||
@@ -292,8 +291,6 @@ where
|
|||||||
let rpc_extensions_builder = Box::new(move |_, _| Ok(rpc_ext_builder(rpc_client.clone())));
|
let rpc_extensions_builder = Box::new(move |_, _| Ok(rpc_ext_builder(rpc_client.clone())));
|
||||||
|
|
||||||
sc_service::spawn_tasks(sc_service::SpawnTasksParams {
|
sc_service::spawn_tasks(sc_service::SpawnTasksParams {
|
||||||
on_demand: None,
|
|
||||||
remote_blockchain: None,
|
|
||||||
rpc_extensions_builder,
|
rpc_extensions_builder,
|
||||||
client: client.clone(),
|
client: client.clone(),
|
||||||
transaction_pool: transaction_pool.clone(),
|
transaction_pool: transaction_pool.clone(),
|
||||||
|
|||||||
@@ -28,10 +28,15 @@ rialto-runtime = { path = "../runtime" }
|
|||||||
|
|
||||||
# Substrate Dependencies
|
# 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 = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||||
frame-benchmarking-cli = { 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" }
|
frame-system-rpc-runtime-api = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||||
node-inspect = { 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 = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||||
pallet-transaction-payment-rpc-runtime-api = { 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" }
|
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-frame-rpc-system = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||||
substrate-prometheus-endpoint = { 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 (parachain) Dependencies
|
||||||
|
|
||||||
polkadot-approval-distribution = { git = "https://github.com/paritytech/polkadot", branch = "master" }
|
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-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-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-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-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-provisioner = { git = "https://github.com/paritytech/polkadot", branch = "master" }
|
||||||
polkadot-node-core-pvf = { 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
|
// 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/>.
|
// 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 bp_rialto::derive_account_from_millau_id;
|
||||||
use polkadot_primitives::v1::{AssignmentId, ValidatorId};
|
use polkadot_primitives::v1::{AssignmentId, ValidatorId};
|
||||||
use rialto_runtime::{
|
use rialto_runtime::{
|
||||||
AccountId, BabeConfig, BalancesConfig, BridgeKovanConfig, BridgeMillauMessagesConfig,
|
AccountId, BabeConfig, BalancesConfig, BeefyConfig, BridgeMillauMessagesConfig,
|
||||||
BridgeRialtoPoaConfig, ConfigurationConfig, GenesisConfig, GrandpaConfig, SessionConfig,
|
ConfigurationConfig, GenesisConfig, GrandpaConfig, SessionConfig, SessionKeys, Signature,
|
||||||
SessionKeys, Signature, SudoConfig, SystemConfig, WASM_BINARY,
|
SudoConfig, SystemConfig, WASM_BINARY,
|
||||||
};
|
};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId;
|
use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId;
|
||||||
@@ -62,10 +63,11 @@ where
|
|||||||
/// Helper function to generate authority keys.
|
/// Helper function to generate authority keys.
|
||||||
pub fn get_authority_keys_from_seed(
|
pub fn get_authority_keys_from_seed(
|
||||||
s: &str,
|
s: &str,
|
||||||
) -> (AccountId, BabeId, GrandpaId, ValidatorId, AssignmentId, AuthorityDiscoveryId) {
|
) -> (AccountId, BabeId, BeefyId, GrandpaId, ValidatorId, AssignmentId, AuthorityDiscoveryId) {
|
||||||
(
|
(
|
||||||
get_account_id_from_seed::<sr25519::Public>(s),
|
get_account_id_from_seed::<sr25519::Public>(s),
|
||||||
get_from_seed::<BabeId>(s),
|
get_from_seed::<BabeId>(s),
|
||||||
|
get_from_seed::<BeefyId>(s),
|
||||||
get_from_seed::<GrandpaId>(s),
|
get_from_seed::<GrandpaId>(s),
|
||||||
get_from_seed::<ValidatorId>(s),
|
get_from_seed::<ValidatorId>(s),
|
||||||
get_from_seed::<AssignmentId>(s),
|
get_from_seed::<AssignmentId>(s),
|
||||||
@@ -183,18 +185,20 @@ impl Alternative {
|
|||||||
|
|
||||||
fn session_keys(
|
fn session_keys(
|
||||||
babe: BabeId,
|
babe: BabeId,
|
||||||
|
beefy: BeefyId,
|
||||||
grandpa: GrandpaId,
|
grandpa: GrandpaId,
|
||||||
para_validator: ValidatorId,
|
para_validator: ValidatorId,
|
||||||
para_assignment: AssignmentId,
|
para_assignment: AssignmentId,
|
||||||
authority_discovery: AuthorityDiscoveryId,
|
authority_discovery: AuthorityDiscoveryId,
|
||||||
) -> SessionKeys {
|
) -> SessionKeys {
|
||||||
SessionKeys { babe, grandpa, para_validator, para_assignment, authority_discovery }
|
SessionKeys { babe, beefy, grandpa, para_validator, para_assignment, authority_discovery }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn testnet_genesis(
|
fn testnet_genesis(
|
||||||
initial_authorities: Vec<(
|
initial_authorities: Vec<(
|
||||||
AccountId,
|
AccountId,
|
||||||
BabeId,
|
BabeId,
|
||||||
|
BeefyId,
|
||||||
GrandpaId,
|
GrandpaId,
|
||||||
ValidatorId,
|
ValidatorId,
|
||||||
AssignmentId,
|
AssignmentId,
|
||||||
@@ -207,7 +211,6 @@ fn testnet_genesis(
|
|||||||
GenesisConfig {
|
GenesisConfig {
|
||||||
system: SystemConfig {
|
system: SystemConfig {
|
||||||
code: WASM_BINARY.expect("Rialto development WASM not available").to_vec(),
|
code: WASM_BINARY.expect("Rialto development WASM not available").to_vec(),
|
||||||
changes_trie_config: Default::default(),
|
|
||||||
},
|
},
|
||||||
balances: BalancesConfig {
|
balances: BalancesConfig {
|
||||||
balances: endowed_accounts.iter().cloned().map(|k| (k, 1 << 50)).collect(),
|
balances: endowed_accounts.iter().cloned().map(|k| (k, 1 << 50)).collect(),
|
||||||
@@ -216,8 +219,7 @@ fn testnet_genesis(
|
|||||||
authorities: Vec::new(),
|
authorities: Vec::new(),
|
||||||
epoch_config: Some(rialto_runtime::BABE_GENESIS_EPOCH_CONFIG),
|
epoch_config: Some(rialto_runtime::BABE_GENESIS_EPOCH_CONFIG),
|
||||||
},
|
},
|
||||||
bridge_rialto_poa: load_rialto_poa_bridge_config(),
|
beefy: BeefyConfig { authorities: Vec::new() },
|
||||||
bridge_kovan: load_kovan_bridge_config(),
|
|
||||||
grandpa: GrandpaConfig { authorities: Vec::new() },
|
grandpa: GrandpaConfig { authorities: Vec::new() },
|
||||||
sudo: SudoConfig { key: root_key },
|
sudo: SudoConfig { key: root_key },
|
||||||
session: SessionConfig {
|
session: SessionConfig {
|
||||||
@@ -233,6 +235,7 @@ fn testnet_genesis(
|
|||||||
x.3.clone(),
|
x.3.clone(),
|
||||||
x.4.clone(),
|
x.4.clone(),
|
||||||
x.5.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]
|
#[test]
|
||||||
fn derived_dave_account_is_as_expected() {
|
fn derived_dave_account_is_as_expected() {
|
||||||
let dave = get_account_id_from_seed::<sr25519::Public>("Dave");
|
let dave = get_account_id_from_seed::<sr25519::Public>("Dave");
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ impl SubstrateCli for Cli {
|
|||||||
/// Parse and run command line arguments
|
/// Parse and run command line arguments
|
||||||
pub fn run() -> sc_cli::Result<()> {
|
pub fn run() -> sc_cli::Result<()> {
|
||||||
let cli = Cli::from_args();
|
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,
|
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_api::ChainApiSubsystem;
|
||||||
pub use polkadot_node_core_chain_selection::ChainSelectionSubsystem;
|
pub use polkadot_node_core_chain_selection::ChainSelectionSubsystem;
|
||||||
pub use polkadot_node_core_dispute_coordinator::DisputeCoordinatorSubsystem;
|
pub use polkadot_node_core_dispute_coordinator::DisputeCoordinatorSubsystem;
|
||||||
pub use polkadot_node_core_dispute_participation::DisputeParticipationSubsystem;
|
pub use polkadot_node_core_provisioner::ProvisionerSubsystem;
|
||||||
pub use polkadot_node_core_provisioner::ProvisioningSubsystem as ProvisionerSubsystem;
|
|
||||||
pub use polkadot_node_core_runtime_api::RuntimeApiSubsystem;
|
pub use polkadot_node_core_runtime_api::RuntimeApiSubsystem;
|
||||||
pub use polkadot_statement_distribution::StatementDistribution as StatementDistributionSubsystem;
|
pub use polkadot_statement_distribution::StatementDistribution as StatementDistributionSubsystem;
|
||||||
|
|
||||||
@@ -160,7 +159,6 @@ pub fn prepared_overseer_builder<Spawner, RuntimeClient>(
|
|||||||
ApprovalVotingSubsystem,
|
ApprovalVotingSubsystem,
|
||||||
GossipSupportSubsystem<AuthorityDiscoveryService>,
|
GossipSupportSubsystem<AuthorityDiscoveryService>,
|
||||||
DisputeCoordinatorSubsystem,
|
DisputeCoordinatorSubsystem,
|
||||||
DisputeParticipationSubsystem,
|
|
||||||
DisputeDistributionSubsystem<AuthorityDiscoveryService>,
|
DisputeDistributionSubsystem<AuthorityDiscoveryService>,
|
||||||
ChainSelectionSubsystem,
|
ChainSelectionSubsystem,
|
||||||
>,
|
>,
|
||||||
@@ -249,7 +247,6 @@ where
|
|||||||
keystore.clone(),
|
keystore.clone(),
|
||||||
Metrics::register(registry)?,
|
Metrics::register(registry)?,
|
||||||
))
|
))
|
||||||
.dispute_participation(DisputeParticipationSubsystem::new())
|
|
||||||
.dispute_distribution(DisputeDistributionSubsystem::new(
|
.dispute_distribution(DisputeDistributionSubsystem::new(
|
||||||
keystore,
|
keystore,
|
||||||
dispute_req_receiver,
|
dispute_req_receiver,
|
||||||
|
|||||||
@@ -17,16 +17,11 @@
|
|||||||
//! Rialto chain node service.
|
//! Rialto chain node service.
|
||||||
//!
|
//!
|
||||||
//! The code is mostly copy of `service/src/lib.rs` file from Polkadot repository
|
//! The code is mostly copy of `service/src/lib.rs` file from Polkadot repository
|
||||||
//! without optional functions.
|
//! without optional functions, and with BEEFY added on top.
|
||||||
|
|
||||||
// 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)]
|
|
||||||
|
|
||||||
use crate::overseer::{OverseerGen, OverseerGenArgs};
|
use crate::overseer::{OverseerGen, OverseerGenArgs};
|
||||||
|
|
||||||
|
use polkadot_client::RuntimeApiCollection;
|
||||||
use polkadot_node_core_approval_voting::Config as ApprovalVotingConfig;
|
use polkadot_node_core_approval_voting::Config as ApprovalVotingConfig;
|
||||||
use polkadot_node_core_av_store::Config as AvailabilityConfig;
|
use polkadot_node_core_av_store::Config as AvailabilityConfig;
|
||||||
use polkadot_node_core_candidate_validation::Config as CandidateValidationConfig;
|
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 sc_telemetry::{Telemetry, TelemetryWorker};
|
||||||
use sp_api::{ConstructRuntimeApi, HeaderT};
|
use sp_api::{ConstructRuntimeApi, HeaderT};
|
||||||
use sp_consensus::SelectChain;
|
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 std::{sync::Arc, time::Duration};
|
||||||
use substrate_prometheus_endpoint::Registry;
|
use substrate_prometheus_endpoint::Registry;
|
||||||
|
|
||||||
@@ -115,52 +110,6 @@ type FullBabeBlockImport =
|
|||||||
type FullBabeLink = sc_consensus_babe::BabeLink<Block>;
|
type FullBabeLink = sc_consensus_babe::BabeLink<Block>;
|
||||||
type FullGrandpaLink = sc_finality_grandpa::LinkHalf<Block, FullClient, FullSelectChain>;
|
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`.
|
// If we're using prometheus, use a registry with a prefix of `polkadot`.
|
||||||
fn set_prometheus_registry(config: &mut Configuration) -> Result<(), Error> {
|
fn set_prometheus_registry(config: &mut Configuration) -> Result<(), Error> {
|
||||||
if let Some(PrometheusConfig { registry, .. }) = config.prometheus_config.as_mut() {
|
if let Some(PrometheusConfig { registry, .. }) = config.prometheus_config.as_mut() {
|
||||||
@@ -170,6 +119,8 @@ fn set_prometheus_registry(config: &mut Configuration) -> Result<(), Error> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Needed here for complex return type while `impl Trait` in type aliases is unstable.
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
pub fn new_partial(
|
pub fn new_partial(
|
||||||
config: &mut Configuration,
|
config: &mut Configuration,
|
||||||
) -> Result<
|
) -> Result<
|
||||||
@@ -184,7 +135,12 @@ pub fn new_partial(
|
|||||||
sc_rpc::DenyUnsafe,
|
sc_rpc::DenyUnsafe,
|
||||||
sc_rpc::SubscriptionTaskExecutor,
|
sc_rpc::SubscriptionTaskExecutor,
|
||||||
) -> Result<jsonrpc_core::IoHandler<sc_service::RpcMetadata>, sc_service::Error>,
|
) -> 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,
|
sc_finality_grandpa::SharedVoterState,
|
||||||
std::time::Duration,
|
std::time::Duration,
|
||||||
Option<Telemetry>,
|
Option<Telemetry>,
|
||||||
@@ -195,7 +151,7 @@ pub fn new_partial(
|
|||||||
where
|
where
|
||||||
RuntimeApi: ConstructRuntimeApi<Block, FullClient> + Send + Sync + 'static,
|
RuntimeApi: ConstructRuntimeApi<Block, FullClient> + Send + Sync + 'static,
|
||||||
<RuntimeApi as ConstructRuntimeApi<Block, FullClient>>::RuntimeApi:
|
<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,
|
ExecutorDispatch: NativeExecutionDispatch + 'static,
|
||||||
{
|
{
|
||||||
set_prometheus_registry(config)?;
|
set_prometheus_registry(config)?;
|
||||||
@@ -226,7 +182,7 @@ where
|
|||||||
let client = Arc::new(client);
|
let client = Arc::new(client);
|
||||||
|
|
||||||
let telemetry = telemetry.map(|(worker, telemetry)| {
|
let telemetry = telemetry.map(|(worker, telemetry)| {
|
||||||
task_manager.spawn_handle().spawn("telemetry", worker.run());
|
task_manager.spawn_handle().spawn("telemetry", None, worker.run());
|
||||||
telemetry
|
telemetry
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -282,7 +238,10 @@ where
|
|||||||
let shared_authority_set = grandpa_link.shared_authority_set().clone();
|
let shared_authority_set = grandpa_link.shared_authority_set().clone();
|
||||||
let shared_voter_state = sc_finality_grandpa::SharedVoterState::empty();
|
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 rpc_setup = shared_voter_state.clone();
|
||||||
|
|
||||||
let slot_duration = babe_config.slot_duration();
|
let slot_duration = babe_config.slot_duration();
|
||||||
@@ -316,14 +275,23 @@ where
|
|||||||
pool,
|
pool,
|
||||||
deny_unsafe,
|
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(
|
io.extend_with(GrandpaApi::to_delegate(GrandpaRpcHandler::new(
|
||||||
shared_authority_set.clone(),
|
shared_authority_set.clone(),
|
||||||
shared_voter_state,
|
shared_voter_state,
|
||||||
justification_stream.clone(),
|
justification_stream.clone(),
|
||||||
subscription_executor,
|
subscription_executor.clone(),
|
||||||
finality_proof_provider,
|
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)
|
Ok(io)
|
||||||
}
|
}
|
||||||
@@ -361,7 +329,7 @@ async fn active_leaves(
|
|||||||
where
|
where
|
||||||
RuntimeApi: ConstructRuntimeApi<Block, FullClient> + Send + Sync + 'static,
|
RuntimeApi: ConstructRuntimeApi<Block, FullClient> + Send + Sync + 'static,
|
||||||
<RuntimeApi as ConstructRuntimeApi<Block, FullClient>>::RuntimeApi:
|
<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,
|
ExecutorDispatch: NativeExecutionDispatch + 'static,
|
||||||
{
|
{
|
||||||
let best_block = select_chain.best_chain().await?;
|
let best_block = select_chain.best_chain().await?;
|
||||||
@@ -406,7 +374,7 @@ pub fn new_full(
|
|||||||
where
|
where
|
||||||
RuntimeApi: ConstructRuntimeApi<Block, FullClient> + Send + Sync + 'static,
|
RuntimeApi: ConstructRuntimeApi<Block, FullClient> + Send + Sync + 'static,
|
||||||
<RuntimeApi as ConstructRuntimeApi<Block, FullClient>>::RuntimeApi:
|
<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,
|
ExecutorDispatch: NativeExecutionDispatch + 'static,
|
||||||
{
|
{
|
||||||
let is_collator = false;
|
let is_collator = false;
|
||||||
@@ -442,6 +410,8 @@ where
|
|||||||
// Substrate nodes.
|
// Substrate nodes.
|
||||||
config.network.extra_sets.push(sc_finality_grandpa::grandpa_peers_set_config());
|
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};
|
use polkadot_network_bridge::{peer_sets_info, IsAuthority};
|
||||||
let is_authority = if role.is_authority() { IsAuthority::Yes } else { IsAuthority::No };
|
let is_authority = if role.is_authority() { IsAuthority::Yes } else { IsAuthority::No };
|
||||||
@@ -474,7 +444,6 @@ where
|
|||||||
transaction_pool: transaction_pool.clone(),
|
transaction_pool: transaction_pool.clone(),
|
||||||
spawn_handle: task_manager.spawn_handle(),
|
spawn_handle: task_manager.spawn_handle(),
|
||||||
import_queue,
|
import_queue,
|
||||||
on_demand: None,
|
|
||||||
block_announce_validator_builder: None,
|
block_announce_validator_builder: None,
|
||||||
warp_sync: Some(warp_sync),
|
warp_sync: Some(warp_sync),
|
||||||
})?;
|
})?;
|
||||||
@@ -533,13 +502,11 @@ where
|
|||||||
rpc_extensions_builder: Box::new(rpc_extensions_builder),
|
rpc_extensions_builder: Box::new(rpc_extensions_builder),
|
||||||
transaction_pool: transaction_pool.clone(),
|
transaction_pool: transaction_pool.clone(),
|
||||||
task_manager: &mut task_manager,
|
task_manager: &mut task_manager,
|
||||||
on_demand: None,
|
|
||||||
remote_blockchain: None,
|
|
||||||
system_rpc_tx,
|
system_rpc_tx,
|
||||||
telemetry: telemetry.as_mut(),
|
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 overseer_client = client.clone();
|
||||||
let spawner = task_manager.spawn_handle();
|
let spawner = task_manager.spawn_handle();
|
||||||
@@ -574,7 +541,9 @@ where
|
|||||||
prometheus_registry.clone(),
|
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)
|
Some(service)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@@ -619,6 +588,7 @@ where
|
|||||||
let handle = handle.clone();
|
let handle = handle.clone();
|
||||||
task_manager.spawn_essential_handle().spawn_blocking(
|
task_manager.spawn_essential_handle().spawn_blocking(
|
||||||
"overseer",
|
"overseer",
|
||||||
|
None,
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
use futures::{pin_mut, select, FutureExt};
|
use futures::{pin_mut, select, FutureExt};
|
||||||
|
|
||||||
@@ -705,7 +675,7 @@ where
|
|||||||
};
|
};
|
||||||
|
|
||||||
let babe = sc_consensus_babe::start_babe(babe_config)?;
|
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
|
// if the node isn't actively participating in consensus then it doesn't
|
||||||
@@ -713,6 +683,23 @@ where
|
|||||||
let keystore_opt =
|
let keystore_opt =
|
||||||
if role.is_authority() { Some(keystore_container.sync_keystore()) } else { None };
|
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 {
|
let config = sc_finality_grandpa::Config {
|
||||||
// FIXME substrate#1578 make this available through chainspec
|
// FIXME substrate#1578 make this available through chainspec
|
||||||
gossip_duration: Duration::from_millis(1000),
|
gossip_duration: Duration::from_millis(1000),
|
||||||
@@ -751,6 +738,7 @@ where
|
|||||||
|
|
||||||
task_manager.spawn_essential_handle().spawn_blocking(
|
task_manager.spawn_essential_handle().spawn_blocking(
|
||||||
"grandpa-voter",
|
"grandpa-voter",
|
||||||
|
None,
|
||||||
sc_finality_grandpa::run_grandpa_voter(grandpa_config)?,
|
sc_finality_grandpa::run_grandpa_voter(grandpa_config)?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,8 +17,6 @@ serde = { version = "1.0", optional = true, features = ["derive"] }
|
|||||||
|
|
||||||
# Bridge dependencies
|
# 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-header-chain = { path = "../../../primitives/header-chain", default-features = false }
|
||||||
bp-message-dispatch = { path = "../../../primitives/message-dispatch", default-features = false }
|
bp-message-dispatch = { path = "../../../primitives/message-dispatch", default-features = false }
|
||||||
bp-messages = { path = "../../../primitives/messages", 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-rialto = { path = "../../../primitives/chain-rialto", default-features = false }
|
||||||
bp-runtime = { path = "../../../primitives/runtime", default-features = false }
|
bp-runtime = { path = "../../../primitives/runtime", default-features = false }
|
||||||
bridge-runtime-common = { path = "../../runtime-common", 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-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-grandpa = { path = "../../../modules/grandpa", default-features = false }
|
||||||
pallet-bridge-messages = { path = "../../../modules/messages", default-features = false }
|
pallet-bridge-messages = { path = "../../../modules/messages", default-features = false }
|
||||||
pallet-shift-session-manager = { path = "../../../modules/shift-session-manager", default-features = false }
|
pallet-shift-session-manager = { path = "../../../modules/shift-session-manager", default-features = false }
|
||||||
|
|
||||||
# Substrate Dependencies
|
# 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-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-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 }
|
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-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-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-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-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-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-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 }
|
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]
|
[features]
|
||||||
default = ["std"]
|
default = ["std"]
|
||||||
std = [
|
std = [
|
||||||
"bp-currency-exchange/std",
|
"beefy-primitives/std",
|
||||||
"bp-eth-poa/std",
|
|
||||||
"bp-header-chain/std",
|
"bp-header-chain/std",
|
||||||
"bp-message-dispatch/std",
|
"bp-message-dispatch/std",
|
||||||
"bp-messages/std",
|
"bp-messages/std",
|
||||||
@@ -99,12 +99,14 @@ std = [
|
|||||||
"pallet-authority-discovery/std",
|
"pallet-authority-discovery/std",
|
||||||
"pallet-babe/std",
|
"pallet-babe/std",
|
||||||
"pallet-balances/std",
|
"pallet-balances/std",
|
||||||
"pallet-bridge-currency-exchange/std",
|
"pallet-beefy/std",
|
||||||
|
"pallet-beefy-mmr/std",
|
||||||
"pallet-bridge-dispatch/std",
|
"pallet-bridge-dispatch/std",
|
||||||
"pallet-bridge-eth-poa/std",
|
|
||||||
"pallet-bridge-grandpa/std",
|
"pallet-bridge-grandpa/std",
|
||||||
"pallet-bridge-messages/std",
|
"pallet-bridge-messages/std",
|
||||||
"pallet-grandpa/std",
|
"pallet-grandpa/std",
|
||||||
|
"pallet-mmr/std",
|
||||||
|
"pallet-mmr-primitives/std",
|
||||||
"pallet-shift-session-manager/std",
|
"pallet-shift-session-manager/std",
|
||||||
"pallet-sudo/std",
|
"pallet-sudo/std",
|
||||||
"pallet-timestamp/std",
|
"pallet-timestamp/std",
|
||||||
@@ -137,8 +139,6 @@ runtime-benchmarks = [
|
|||||||
"frame-support/runtime-benchmarks",
|
"frame-support/runtime-benchmarks",
|
||||||
"frame-system/runtime-benchmarks",
|
"frame-system/runtime-benchmarks",
|
||||||
"libsecp256k1",
|
"libsecp256k1",
|
||||||
"pallet-bridge-currency-exchange/runtime-benchmarks",
|
|
||||||
"pallet-bridge-eth-poa/runtime-benchmarks",
|
|
||||||
"pallet-bridge-messages/runtime-benchmarks",
|
"pallet-bridge-messages/runtime-benchmarks",
|
||||||
"sp-runtime/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",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -30,30 +30,28 @@
|
|||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs"));
|
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 millau_messages;
|
||||||
pub mod parachains;
|
pub mod parachains;
|
||||||
pub mod rialto_poa;
|
|
||||||
|
|
||||||
use crate::millau_messages::{ToMillauMessagePayload, WithMillauMessageBridge};
|
use crate::millau_messages::{ToMillauMessagePayload, WithMillauMessageBridge};
|
||||||
|
|
||||||
|
use beefy_primitives::{crypto::AuthorityId as BeefyId, mmr::MmrLeafVersion, ValidatorSet};
|
||||||
use bridge_runtime_common::messages::{
|
use bridge_runtime_common::messages::{
|
||||||
source::estimate_message_dispatch_and_delivery_fee, MessageBridge,
|
source::estimate_message_dispatch_and_delivery_fee, MessageBridge,
|
||||||
};
|
};
|
||||||
use pallet_grandpa::{
|
use pallet_grandpa::{
|
||||||
fg_primitives, AuthorityId as GrandpaId, AuthorityList as GrandpaAuthorityList,
|
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 pallet_transaction_payment::{FeeDetails, Multiplier, RuntimeDispatchInfo};
|
||||||
use sp_api::impl_runtime_apis;
|
use sp_api::impl_runtime_apis;
|
||||||
use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId;
|
use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId;
|
||||||
use sp_core::{crypto::KeyTypeId, OpaqueMetadata};
|
use sp_core::{crypto::KeyTypeId, OpaqueMetadata};
|
||||||
use sp_runtime::{
|
use sp_runtime::{
|
||||||
create_runtime_str, generic, impl_opaque_keys,
|
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},
|
transaction_validity::{TransactionSource, TransactionValidity},
|
||||||
ApplyExtrinsicResult, FixedPointNumber, MultiSignature, MultiSigner, Perquintill,
|
ApplyExtrinsicResult, FixedPointNumber, MultiSignature, MultiSigner, Perquintill,
|
||||||
};
|
};
|
||||||
@@ -72,8 +70,6 @@ pub use frame_support::{
|
|||||||
|
|
||||||
pub use frame_system::Call as SystemCall;
|
pub use frame_system::Call as SystemCall;
|
||||||
pub use pallet_balances::Call as BalancesCall;
|
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_grandpa::Call as BridgeGrandpaMillauCall;
|
||||||
pub use pallet_bridge_messages::Call as MessagesCall;
|
pub use pallet_bridge_messages::Call as MessagesCall;
|
||||||
pub use pallet_sudo::Call as SudoCall;
|
pub use pallet_sudo::Call as SudoCall;
|
||||||
@@ -109,9 +105,6 @@ pub type Hash = bp_rialto::Hash;
|
|||||||
/// Hashing algorithm used by the chain.
|
/// Hashing algorithm used by the chain.
|
||||||
pub type Hashing = bp_rialto::Hasher;
|
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
|
/// 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
|
/// 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
|
/// 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 struct SessionKeys {
|
||||||
pub babe: Babe,
|
pub babe: Babe,
|
||||||
pub grandpa: Grandpa,
|
pub grandpa: Grandpa,
|
||||||
|
pub beefy: Beefy,
|
||||||
pub para_validator: Initializer,
|
pub para_validator: Initializer,
|
||||||
pub para_assignment: SessionInfo,
|
pub para_assignment: SessionInfo,
|
||||||
pub authority_discovery: AuthorityDiscovery,
|
pub authority_discovery: AuthorityDiscovery,
|
||||||
@@ -253,46 +247,8 @@ impl pallet_babe::Config for Runtime {
|
|||||||
type WeightInfo = ();
|
type WeightInfo = ();
|
||||||
}
|
}
|
||||||
|
|
||||||
type RialtoPoA = pallet_bridge_eth_poa::Instance1;
|
impl pallet_beefy::Config for Runtime {
|
||||||
impl pallet_bridge_eth_poa::Config<RialtoPoA> for Runtime {
|
type BeefyId = BeefyId;
|
||||||
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_bridge_dispatch::Config for Runtime {
|
impl pallet_bridge_dispatch::Config for Runtime {
|
||||||
@@ -307,68 +263,6 @@ impl pallet_bridge_dispatch::Config for Runtime {
|
|||||||
type AccountIdConverter = bp_rialto::AccountIdConverter;
|
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 {
|
impl pallet_grandpa::Config for Runtime {
|
||||||
type Event = Event;
|
type Event = Event;
|
||||||
type Call = Call;
|
type Call = Call;
|
||||||
@@ -386,6 +280,38 @@ impl pallet_grandpa::Config for Runtime {
|
|||||||
type MaxAuthorities = MaxAuthorities;
|
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! {
|
parameter_types! {
|
||||||
pub const MinimumPeriod: u64 = bp_rialto::SLOT_DURATION / 2;
|
pub const MinimumPeriod: u64 = bp_rialto::SLOT_DURATION / 2;
|
||||||
}
|
}
|
||||||
@@ -579,11 +505,10 @@ construct_runtime!(
|
|||||||
Grandpa: pallet_grandpa::{Pallet, Call, Storage, Config, Event},
|
Grandpa: pallet_grandpa::{Pallet, Call, Storage, Config, Event},
|
||||||
ShiftSessionManager: pallet_shift_session_manager::{Pallet},
|
ShiftSessionManager: pallet_shift_session_manager::{Pallet},
|
||||||
|
|
||||||
// Eth-PoA chains bridge modules.
|
// BEEFY Bridges support.
|
||||||
BridgeRialtoPoa: pallet_bridge_eth_poa::<Instance1>::{Pallet, Call, Config, Storage, ValidateUnsigned},
|
Beefy: pallet_beefy::{Pallet, Storage, Config<T>},
|
||||||
BridgeKovan: pallet_bridge_eth_poa::<Instance2>::{Pallet, Call, Config, Storage, ValidateUnsigned},
|
Mmr: pallet_mmr::{Pallet, Storage},
|
||||||
BridgeRialtoCurrencyExchange: pallet_bridge_currency_exchange::<Instance1>::{Pallet, Call},
|
MmrLeaf: pallet_beefy_mmr::{Pallet, Storage},
|
||||||
BridgeKovanCurrencyExchange: pallet_bridge_currency_exchange::<Instance2>::{Pallet, Call},
|
|
||||||
|
|
||||||
// Millau bridge modules.
|
// Millau bridge modules.
|
||||||
BridgeMillauGrandpa: pallet_bridge_grandpa::{Pallet, Call, Storage},
|
BridgeMillauGrandpa: pallet_bridge_grandpa::{Pallet, Call, Storage},
|
||||||
@@ -694,43 +619,42 @@ impl_runtime_apis! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl bp_eth_poa::RialtoPoAHeaderApi<Block> for Runtime {
|
impl beefy_primitives::BeefyApi<Block> for Runtime {
|
||||||
fn best_block() -> (u64, bp_eth_poa::H256) {
|
fn validator_set() -> ValidatorSet<BeefyId> {
|
||||||
let best_block = BridgeRialtoPoa::best_block();
|
Beefy::validator_set()
|
||||||
(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 bp_eth_poa::KovanHeaderApi<Block> for Runtime {
|
impl pallet_mmr_primitives::MmrApi<Block, Hash> for Runtime {
|
||||||
fn best_block() -> (u64, bp_eth_poa::H256) {
|
fn generate_proof(leaf_index: u64)
|
||||||
let best_block = BridgeKovan::best_block();
|
-> Result<(EncodableOpaqueLeaf, MmrProof<Hash>), MmrError>
|
||||||
(best_block.number, best_block.hash)
|
{
|
||||||
|
Mmr::generate_proof(leaf_index)
|
||||||
|
.map(|(leaf, proof)| (EncodableOpaqueLeaf::from_leaf(&leaf), proof))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finalized_block() -> (u64, bp_eth_poa::H256) {
|
fn verify_proof(leaf: EncodableOpaqueLeaf, proof: MmrProof<Hash>)
|
||||||
let finalized_block = BridgeKovan::finalized_block();
|
-> Result<(), MmrError>
|
||||||
(finalized_block.number, finalized_block.hash)
|
{
|
||||||
|
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 {
|
fn verify_proof_stateless(
|
||||||
BridgeKovan::is_import_requires_receipts(header)
|
root: Hash,
|
||||||
}
|
leaf: EncodableOpaqueLeaf,
|
||||||
|
proof: MmrProof<Hash>
|
||||||
fn is_known_block(hash: bp_eth_poa::H256) -> bool {
|
) -> Result<(), MmrError> {
|
||||||
BridgeKovan::is_known_block(hash)
|
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 {
|
impl sp_transaction_pool::runtime_api::TaggedTransactionQueue<Block> for Runtime {
|
||||||
fn validate_transaction(
|
fn validate_transaction(
|
||||||
source: TransactionSource,
|
source: TransactionSource,
|
||||||
@@ -846,6 +758,13 @@ impl_runtime_apis! {
|
|||||||
polkadot_runtime_parachains::runtime_api_impl::v1::persisted_validation_data::<Runtime>(para_id, assumption)
|
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(
|
fn check_validation_outputs(
|
||||||
para_id: polkadot_primitives::v1::Id,
|
para_id: polkadot_primitives::v1::Id,
|
||||||
outputs: polkadot_primitives::v1::CandidateCommitments,
|
outputs: polkadot_primitives::v1::CandidateCommitments,
|
||||||
@@ -1029,17 +948,10 @@ impl_runtime_apis! {
|
|||||||
use frame_benchmarking::{list_benchmark, Benchmarking, BenchmarkList};
|
use frame_benchmarking::{list_benchmark, Benchmarking, BenchmarkList};
|
||||||
use frame_support::traits::StorageInfoTrait;
|
use frame_support::traits::StorageInfoTrait;
|
||||||
|
|
||||||
use pallet_bridge_currency_exchange::benchmarking::Pallet as BridgeCurrencyExchangeBench;
|
|
||||||
use pallet_bridge_messages::benchmarking::Pallet as MessagesBench;
|
use pallet_bridge_messages::benchmarking::Pallet as MessagesBench;
|
||||||
|
|
||||||
let mut list = Vec::<BenchmarkList>::new();
|
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_messages, MessagesBench::<Runtime, WithMillauMessagesInstance>);
|
||||||
list_benchmark!(list, extra, pallet_bridge_grandpa, BridgeMillauGrandpa);
|
list_benchmark!(list, extra, pallet_bridge_grandpa, BridgeMillauGrandpa);
|
||||||
|
|
||||||
@@ -1073,46 +985,6 @@ impl_runtime_apis! {
|
|||||||
let mut batches = Vec::<BenchmarkBatch>::new();
|
let mut batches = Vec::<BenchmarkBatch>::new();
|
||||||
let params = (&config, &whitelist);
|
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 crate::millau_messages::{ToMillauMessagePayload, WithMillauMessageBridge};
|
||||||
use bp_runtime::messages::DispatchFeePayment;
|
use bp_runtime::messages::DispatchFeePayment;
|
||||||
use bridge_runtime_common::messages;
|
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!(
|
add_benchmark!(
|
||||||
params,
|
params,
|
||||||
batches,
|
batches,
|
||||||
@@ -1327,48 +1192,8 @@ where
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use bp_currency_exchange::DepositInto;
|
|
||||||
use bridge_runtime_common::messages;
|
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]
|
#[test]
|
||||||
fn ensure_rialto_message_lane_weights_are_correct() {
|
fn ensure_rialto_message_lane_weights_are_correct() {
|
||||||
type Weights = pallet_bridge_messages::weights::RialtoWeight<Runtime>;
|
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]
|
#[test]
|
||||||
fn call_size() {
|
fn call_size() {
|
||||||
const MAX_CALL_SIZE: usize = 230; // value from polkadot-runtime tests
|
const DOT_MAX_CALL_SZ: usize = 230;
|
||||||
assert!(core::mem::size_of::<Call>() <= MAX_CALL_SIZE);
|
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;
|
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 {}
|
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",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -24,7 +24,7 @@ USER user
|
|||||||
|
|
||||||
WORKDIR /home/user
|
WORKDIR /home/user
|
||||||
|
|
||||||
ARG PROJECT=ethereum-poa-relay
|
ARG PROJECT=substrate-relay
|
||||||
|
|
||||||
COPY --chown=user:user ./${PROJECT} ./
|
COPY --chown=user:user ./${PROJECT} ./
|
||||||
COPY --chown=user:user ./bridge-entrypoint.sh ./
|
COPY --chown=user:user ./bridge-entrypoint.sh ./
|
||||||
|
|||||||
@@ -44,16 +44,16 @@ the monitoring Compose file is _not_ optional, and must be included for bridge d
|
|||||||
|
|
||||||
### Running and Updating Deployments
|
### Running and Updating Deployments
|
||||||
We currently support two bridge deployments
|
We currently support two bridge deployments
|
||||||
1. Ethereum PoA to Rialto Substrate
|
1. Rialto Substrate to Millau Substrate
|
||||||
2. Rialto Substrate to Millau Substrate
|
2. Westend Substrate to Millau Substrate
|
||||||
|
|
||||||
These bridges can be deployed using our [`./run.sh`](./run.sh) script.
|
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
|
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
|
```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
|
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.
|
You can also bring down a deployment using the script with the `stop` argument.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./run.sh poa-rialto stop
|
./run.sh rialto-millau stop
|
||||||
```
|
```
|
||||||
|
|
||||||
### Adding Deployments
|
### Adding Deployments
|
||||||
@@ -80,7 +80,6 @@ not strictly required.
|
|||||||
## General Notes
|
## General Notes
|
||||||
|
|
||||||
Rialto authorities are named: `Alice`, `Bob`, `Charlie`, `Dave`, `Eve`.
|
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`.
|
Millau authorities are named: `Alice`, `Bob`, `Charlie`, `Dave`, `Eve`.
|
||||||
|
|
||||||
Both authorities and following accounts have enough funds (for test purposes) on corresponding Substrate chains:
|
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`.
|
- on Millau: `Ferdie`, `George`, `Harry`.
|
||||||
|
|
||||||
Names of accounts on Substrate (Rialto and Millau) chains may be prefixed with `//` and used as
|
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
|
seeds for the `sr25519` keys. This seed may also be used in the signer argument in Substrate relays.
|
||||||
and PoA relays. Example:
|
Example:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./substrate-relay relay-headers rialto-to-millau \
|
./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
|
Some accounts are used by bridge components. Using these accounts to sign other transactions
|
||||||
is not recommended, because this may lead to nonces conflict.
|
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:
|
Following accounts are used when `rialto-millau` bridge is running:
|
||||||
|
|
||||||
- Millau's `Charlie` signs complex headers+messages relay transactions on Millau chain;
|
- Millau's `Charlie` signs complex headers+messages relay transactions on Millau chain;
|
||||||
@@ -133,10 +125,10 @@ Following accounts are used when `westend-millau` bridge is running:
|
|||||||
When the network is running you can query logs from individual nodes using:
|
When the network is running you can query logs from individual nodes using:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker logs rialto_poa-node-bertha_1 -f
|
docker logs rialto_millau-node-charlie_1 -f
|
||||||
```
|
```
|
||||||
|
|
||||||
To kill all left over containers and start the network from scratch next time:
|
To kill all leftover containers and start the network from scratch next time:
|
||||||
```bash
|
```bash
|
||||||
docker ps -a --format "{{.ID}}" | xargs docker rm # This removes all containers!
|
docker ps -a --format "{{.ID}}" | xargs docker rm # This removes all containers!
|
||||||
```
|
```
|
||||||
@@ -190,7 +182,6 @@ Here are the arguments currently supported:
|
|||||||
- `PROJECT`: Project to build withing bridges repo. Can be one of:
|
- `PROJECT`: Project to build withing bridges repo. Can be one of:
|
||||||
- `rialto-bridge-node`
|
- `rialto-bridge-node`
|
||||||
- `millau-bridge-node`
|
- `millau-bridge-node`
|
||||||
- `ethereum-poa-relay`
|
|
||||||
- `substrate-relay`
|
- `substrate-relay`
|
||||||
|
|
||||||
### GitHub Actions
|
### 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;"]
|
|
||||||
-474
@@ -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
|
|
||||||
}
|
|
||||||
-694
@@ -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
|
|
||||||
}
|
|
||||||
-694
@@ -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: ð-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
|
|
||||||
-99
@@ -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
|
|
||||||
-15
@@ -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
|
|
||||||
-26
@@ -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
|
|
||||||
-16
@@ -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
@@ -1 +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
|
|
||||||
+3
-3
@@ -1441,7 +1441,7 @@
|
|||||||
"pluginVersion": "7.1.3",
|
"pluginVersion": "7.1.3",
|
||||||
"targets": [
|
"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,
|
"instant": true,
|
||||||
"interval": "",
|
"interval": "",
|
||||||
"legendFormat": "1 CPU = 100",
|
"legendFormat": "1 CPU = 100",
|
||||||
@@ -1499,7 +1499,7 @@
|
|||||||
"steppedLine": false,
|
"steppedLine": false,
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"expr": "Millau_to_Rialto_MessageLane_00000000_system_average_load",
|
"expr": "system_average_load{instance='relay-millau-rialto:9616'}",
|
||||||
"interval": "",
|
"interval": "",
|
||||||
"legendFormat": "Average system load in last {{over}}",
|
"legendFormat": "Average system load in last {{over}}",
|
||||||
"refId": "A"
|
"refId": "A"
|
||||||
@@ -1592,7 +1592,7 @@
|
|||||||
"steppedLine": false,
|
"steppedLine": false,
|
||||||
"targets": [
|
"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": "",
|
"interval": "",
|
||||||
"legendFormat": "Process memory, MB",
|
"legendFormat": "Process memory, MB",
|
||||||
"refId": "A"
|
"refId": "A"
|
||||||
|
|||||||
+3
-3
@@ -1190,7 +1190,7 @@
|
|||||||
"pluginVersion": "7.1.3",
|
"pluginVersion": "7.1.3",
|
||||||
"targets": [
|
"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,
|
"instant": true,
|
||||||
"interval": "",
|
"interval": "",
|
||||||
"legendFormat": "1 CPU = 100",
|
"legendFormat": "1 CPU = 100",
|
||||||
@@ -1248,7 +1248,7 @@
|
|||||||
"steppedLine": false,
|
"steppedLine": false,
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"expr": "Rialto_to_Millau_MessageLane_00000000_system_average_load",
|
"expr": "system_average_load{instance='relay-millau-rialto:9616'}",
|
||||||
"interval": "",
|
"interval": "",
|
||||||
"legendFormat": "Average system load in last {{over}}",
|
"legendFormat": "Average system load in last {{over}}",
|
||||||
"refId": "A"
|
"refId": "A"
|
||||||
@@ -1341,7 +1341,7 @@
|
|||||||
"steppedLine": false,
|
"steppedLine": false,
|
||||||
"targets": [
|
"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": "",
|
"interval": "",
|
||||||
"legendFormat": "Process memory, MB",
|
"legendFormat": "Process memory, MB",
|
||||||
"refId": "A"
|
"refId": "A"
|
||||||
|
|||||||
+8
-8
@@ -65,7 +65,7 @@
|
|||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"exemplar": true,
|
"exemplar": true,
|
||||||
"expr": "Rialto_to_Millau_MessageLane_00000000_rialto_storage_proof_overhead",
|
"expr": "rialto_storage_proof_overhead{instance='relay-millau-rialto:9616'}",
|
||||||
"interval": "",
|
"interval": "",
|
||||||
"legendFormat": "Actual overhead",
|
"legendFormat": "Actual overhead",
|
||||||
"refId": "A"
|
"refId": "A"
|
||||||
@@ -169,14 +169,14 @@
|
|||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"exemplar": true,
|
"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": "",
|
"interval": "",
|
||||||
"legendFormat": "Outside of runtime (actually Polkadot -> Kusama)",
|
"legendFormat": "Outside of runtime (actually Polkadot -> Kusama)",
|
||||||
"refId": "A"
|
"refId": "A"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"exemplar": true,
|
"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,
|
"hide": false,
|
||||||
"interval": "",
|
"interval": "",
|
||||||
"legendFormat": "At runtime",
|
"legendFormat": "At runtime",
|
||||||
@@ -187,7 +187,7 @@
|
|||||||
"timeFrom": null,
|
"timeFrom": null,
|
||||||
"timeRegions": [],
|
"timeRegions": [],
|
||||||
"timeShift": null,
|
"timeShift": null,
|
||||||
"title": "Rialto: Millau -> Rialto conversion rate",
|
"title": "Millau: Rialto -> Millau conversion rate",
|
||||||
"tooltip": {
|
"tooltip": {
|
||||||
"shared": true,
|
"shared": true,
|
||||||
"sort": 0,
|
"sort": 0,
|
||||||
@@ -273,7 +273,7 @@
|
|||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"exemplar": true,
|
"exemplar": true,
|
||||||
"expr": "Millau_to_Rialto_MessageLane_00000000_millau_storage_proof_overhead",
|
"expr": "millau_storage_proof_overhead{instance='relay-millau-rialto:9616'}",
|
||||||
"interval": "",
|
"interval": "",
|
||||||
"legendFormat": "Actual overhead",
|
"legendFormat": "Actual overhead",
|
||||||
"refId": "A"
|
"refId": "A"
|
||||||
@@ -377,14 +377,14 @@
|
|||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"exemplar": true,
|
"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": "",
|
"interval": "",
|
||||||
"legendFormat": "Outside of runtime (actually Kusama -> Polkadot)",
|
"legendFormat": "Outside of runtime (actually Kusama -> Polkadot)",
|
||||||
"refId": "A"
|
"refId": "A"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"exemplar": true,
|
"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,
|
"hide": false,
|
||||||
"interval": "",
|
"interval": "",
|
||||||
"legendFormat": "At runtime",
|
"legendFormat": "At runtime",
|
||||||
@@ -395,7 +395,7 @@
|
|||||||
"timeFrom": null,
|
"timeFrom": null,
|
||||||
"timeRegions": [],
|
"timeRegions": [],
|
||||||
"timeShift": null,
|
"timeShift": null,
|
||||||
"title": "Millau: Rialto -> Millau conversion rate",
|
"title": "Rialto: Millau -> Rialto conversion rate",
|
||||||
"tooltip": {
|
"tooltip": {
|
||||||
"shared": true,
|
"shared": true,
|
||||||
"sort": 0,
|
"sort": 0,
|
||||||
|
|||||||
+3
-3
@@ -401,7 +401,7 @@
|
|||||||
"steppedLine": false,
|
"steppedLine": false,
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"expr": "Westend_to_Millau_Sync_system_average_load",
|
"expr": "system_average_load{instance='relay-headers-westend-to-millau:9616'}",
|
||||||
"interval": "",
|
"interval": "",
|
||||||
"legendFormat": "Average system load in last {{over}}",
|
"legendFormat": "Average system load in last {{over}}",
|
||||||
"refId": "A"
|
"refId": "A"
|
||||||
@@ -500,7 +500,7 @@
|
|||||||
"pluginVersion": "7.1.3",
|
"pluginVersion": "7.1.3",
|
||||||
"targets": [
|
"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,
|
"instant": true,
|
||||||
"interval": "",
|
"interval": "",
|
||||||
"legendFormat": "1 CPU = 100",
|
"legendFormat": "1 CPU = 100",
|
||||||
@@ -615,7 +615,7 @@
|
|||||||
"steppedLine": false,
|
"steppedLine": false,
|
||||||
"targets": [
|
"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": "",
|
"interval": "",
|
||||||
"legendFormat": "Process memory, MB",
|
"legendFormat": "Process memory, MB",
|
||||||
"refId": "A"
|
"refId": "A"
|
||||||
|
|||||||
@@ -1 +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 @@
|
|||||||
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-rpc-external
|
||||||
- --unsafe-ws-external
|
- --unsafe-ws-external
|
||||||
environment:
|
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:
|
ports:
|
||||||
- "19933:9933"
|
- "19933:9933"
|
||||||
- "19944:9944"
|
- "19944:9944"
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ services:
|
|||||||
- --unsafe-rpc-external
|
- --unsafe-rpc-external
|
||||||
- --unsafe-ws-external
|
- --unsafe-ws-external
|
||||||
environment:
|
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:
|
ports:
|
||||||
- "9933:9933"
|
- "9933:9933"
|
||||||
- "9944:9944"
|
- "9944:9944"
|
||||||
|
|||||||
@@ -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.
|
# 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`
|
# To update a deployment to use the latest images available from the Docker Hub add the `update`
|
||||||
# argument after the bridge name.
|
# argument after the bridge name.
|
||||||
@@ -30,7 +30,6 @@ function show_help () {
|
|||||||
echo Error: $1
|
echo Error: $1
|
||||||
echo " "
|
echo " "
|
||||||
echo "Usage:"
|
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 rialto-millau [stop|update] Run Rialto <> Millau Networks & Bridge"
|
||||||
echo " ./run.sh westend-millau [stop|update] Run Westend -> Millau Networks & Bridge"
|
echo " ./run.sh westend-millau [stop|update] Run Westend -> Millau Networks & Bridge"
|
||||||
echo " "
|
echo " "
|
||||||
@@ -39,13 +38,12 @@ function show_help () {
|
|||||||
echo " --no-ui Disable UI"
|
echo " --no-ui Disable UI"
|
||||||
echo " "
|
echo " "
|
||||||
echo "You can start multiple bridges at once by passing several bridge names:"
|
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
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
RIALTO=' -f ./networks/rialto.yml -f ./networks/rialto-parachain.yml'
|
RIALTO=' -f ./networks/rialto.yml -f ./networks/rialto-parachain.yml'
|
||||||
MILLAU=' -f ./networks/millau.yml'
|
MILLAU=' -f ./networks/millau.yml'
|
||||||
ETH_POA=' -f ./networks/eth-poa.yml'
|
|
||||||
MONITORING=' -f ./monitoring/docker-compose.yml'
|
MONITORING=' -f ./monitoring/docker-compose.yml'
|
||||||
UI=' -f ./ui/docker-compose.yml'
|
UI=' -f ./ui/docker-compose.yml'
|
||||||
|
|
||||||
@@ -65,14 +63,6 @@ do
|
|||||||
shift
|
shift
|
||||||
continue
|
continue
|
||||||
;;
|
;;
|
||||||
poa-rialto)
|
|
||||||
BRIDGES+=($i)
|
|
||||||
NETWORKS+=${RIALTO}
|
|
||||||
RIALTO=''
|
|
||||||
NETWORKS+=${ETH_POA}
|
|
||||||
ETH_POA=''
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
rialto-millau)
|
rialto-millau)
|
||||||
BRIDGES+=($i)
|
BRIDGES+=($i)
|
||||||
NETWORKS+=${RIALTO}
|
NETWORKS+=${RIALTO}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ services:
|
|||||||
VIRTUAL_PORT: 80
|
VIRTUAL_PORT: 80
|
||||||
LETSENCRYPT_HOST: ui.brucke.link
|
LETSENCRYPT_HOST: ui.brucke.link
|
||||||
LETSENCRYPT_EMAIL: admin@parity.io
|
LETSENCRYPT_EMAIL: admin@parity.io
|
||||||
CHAIN_1_SUBSTRATE_PROVIDER: ws://localhost:9944
|
CHAIN_1_SUBSTRATE_PROVIDER: ${UI_CHAIN_1:-ws://localhost:9944}
|
||||||
CHAIN_2_SUBSTRATE_PROVIDER: ws://localhost:19944
|
CHAIN_2_SUBSTRATE_PROVIDER: ${UI_CHAIN_2:-ws://localhost:19944}
|
||||||
ports:
|
ports:
|
||||||
- "8080:80"
|
- "8080:80"
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
# Bridge Architecture Diagrams
|
|
||||||
|
|
||||||
## Bridge Relay
|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
## Runtime Modules
|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
## Usage
|
|
||||||

|
|
||||||

|
|
||||||
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 |
@@ -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,
|
|
||||||
¤t_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(¤t_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(¤t_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(¤t_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(¤t_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(),],
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -239,7 +239,7 @@ pub mod pallet {
|
|||||||
operational: bool,
|
operational: bool,
|
||||||
) -> DispatchResultWithPostInfo {
|
) -> DispatchResultWithPostInfo {
|
||||||
ensure_owner_or_root::<T, I>(origin)?;
|
ensure_owner_or_root::<T, I>(origin)?;
|
||||||
<IsHalted<T, I>>::put(operational);
|
<IsHalted<T, I>>::put(!operational);
|
||||||
|
|
||||||
if operational {
|
if operational {
|
||||||
log::info!(target: "runtime::bridge-grandpa", "Resuming pallet operations.");
|
log::info!(target: "runtime::bridge-grandpa", "Resuming pallet operations.");
|
||||||
@@ -798,9 +798,13 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn pallet_rejects_transactions_if_halted() {
|
fn pallet_rejects_transactions_if_halted() {
|
||||||
run_test(|| {
|
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_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-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 }
|
sp-trie = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
hex-literal = "0.3"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["std"]
|
default = ["std"]
|
||||||
std = [
|
std = [
|
||||||
|
|||||||
@@ -256,10 +256,22 @@ pub fn storage_map_final_key_identity(
|
|||||||
///
|
///
|
||||||
/// Copied from `frame_support::parameter_types` macro
|
/// Copied from `frame_support::parameter_types` macro
|
||||||
pub fn storage_parameter_key(parameter_name: &str) -> StorageKey {
|
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.push(b':');
|
||||||
buffer.extend_from_slice(parameter_name.as_bytes());
|
buffer.extend_from_slice(parameter_name.as_bytes());
|
||||||
buffer.push(b':');
|
buffer.push(b':');
|
||||||
buffer.push(0);
|
|
||||||
StorageKey(sp_io::hashing::twox_128(&buffer).to_vec())
|
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: ð-host
|
|
||||||
long: eth-host
|
|
||||||
value_name: ETH_HOST
|
|
||||||
help: Connect to Ethereum node websocket server at given host.
|
|
||||||
takes_value: true
|
|
||||||
- eth-port: ð-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: ð-chain-id
|
|
||||||
long: eth-chain-id
|
|
||||||
value_name: ETH_CHAIN_ID
|
|
||||||
help: Chain ID to use for signing.
|
|
||||||
- eth-signer: ð-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
Reference in New Issue
Block a user