Compare commits

...

45 Commits

Author SHA1 Message Date
cc7721e389
optimize storage layout for TapestryStrands and fix OCW deadlock
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-11-01 21:06:31 +03:00
5622336d3f
move back to the weav prefix
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-11-01 18:59:48 +03:00
07cf35d08c
make flag correct for fix-weaver-keystore
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-11-01 18:05:34 +03:00
8dfc37e97e
fix migration struct in the runtime
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-11-01 15:43:41 +03:00
d973b096d9
purge attestation during on_initialize
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-11-01 15:30:42 +03:00
4842fc5108
update keytype ID prefix to 'wevr' for validator consistency
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-11-01 14:40:39 +03:00
d43c616749
resolve global dos vulnerability by isolating attested blocks per network
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-11-01 14:37:02 +03:00
52a1aa0fab
avoid clone during local keys extraction
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-10-31 16:45:14 +03:00
c25c3edd30
remove dead RPC endpoint from tester and bump runtime version
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-10-30 19:59:32 +03:00
2ade4c9637
pace RPC requests, extend storage lock, and improve logs
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-10-30 19:45:04 +03:00
014ed7d5dc
resolve infinite loop in phase switching and block drift
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-10-30 18:29:38 +03:00
a7d3a3fee8
update RPC scripts to support weaver
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-10-30 14:56:23 +03:00
844fc81680
update default weights and runtime weights
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-10-30 11:24:46 +03:00
8e44a7f5d2
Merge branch 'pallet-weaver' into pallet-combined
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-10-29 14:25:50 +03:00
5380ac0b36
Merge branch 'pallet-networks' into pallet-combined
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-10-29 14:24:57 +03:00
9610423617
Merge branch 'pallet-helpers' into pallet-combined
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-10-29 14:22:36 +03:00
d0db73170d
extend unsigned validation to prevent transaction replay
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-10-27 19:15:22 +03:00
d18b8ba11f
refactor package storage to use classic bitmap and dense data blob
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-10-26 21:02:20 +03:00
c22925d758
new governance functions added into GovernanceAction enum
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-10-25 19:22:21 +03:00
6df2088756
add setBondDepository to EVM governance requests
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-10-21 19:50:38 +03:00
6ae8fd9ed4
extend loom state to track last extracted median block
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-10-21 19:32:57 +03:00
ba575d0ab8
secure unsigned validation with help of sliding window
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-10-21 18:46:47 +03:00
ab7f978ccc
expose exodus-pallet API in casper runtime
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-10-21 17:27:12 +03:00
66ca7545ce
merge EVM chains by type into a single ExodusRequest during Round 7
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-10-21 17:25:35 +03:00
bdbf7bd20f
fix for https://ghostforum.org/t/high-lack-of-key-epoch-invalidation-across-dkg-rotations-grants-indefinite-signing-power-to-retired-validator-coalitions-missing-chainid-binding-in-gatekeeper-rotate-amplifies-cross-chain-impact/125 and some other small fixes
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-10-15 21:02:02 +03:00
f72387c9cb
fix for https://ghostforum.org/t/critical-unlimited-token-minting-via-weaver-session-replay/100
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-10-10 18:17:08 +03:00
d940ede8ac
fix for https://ghostforum.org/t/weaver-networkattestations-vec-never-grows-validator-set-growth-locks-out-new-authorities-and-halts-the-bridge/163
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-10-10 18:01:42 +03:00
653bf08486
fix for https://ghostforum.org/t/h16-dkg-round-1-rejects-all-honest-packages-unless-exactly-threshold-n-1-registered-round-0-deterministic-bridge-halt-for-n-7/123
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-10-10 17:16:31 +03:00
1dc61fb17d
fix GJKR arbitration, avoid invalid kicks, and synchronize round 5 output
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-10-10 15:48:50 +03:00
d488bf9a43
fix for https://ghostforum.org/t/pallets-exodus-round-5-to-6-bitmap-intersection-desyncs-the-proof-gate-for-dense-validator-sets-dkg-restart-loop-on-ordinary-dropouts/168
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-10-09 19:34:10 +03:00
74d74f4037
fix for https://ghostforum.org/t/cross-generation-dkg-package-replay-signed-payload-does-not-bind-dkg-index/105
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-10-09 19:07:35 +03:00
7ad0159517
fix for https://ghostforum.org/t/dkg-finalization-gated-on-old-set-orphan-rotation-requests-accumulate-without-expiry/132
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-10-09 17:55:22 +03:00
c884ec7b77
fix fir https://ghostforum.org/t/malicious-authority-can-permanently-kill-an-exodus-signing-session-via-an-undecodable-nonce-commitment/103
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-10-09 16:41:56 +03:00
e3a81e3902
https://ghostforum.org/t/bounty-of-100-encodes-as-0-in-evm-calldata-as-u32-wrap/133
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-10-09 15:23:20 +03:00
d7ac7885b6
Merge branch 'pallet-exodus' into pallet-weaver
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-10-09 15:07:43 +03:00
f3e7122d43
remove Header struct and implement stateless merkle verification
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-10-09 14:56:43 +03:00
84b6dfad9f
fix for https://ghostforum.org/t/critical-utxo-multi-mint-via-non-canonical-varint-re-encoding/111
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-10-09 13:23:23 +03:00
b6759bac6f
optimize merkle tree prefixed computations
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-10-04 19:47:21 +03:00
5f39a7d9b1
optimize pallet hashing to eliminate dynamic memory allocations
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-10-02 15:35:38 +03:00
8abd114a5b
more secure and optimized Round 5 metadata hashing pipeline
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-10-01 21:41:27 +03:00
6b0033425c
implement more secure entropy hedging for offchain RNG
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-10-01 21:20:17 +03:00
f77ca05d3f
avoid partial writes during register_incoming/register_outgoing
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-09-26 16:20:46 +03:00
339ea52864
avoid memory allocation during and_provides
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-09-26 16:04:57 +03:00
d8bfe2a2ac
correct replay check logic and secure unsigned thread validation
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-09-26 15:13:07 +03:00
77bf4419e0
prevent exploit via compute_arguments_hash bypass
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-09-26 14:30:22 +03:00
58 changed files with 6001 additions and 3662 deletions

20
Cargo.lock generated
View File

@ -1195,7 +1195,7 @@ dependencies = [
[[package]]
name = "casper-runtime"
version = "4.0.14"
version = "4.0.20"
dependencies = [
"casper-runtime-constants",
"frame-benchmarking",
@ -1209,6 +1209,7 @@ dependencies = [
"frame-try-runtime",
"ghost-core-primitives",
"ghost-exodus",
"ghost-exodus-runtime-api",
"ghost-governance",
"ghost-helpers",
"ghost-networks",
@ -3684,7 +3685,7 @@ dependencies = [
[[package]]
name = "ghost-exodus"
version = "0.0.17"
version = "0.1.7"
dependencies = [
"chacha20poly1305",
"frame-benchmarking",
@ -3694,6 +3695,7 @@ dependencies = [
"frost-core",
"frost-ed25519",
"frost-secp256k1-tr",
"ghost-exodus-runtime-api",
"ghost-helpers",
"ghost-networks",
"ghost-traits",
@ -3725,15 +3727,17 @@ dependencies = [
[[package]]
name = "ghost-exodus-runtime-api"
version = "0.0.0"
version = "0.0.2"
dependencies = [
"ghost-helpers",
"parity-scale-codec",
"sp-api",
"sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)",
]
[[package]]
name = "ghost-governance"
version = "0.3.10"
version = "0.3.11"
dependencies = [
"frame-benchmarking",
"frame-support",
@ -3758,7 +3762,7 @@ dependencies = [
[[package]]
name = "ghost-helpers"
version = "0.0.11"
version = "0.0.16"
dependencies = [
"frame-support",
"ghost-traits",
@ -3840,7 +3844,7 @@ dependencies = [
[[package]]
name = "ghost-networks"
version = "0.2.10"
version = "0.2.12"
dependencies = [
"frame-benchmarking",
"frame-support",
@ -4086,7 +4090,7 @@ dependencies = [
[[package]]
name = "ghost-traits"
version = "0.4.6"
version = "0.4.10"
dependencies = [
"frame-support",
"frost-core",
@ -4110,7 +4114,7 @@ dependencies = [
[[package]]
name = "ghost-weaver"
version = "0.0.14"
version = "0.0.27"
dependencies = [
"frame-benchmarking",
"frame-support",

View File

@ -239,6 +239,7 @@ ghost-networks = { path = "pallets/networks", default-features = false }
ghost-governance = { path = "pallets/governance", default-features = false }
ghost-weaver = { path = "pallets/weaver", default-features = false }
ghost-exodus = { path = "pallets/exodus", default-features = false }
ghost-exodus-runtime-api = { path = "pallets/exodus/runtime-api", default-features = false }
ghost-sudo = { path = "pallets/sudo", default-features = false }
ghost-nomination-pools = { path = "pallets/pools", default-features = false }
ghost-nomination-pools-runtime-api = { path = "pallets/pools/runtime-api", default-features = false }

View File

@ -1,6 +1,6 @@
[package]
name = "ghost-exodus"
version = "0.0.17"
version = "0.1.7"
description = "Threshold signature generation with DKG included"
license.workspace = true
authors.workspace = true
@ -40,6 +40,7 @@ parking_lot = { workspace = true }
ghost-traits = { workspace = true }
ghost-helpers = { workspace = true }
ghost-exodus-runtime-api = { workspace = true }
pallet-balances = { workspace = true }
pallet-session = { workspace = true }
@ -80,6 +81,7 @@ std = [
"frost-core/std",
"ghost-traits/std",
"ghost-helpers/std",
"ghost-exodus-runtime-api/std",
"pallet-balances/std",
"pallet-session/std",
"pallet-staking/std",

View File

@ -1,6 +1,6 @@
[package]
name = "ghost-exodus-runtime-api"
version = "0.0.0"
version = "0.0.2"
description = "RPC runtime API for ghost EXODUS pallet"
license.workspace = true
authors.workspace = true
@ -14,7 +14,14 @@ targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { workspace = true, features = ["derive"] }
sp-api = { workspace = true }
sp-std = { workspace = true }
ghost-helpers = { workspace = true }
[features]
default = ["std"]
std = ["codec/std", "sp-api/std"]
std = [
"codec/std",
"sp-api/std",
"sp-std/std",
"ghost-helpers/std",
]

View File

@ -1,7 +1,17 @@
#![cfg_attr(not(feature = "std"), no_std)]
use sp_std::vec::Vec;
use ghost_helpers::networks::{NetworkType, NetworkCurve};
pub type PreparedEvmMessage = Option<(Vec<u8>, Vec<u8>, Vec<u8>)>;
sp_api::decl_runtime_apis! {
pub trait ExodusApi<NetworkId> {
fn verifying_key(network_id: NetworkId) -> Vec<u8>;
pub trait ExodusApi {
fn get_evm_rotation_metadata(dkg_index: u64, network_type: NetworkType) -> PreparedEvmMessage;
fn get_evm_bridge_metadata(dkg_index: u64, network_id: u64, session: u64) -> PreparedEvmMessage;
fn get_evm_governance_metadata(dkg_index: u64, network_id: u64, session: u64) -> PreparedEvmMessage;
fn pending_exodus_request_count() -> u32;
fn is_qualification_ongoing(curve: NetworkCurve) -> bool;
}
}

View File

@ -7,7 +7,7 @@ use frame_system::RawOrigin;
use ghost_helpers::networks::NetworkDataBuilder;
fn prepare_pallet<T: Config>(authorities_len: usize) -> (
PendingDkgAuthorities<ParticipantsBitmap<T>, BlockNumberFor<T>>,
PendingDkgAuthorities<T>,
NetworkCurve,
T::AuthorityId,
AuthIndex
@ -47,11 +47,13 @@ mod benchmarks {
prepare_pallet::<T>(1);
dkg_state.next_phase();
let dkg_index = dkg_state.get_dkg_index();
QualificationDkgState::<T>::insert(&network_curve, dkg_state);
let round0_package = PackageContext::default()
.with_network_curve(NetworkCurve::Secp256k1)
.with_authority_index(0)
.with_dkg_index(dkg_index)
.with_dkg_round0(dummy_hash);
let dkg_package = DkgPackage::Round0(round0_package);
@ -83,10 +85,10 @@ mod benchmarks {
dkg_state.next_phase();
dkg_state.insert_index(authority_index);
let dkg_index = dkg_state.get_dkg_index();
QualificationDkgState::<T>::insert(&network_curve, dkg_state);
let mut rng = Pallet::<T>::get_offchain_rng(authority_index);
let mut rng = ChaCha20Rng::seed_from_u64(694201337);
let (_, public_package) = network_curve.dkg_part1(
authority_index,
max_signers,
@ -97,9 +99,12 @@ mod benchmarks {
let package_hash = ExodusHash::from(blake2_256(&public_package));
Round0Packages::<T>::insert(network_curve, authority_index, package_hash);
let current_block = <T as Config>::BlockNumberProvider::current_block_number();
let round1_package = PackageContext::default()
.with_block_number(current_block)
.with_network_curve(NetworkCurve::Secp256k1)
.with_authority_index(0)
.with_dkg_index(dkg_index)
.with_dkg_round1(public_package)
.expect("Round1 package should be valid");
@ -120,9 +125,9 @@ mod benchmarks {
#[benchmark]
fn register_encrypted_round2_packages(
c: Linear<1, { T::MaxAuthorities::get() / 8 }>
c: Linear<1, { T::MaxAuthorities::get() }>
) -> Result<(), BenchmarkError> {
let max_signers = (c * 8) as AuthIndex;
let max_signers = c as AuthIndex;
let (mut dkg_state, network_curve, authority, authority_index) =
prepare_pallet::<T>(max_signers as usize);
@ -131,6 +136,8 @@ mod benchmarks {
dkg_state.next_phase();
dkg_state.next_phase();
let dkg_index = dkg_state.get_dkg_index();
(0..max_signers).for_each(|i| {
dkg_state.insert_index(i);
});
@ -142,29 +149,29 @@ mod benchmarks {
QualificationDkgState::<T>::insert(&network_curve, dkg_state);
let bitmask_len = padded_participants.div_ceil(8);
let blob_len = round2_encrypted_package_size(
padded_participants,
padded_participants - 1,
network_curve.scalar_bytes_len(),
network_curve.header_bytes_len(),
);
let mut bitmask = vec![0u8; bitmask_len];
let mut bitmask = ParticipantsBitmap::<T>::empty(padded_participants);
let blob = vec![0u8; blob_len];
(0..max_signers).for_each(|i| {
if i != authority_index {
let byte_index = (i / 8) as usize;
let bit_index = (i % 8) as u8;
bitmask[byte_index] |= 1 << bit_index;
bitmask.insert(i);
}
});
let encryption_bundle = BundledPackages { bitmask, blob };
let current_block = <T as Config>::BlockNumberProvider::current_block_number();
let round2_package = PackageContext::default()
.with_block_number(current_block)
.with_network_curve(NetworkCurve::Secp256k1)
.with_authority_index(authority_index)
.with_dkg_index(dkg_index)
.with_dkg_round2(encryption_bundle)
.expect("Round2 package should be valid");
@ -185,9 +192,9 @@ mod benchmarks {
#[benchmark]
fn register_round3_complaints(
c: Linear<1, { T::MaxAuthorities::get() / 8 }>
c: Linear<1, { T::MaxAuthorities::get() }>
) -> Result<(), BenchmarkError> {
let max_signers = (c * 8) as AuthIndex;
let max_signers = c as AuthIndex;
let (mut dkg_state, network_curve, authority, authority_index) =
prepare_pallet::<T>(max_signers as usize);
@ -197,26 +204,28 @@ mod benchmarks {
dkg_state.next_phase();
dkg_state.next_phase();
let dkg_index = dkg_state.get_dkg_index();
(0..max_signers).for_each(|i| {
dkg_state.insert_index(i);
});
QualificationDkgState::<T>::insert(&network_curve, dkg_state);
let bitvec_len = (max_signers + 7) / 8;
let mut accused_indexes = vec![0u8; bitvec_len as usize];
let mut accused_indexes = ParticipantsBitmap::<T>::empty(max_signers);
(0..max_signers).for_each(|i| {
if i != authority_index {
let byte_index = (i / 8) as usize;
let bit_index = (i % 8) as u8;
accused_indexes[byte_index] |= 1 << bit_index;
accused_indexes.insert(i);
}
});
let current_block = <T as Config>::BlockNumberProvider::current_block_number();
let round3_package = PackageContext::default()
.with_block_number(current_block)
.with_network_curve(NetworkCurve::Secp256k1)
.with_authority_index(authority_index)
.with_dkg_index(dkg_index)
.with_dkg_round3(accused_indexes)
.expect("Round3 package should be valid");
@ -240,9 +249,9 @@ mod benchmarks {
#[benchmark]
fn register_round4_justifications(
c: Linear<1, { T::MaxAuthorities::get() / 8 }>
c: Linear<1, { T::MaxAuthorities::get() }>
) -> Result<(), BenchmarkError> {
let max_signers = (c * 8) as AuthIndex;
let max_signers = c as AuthIndex;
let (mut dkg_state, network_curve, authority, authority_index) =
prepare_pallet::<T>(max_signers as usize);
@ -253,6 +262,8 @@ mod benchmarks {
dkg_state.next_phase();
dkg_state.next_phase();
let dkg_index = dkg_state.get_dkg_index();
(0..max_signers).for_each(|i| {
dkg_state.insert_index(i);
});
@ -264,29 +275,29 @@ mod benchmarks {
QualificationDkgState::<T>::insert(&network_curve, dkg_state);
let bitmask_len = padded_participants.div_ceil(8);
let blob_len = round2_package_size(
padded_participants,
padded_participants - 1,
network_curve.scalar_bytes_len(),
network_curve.header_bytes_len(),
);
let mut bitmask = vec![0u8; bitmask_len];
let mut bitmask = ParticipantsBitmap::<T>::empty(padded_participants);
let blob = vec![0u8; blob_len];
(0..max_signers).for_each(|i| {
if i != authority_index {
let byte_index = (i / 8) as usize;
let bit_index = (i % 8) as u8;
bitmask[byte_index] |= 1 << bit_index;
bitmask.insert(i);
}
});
let justification_bundle = BundledPackages { bitmask, blob };
let current_block = <T as Config>::BlockNumberProvider::current_block_number();
let round4_package = PackageContext::default()
.with_block_number(current_block)
.with_network_curve(NetworkCurve::Secp256k1)
.with_authority_index(authority_index)
.with_dkg_index(dkg_index)
.with_dkg_round4(justification_bundle)
.expect("Round4 package should be valid");
@ -332,11 +343,19 @@ mod benchmarks {
let dummy_merkle_root = ExodusHash::repeat_byte(69);
let dummy_verifying_key = vec![69u8; network_curve.element_bytes_len()];
let dummy_key_members = ParticipantsBitmap::<T>::empty(4);
let current_block = <T as Config>::BlockNumberProvider::current_block_number();
let round5_package = PackageContext::default()
.with_block_number(current_block)
.with_network_curve(NetworkCurve::Secp256k1)
.with_authority_index(authority_index)
.with_dkg_round5(dummy_verifying_key.clone(), dummy_merkle_root)
.with_dkg_index(dkg_index)
.with_dkg_round5(
dummy_verifying_key.clone(),
dummy_key_members.clone(),
dummy_merkle_root,
)
.expect("Round5 package should be valid");
let dkg_package = DkgPackage::Round5(round5_package);
@ -354,20 +373,22 @@ mod benchmarks {
).expect("Bounded verifying key should be correct");
let metadata_hashes = [
SubstrateBlake2Hasher::hash(dummy_merkle_root.as_ref()),
SubstrateBlake2Hasher::hash(bounded_verifying_key.as_ref()),
dummy_merkle_root.as_ref(),
bounded_verifying_key.as_ref(),
dummy_key_members.inner_as_ref(),
];
let verifying_hash = sequential_hash::<SubstrateBlake2Hasher, _>(metadata_hashes);
let verifying_hash = cumulative_hash::<SubstrateBlake2Hasher, _>(metadata_hashes);
let verification_key = (network_curve, dkg_index, verifying_hash);
let participants = Verifications::<T>::get(verification_key);
assert!(participants.contains(authority_index));
let consensus = VerifyingKeyConsensus::<T>::get(&network_curve, dkg_index);
assert!(consensus.participants.contains(authority_index));
assert_eq!(consensus.participants.count_ones::<AuthIndex>(), 1);
assert_eq!(consensus.merkle_root, dummy_merkle_root);
assert_eq!(consensus.element_bytes, bounded_verifying_key);
assert!(consensus.state.participants.contains(authority_index));
assert_eq!(consensus.state.participants.count_ones::<AuthIndex>(), 1);
assert_eq!(consensus.state.merkle_root, dummy_merkle_root);
assert_eq!(consensus.state.element_bytes, bounded_verifying_key);
assert_eq!(consensus.members, dummy_key_members);
Ok(())
}
@ -415,10 +436,16 @@ mod benchmarks {
dummy_values.insert(identifier, dummy_value.clone());
}
let identifier_size = network_curve.scalar_bytes_len();
let merkle_tree =
NetworkCurve::build_merkle_tree(
&dummy_values,
|value: &Vec<u8>| Ok(value.clone()),
identifier_size + dummy_value.len(),
&dummy_values,
|value, buffer| {
buffer.extend_from_slice(value.as_ref());
Ok(())
}
).expect("Merkle tree for verifying shares should be valid");
let merkle_root = merkle_tree.last().copied().unwrap();
@ -434,13 +461,16 @@ mod benchmarks {
});
VerifyingKeyConsensus::<T>::mutate(&network_curve, &dkg_index, |consensus| {
consensus.participants.insert(authority_index);
consensus.merkle_root = dummy_merkle_root;
consensus.state.participants.insert(authority_index);
consensus.state.merkle_root = dummy_merkle_root;
});
let current_block = <T as Config>::BlockNumberProvider::current_block_number();
let round6_package = PackageContext::default()
.with_block_number(current_block)
.with_network_curve(NetworkCurve::Secp256k1)
.with_authority_index(authority_index)
.with_dkg_index(dkg_index)
.with_dkg_round6(dummy_verifying_share.clone(), dummy_merkle_proof)
.expect("Round6 package should be valid");
@ -482,10 +512,22 @@ mod benchmarks {
}
dkg_state.insert_index(authority_index);
let exodus_session = CurrentExodus::<T>::get();
let all_exodus_sessions = CurrentExodusSessions::<T>::get();
let exodus_session = all_exodus_sessions
.get(&network_id)
.copied()
.unwrap_or(0);
let dummy_context = ExodusContext::new(network_id, exodus_session);
let dummy_address = EvmAddress::repeat_byte(69);
let dummy_nonce = vec![0u8; network_curve.element_bytes_len()];
// generator point for secp256k1
let dummy_nonce = [
0x02, 0x79, 0xbe, 0x66, 0x7e, 0xf9, 0xdc, 0xbb, 0xac, 0x55,
0xa0, 0x62, 0x95, 0xce, 0x87, 0x0b, 0x07, 0x02, 0x9b, 0xfc,
0xdb, 0x2d, 0xce, 0x28, 0xd9, 0x59, 0xf2, 0x81, 0x5b, 0x16,
0xf8, 0x17, 0x98
];
let create_bond = GovernanceAction::Create {
market: [69, 420, 1337, 0],
@ -494,27 +536,30 @@ mod benchmarks {
intervals: [34, 35],
booleans: [true, true],
};
let request = ExodusRequest::<NetworkIdOf<T>, BalanceOf<T>>::evm_governance(
exodus_session,
network_id,
let request = ExodusRequest::<T>::evm_governance(
dummy_address,
create_bond,
);
let current_block = <T as Config>::BlockNumberProvider::current_block_number();
let authorities = QualificationAuthorities::<T>::get(&network_curve);
let active_dkg: ActivatedState<T> = (&dkg_state).into();
let active_dkg: ReadyDkgAuthorities<T> = (&dkg_state).into();
let active_dkg_index = active_dkg.get_dkg_index();
QualificationDkgState::<T>::insert(&network_curve, dkg_state);
ActiveDkgAuthorities::<T>::insert(&network_curve, active_dkg);
ActiveAuthorities::<T>::insert(&network_curve, authorities);
ExodusRequests::<T>::insert((network_curve, exodus_session), request);
ExodusRequests::<T>::insert((&network_curve, &dummy_context), request);
let nonce_package = PackageContext::default()
.with_block_number(current_block)
.with_network_curve(NetworkCurve::Secp256k1)
.with_authority_index(authority_index)
.with_dkg_index(active_dkg_index)
.with_exodus_nonce_commitment(
exodus_session,
dummy_nonce.clone(),
dummy_nonce.clone(),
dummy_context,
dummy_nonce.to_vec(),
dummy_nonce.to_vec(),
)
.expect("Nonce commitment package should be valid");
@ -527,21 +572,21 @@ mod benchmarks {
#[extrinsic_call]
_(RawOrigin::None, exodus_package, signature);
let roast_session = RoastSessions::<T>::get(&exodus_session, authority_index)
let roast_session = RoastSessions::<T>::get(&dummy_context, authority_index)
.expect("ROAST session should exist after nonce commitment");
let roast_state = RoastSessionStates::<T>::get(&exodus_session, roast_session);
let roast_state = RoastSessionStates::<T>::get(&dummy_context, roast_session);
assert!(roast_state.nonce_committers.contains(authority_index));
assert!(!roast_state.group_committers.contains(authority_index));
assert!(!roast_state.partial_signers.contains(authority_index));
let registered_nonces = NonceCommitments::<T>::get(
(exodus_session, roast_session),
(dummy_context, roast_session),
authority_index,
).expect("Nonces should be registered during nonce commitment");
assert_eq!(registered_nonces.hiding, dummy_nonce.clone());
assert_eq!(registered_nonces.binding, dummy_nonce);
assert_eq!(registered_nonces.hiding.as_ref(), dummy_nonce);
assert_eq!(registered_nonces.binding.as_ref(), dummy_nonce);
Ok(())
}
@ -558,8 +603,13 @@ mod benchmarks {
}
dkg_state.insert_index(authority_index);
let exodus_session = CurrentExodus::<T>::get();
let all_exodus_sessions = CurrentExodusSessions::<T>::get();
let exodus_session = all_exodus_sessions
.get(&network_id)
.copied()
.unwrap_or(0);
let dummy_context = ExodusContext::new(network_id, exodus_session);
let dummy_bytes = EvmBytes32::repeat_byte(69);
let dummy_address = EvmAddress::repeat_byte(69);
let dummy_merkle_root = ExodusHash::repeat_byte(69);
@ -572,33 +622,36 @@ mod benchmarks {
intervals: [34, 35],
booleans: [true, true],
};
let request = ExodusRequest::<NetworkIdOf<T>, BalanceOf<T>>::evm_governance(
exodus_session,
network_id,
let request = ExodusRequest::<T>::evm_governance(
dummy_address,
create_bond,
);
let authorities = QualificationAuthorities::<T>::get(&network_curve);
let active_dkg: ActivatedState<T> = (&dkg_state).into();
let active_dkg: ReadyDkgAuthorities<T> = (&dkg_state).into();
let active_dkg_index = active_dkg.get_dkg_index();
QualificationDkgState::<T>::insert(&network_curve, dkg_state);
ActiveDkgAuthorities::<T>::insert(&network_curve, active_dkg);
ActiveAuthorities::<T>::insert(&network_curve, authorities);
ExodusRequests::<T>::insert((network_curve, exodus_session), request);
ExodusRequests::<T>::insert((network_curve, dummy_context), request);
let mut roast_state = RoastSessionState::<ParticipantsBitmap::<T>>::default();
roast_state.status = RoastStatus::GroupCommitments;
roast_state.nonce_committers.insert(authority_index);
let roast_session = 3;
RoastSessions::<T>::insert(&exodus_session, authority_index, roast_session);
RoastSessionStates::<T>::insert(exodus_session, roast_session, roast_state);
let current_block = <T as Config>::BlockNumberProvider::current_block_number();
RoastSessions::<T>::insert(&dummy_context, authority_index, roast_session);
RoastSessionStates::<T>::insert(dummy_context, roast_session, roast_state);
let group_package = PackageContext::default()
.with_block_number(current_block)
.with_network_curve(NetworkCurve::Secp256k1)
.with_authority_index(authority_index)
.with_dkg_index(active_dkg_index)
.with_exodus_group_commitment(
exodus_session,
dummy_context,
dummy_group_commitment.clone(),
dummy_bytes,
)
@ -618,16 +671,16 @@ mod benchmarks {
dummy_group_commitment,
).expect("Group commtiment should be valid");
let roast_state = RoastSessionStates::<T>::get(&exodus_session, roast_session);
let roast_state = RoastSessionStates::<T>::get(&dummy_context, roast_session);
assert!(roast_state.nonce_committers.contains(authority_index));
assert!(roast_state.group_committers.contains(authority_index));
let consensus = GroupCommitmentsConsensus::<T>::get(exodus_session, roast_session);
let consensus = GroupCommitmentsConsensus::<T>::get(dummy_context, roast_session);
assert!(consensus.participants.contains(authority_index));
assert_eq!(&consensus.element_bytes, &bounded_group_commitment);
assert_eq!(consensus.merkle_root, dummy_merkle_root);
let group_commitment_key = (exodus_session, roast_session, dummy_merkle_root, bounded_group_commitment);
let group_commitment_key = (dummy_context, roast_session, dummy_merkle_root, bounded_group_commitment);
let participants = GroupCommitters::<T>::get(group_commitment_key);
assert!(participants.contains(authority_index));
@ -650,7 +703,13 @@ mod benchmarks {
dkg_state.insert_index(authority_index);
let dkg_index = dkg_state.get_dkg_index();
let exodus_session = CurrentExodus::<T>::get();
let all_exodus_sessions = CurrentExodusSessions::<T>::get();
let exodus_session = all_exodus_sessions
.get(&network_id)
.copied()
.unwrap_or(0);
let exodus_context = ExodusContext::new(network_id, exodus_session);
let mut dummy_element = vec![0u8; network_curve.element_bytes_len()];
dummy_element[0] = 0x02;
@ -670,9 +729,7 @@ mod benchmarks {
intervals: [34, 35],
booleans: [true, true],
};
let request = ExodusRequest::<NetworkIdOf<T>, BalanceOf<T>>::evm_governance(
exodus_session,
network_id,
let request = ExodusRequest::<T>::evm_governance(
dummy_address,
create_bond,
);
@ -697,10 +754,16 @@ mod benchmarks {
dummy_values.insert(identifier, dummy_binding_factor.clone());
}
let identifier_size = network_curve.scalar_bytes_len();
let merkle_tree =
NetworkCurve::build_merkle_tree(
&dummy_values,
|value: &Vec<u8>| Ok(value.clone()),
identifier_size + dummy_scalar.len(),
&dummy_values,
|value, buffer| {
buffer.extend_from_slice(value.as_ref());
Ok(())
}
).expect("Merkle tree for binding factor should be valid");
let merkle_root = merkle_tree.last().copied().unwrap();
@ -717,13 +780,13 @@ mod benchmarks {
});
let authorities = QualificationAuthorities::<T>::get(&network_curve);
let active_dkg: ActivatedState<T> = (&dkg_state).into();
let active_dkg: ReadyDkgAuthorities<T> = (&dkg_state).into();
let active_dkg_index = active_dkg.get_dkg_index();
QualificationDkgState::<T>::insert(&network_curve, dkg_state);
ActiveDkgAuthorities::<T>::insert(&network_curve, active_dkg);
ActiveAuthorities::<T>::insert(&network_curve, authorities);
ExodusRequests::<T>::insert((network_curve, exodus_session), request);
ExodusRequests::<T>::insert((network_curve, exodus_context), request);
let mut roast_state = RoastSessionState::<ParticipantsBitmap::<T>>::default();
roast_state.status = RoastStatus::PartialSignatures;
@ -736,8 +799,8 @@ mod benchmarks {
});
let roast_session = 3;
RoastSessions::<T>::insert(&exodus_session, authority_index, roast_session);
RoastSessionStates::<T>::insert(exodus_session, roast_session, roast_state);
RoastSessions::<T>::insert(&exodus_context, authority_index, roast_session);
RoastSessionStates::<T>::insert(exodus_context, roast_session, roast_state);
let bounded_group_commitment = BoundedVec::try_from(dummy_group_commitment).unwrap();
let dummy_verifying_key = BoundedVec::try_from(dummy_element.clone()).unwrap();
@ -750,9 +813,9 @@ mod benchmarks {
ActiveVerifyingKey::<T>::insert(&network_curve, dummy_verifying_key);
VerifyingShares::<T>::insert((&network_curve, active_dkg_index, authority_index), dummy_verifying_share);
NonceCommitments::<T>::insert((&exodus_session, &roast_session), authority_index, dummy_nonce);
NonceCommitments::<T>::insert((&exodus_context, &roast_session), authority_index, dummy_nonce);
GroupCommitmentsConsensus::<T>::mutate(&exodus_session, &roast_session, |consensus| {
GroupCommitmentsConsensus::<T>::mutate(&exodus_context, &roast_session, |consensus| {
consensus.element_bytes = bounded_group_commitment.clone();
consensus.merkle_root = dummy_merkle_root;
(0..threshold).for_each(|i| {
@ -760,11 +823,14 @@ mod benchmarks {
});
});
let current_block = <T as Config>::BlockNumberProvider::current_block_number();
let share_package = PackageContext::default()
.with_block_number(current_block)
.with_network_curve(NetworkCurve::Secp256k1)
.with_authority_index(authority_index)
.with_dkg_index(active_dkg_index)
.with_exodus_signature_share(
exodus_session,
exodus_context,
dummy_signature_share.clone(),
dummy_merkle_proof,
dummy_binding_factor
@ -780,13 +846,13 @@ mod benchmarks {
#[extrinsic_call]
_(RawOrigin::None, exodus_package, signature);
let maybe_roast_session = RoastSessions::<T>::get(&exodus_session, authority_index);
let maybe_roast_session = RoastSessions::<T>::get(&exodus_context, authority_index);
assert!(maybe_roast_session.is_none());
let scalar_exists = SignatureScalars::<T>::contains_key(&exodus_session, &roast_session);
let consensus_exists = GroupCommitmentsConsensus::<T>::contains_key(&exodus_session, &roast_session);
let scalar_exists = SignatureScalars::<T>::contains_key(&exodus_context, &roast_session);
let consensus_exists = GroupCommitmentsConsensus::<T>::contains_key(&exodus_context, &roast_session);
let committers_exists = GroupCommitters::<T>::contains_key((
&exodus_session,
&exodus_context,
&roast_session,
&dummy_merkle_root,
&bounded_group_commitment,
@ -796,8 +862,8 @@ mod benchmarks {
assert!(!consensus_exists);
assert!(!committers_exists);
assert!(!ExodusRequests::<T>::contains_key((&network_curve, &exodus_session)));
assert!(ExodusSignedGovernance::<T>::contains_key((&exodus_session, network_id), dkg_index));
assert!(!ExodusRequests::<T>::contains_key((&network_curve, &exodus_context)));
assert!(ExodusSignedGovernance::<T>::contains_key(dkg_index, &exodus_context));
Ok(())
}
@ -829,7 +895,11 @@ mod benchmarks {
let _ = T::NetworkDataHandler::register_incoming(&network_id, amount).unwrap();
let total_issuance = T::Currency::total_issuance();
let current_exodus_session = CurrentExodus::<T>::get();
let all_exodus_sessions = CurrentExodusSessions::<T>::get();
let current_exodus_session = all_exodus_sessions
.get(&network_id)
.copied()
.unwrap_or(0);
#[extrinsic_call]
_(
@ -840,8 +910,12 @@ mod benchmarks {
receiver,
);
assert!(ExodusRequests::<T>::contains_key((&network_curve, current_exodus_session)));
assert_eq!(CurrentExodus::<T>::get(), current_exodus_session + 1);
let all_exodus_sessions = CurrentExodusSessions::<T>::get();
let last_exodus_session = all_exodus_sessions.get(&network_id).copied().unwrap();
let context = ExodusContext::new(network_id, current_exodus_session);
assert!(ExodusRequests::<T>::contains_key((&network_curve, &context)));
assert_eq!(last_exodus_session, current_exodus_session + 1);
assert_eq!(T::Currency::balance(&caller), initial_balance - amount);
assert_eq!(T::Currency::total_issuance(), total_issuance - amount);

View File

@ -43,6 +43,7 @@ pub enum ExodusError {
InvalidMerkleProof,
InvalidIdentityElement,
NoActiveAuthorities,
TreeGenerationFailed,
Unknown,
}
@ -88,6 +89,7 @@ impl core::fmt::Debug for ExodusError {
ExodusError::InvalidMerkleProof => write!(fmt, "Invalid Merkle proof provided."),
ExodusError::InvalidIdentityElement => write!(fmt, "Invalid identity element provided."),
ExodusError::NoActiveAuthorities => write!(fmt, "No active authorities for signing EXODUS transactions."),
ExodusError::TreeGenerationFailed => write!(fmt, "Tree generation failed because of inconsistent indexation."),
ExodusError::Unknown => write!(fmt, "Unknown error."),
}
}

View File

@ -3,6 +3,7 @@ use rand_chacha::rand_core::{CryptoRng, RngCore};
use ghost_traits::exodus::{
DistributedKeyGeneration, IdentifierConverter, MerkleTreeBuilder,
CiphersuiteSizes,
};
use crate::{AuthIndex, ExodusError, NetworkCurve};
@ -241,6 +242,8 @@ impl DistributedKeyGeneration<AuthIndex, ExodusError> for NetworkCurve {
round2_packages_bytes: &Self::RoundPackages,
) -> Self::Part3Result {
with_ciphersuite!(self, |f| {
type InnerGroup = <Ciphersuite as frost_core::Ciphersuite>::Group;
let round2_secret_package = f::keys::dkg::round2::SecretPackage::deserialize(round2_secret_package_bytes)
.map_err(|_| ExodusError::DeserializationError)?;
@ -291,13 +294,24 @@ impl DistributedKeyGeneration<AuthIndex, ExodusError> for NetworkCurve {
let key_package_bytes = key_package.serialize()
.map_err(|_| ExodusError::SerializationError)?;
let identifier_size = self.scalar_bytes_len();
let verifying_share_size = self.element_bytes_len();
let verifying_shares_merkle_tree =
Self::build_merkle_tree(
identifier_size + verifying_share_size,
pubkey_package.verifying_shares(),
|verifying_share| verifying_share.serialize()
.map_err(|_| ExodusError::SerializationError)
|verifying_share, buffer| {
let serialized = <InnerGroup as frost_core::Group>::serialize(
&verifying_share.to_element()
).map_err(|_| ExodusError::SerializationError)?;
buffer.extend_from_slice(serialized.as_ref());
Ok(())
}
)?;
// TODO: make it better
let (_, merkle_proof) =
Self::generate_merkle_proof_from_tree(
pubkey_package.verifying_shares(),

View File

@ -1,5 +1,5 @@
use ghost_traits::exodus::{
IdentifierConverter, MerkleTreeBuilder,
CiphersuiteSizes, IdentifierConverter, MerkleTreeBuilder,
FlexibleRoundOptimizedSchnorrThresholdSignature,
};
use sp_std::{
@ -15,29 +15,6 @@ impl FlexibleRoundOptimizedSchnorrThresholdSignature<AuthIndex, ExodusError> for
type Packages<'a> = BTreeMap<AuthIndex, &'a [u8]>;
type NoncePackages<'a> = BTreeMap<AuthIndex, (&'a [u8], &'a [u8])>;
fn header_bytes_len(&self) -> usize { 5 }
fn signature_bytes_len(&self) -> usize {
match self {
NetworkCurve::Secp256k1 => 64,
NetworkCurve::Ed25519 => 64,
}
}
fn element_bytes_len(&self) -> usize {
match self {
NetworkCurve::Secp256k1 => 33,
NetworkCurve::Ed25519 => 32,
}
}
fn scalar_bytes_len(&self) -> usize {
match self {
NetworkCurve::Secp256k1 => 32,
NetworkCurve::Ed25519 => 32,
}
}
fn verify_signature_share(
&self,
index: AuthIndex,
@ -186,6 +163,19 @@ impl FlexibleRoundOptimizedSchnorrThresholdSignature<AuthIndex, ExodusError> for
})
}
fn is_nonce_valid_point(&self, nonce_bytes: &[u8]) -> Result<(), ExodusError> {
with_ciphersuite!(self, |f| {
type InnerGroup = <Ciphersuite as frost_core::Ciphersuite>::Group;
let serialized_nonce = <InnerGroup as frost_core::Group>::Serialization::try_from(nonce_bytes)
.map_err(|_| ExodusError::DeserializationError)?;
<InnerGroup as frost_core::Group>::deserialize(&serialized_nonce)
.map_err(|_| ExodusError::DeserializationError)
.map(|_| ())
})
}
fn is_signature_valid(
&self,
verifying_key_bytes: &[u8],
@ -330,11 +320,16 @@ impl FlexibleRoundOptimizedSchnorrThresholdSignature<AuthIndex, ExodusError> for
return Err(ExodusError::InvalidParticipantId);
}
let identifier_size = self.scalar_bytes_len();
let binding_factor_size = self.scalar_bytes_len();
let binding_factor_tree = Self::build_merkle_tree(
identifier_size + binding_factor_size,
&binding_factors_list,
|binding_factor| {
|binding_factor, buffer| {
let serialized = <InnerField as frost_core::Field>::serialize(binding_factor);
Ok(serialized.as_ref().to_vec())
buffer.extend_from_slice(serialized.as_ref());
Ok(())
}
)?;

View File

@ -13,15 +13,16 @@ impl MerkleTreeBuilder<AuthIndex, ExodusError> for NetworkCurve {
type Hash = <SubstrateBlake2Hasher as GhostHasher>::Hash;
fn build_merkle_tree<C, V, F>(
size_hint: usize,
values: &BTreeMap<Identifier<C>, V>,
serialize_fn: F,
update_preimage_buffer: F,
) -> Result<Vec<Self::Hash>, ExodusError>
where
C: frost_core::Ciphersuite,
<<C as Ciphersuite>::Group as Group>::Field: frost_core::Field,
F: Fn(&V) -> Result<Vec<u8>, ExodusError>
F: Fn(&V, &mut Vec<u8>) -> Result<(), ExodusError>
{
use ghost_helpers::merkle_tree::generate_tree;
use ghost_helpers::merkle_tree::{generate_tree, MerkleTreeError};
type CField<C> = <<C as Ciphersuite>::Group as Group>::Field;
@ -34,25 +35,28 @@ impl MerkleTreeBuilder<AuthIndex, ExodusError> for NetworkCurve {
})
.ok_or(ExodusError::IncorrectNumberOfIdentifiers)?;
generate_tree::<SubstrateBlake2Hasher, _, _, _, _>(
let maybe_tree = generate_tree::<SubstrateBlake2Hasher, _, _, _, _>(
max_index,
size_hint,
values.iter(),
|(identifier, value)| {
|(identifier, value), buffer| {
let id_scalar = identifier.to_scalar();
let id_bytes = <CField<C> as frost_core::Field>::serialize(&id_scalar);
let index = Self::convert_identifier_to_index(id_bytes.as_ref())?;
let value_bytes = serialize_fn(value).map_err(|_| ExodusError::SerializationError)?;
// NOTE: revisit
// Is it possible to use [u8; CONSTANT] where constant is
// purely dependant on provided generic.
let mut preimage = Vec::<u8>::with_capacity(id_bytes.as_ref().len() + value_bytes.len());
preimage.extend_from_slice(id_bytes.as_ref());
preimage.extend_from_slice(value_bytes.as_ref());
buffer.extend_from_slice(id_bytes.as_ref());
update_preimage_buffer(value, buffer)?;
Ok((index as usize, preimage))
})
Ok(index as usize)
});
match maybe_tree {
Ok(tree) => Ok(tree),
Err(MerkleTreeError::Generate(exodus_err)) => Err(exodus_err),
Err(MerkleTreeError::InvalidIndex) => Err(ExodusError::TreeGenerationFailed),
Err(MerkleTreeError::Empty) => Err(ExodusError::IncorrectNumberOfIdentifiers),
}
}
fn generate_merkle_proof_from_tree<C, V, F>(
@ -66,7 +70,7 @@ impl MerkleTreeBuilder<AuthIndex, ExodusError> for NetworkCurve {
<<C as Ciphersuite>::Group as Group>::Field: frost_core::Field,
F: Fn(&V) -> Result<Vec<u8>, ExodusError>
{
use ghost_helpers::merkle_tree::generate_proof;
use ghost_helpers::merkle_tree::{generate_proof, MerkleTreeError};
type CField<C> = <<C as Ciphersuite>::Group as Group>::Field;
@ -75,7 +79,7 @@ impl MerkleTreeBuilder<AuthIndex, ExodusError> for NetworkCurve {
.map_err(|_| ExodusError::InvalidParticipantId)?;
let value = values.get(&identifier).ok_or(ExodusError::InvalidParticipantId)?;
let value_bytes = serialize_fn(&value).map_err(|_| ExodusError::SerializationError)?;
let value_bytes = serialize_fn(&value)?;
let max_index = values.keys()
.next_back()
@ -86,11 +90,15 @@ impl MerkleTreeBuilder<AuthIndex, ExodusError> for NetworkCurve {
})
.ok_or(ExodusError::IncorrectNumberOfIdentifiers)?;
let proof = generate_proof::<SubstrateBlake2Hasher, _>(
let proof = generate_proof::<SubstrateBlake2Hasher, _, _>(
merkle_tree,
max_index,
authority_index,
);
).map_err(|err| match err {
MerkleTreeError::Generate(exodus_err) => exodus_err,
MerkleTreeError::InvalidIndex => ExodusError::TreeGenerationFailed,
MerkleTreeError::Empty => ExodusError::IncorrectNumberOfIdentifiers,
})?;
Ok((value_bytes, proof))
}

View File

@ -72,3 +72,6 @@ mod ecdh;
#[macro_use]
mod frost;
#[macro_use]
mod sizes;

View File

@ -0,0 +1,29 @@
use ghost_helpers::networks::NetworkCurve;
use ghost_traits::exodus::CiphersuiteSizes;
use crate::{AuthIndex, ExodusError};
impl CiphersuiteSizes<AuthIndex, ExodusError> for NetworkCurve {
fn header_bytes_len(&self) -> usize { 5 }
fn signature_bytes_len(&self) -> usize {
match self {
NetworkCurve::Secp256k1 => 64,
NetworkCurve::Ed25519 => 64,
}
}
fn element_bytes_len(&self) -> usize {
match self {
NetworkCurve::Secp256k1 => 33,
NetworkCurve::Ed25519 => 32,
}
}
fn scalar_bytes_len(&self) -> usize {
match self {
NetworkCurve::Secp256k1 => 32,
NetworkCurve::Ed25519 => 32,
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -41,7 +41,7 @@ impl frame_system::Config for Runtime {
}
parameter_types! {
pub static FixedAuthorities: Vec<u64> = vec![0, 1, 2, 3];
pub static FixedAuthorities: Vec<u64> = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
}
pub struct TestSessionManager;
@ -136,14 +136,15 @@ impl ghost_exodus::Config for Runtime {
type DisabledValidators = Session;
type UnsignedPriority = ConstU64<{ 1 << 20 }>;
type UnsignedLongevity = ConstU64<1>;
type MaxAuthorities = ConstU32<10>;
type MaxAuthoritiesChunks = ConstU32<1>;
type UnsignedLongevity = ConstU64<5>;
type MaxAuthorities = ConstU32<15>;
type MaxAuthoritiesChunks = ConstU32<2>;
type DkgRoundPeriod = ConstU64<100>;
type DkgRoundPeriod = ConstU64<1000>;
type RemovalLimit = ConstU32<20>;
type MaxCursorLen = ConstU32<20>;
type MaxCursorLen = ConstU32<256>;
type BaseEvmAmount = ConstU128<5>;
type MaxExodusRequestCount = ConstU32<40>;
type WeightInfo = ();
}

File diff suppressed because it is too large Load Diff

View File

@ -6,6 +6,30 @@ use frame_support::{BoundedVec, traits::ConstU32};
use crate::{ExodusHash, ELEMENT_MAX_BYTES};
#[derive(Default, Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
pub struct QualifiedConsensusState<Bitmap> {
pub state: ConsensusState<Bitmap>,
pub members: Bitmap,
}
impl<Bitmap> QualifiedConsensusState<Bitmap> {
pub fn new(
element_bytes: BoundedVec<u8, ConstU32<ELEMENT_MAX_BYTES>>,
participants: Bitmap,
merkle_root: ExodusHash,
members: Bitmap,
) -> Self {
Self {
members,
state: ConsensusState::new(
element_bytes,
participants,
merkle_root,
)
}
}
}
#[derive(Default, Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
pub struct ConsensusState<Bitmap> {
pub element_bytes: BoundedVec<u8, ConstU32<ELEMENT_MAX_BYTES>>,

View File

@ -1,28 +1,46 @@
use sp_runtime::{RuntimeDebug, traits::UniqueSaturatedInto};
use codec::{Encode, Decode};
use scale_info::TypeInfo;
use strum::EnumCount;
use frame_support::{BoundedVec, traits::ConstU32};
use ghost_helpers::networks::NetworkType;
use ghost_traits::bounded_bitmap::{
BoundedBitmapGenerator, BoundedBitmapReader, BoundedBitmapWriter,
use ghost_traits::{
networks::NetworkDataBasicHandler,
bounded_bitmap::{
BoundedBitmapGenerator, BoundedBitmapReader, BoundedBitmapWriter,
},
};
use crate::{ELEMENT_MAX_BYTES, ExodusSession, DkgIndex};
use crate::{
ELEMENT_MAX_BYTES, Config, NetworkType, NetworkIdOf, ExodusContext,
ParticipantsBitmap, BlockNumberFor, DkgIndex,
};
type RotationSessions = BoundedVec<RotationSession, ConstU32<{ NetworkType::COUNT as u32 }>>;
type RotationItem<T> = (NetworkType, ExodusContext<NetworkIdOf<T>>);
type Rotations<T> = BoundedVec<
RotationItem<T>,
<<T as Config>::NetworkDataHandler as NetworkDataBasicHandler>::MaxNetworks,
>;
#[derive(Default, Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo)]
pub struct ReadyDkgAuthorities<AuthoritiesBitmap> {
authorities_indexes: AuthoritiesBitmap,
#[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct ReadyDkgAuthorities<T: Config> {
authorities_indexes: ParticipantsBitmap<T>,
dkg_index: DkgIndex,
}
impl<AuthoritiesBitmap> ReadyDkgAuthorities<AuthoritiesBitmap>
impl<T: Config> Default for ReadyDkgAuthorities<T> {
fn default() -> Self {
Self {
authorities_indexes: Default::default(),
dkg_index: Default::default(),
}
}
}
impl<T: Config> ReadyDkgAuthorities<T>
where
AuthoritiesBitmap: BoundedBitmapReader,
ParticipantsBitmap<T>: BoundedBitmapReader,
{
pub fn contains_index<N>(&self, index: N) -> bool
where N: UniqueSaturatedInto<usize>,
@ -30,7 +48,7 @@ where
self.authorities_indexes.contains(index)
}
pub fn get_indexes(&self) -> &AuthoritiesBitmap {
pub fn get_indexes(&self) -> &ParticipantsBitmap<T> {
&self.authorities_indexes
}
@ -39,15 +57,15 @@ where
}
}
impl<AuthoritiesBitmap, Block>
From<&PendingDkgAuthorities<AuthoritiesBitmap, Block>>
for ReadyDkgAuthorities<AuthoritiesBitmap>
impl<T: Config>
From<&PendingDkgAuthorities<T>>
for ReadyDkgAuthorities<T>
where
Block: Copy,
AuthoritiesBitmap: BoundedBitmapWriter + BoundedBitmapReader + BoundedBitmapGenerator + Clone,
for<'a> AuthoritiesBitmap: core::ops::BitAndAssign<&'a AuthoritiesBitmap>,
BlockNumberFor<T>: Copy,
ParticipantsBitmap<T>: BoundedBitmapWriter + BoundedBitmapReader + BoundedBitmapGenerator + Clone,
for<'a> ParticipantsBitmap<T>: core::ops::BitAndAssign<&'a ParticipantsBitmap<T>>,
{
fn from(pending: &PendingDkgAuthorities<AuthoritiesBitmap, Block>) -> Self {
fn from(pending: &PendingDkgAuthorities<T>) -> Self {
Self {
authorities_indexes: pending.get_indexes().clone(),
dkg_index: pending.get_dkg_index(),
@ -55,34 +73,40 @@ where
}
}
#[derive(Default, Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo)]
pub struct RotationSession {
pub exodus_session: ExodusSession,
pub network_type: NetworkType
}
#[derive(Default, Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
pub struct PendingDkgAuthorities<AuthoritiesBitmap, Block> {
#[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct PendingDkgAuthorities<T: Config> {
verifying_key: BoundedVec<u8, ConstU32<ELEMENT_MAX_BYTES>>,
authorities_indexes: AuthoritiesBitmap,
rotation_sessions: RotationSessions,
dkg_state: DkgState<Block>,
authorities_indexes: ParticipantsBitmap<T>,
rotations: Rotations<T>,
dkg_state: DkgState<BlockNumberFor<T>>,
}
impl<AuthoritiesBitmap, Block> PendingDkgAuthorities<AuthoritiesBitmap, Block>
impl<T: Config> Default for PendingDkgAuthorities<T> {
fn default() -> Self {
Self {
verifying_key: Default::default(),
authorities_indexes: Default::default(),
rotations: Default::default(),
dkg_state: Default::default(),
}
}
}
impl<T: Config> PendingDkgAuthorities<T>
where
Block: Copy,
AuthoritiesBitmap: BoundedBitmapWriter + BoundedBitmapReader + BoundedBitmapGenerator + Clone,
for<'a> AuthoritiesBitmap: core::ops::BitAndAssign<&'a AuthoritiesBitmap>,
BlockNumberFor<T>: Copy,
ParticipantsBitmap<T>: BoundedBitmapWriter + BoundedBitmapReader + BoundedBitmapGenerator + Clone,
for<'a> ParticipantsBitmap<T>: core::ops::BitAndAssign<&'a ParticipantsBitmap<T>>,
{
pub fn new_dkg(&mut self, authorities_len: usize) {
self.authorities_indexes = AuthoritiesBitmap::empty(authorities_len);
self.authorities_indexes = ParticipantsBitmap::<T>::empty(authorities_len);
self.dkg_state.restart();
self.rotation_sessions = Default::default();
self.rotations = Default::default();
}
pub fn set_block(&mut self, block: Block) {
self.dkg_state.set_block(block);
pub fn set_release_block(&mut self, block: BlockNumberFor<T>) {
self.dkg_state.set_release_block(block);
}
pub fn set_verifying_key(
@ -101,7 +125,7 @@ where
self.verifying_key = Default::default();
self.authorities_indexes.nullify();
self.dkg_state.restart();
self.rotation_sessions = Default::default();
self.rotations = Default::default();
}
pub fn next_phase(&mut self) {
@ -109,7 +133,7 @@ where
}
#[inline(always)]
pub fn intersect_indexes(&mut self, mask: &AuthoritiesBitmap) {
pub fn intersect_indexes(&mut self, mask: &ParticipantsBitmap<T>) {
self.authorities_indexes &= mask;
}
@ -125,8 +149,8 @@ where
self.dkg_state.get_dkg_index()
}
pub fn get_block(&self) -> Block {
self.dkg_state.get_block()
pub fn get_release_block(&self) -> BlockNumberFor<T> {
self.dkg_state.get_release_block()
}
pub fn get_phase(&self) -> DkgPhase {
@ -183,17 +207,13 @@ where
self.authorities_indexes.insert(i)
}
pub fn try_push_rotation_session(
pub fn try_push_rotation(
&mut self,
exodus_session: ExodusSession,
network_type: NetworkType,
exodus_context: ExodusContext<NetworkIdOf<T>>,
) -> Option<()> {
let rotation_session = RotationSession {
exodus_session,
network_type,
};
self.rotation_sessions.try_push(rotation_session).ok()
let item = (network_type, exodus_context);
self.rotations.try_push(item).ok()
}
pub fn count_ones<N>(&self) -> N
@ -208,43 +228,44 @@ where
self.authorities_indexes.highest_bit()
}
pub fn get_indexes(&self) -> &AuthoritiesBitmap {
pub fn get_indexes(&self) -> &ParticipantsBitmap<T> {
&self.authorities_indexes
}
pub fn iter_rotation_sessions(&self) -> impl Iterator<Item = &RotationSession> {
self.rotation_sessions.iter()
pub fn iter_rotations(
&self
) -> impl Iterator<Item = &RotationItem<T>> {
self.rotations.iter()
}
}
#[derive(Default, Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo)]
pub struct DkgState<Block> {
block: Block,
index: DkgIndex,
phase: DkgPhase,
release_block: Block,
dkg_index: DkgIndex,
dkg_phase: DkgPhase,
}
impl<Block: Copy> DkgState<Block> {
fn is_dkg_pending(&self) -> bool { self.phase.is_dkg_pending() }
fn is_dkg_vacant(&self) -> bool { self.phase.is_dkg_vacant() }
fn is_dkg_pending(&self) -> bool { self.dkg_phase.is_dkg_pending() }
fn is_dkg_vacant(&self) -> bool { self.dkg_phase.is_dkg_vacant() }
fn get_dkg_index(&self) -> DkgIndex { self.index }
fn get_phase(&self) -> DkgPhase { self.phase }
fn get_block(&self) -> Block { self.block }
fn get_dkg_index(&self) -> DkgIndex { self.dkg_index }
fn get_phase(&self) -> DkgPhase { self.dkg_phase }
fn get_release_block(&self) -> Block { self.release_block }
fn restart(&mut self) {
self.phase = Default::default();
self.index = self.index.checked_add(1)
self.dkg_phase = Default::default();
self.dkg_index = self.dkg_index.checked_add(1)
.unwrap_or_default();
}
fn next_phase(&mut self) {
self.phase.next_phase()
self.dkg_phase.next_phase()
}
fn set_block(&mut self, block: Block) {
self.block = block;
fn set_release_block(&mut self, release_block: Block) {
self.release_block = release_block;
}
}

View File

@ -6,8 +6,8 @@ use sp_runtime::{
};
use crate::{
EvmAddress, EvmBytes32, ExodusSession, RoastSession,
MAX_MESSAGE_SIZE,
EvmAddress, EvmBytes32, ExodusContext, RoastSession, EvmSessionsBundle,
Config, BalanceOf, MAX_MESSAGE_SIZE,
};
pub trait EvmAbiEncode {
@ -172,8 +172,9 @@ pub enum GovernanceStatus {
RewardManager = 4,
}
#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, RuntimeDebug)]
#[derive(Encode, Decode, Clone, Copy, PartialEq, TypeInfo, RuntimeDebug)]
pub enum GovernanceAction {
SetRouterRegistryIndex { index: u128 },
SetDistributor { distributor: EvmAddress },
SetWarmupPeriod { warmup_period: u128 },
UpdateGatekeeperAddress { new_gatekeeper: EvmAddress },
@ -188,6 +189,7 @@ pub enum GovernanceAction {
ForfeitReserves { router: EvmAddress, liquidity: u128, destroyer_mode: bool },
RedeemReserves { router: EvmAddress, amount: u128 },
Withdraw { token: EvmAddress, amount: u128 },
SetBondDepository { address: EvmAddress },
AuditReserves,
Close { id: u128 },
@ -198,6 +200,8 @@ pub enum GovernanceAction {
intervals: [u32; 2],
booleans: [bool; 2],
},
Approve { spender: EvmAddress, value: u128, },
}
impl GovernanceAction {
@ -206,6 +210,13 @@ impl GovernanceAction {
message_buffer: &'a mut [u8; MAX_MESSAGE_SIZE as usize],
) -> Option<&'a [u8]> {
match &self {
Self::SetRouterRegistryIndex { index } => {
abi_encode_evm_message!(
message_buffer,
"setRouterRegistryIndex(uint256)",
[index]
)
},
Self::SetDistributor { distributor } => {
abi_encode_evm_message!(
message_buffer,
@ -290,6 +301,13 @@ impl GovernanceAction {
[token, amount]
)
},
Self::SetBondDepository { address } => {
abi_encode_evm_message!(
message_buffer,
"setBondDepository(address)",
[address]
)
},
Self::AuditReserves => {
abi_encode_evm_message!(message_buffer, "auditReserves()", [])
},
@ -304,64 +322,75 @@ impl GovernanceAction {
[market, terms, token, intervals, &booleans_u8]
)
},
Self::Approve { spender, value } => {
abi_encode_evm_message!(
message_buffer,
"approve(address,uint256)",
[spender, value]
)
},
}
}
}
#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, RuntimeDebug)]
pub struct ExodusRequest<NetworkId, Balance> {
pub r#type: ExodusRequestType<NetworkId, Balance>,
#[scale_info(skip_type_params(T))]
pub struct ExodusRequest<T: Config> {
pub r#type: ExodusRequestType<T>,
pub next_roast_session: RoastSession,
}
#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, RuntimeDebug)]
pub enum ExodusRequestType<NetworkId, Balance> {
#[scale_info(skip_type_params(T))]
pub enum ExodusRequestType<T: Config> {
EvmRotation {
exodus_session: ExodusSession,
public_key: EvmBytes32,
parity: u8,
xor_key: u128,
bitmask: u128,
sessions: EvmSessionsBundle<T>,
},
UtxoRotation,
EvmBridgeOut {
exodus_session: ExodusSession,
network_id: NetworkId,
amount: Balance,
bounty: Perbill,
receiver: EvmAddress,
},
EvmGovernance {
exodus_session: ExodusSession,
network_id: NetworkId,
target: EvmAddress,
action: GovernanceAction,
},
UtxoBridgeOut { network_id: NetworkId },
EvmBridgeOut { amount: BalanceOf<T>, bounty: Perbill, receiver: EvmAddress },
EvmGovernance { target: EvmAddress, action: GovernanceAction },
UtxoBridgeOut,
}
impl<NetworkId, Balance> ExodusRequestType<NetworkId, Balance>
impl<T: Config> ExodusRequestType<T>
where
NetworkId: UniqueSaturatedInto<u64> + Copy,
Balance: UniqueSaturatedInto<u128> + Copy,
BalanceOf<T>: UniqueSaturatedInto<u128> + Copy,
{
pub fn get_message<'a>(
pub fn get_message<'a, NetworkId>(
&self,
message_buffer: &'a mut [u8; MAX_MESSAGE_SIZE as usize],
) -> Option<&'a [u8]> {
exodus_context: ExodusContext<NetworkId>,
) -> Option<&'a [u8]>
where
NetworkId: UniqueSaturatedInto<u64> + Copy + Default,
{
match &self {
ExodusRequestType::EvmRotation { exodus_session, public_key, parity } => {
ExodusRequestType::EvmRotation { public_key, xor_key, bitmask, sessions } => {
let mut buffer = [0u8; 32];
buffer[..16].copy_from_slice(&xor_key.to_be_bytes());
buffer[16..].copy_from_slice(&bitmask.to_be_bytes());
let packed_bits = EvmBytes32::from(&buffer);
let payload = sessions.as_slice();
abi_encode_evm_message!(
message_buffer,
"rotate(uint256,bytes32,uint8)",
[exodus_session, public_key, parity]
"rotate(uint256,bytes32,uint256,bytes)",
[&exodus_context.session, public_key, &packed_bits, payload]
)
},
ExodusRequestType::EvmGovernance { exodus_session, network_id, target, action } => {
ExodusRequestType::EvmGovernance { target, action } => {
let mut action_buffer = [0u8; MAX_MESSAGE_SIZE as usize];
let action_payload = action.get_message(&mut action_buffer)?;
let mut metadata_buffer = [0u8; 32];
let chain_id_u64: u64 = exodus_context.network_id
.unique_saturated_into();
let chain_id_u64: u64 = (*network_id).unique_saturated_into();
metadata_buffer[0..8].copy_from_slice(&chain_id_u64.to_be_bytes());
metadata_buffer[8..28].copy_from_slice(target.as_ref());
@ -370,14 +399,16 @@ where
abi_encode_evm_message!(
message_buffer,
"govern(uint256,uint256,bytes)",
[exodus_session, &packed_metadata, action_payload]
[&exodus_context.session, &packed_metadata, action_payload]
)
},
ExodusRequestType::EvmBridgeOut { exodus_session, network_id, amount, bounty, receiver } => {
ExodusRequestType::EvmBridgeOut { amount, bounty, receiver } => {
let mut buffer = [0u8; 32];
buffer[0..20].copy_from_slice(receiver.as_ref());
let chain_id_u64: u64 = (*network_id).unique_saturated_into();
let chain_id_u64: u64 = exodus_context.network_id
.unique_saturated_into();
buffer[20..28].copy_from_slice(&chain_id_u64.to_be_bytes());
let scaled_bounty = multiply_by_rational_with_rounding(
@ -387,7 +418,7 @@ where
sp_runtime::Rounding::Down
)?;
let bounty_uint32 = scaled_bounty as u32;
let bounty_uint32 = scaled_bounty.min(u32::MAX as u128) as u32;
buffer[28..32].copy_from_slice(&bounty_uint32.to_be_bytes());
let mut amount_buffer = [0u8; 32];
@ -400,49 +431,78 @@ where
abi_encode_evm_message!(
message_buffer,
"recall(uint256,uint256,uint256)",
[exodus_session, &amount_u256, &packed_metadata]
[&exodus_context.session, &amount_u256, &packed_metadata]
)
}
_ => None
}
}
pub fn is_evm_rotation(&self) -> bool {
match &self {
ExodusRequestType::EvmRotation { .. } => true,
_ => false,
}
}
pub fn is_utxo_rotation(&self) -> bool {
match &self {
ExodusRequestType::UtxoRotation { .. } => true,
_ => false,
}
}
pub fn is_bridge_out(&self) -> bool {
match &self {
ExodusRequestType::EvmBridgeOut { .. } | ExodusRequestType::UtxoBridgeOut { .. } => true,
_ => false,
}
}
pub fn is_evm_governance(&self) -> bool {
match &self {
ExodusRequestType::EvmGovernance { .. } => true,
_ => false,
}
}
}
impl<NetworkId, Balance> ExodusRequest<NetworkId, Balance>
impl<T: Config> ExodusRequest<T>
where
NetworkId: UniqueSaturatedInto<u64> + Copy,
Balance: UniqueSaturatedInto<u128> + Copy,
BalanceOf<T>: UniqueSaturatedInto<u128> + Copy,
{
pub fn get_message<'a>(
pub fn get_message<'a, NetworkId>(
&self,
message_buffer: &'a mut [u8; MAX_MESSAGE_SIZE as usize],
) -> Option<&'a [u8]> {
self.r#type.get_message(message_buffer)
exodus_context: ExodusContext<NetworkId>,
) -> Option<&'a [u8]>
where
NetworkId: UniqueSaturatedInto<u64> + Copy + Default,
{
self.r#type.get_message(message_buffer, exodus_context)
}
pub fn evm_rotation(
exodus_session: ExodusSession,
public_key: EvmBytes32,
parity: u8,
xor_key: u128,
bitmask: u128,
sessions: EvmSessionsBundle<T>,
) -> Self {
let rotation = ExodusRequestType::EvmRotation {
exodus_session,
public_key,
parity,
xor_key,
bitmask,
sessions,
};
Self::default_request_for(rotation)
}
pub fn evm_bridge_out(
exodus_session: ExodusSession,
network_id: NetworkId,
amount: Balance,
amount: BalanceOf<T>,
bounty: Perbill,
receiver: EvmAddress,
receiver: EvmAddress
) -> Self {
let evm_bridge_out = ExodusRequestType::EvmBridgeOut {
exodus_session,
network_id,
amount,
bounty,
receiver,
@ -450,81 +510,93 @@ where
Self::default_request_for(evm_bridge_out)
}
pub fn evm_governance(
exodus_session: ExodusSession,
network_id: NetworkId,
target: EvmAddress,
action: GovernanceAction,
) -> Self {
pub fn evm_governance(target: EvmAddress, action: GovernanceAction) -> Self {
let evm_governance = ExodusRequestType::EvmGovernance {
exodus_session,
network_id,
target,
action,
};
Self::default_request_for(evm_governance)
}
pub fn is_rotation(&self) -> bool {
match &self.r#type {
ExodusRequestType::EvmRotation { .. } | ExodusRequestType::UtxoRotation { .. } => true,
_ => false,
fn default_request_for(request_type: ExodusRequestType<T>) -> Self {
Self {
r#type: request_type,
next_roast_session: Default::default()
}
}
fn default_request_for(
request_type: ExodusRequestType<NetworkId, Balance>,
) -> Self {
Self { r#type: request_type, next_roast_session: Default::default() }
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mock::*;
use sp_io::hashing::keccak_256;
#[test]
fn test_rotation_message_valid() {
let mut buffer = [0u8; MAX_MESSAGE_SIZE as usize];
let public_key = EvmBytes32::repeat_byte(0x11);
let session = 69u64;
let session = 69;
let network_id = 420;
let rotation_request = ExodusRequest::<u64, u128>::evm_rotation(
session,
let context = ExodusContext::new(network_id, session);
let dummy_xor_key = u128::MAX;
let dummy_evm_bitmask = 1337;
let mut dummy_packed_sessions =
EvmSessionsBundle::<Runtime>::with_max_capacity();
let max_size = EvmSessionsBundle::<Runtime>::bound();
dummy_packed_sessions.bounded_resize(max_size, 69);
let rotation_request = ExodusRequest::<Runtime>::evm_rotation(
public_key.clone(),
1,
dummy_xor_key,
dummy_evm_bitmask,
dummy_packed_sessions,
);
let calldata = rotation_request.get_message(&mut buffer).unwrap();
assert_eq!(calldata.len(), 100);
let calldata = rotation_request.get_message(&mut buffer, context).unwrap();
// prefix + public_key + packed_bits + pointer + length + bytes array + padding
// 4 + 32 + 32 + 32 + 32 + 32 + (8 * 5) + (32 - 40 % 32)
assert_eq!(calldata.len(), 228);
let expected_hash = keccak_256("rotate(uint256,bytes32,uint8)".as_bytes());
let expected_hash = keccak_256("rotate(uint256,bytes32,uint256,bytes)".as_bytes());
assert_eq!(calldata[0..4], expected_hash[0..4]);
assert_eq!(calldata[35], 0x45);
assert_eq!(&calldata[4..35], &[0u8; 31]);
assert_eq!(&calldata[36..68], public_key.as_ref());
assert_eq!(calldata[99], 1);
assert_eq!(&calldata[68..99], &[0u8; 31]);
assert_eq!(&calldata[68..84], &dummy_xor_key.to_be_bytes());
assert_eq!(&calldata[84..100], &dummy_evm_bitmask.to_be_bytes());
assert_eq!(&calldata[100..131], &[0u8; 31]);
assert_eq!(calldata[131], 128);
assert_eq!(&calldata[132..163], &[0u8; 31]);
assert_eq!(calldata[163], 40);
assert_eq!(calldata[164], 69);
assert_eq!(&calldata[204..228], &[0u8; 24]);
}
#[test]
fn test_bridge_out_message_valid() {
let mut buffer = [0u8; MAX_MESSAGE_SIZE as usize];
let receiver = EvmAddress::repeat_byte(0xAA);
let network_id = 420u64;
let session = 69u64;
let network_id = 420;
let session = 69;
let bridge_request = ExodusRequest::<u64, u128>::evm_bridge_out(
session,
network_id,
1_000_000u128,
let context = ExodusContext::new(network_id, session);
let bridge_request = ExodusRequest::<Runtime>::evm_bridge_out(
1_000_000u64,
sp_runtime::Perbill::from_percent(1),
receiver.clone()
);
let calldata = bridge_request.get_message(&mut buffer).unwrap();
let calldata = bridge_request.get_message(&mut buffer, context).unwrap();
assert_eq!(calldata.len(), 100);
let expected_hash = keccak_256("recall(uint256,uint256,uint256)".as_bytes());
@ -549,8 +621,8 @@ mod tests {
fn test_governance_message_valid() {
let mut buffer = [0u8; MAX_MESSAGE_SIZE as usize];
let target = EvmAddress::repeat_byte(0xAA);
let network_id = 420u64;
let session = 69u64;
let network_id = 420;
let session = 69;
let actions = vec![
GovernanceAction::SetDistributor { distributor: EvmAddress::repeat_byte(0x11) },
@ -592,16 +664,16 @@ mod tests {
for (index, action) in actions.into_iter().enumerate() {
let action_clone = action.clone();
let current_session = session + (index as u64);
let context = ExodusContext::new(network_id, current_session);
let governance_request = ExodusRequest::<u64, u128>::evm_governance(
session,
network_id,
let governance_request = ExodusRequest::<Runtime>::evm_governance(
target,
action,
);
let message = governance_request
.get_message(&mut buffer)
.get_message(&mut buffer, context)
.get_or_insert(&[])
.to_vec();
@ -610,7 +682,7 @@ mod tests {
let expected_master_hash = keccak_256("govern(uint256,uint256,bytes)".as_bytes());
assert_eq!(message[0..4], expected_master_hash[0..4]);
assert_eq!(message[35], 69);
assert_eq!(message[35], current_session as u8);
assert_eq!(&message[4..35], &[0u8; 31]);
assert_eq!(message[43], 0xA4);
@ -626,6 +698,12 @@ mod tests {
assert_eq!((message.len() - 4) % 32, 0);
match action_clone {
GovernanceAction::SetRouterRegistryIndex { index } => {
let hash = keccak_256("setRouterRegistryIndex(uint256)".as_bytes());
assert_eq!(inner_payload[0..4], hash[0..4]);
let decoded_warmup = u32::from_be_bytes(inner_payload[32..36].try_into().unwrap());
assert_eq!(decoded_warmup, index as u32);
},
GovernanceAction::SetDistributor { distributor } => {
let hash = keccak_256("setDistributor(address)".as_bytes());
assert_eq!(inner_payload[0..4], hash[0..4]);
@ -634,7 +712,8 @@ mod tests {
GovernanceAction::SetWarmupPeriod { warmup_period } => {
let hash = keccak_256("setWarmupPeriod(uint256)".as_bytes());
assert_eq!(inner_payload[0..4], hash[0..4]);
assert_eq!(inner_payload[inner_payload.len() - 1], warmup_period as u8);
let decoded_warmup = u32::from_be_bytes(inner_payload[32..36].try_into().unwrap());
assert_eq!(decoded_warmup, warmup_period as u32);
},
GovernanceAction::UpdateGatekeeperAddress { new_gatekeeper } => {
let hash = keccak_256("updateGatekeeperAddress(address)".as_bytes());
@ -644,7 +723,8 @@ mod tests {
GovernanceAction::SetBounty { bounty } => {
let hash = keccak_256("setBounty(uint256)".as_bytes());
assert_eq!(inner_payload[0..4], hash[0..4]);
assert_eq!(inner_payload[inner_payload.len() - 1], bounty as u8);
let decoded_bounty = u32::from_be_bytes(inner_payload[32..36].try_into().unwrap());
assert_eq!(decoded_bounty, bounty as u32);
},
GovernanceAction::AddPool { address } => {
let hash = keccak_256("addPool(address)".as_bytes());
@ -654,13 +734,16 @@ mod tests {
GovernanceAction::RemovePool { index } => {
let hash = keccak_256("removePool(uint256)".as_bytes());
assert_eq!(inner_payload[0..4], hash[0..4]);
assert_eq!(inner_payload[inner_payload.len() - 1], index as u8);
let decoded_index = u32::from_be_bytes(inner_payload[32..36].try_into().unwrap());
assert_eq!(decoded_index, index as u32);
},
GovernanceAction::SetAdjustment { rate, target, add } => {
let hash = keccak_256("setAdjustment(uint256,uint256,bool)".as_bytes());
assert_eq!(inner_payload[0..4], hash[0..4]);
assert_eq!(inner_payload[35], rate as u8);
assert_eq!(inner_payload[67], target as u8);
let decoded_rate = u32::from_be_bytes(inner_payload[32..36].try_into().unwrap());
assert_eq!(decoded_rate, rate as u32);
let decoded_target = u32::from_be_bytes(inner_payload[64..68].try_into().unwrap());
assert_eq!(decoded_target, target as u32);
assert_eq!(inner_payload[99], add as u8);
},
GovernanceAction::Enable { status, address, calculator } => {
@ -680,7 +763,8 @@ mod tests {
let hash = keccak_256("forfeitReserves(address,uint256,bool)".as_bytes());
assert_eq!(inner_payload[0..4], hash[0..4]);
assert_eq!(&inner_payload[16..36], router.as_ref());
assert_eq!(inner_payload[67], liquidity as u8);
let decoded_liquidity = u32::from_be_bytes(inner_payload[64..68].try_into().unwrap());
assert_eq!(decoded_liquidity, liquidity as u32);
assert_eq!(inner_payload[99], destroyer_mode as u8);
},
GovernanceAction::RedeemReserves { router, amount } => {
@ -693,7 +777,13 @@ mod tests {
let hash = keccak_256("withdraw(address,uint256)".as_bytes());
assert_eq!(inner_payload[0..4], hash[0..4]);
assert_eq!(&inner_payload[16..36], token.as_ref());
assert_eq!(inner_payload[67], amount as u8);
let decoded_amount = u32::from_be_bytes(inner_payload[64..68].try_into().unwrap());
assert_eq!(decoded_amount, amount as u32);
},
GovernanceAction::SetBondDepository { address } => {
let hash = keccak_256("setBondDepository(address)".as_bytes());
assert_eq!(inner_payload[0..4], hash[0..4]);
assert_eq!(&inner_payload[16..36], address.as_ref());
},
GovernanceAction::AuditReserves => {
let hash = keccak_256("auditReserves()".as_bytes());
@ -703,7 +793,8 @@ mod tests {
GovernanceAction::Close { id } => {
let hash = keccak_256("close(uint256)".as_bytes());
assert_eq!(inner_payload[0..4], hash[0..4]);
assert_eq!(inner_payload[inner_payload.len() - 1], id as u8);
let decoded_id = u32::from_be_bytes(inner_payload[32..36].try_into().unwrap());
assert_eq!(decoded_id, id as u32);
},
GovernanceAction::Create { market, terms, token, intervals, booleans } => {
let hash = keccak_256("create(uint256[4],uint256[2],address,uint32[2],bool[2])".as_bytes());
@ -726,6 +817,13 @@ mod tests {
assert_eq!(inner_payload[323], booleans[0] as u8);
assert_eq!(inner_payload[355], booleans[1] as u8);
},
GovernanceAction::Approve { spender, value } => {
let hash = keccak_256("approve(address,uint256)".as_bytes());
assert_eq!(inner_payload[0..4], hash[0..4]);
assert_eq!(&inner_payload[16..36], spender.as_ref());
let decoded_amount = u32::from_be_bytes(inner_payload[64..68].try_into().unwrap());
assert_eq!(decoded_amount, value as u32);
}
}
}

View File

@ -8,7 +8,7 @@ use sp_runtime::{
};
use super::{DkgPackage, ExodusPackage, ExodusOk, PackageContext};
use crate::{AuthIndex, ExodusSession, ExodusHash, ExodusError};
use crate::{AuthIndex, ExodusContext, DkgIndex, ExodusHash, ExodusError};
fn create_storage_key(prefix: &[u8], index: &[u8], network: &[u8]) -> Vec<u8> {
let mut key = crate::DB_STORAGE_PREFIX.to_vec();
@ -59,10 +59,10 @@ pub struct ExodusShare;
#[derive(Default, Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)]
pub struct ExodusExile;
type ExodusNonces<BlockNumber> = BTreeMap<ExodusSession, PersistentData<BlockNumber, ExodusNonce>>;
type ExodusGroups<BlockNumber> = BTreeMap<ExodusSession, PersistentData<BlockNumber, ExodusGroup>>;
type ExodusShares<BlockNumber> = BTreeMap<ExodusSession, PersistentData<BlockNumber, ExodusShare>>;
type ExodusExiles<BlockNumber> = BTreeMap<ExodusSession, PersistentData<BlockNumber, ExodusExile>>;
type ExodusNonces<NetworkId, BlockNumber> = BTreeMap<ExodusContext<NetworkId>, PersistentData<BlockNumber, ExodusNonce>>;
type ExodusGroups<NetworkId, BlockNumber> = BTreeMap<ExodusContext<NetworkId>, PersistentData<BlockNumber, ExodusGroup>>;
type ExodusShares<NetworkId, BlockNumber> = BTreeMap<ExodusContext<NetworkId>, PersistentData<BlockNumber, ExodusShare>>;
type ExodusExiles<NetworkId, BlockNumber> = BTreeMap<ExodusContext<NetworkId>, PersistentData<BlockNumber, ExodusExile>>;
pub struct ExodusStorageWrapper<ExodusInnerType> {
pub inner: ExodusInnerType,
@ -75,7 +75,7 @@ impl<ExodusInnerType> ExodusStorageWrapper<ExodusInnerType> {
}
}
#[derive(Default)]
#[derive(Default, Encode)]
pub struct ExodusStorage<BlockNumber, NetworkCurve>
where
BlockNumber: Default,
@ -113,34 +113,37 @@ where
self
}
pub fn read_local_storage(&self) -> ExodusRecords<B> {
pub fn read_local_storage<NI>(&self) -> ExodusRecords<NI, B>
where
NI: Default + Encode + Decode + AtLeast32BitUnsigned,
{
let encoded_index = self.authority_index.encode();
let encoded_network = self.network_curve.encode();
let nonces_key = create_storage_key(crate::EXODUS_PREFIX_NONCES, &encoded_index, &encoded_network);
let exodus_nonces = StorageValueRef::persistent(&nonces_key)
.get::<ExodusNonces<B>>()
.get::<ExodusNonces<NI, B>>()
.ok()
.flatten()
.unwrap_or_default();
let shares_key = create_storage_key(crate::EXODUS_PREFIX_SHARES, &encoded_index, &encoded_network);
let exodus_shares = StorageValueRef::persistent(&shares_key)
.get::<ExodusShares<B>>()
.get::<ExodusShares<NI, B>>()
.ok()
.flatten()
.unwrap_or_default();
let groups_key = create_storage_key(crate::EXODUS_PREFIX_BLENDS, &encoded_index, &encoded_network);
let exodus_groups = StorageValueRef::persistent(&groups_key)
.get::<ExodusGroups<B>>()
.get::<ExodusGroups<NI, B>>()
.ok()
.flatten()
.unwrap_or_default();
let exiles_key = create_storage_key(crate::EXODUS_PREFIX_EXILES, &encoded_index, &encoded_network);
let exodus_exiles = StorageValueRef::persistent(&exiles_key)
.get::<ExodusExiles<B>>()
.get::<ExodusExiles<NI, B>>()
.ok()
.flatten()
.unwrap_or_default();
@ -153,10 +156,11 @@ where
}
}
pub fn prepare_signed_call_with_context<T, A, F1, F2, F3>(
pub fn prepare_signed_call_with_context<T, A, F1, F2, F3, NI>(
&self,
authority_key: &A,
exodus_records: &mut ExodusRecords<B>,
current_dkg_index: DkgIndex,
exodus_records: &mut ExodusRecords<NI, B>,
make_data: F1,
submit_call: F2,
insert_data: F3,
@ -164,13 +168,16 @@ where
where
T: crate::pallet::Config,
A: RuntimeAppPublic,
F1: FnOnce(PackageContext<NC>) -> Result<ExodusPackage<NC, T>, ExodusError>,
F2: FnOnce(ExodusPackage<NC, T>, A::Signature) -> Result<(), ExodusError>,
F3: FnOnce(&mut ExodusRecords<B>, B),
NI: Default + Encode + Decode + AtLeast32BitUnsigned,
F1: FnOnce(PackageContext<NC, B>) -> Result<ExodusPackage<NC, NI, B, T>, ExodusError>,
F2: FnOnce(ExodusPackage<NC, NI, B, T>, A::Signature) -> Result<(), ExodusError>,
F3: FnOnce(&mut ExodusRecords<NI, B>, B),
{
let context = PackageContext::default()
.with_block_number(self.current_block)
.with_authority_index(self.authority_index)
.with_network_curve(self.network_curve);
.with_network_curve(self.network_curve)
.with_dkg_index(current_dkg_index);
let package = make_data(context)?;
@ -203,7 +210,10 @@ where
StorageValueRef::persistent(&exiles_key).clear();
}
pub fn try_write_local_storage(&self, records: &ExodusRecords<B>) {
pub fn try_write_local_storage<NI: Default>(&self, records: &ExodusRecords<NI, B>)
where
NI: Encode + Decode + AtLeast32BitUnsigned,
{
let encoded_index = self.authority_index.encode();
let encoded_network = self.network_curve.encode();
@ -229,99 +239,104 @@ where
}
}
pub struct ExodusRecords<BlockNumber>
pub struct ExodusRecords<NetworkId, BlockNumber>
where
NetworkId: Default,
BlockNumber: AtLeast32BitUnsigned,
{
exodus_nonces: ExodusStorageWrapper<ExodusNonces<BlockNumber>>,
exodus_shares: ExodusStorageWrapper<ExodusShares<BlockNumber>>,
exodus_groups: ExodusStorageWrapper<ExodusGroups<BlockNumber>>,
exodus_exiles: ExodusStorageWrapper<ExodusExiles<BlockNumber>>,
exodus_nonces: ExodusStorageWrapper<ExodusNonces<NetworkId, BlockNumber>>,
exodus_shares: ExodusStorageWrapper<ExodusShares<NetworkId, BlockNumber>>,
exodus_groups: ExodusStorageWrapper<ExodusGroups<NetworkId, BlockNumber>>,
exodus_exiles: ExodusStorageWrapper<ExodusExiles<NetworkId, BlockNumber>>,
}
impl<BlockNumber> ExodusRecords<BlockNumber>
impl<NetworkId, BlockNumber> ExodusRecords<NetworkId, BlockNumber>
where
BlockNumber: AtLeast32BitUnsigned + Default,
NetworkId: AtLeast32BitUnsigned + Default,
{
pub fn get_secret_nonce(&self, exodus_session: &ExodusSession) -> Option<&[u8]> {
pub fn get_secret_nonce(
&self,
exodus_context: &ExodusContext<NetworkId>,
) -> Option<&[u8]> {
self.exodus_nonces.inner
.get(exodus_session)
.get(exodus_context)
.map(|v| v.data.0.as_slice())
}
pub fn get_group_record(
&self,
exodus_session: &ExodusSession,
exodus_context: &ExodusContext<NetworkId>,
) -> Option<ExodusGroup> {
self.exodus_groups.inner
.get(exodus_session)
.get(exodus_context)
.cloned()
.map(|storage| storage.data)
}
pub fn is_exodus_session_banned(
&self,
exodus_session: &ExodusSession,
exodus_context: &ExodusContext<NetworkId>,
) -> bool {
self.exodus_exiles.inner.contains_key(exodus_session)
self.exodus_exiles.inner.contains_key(exodus_context)
}
pub fn is_group_registered(
&self,
exodus_session: &ExodusSession,
exodus_context: &ExodusContext<NetworkId>,
) -> bool {
self.exodus_groups.inner.contains_key(exodus_session)
self.exodus_groups.inner.contains_key(exodus_context)
}
pub fn is_waiting_period_for_nonce(
&self,
exodus_session: &ExodusSession,
current_block: &BlockNumber
exodus_context: &ExodusContext<NetworkId>,
current_block: &BlockNumber,
) -> bool {
self.exodus_nonces.inner
.get(exodus_session)
.get(exodus_context)
.map(|data| data.release_block.gt(current_block))
.unwrap_or(false)
}
pub fn is_waiting_period_for_share(
&self,
exodus_session: &ExodusSession,
exodus_context: &ExodusContext<NetworkId>,
current_block: &BlockNumber,
) -> bool {
self.exodus_shares.inner
.get(exodus_session)
.get(exodus_context)
.map(|data| data.release_block.gt(&current_block))
.unwrap_or(false)
}
pub fn is_waiting_period_for_group(
&self,
exodus_session: &ExodusSession,
exodus_context: &ExodusContext<NetworkId>,
current_block: &BlockNumber,
) -> bool {
self.exodus_groups.inner
.get(exodus_session)
.get(exodus_context)
.map(|data| data.release_block.gt(&current_block))
.unwrap_or(false)
}
pub fn exclude_exodus_session(
&mut self,
exodus_session: ExodusSession,
exodus_context: ExodusContext<NetworkId>
) {
let exodus_exile = PersistentData {
data: ExodusExile,
release_block: Default::default(),
};
self.exodus_exiles.inner.insert(exodus_session, exodus_exile);
self.exodus_exiles.data_changed = true;
self.exodus_exiles.inner.insert(exodus_context, exodus_exile);
}
pub fn insert_exodus_nonce(
&mut self,
exodus_session: ExodusSession,
exodus_context: ExodusContext<NetworkId>,
next_release_block: BlockNumber,
nonce_commitment: Vec<u8>,
) {
@ -330,13 +345,13 @@ where
release_block: next_release_block,
};
self.exodus_nonces.inner.insert(exodus_session, exodus_nonce);
self.exodus_nonces.data_changed = true;
self.exodus_nonces.inner.insert(exodus_context, exodus_nonce);
}
pub fn insert_exodus_share(
&mut self,
exodus_session: ExodusSession,
exodus_context: ExodusContext<NetworkId>,
next_release_block: BlockNumber,
) {
let exodus_share = PersistentData {
@ -344,13 +359,13 @@ where
release_block: next_release_block,
};
self.exodus_shares.inner.insert(exodus_session, exodus_share);
self.exodus_shares.data_changed = true;
self.exodus_shares.inner.insert(exodus_context, exodus_share);
}
pub fn insert_exodus_group(
&mut self,
exodus_session: ExodusSession,
exodus_context: ExodusContext<NetworkId>,
next_release_block: BlockNumber,
self_binding_factor: Vec<u8>,
binding_factors_proof: Vec<ExodusHash>,
@ -360,12 +375,12 @@ where
release_block: next_release_block,
};
self.exodus_groups.inner.insert(exodus_session, exodus_group);
self.exodus_groups.data_changed = true;
self.exodus_groups.inner.insert(exodus_context, exodus_group);
}
}
#[derive(Default, Clone, Copy)]
#[derive(Default, Encode, Clone, Copy)]
pub struct DkgStorage<BlockNumber, NetworkCurve>
where
BlockNumber: Copy + Clone,
@ -406,10 +421,10 @@ where
self.current_block.saturating_add(self.block_longevity)
}
pub fn get_dkg_result(
pub fn get_dkg_result<NI>(
&self,
dkg_record: DkgRecord<B>,
) -> Result<ExodusOk<NC>, ExodusError> {
) -> Result<ExodusOk<NC, NI>, ExodusError> {
match dkg_record.prefix_slice {
crate::DKG_ROUND0_PREFIX => return Ok(ExodusOk::DkgRoundRegistered(0u8, self.authority_index, self.network_curve)),
crate::DKG_ROUND1_PREFIX => return Ok(ExodusOk::DkgRoundRegistered(1u8, self.authority_index, self.network_curve)),
@ -454,6 +469,7 @@ where
pub fn prepare_signed_call_with_context<T, A, F1, F2>(
&self,
authority_key: &A,
current_dkg_index: DkgIndex,
dkg_record: &mut DkgRecord<B>,
make_data: F1,
submit_call: F2,
@ -461,12 +477,14 @@ where
where
T: crate::pallet::Config,
A: RuntimeAppPublic,
F1: FnOnce(PackageContext<NC>) -> Result<DkgPackage<NC, T>, ExodusError>,
F2: FnOnce(DkgPackage<NC, T>, A::Signature) -> Result<(), ExodusError>,
F1: FnOnce(PackageContext<NC, B>) -> Result<DkgPackage<NC, B, T>, ExodusError>,
F2: FnOnce(DkgPackage<NC, B, T>, A::Signature) -> Result<(), ExodusError>,
{
let context = PackageContext::default()
.with_block_number(self.current_block)
.with_authority_index(self.authority_index)
.with_network_curve(self.network_curve);
.with_network_curve(self.network_curve)
.with_dkg_index(current_dkg_index);
let package = make_data(context)?;

View File

@ -3,37 +3,85 @@ use scale_info::TypeInfo;
use sp_runtime::RuntimeDebug;
use codec::{Encode, Decode};
use sp_runtime::traits::UniqueSaturatedInto;
use frame_support::{BoundedVec, traits::ConstU32};
use crate::{AuthIndex, ExodusSession, ExodusHash, ExodusError};
use ghost_traits::bounded_bitmap::BoundedBitmapReader;
pub trait PackageMetadata<NetworkCurve>
use crate::{
AuthIndex, DkgIndex, ExodusSession, ExodusHash, ExodusError,
ParticipantsBitmap,
};
pub trait IndexedBundle<T>
where
T: crate::pallet::Config,
{
fn blob(&self) -> &[u8];
fn bitmask(&self) -> &ParticipantsBitmap<T>;
fn get_package_by_index<D: Decode>(&self, index: AuthIndex, size: usize) -> Option<D> {
let index_usize = index.unique_saturated_into();
let popcount = self.bitmask().count_ones_before::<usize>(index_usize)?;
popcount.checked_mul(size)
.and_then(|start| start.checked_add(size).map(|end| (start, end)))
.and_then(|(start, end)| self.blob().get(start..end))
.and_then(|raw_slice| D::decode(&mut &raw_slice[..]).ok())
}
}
pub trait PackageMetadata<NetworkCurve, BlockNumber>
where
NetworkCurve: Copy,
BlockNumber: Copy,
{
fn context(&self) -> &PackageContext<NetworkCurve>;
fn package_context(&self) -> &PackageContext<NetworkCurve, BlockNumber>;
fn get_authority_index(&self) -> AuthIndex {
self.context().get_authority_index()
self.package_context().get_authority_index()
}
fn get_network_curve(&self) -> NetworkCurve {
self.context().get_network_curve()
self.package_context().get_network_curve()
}
fn get_dkg_index(&self) -> DkgIndex {
self.package_context().get_dkg_index()
}
fn get_block_number(&self) -> BlockNumber {
self.package_context().get_block_number()
}
fn get_metadata(&self) -> (NetworkCurve, AuthIndex, DkgIndex) {
let network_curve = self.get_network_curve();
let authority_index = self.get_authority_index();
let dkg_index = self.get_dkg_index();
(network_curve, authority_index, dkg_index)
}
}
pub trait ExodusMetadata<NetworkId>
where
NetworkId: Default + Copy,
{
fn get_exodus_context(&self) -> &ExodusContext<NetworkId>;
fn copy_exodus_context(&self) -> ExodusContext<NetworkId>;
}
#[derive(Encode, Decode, Clone, RuntimeDebug, TypeInfo, PartialEq, Eq)]
#[scale_info(skip_type_params(T))]
pub enum ExodusPackage<NetworkCurve, T>
pub enum ExodusPackage<NetworkCurve, NetworkId, BlockNumber, T>
where
NetworkId: Default,
T: crate::pallet::Config,
{
NonceCommitment(ExodusNonceCommitment<NetworkCurve>),
GroupCommitment(ExodusGroupCommitment<NetworkCurve>),
SignatureShare(ExodusSignatureShare<NetworkCurve, T>),
NonceCommitment(ExodusNonceCommitment<NetworkCurve, NetworkId, BlockNumber>),
GroupCommitment(ExodusGroupCommitment<NetworkCurve, NetworkId, BlockNumber>),
SignatureShare(ExodusSignatureShare<NetworkCurve, NetworkId, BlockNumber, T>),
}
impl<NetworkCurve, T> ExodusPackage<NetworkCurve, T>
impl<NC, NI, B, T> ExodusPackage<NC, NI, B, T>
where
T: crate::pallet::Config,
NI: Default,
{
pub fn bytes_len(&self) -> u32 {
let length = match self {
@ -51,32 +99,24 @@ where
length as u32
}
pub fn get_exodus_session(&self) -> ExodusSession {
match &self {
Self::SignatureShare(package) => package.session,
Self::NonceCommitment(package) => package.session,
Self::GroupCommitment(package) => package.session,
}
}
}
#[derive(Encode, Decode, Clone, RuntimeDebug, TypeInfo, PartialEq, Eq)]
#[derive(RuntimeDebug, Encode, Decode, Clone, TypeInfo, PartialEq, Eq)]
#[scale_info(skip_type_params(T))]
pub enum DkgPackage<NetworkCurve, T>
pub enum DkgPackage<NetworkCurve, BlockNumber, T>
where
T: crate::pallet::Config,
{
Round0(Round0Package<NetworkCurve>),
Round1(Round1Package<NetworkCurve, T>),
Round2(Round2Package<NetworkCurve, T>),
Round3(Round3Package<NetworkCurve, T>),
Round4(Round4Package<NetworkCurve, T>),
Round5(Round5Package<NetworkCurve>),
Round6(Round6Package<NetworkCurve, T>),
Round0(Round0Package<NetworkCurve, BlockNumber>),
Round1(Round1Package<NetworkCurve, BlockNumber, T>),
Round2(Round2Package<NetworkCurve, BlockNumber, T>),
Round3(Round3Package<NetworkCurve, BlockNumber, T>),
Round4(Round4Package<NetworkCurve, BlockNumber, T>),
Round5(Round5Package<NetworkCurve, BlockNumber, T>),
Round6(Round6Package<NetworkCurve, BlockNumber, T>),
}
impl<NetworkCurve, T> DkgPackage<NetworkCurve, T>
impl<NC, B, T> DkgPackage<NC, B, T>
where
T: crate::pallet::Config,
{
@ -84,12 +124,19 @@ where
let length = match self {
Self::Round0(_) => ExodusHash::len_bytes(),
Self::Round1(round1) => round1.package.len(),
Self::Round2(round2) => round2.bundle.bitmask.len(),
Self::Round3(round3) => round3.indices.len(),
Self::Round4(round4) => round4.bundle.bitmask.len(),
Self::Round2(round2) => round2.bundle.bitmask.count_ones(),
Self::Round3(round3) => {
round3.indices.highest_bit::<usize>().unwrap_or_default()
},
Self::Round4(round4) => round4.bundle.bitmask.count_ones(),
Self::Round5(round5) => {
let key_members_size = round5.key_members
.highest_bit::<usize>()
.unwrap_or_default();
round5.verifying_key.len()
.saturating_add(ExodusHash::len_bytes())
.saturating_add(key_members_size)
},
Self::Round6(round6) => round6.merkle_proof.len(),
};
@ -98,15 +145,18 @@ where
}
}
#[derive(Default, RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
pub struct PackageContext<NetworkCurve> {
#[derive(Default, RuntimeDebug, Clone, Eq, PartialEq, Encode, Decode, TypeInfo)]
pub struct PackageContext<NetworkCurve, BlockNumber> {
authority_index: AuthIndex,
network_curve: NetworkCurve
network_curve: NetworkCurve,
block_number: BlockNumber,
dkg_index: DkgIndex,
}
impl<NetworkCurve> PackageContext<NetworkCurve>
impl<NetworkCurve, BlockNumber> PackageContext<NetworkCurve, BlockNumber>
where
NetworkCurve: Copy,
BlockNumber: Copy,
{
pub fn get_authority_index(&self) -> AuthIndex {
self.authority_index
@ -116,6 +166,14 @@ where
self.network_curve
}
pub fn get_dkg_index(&self) -> DkgIndex {
self.dkg_index
}
pub fn get_block_number(&self) -> BlockNumber {
self.block_number
}
pub fn with_network_curve(mut self, network_curve: NetworkCurve) -> Self {
self.network_curve = network_curve;
self
@ -125,334 +183,392 @@ where
self.authority_index = authority_index;
self
}
pub fn with_dkg_index(mut self, dkg_index: DkgIndex) -> Self {
self.dkg_index = dkg_index;
self
}
pub fn with_block_number(mut self, block_number: BlockNumber) -> Self {
self.block_number = block_number;
self
}
}
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
pub struct BundledPackages {
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Encode, Decode, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct BundledPackages<T>
where
T: crate::pallet::Config
{
pub bitmask: ParticipantsBitmap<T>,
pub blob: Vec<u8>,
pub bitmask: Vec<u8>,
}
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
impl<T> IndexedBundle<T> for BundledPackages<T>
where
T: crate::pallet::Config,
{
fn blob(&self) -> &[u8] { &self.blob }
fn bitmask(&self) -> &ParticipantsBitmap<T> { &self.bitmask }
}
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Encode, Decode, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct EncryptionBundledPackages<T>
where
T: crate::pallet::Config,
{
pub blob: BoundedVec<u8, crate::EncryptedRound2BlobMaxBytes<T>>,
pub bitmask: BoundedVec<u8, crate::MaxAuthoritiesBitmaskSize<T>>
pub bitmask: ParticipantsBitmap<T>,
}
impl<T: crate::pallet::Config> EncryptionBundledPackages<T> {
pub fn len(&self) -> usize {
let blob_len = self.blob.len();
let bitmask_len = self.bitmask.len();
bitmask_len.saturating_add(blob_len)
}
impl<T> IndexedBundle<T> for EncryptionBundledPackages<T>
where
T: crate::pallet::Config,
{
fn blob(&self) -> &[u8] { &self.blob }
fn bitmask(&self) -> &ParticipantsBitmap<T> { &self.bitmask }
}
impl<T: crate::pallet::Config> TryFrom<BundledPackages> for EncryptionBundledPackages<T> {
impl<T: crate::pallet::Config> TryFrom<BundledPackages<T>> for EncryptionBundledPackages<T> {
type Error = ExodusError;
fn try_from(source: BundledPackages) -> Result<Self, Self::Error> {
let blob =
BoundedVec::<u8, crate::EncryptedRound2BlobMaxBytes<T>>::try_from(source.blob)
.map_err(|_| ExodusError::DeserializationError)?;
let bitmask = BoundedVec::<u8, crate::MaxAuthoritiesBitmaskSize<T>>::try_from(source.bitmask)
fn try_from(source: BundledPackages<T>) -> Result<Self, Self::Error> {
let blob = BoundedVec::try_from(source.blob)
.map_err(|_| ExodusError::DeserializationError)?;
Ok(Self { blob, bitmask })
Ok(Self { blob, bitmask: source.bitmask })
}
}
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Encode, Decode, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct JustificationsBundledPackages<T>
where
T: crate::pallet::Config,
{
pub blob: BoundedVec<u8, crate::Round2BlobMaxBytes<T>>,
pub bitmask: BoundedVec<u8, crate::MaxAuthoritiesBitmaskSize<T>>
pub bitmask: ParticipantsBitmap<T>,
}
impl<T: crate::pallet::Config> JustificationsBundledPackages<T> {
pub fn len(&self) -> usize {
let blob_len = self.blob.len();
let bitmask_len = self.bitmask.len();
bitmask_len.saturating_add(blob_len)
}
}
impl<T: crate::pallet::Config> TryFrom<BundledPackages> for JustificationsBundledPackages<T> {
type Error = ExodusError;
fn try_from(source: BundledPackages) -> Result<Self, Self::Error> {
let blob =
BoundedVec::<u8, crate::Round2BlobMaxBytes<T>>::try_from(source.blob)
.map_err(|_| ExodusError::DeserializationError)?;
let bitmask = BoundedVec::<u8, crate::MaxAuthoritiesBitmaskSize<T>>::try_from(source.bitmask)
.map_err(|_| ExodusError::DeserializationError)?;
Ok(Self { blob, bitmask })
}
}
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct ExodusNonceCommitment<NetworkCurve> {
context: PackageContext<NetworkCurve>,
pub hiding_commitment: BoundedVec<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>,
pub binding_commitment: BoundedVec<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>,
pub session: ExodusSession,
}
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
pub struct ExodusGroupCommitment<NetworkCurve> {
context: PackageContext<NetworkCurve>,
pub group_commitment: BoundedVec<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>,
pub binding_factors_root: ExodusHash,
pub session: ExodusSession,
}
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct ExodusSignatureShare<NetworkCurve, T>
impl<T> IndexedBundle<T> for JustificationsBundledPackages<T>
where
T: crate::pallet::Config,
{
context: PackageContext<NetworkCurve>,
fn blob(&self) -> &[u8] { &self.blob }
fn bitmask(&self) -> &ParticipantsBitmap<T> { &self.bitmask }
}
impl<T: crate::pallet::Config> TryFrom<BundledPackages<T>> for JustificationsBundledPackages<T> {
type Error = ExodusError;
fn try_from(source: BundledPackages<T>) -> Result<Self, Self::Error> {
let blob = BoundedVec::try_from(source.blob)
.map_err(|_| ExodusError::DeserializationError)?;
Ok(Self { blob, bitmask: source.bitmask })
}
}
#[derive(RuntimeDebug, Default, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Encode, Decode, TypeInfo)]
pub struct ExodusContext<NetworkId: Default> {
pub session: ExodusSession,
pub network_id: NetworkId,
}
impl<NetworkId: Default + Copy> ExodusContext<NetworkId> {
pub fn new(network_id: NetworkId, session: ExodusSession) -> Self {
Self { session, network_id }
}
pub fn with_network_id(mut self, network_id: NetworkId) -> Self {
self.network_id = network_id;
self
}
pub fn with_exodus_session(mut self, session: ExodusSession) -> Self {
self.session = session;
self
}
}
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Encode, Decode, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct ExodusNonceCommitment<NetworkCurve, NetworkId: Default, BlockNumber> {
package_context: PackageContext<NetworkCurve, BlockNumber>,
exodus_context: ExodusContext<NetworkId>,
pub hiding_commitment: BoundedVec<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>,
pub binding_commitment: BoundedVec<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>,
}
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Encode, Decode, TypeInfo)]
pub struct ExodusGroupCommitment<NetworkCurve, NetworkId: Default, BlockNumber> {
package_context: PackageContext<NetworkCurve, BlockNumber>,
exodus_context: ExodusContext<NetworkId>,
pub group_commitment: BoundedVec<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>,
pub binding_factors_root: ExodusHash,
}
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Encode, Decode, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct ExodusSignatureShare<NetworkCurve, NetworkId, BlockNumber, T>
where
NetworkId: Default,
T: crate::pallet::Config,
{
package_context: PackageContext<NetworkCurve, BlockNumber>,
exodus_context: ExodusContext<NetworkId>,
pub binding_factors_proof: BoundedVec<ExodusHash, crate::BindingFactorsProof<T>>,
pub signature_share: BoundedVec<u8, ConstU32<{ crate::SCALAR_MAX_BYTES }>>,
pub self_binding_factor: BoundedVec<u8, ConstU32<{ crate::SCALAR_MAX_BYTES }>>,
pub session: ExodusSession,
}
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
pub struct Round0Package<NetworkCurve> {
context: PackageContext<NetworkCurve>,
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Encode, Decode, TypeInfo)]
pub struct Round0Package<NetworkCurve, BlockNumber> {
context: PackageContext<NetworkCurve, BlockNumber>,
pub package_hash: ExodusHash,
}
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Encode, Decode, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct Round1Package<NetworkCurve, T>
pub struct Round1Package<NetworkCurve, BlockNumber, T>
where
T: crate::pallet::Config,
{
context: PackageContext<NetworkCurve>,
context: PackageContext<NetworkCurve, BlockNumber>,
pub package: BoundedVec<u8, crate::Round1MaxBytes<T>>,
}
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Encode, Decode, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct Round2Package<NetworkCurve, T>
pub struct Round2Package<NetworkCurve, BlockNumber, T>
where
T: crate::pallet::Config,
{
context: PackageContext<NetworkCurve>,
context: PackageContext<NetworkCurve, BlockNumber>,
pub bundle: EncryptionBundledPackages<T>,
}
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Encode, Decode, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct Round3Package<NetworkCurve, T>
pub struct Round3Package<NetworkCurve, BlockNumber, T>
where
T: crate::pallet::Config,
{
context: PackageContext<NetworkCurve>,
pub indices: BoundedVec<u8, crate::AccusedIndicesMaxBytes<T>>,
pub indices: ParticipantsBitmap<T>,
context: PackageContext<NetworkCurve, BlockNumber>,
}
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Encode, Decode, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct Round4Package<NetworkCurve, T>
pub struct Round4Package<NetworkCurve, BlockNumber, T>
where
T: crate::pallet::Config,
{
context: PackageContext<NetworkCurve>,
context: PackageContext<NetworkCurve, BlockNumber>,
pub bundle: JustificationsBundledPackages<T>,
}
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
pub struct Round5Package<NetworkCurve> {
context: PackageContext<NetworkCurve>,
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Encode, Decode, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct Round5Package<NetworkCurve, BlockNumber, T>
where
T: crate::pallet::Config,
{
context: PackageContext<NetworkCurve, BlockNumber>,
pub key_members: ParticipantsBitmap<T>,
pub verifying_key: BoundedVec<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>,
pub merkle_root: ExodusHash,
}
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Encode, Decode, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct Round6Package<NetworkCurve, T>
pub struct Round6Package<NetworkCurve, BlockNumber, T>
where
T: crate::pallet::Config,
{
context: PackageContext<NetworkCurve>,
context: PackageContext<NetworkCurve, BlockNumber>,
pub verifying_share: BoundedVec<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>,
pub merkle_proof: BoundedVec<ExodusHash, crate::MerkleProofMaxSize<T>>,
}
impl<NetworkCurve> PackageContext<NetworkCurve> {
pub fn with_exodus_nonce_commitment(
impl<NetworkCurve, BlockNumber> PackageContext<NetworkCurve, BlockNumber> {
pub fn with_exodus_nonce_commitment<NetworkId: Default>(
self,
session: ExodusSession,
exodus_context: ExodusContext<NetworkId>,
hiding_commitment_vec: Vec<u8>,
binding_commitment_vec: Vec<u8>,
) -> Result<ExodusNonceCommitment<NetworkCurve>, ExodusError> {
let hiding_commitment =
BoundedVec::<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>::try_from(hiding_commitment_vec)
.map_err(|_| ExodusError::DeserializationError)?;
) -> Result<ExodusNonceCommitment<NetworkCurve, NetworkId, BlockNumber>, ExodusError> {
let hiding_commitment = BoundedVec::try_from(hiding_commitment_vec)
.map_err(|_| ExodusError::DeserializationError)?;
let binding_commitment =
BoundedVec::<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>::try_from(binding_commitment_vec)
.map_err(|_| ExodusError::DeserializationError)?;
let binding_commitment = BoundedVec::try_from(binding_commitment_vec)
.map_err(|_| ExodusError::DeserializationError)?;
Ok(ExodusNonceCommitment { context: self, hiding_commitment, binding_commitment, session })
}
pub fn with_exodus_group_commitment(
self,
session: ExodusSession,
commitment_vec: Vec<u8>,
binding_factors_root: ExodusHash,
) -> Result<ExodusGroupCommitment<NetworkCurve>, ExodusError> {
let group_commitment =
BoundedVec::<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>::try_from(commitment_vec)
.map_err(|_| ExodusError::DeserializationError)?;
Ok(ExodusGroupCommitment {
context: self,
group_commitment,
binding_factors_root,
session,
Ok(ExodusNonceCommitment {
package_context: self,
exodus_context,
hiding_commitment,
binding_commitment,
})
}
pub fn with_exodus_signature_share<T: crate::pallet::Config>(
pub fn with_exodus_group_commitment<NetworkId: Default>(
self,
session: ExodusSession,
exodus_context: ExodusContext<NetworkId>,
commitment_vec: Vec<u8>,
binding_factors_root: ExodusHash,
) -> Result<ExodusGroupCommitment<NetworkCurve, NetworkId, BlockNumber>, ExodusError> {
let group_commitment = BoundedVec::try_from(commitment_vec)
.map_err(|_| ExodusError::DeserializationError)?;
Ok(ExodusGroupCommitment {
package_context: self,
exodus_context,
group_commitment,
binding_factors_root,
})
}
pub fn with_exodus_signature_share<NetworkId: Default, T: crate::pallet::Config>(
self,
exodus_context: ExodusContext<NetworkId>,
signature_share_vec: Vec<u8>,
binding_factors_proof_vec: Vec<ExodusHash>,
self_binding_factor_vec: Vec<u8>,
) -> Result<ExodusSignatureShare<NetworkCurve, T>, ExodusError> {
let signature_share =
BoundedVec::<u8, ConstU32<{ crate::SCALAR_MAX_BYTES }>>::try_from(signature_share_vec)
.map_err(|_| ExodusError::DeserializationError)?;
) -> Result<ExodusSignatureShare<NetworkCurve, NetworkId, BlockNumber, T>, ExodusError> {
let signature_share = BoundedVec::try_from(signature_share_vec)
.map_err(|_| ExodusError::DeserializationError)?;
let binding_factors_proof =
BoundedVec::<ExodusHash, crate::BindingFactorsProof<T>>::try_from(binding_factors_proof_vec)
.map_err(|_| ExodusError::DeserializationError)?;
let binding_factors_proof = BoundedVec::try_from(binding_factors_proof_vec)
.map_err(|_| ExodusError::DeserializationError)?;
let self_binding_factor =
BoundedVec::<u8, ConstU32<{ crate::SCALAR_MAX_BYTES }>>::try_from(self_binding_factor_vec)
.map_err(|_| ExodusError::DeserializationError)?;
Ok(ExodusSignatureShare {
context: self,
package_context: self,
exodus_context,
binding_factors_proof,
self_binding_factor,
signature_share,
session,
})
}
pub fn with_dkg_round0(
self,
package_hash: ExodusHash,
) -> Round0Package<NetworkCurve> {
) -> Round0Package<NetworkCurve, BlockNumber> {
Round0Package { context: self, package_hash }
}
pub fn with_dkg_round1<T: crate::pallet::Config>(
self,
package_vec: Vec<u8>,
) -> Result<Round1Package<NetworkCurve, T>, ExodusError> {
let package =
BoundedVec::<u8, crate::Round1MaxBytes<T>>::try_from(package_vec)
.map_err(|_| ExodusError::DeserializationError)?;
) -> Result<Round1Package<NetworkCurve, BlockNumber, T>, ExodusError> {
let package = BoundedVec::try_from(package_vec)
.map_err(|_| ExodusError::DeserializationError)?;
Ok(Round1Package { context: self, package })
}
pub fn with_dkg_round2<T: crate::pallet::Config>(
self,
bundle_raw: BundledPackages,
) -> Result<Round2Package<NetworkCurve, T>, ExodusError> {
bundle_raw: BundledPackages<T>,
) -> Result<Round2Package<NetworkCurve, BlockNumber, T>, ExodusError> {
let bundle: EncryptionBundledPackages<T> = bundle_raw.try_into()?;
Ok(Round2Package { context: self, bundle })
}
pub fn with_dkg_round3<T: crate::pallet::Config>(
self,
indices_vec: Vec<u8>
) -> Result<Round3Package<NetworkCurve, T>, ExodusError> {
let indices =
BoundedVec::<u8, crate::AccusedIndicesMaxBytes<T>>::try_from(indices_vec)
.map_err(|_| ExodusError::DeserializationError)?;
indices: ParticipantsBitmap<T>,
) -> Result<Round3Package<NetworkCurve, BlockNumber, T>, ExodusError> {
Ok(Round3Package { context: self, indices })
}
pub fn with_dkg_round4<T: crate::pallet::Config>(
self,
bundle_raw: BundledPackages,
) -> Result<Round4Package<NetworkCurve, T>, ExodusError> {
bundle_raw: BundledPackages<T>,
) -> Result<Round4Package<NetworkCurve, BlockNumber, T>, ExodusError> {
let bundle: JustificationsBundledPackages<T> = bundle_raw.try_into()?;
Ok(Round4Package { context: self, bundle })
}
pub fn with_dkg_round5(
pub fn with_dkg_round5<T: crate::pallet::Config>(
self,
verifying_key_vec: Vec<u8>,
key_members: ParticipantsBitmap<T>,
merkle_root: ExodusHash,
) -> Result<Round5Package<NetworkCurve>, ExodusError> {
let verifying_key =
BoundedVec::<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>::try_from(verifying_key_vec)
.map_err(|_| ExodusError::DeserializationError)?;
) -> Result<Round5Package<NetworkCurve, BlockNumber, T>, ExodusError> {
let verifying_key = BoundedVec::try_from(verifying_key_vec)
.map_err(|_| ExodusError::DeserializationError)?;
Ok(Round5Package { context: self, verifying_key, merkle_root })
Ok(Round5Package { context: self, key_members, verifying_key, merkle_root })
}
pub fn with_dkg_round6<T: crate::pallet::Config>(
self,
verifying_share_vec: Vec<u8>,
merkle_proof_vec: Vec<ExodusHash>,
) -> Result<Round6Package<NetworkCurve, T>, ExodusError> {
let verifying_share =
BoundedVec::<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>::try_from(verifying_share_vec)
.map_err(|_| ExodusError::DeserializationError)?;
) -> Result<Round6Package<NetworkCurve, BlockNumber, T>, ExodusError> {
let verifying_share = BoundedVec::try_from(verifying_share_vec)
.map_err(|_| ExodusError::DeserializationError)?;
let merkle_proof =
BoundedVec::<ExodusHash, crate::MerkleProofMaxSize<T>>::try_from(merkle_proof_vec)
.map_err(|_| ExodusError::DeserializationError)?;
let merkle_proof = BoundedVec::try_from(merkle_proof_vec)
.map_err(|_| ExodusError::DeserializationError)?;
Ok(Round6Package { context: self, verifying_share, merkle_proof })
}
}
impl<NC, T> PackageMetadata<NC> for ExodusPackage<NC, T>
impl<NC, NI, B, T> ExodusMetadata<NI> for ExodusPackage<NC, NI, B, T>
where
NC: Copy,
NI: Default + Copy,
T: crate::pallet::Config,
{
fn context(&self) -> &PackageContext<NC> {
fn copy_exodus_context(&self) -> ExodusContext<NI> {
match self {
Self::NonceCommitment(package) => &package.context,
Self::GroupCommitment(package) => &package.context,
Self::SignatureShare(package) => &package.context,
Self::NonceCommitment(package) => (*package).exodus_context,
Self::GroupCommitment(package) => (*package).exodus_context,
Self::SignatureShare(package) => (*package).exodus_context,
}
}
fn get_exodus_context(&self) -> &ExodusContext<NI> {
match self {
Self::NonceCommitment(package) => &package.exodus_context,
Self::GroupCommitment(package) => &package.exodus_context,
Self::SignatureShare(package) => &package.exodus_context,
}
}
}
impl<NC, T> PackageMetadata<NC> for DkgPackage<NC, T>
impl<NC, NI, B, T> PackageMetadata<NC, B> for ExodusPackage<NC, NI, B, T>
where
NI: Default,
NC: Copy,
B: Copy,
T: crate::pallet::Config,
{
fn context(&self) -> &PackageContext<NC> {
fn package_context(&self) -> &PackageContext<NC, B> {
match self {
Self::NonceCommitment(package) => &package.package_context,
Self::GroupCommitment(package) => &package.package_context,
Self::SignatureShare(package) => &package.package_context,
}
}
}
impl<NC, B, T> PackageMetadata<NC, B> for DkgPackage<NC, B, T>
where
NC: Copy,
B: Copy,
T: crate::pallet::Config,
{
fn package_context(&self) -> &PackageContext<NC, B> {
match self {
Self::Round0(package) => &package.context,
Self::Round1(package) => &package.context,

View File

@ -1,28 +1,33 @@
use scale_info::TypeInfo;
use codec::{Encode, Decode};
use frame_support::{BoundedVec, traits::ConstU32};
use sp_runtime::{RuntimeDebug, traits::UniqueSaturatedInto};
use sp_runtime::{
RuntimeDebug,
traits::{AtLeast32BitUnsigned, UniqueSaturatedInto},
};
use crate::{BalanceOf, Config, ExodusContext};
use super::ExodusRequestType;
type ExodusNonceT = BoundedVec<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>;
type ExodusSignatureT = BoundedVec<u8, ConstU32<{ crate::SCALAR_MAX_BYTES }>>;
type Type<NetworkId, Balance> = super::ExodusRequestType<NetworkId, Balance>;
#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, RuntimeDebug)]
pub struct ExodusSignedMessage<NetworkId: Default, Balance: Default> {
#[scale_info(skip_type_params(T))]
pub struct ExodusSignedMessage<T: Config> {
pub nonce: ExodusNonceT,
pub signature: ExodusSignatureT,
pub r#type: Type<NetworkId, Balance>,
pub r#type: ExodusRequestType<T>,
}
impl<NetworkId, Balance> ExodusSignedMessage<NetworkId, Balance>
impl<T: Config> ExodusSignedMessage<T>
where
NetworkId: UniqueSaturatedInto<u64> + Copy + Default,
Balance: UniqueSaturatedInto<u128> + Copy + Default,
BalanceOf<T>: UniqueSaturatedInto<u128> + Copy + Default,
{
pub fn new<E, S>(
nonce_iter: E,
signature_iter: S,
r#type: Type<NetworkId, Balance>
r#type: ExodusRequestType<T>,
) -> Option<Self>
where
E: Iterator<Item = u8> + ExactSizeIterator,
@ -37,10 +42,24 @@ where
Some(Self { nonce, signature, r#type })
}
pub fn get_message<'a>(
pub fn get_message<'a, NetworkId: Default + Copy + AtLeast32BitUnsigned>(
&self,
message_buffer: &'a mut [u8; crate::MAX_MESSAGE_SIZE as usize],
exodus_context: ExodusContext<NetworkId>,
) -> Option<&'a [u8]> {
self.r#type.get_message(message_buffer)
self.r#type.get_message(message_buffer, exodus_context)
}
pub fn is_evm_rotation(&self) -> bool {
self.r#type.is_evm_rotation()
}
pub fn is_utxo_rotation(&self) -> bool {
self.r#type.is_utxo_rotation()
}
pub fn is_bridge_out(&self) -> bool {
self.r#type.is_bridge_out()
}
pub fn is_evm_governance(&self) -> bool {
self.r#type.is_evm_governance()
}
}

View File

@ -1,16 +1,19 @@
use sp_runtime::traits::UniqueSaturatedInto;
use crate::{AuthIndex, ExodusSession, DkgIndex, RoastSession};
pub enum ExodusOk<NetworkCurve> {
pub enum ExodusOk<NetworkCurve, NetworkId> {
ExodusRequestsFinished(AuthIndex, NetworkCurve, u32, u32),
ExodusEmptyRequests(AuthIndex, NetworkCurve),
ExodusWaitingState(AuthIndex, NetworkCurve),
ExodusNonceCommitted(ExodusSession, RoastSession, AuthIndex, NetworkCurve),
ExodusGroupCommitted(ExodusSession, RoastSession, AuthIndex, NetworkCurve),
ExodusSignedPartially(ExodusSession, RoastSession, AuthIndex, NetworkCurve),
ExodusNonceCommitted(ExodusSession, NetworkId, RoastSession, AuthIndex, NetworkCurve),
ExodusGroupCommitted(ExodusSession, NetworkId, RoastSession, AuthIndex, NetworkCurve),
ExodusSignedPartially(ExodusSession, NetworkId, RoastSession, AuthIndex, NetworkCurve),
ExodusVerifyingShareMissed(AuthIndex, DkgIndex, NetworkCurve),
DkgNotPartOfRound(u8, AuthIndex, NetworkCurve),
DkgRoundRegistered(u8, AuthIndex, NetworkCurve),
DkgPreviousPurged(AuthIndex, NetworkCurve),
DkgPreviousPurged(AuthIndex, NetworkCurve, u32),
DkgLocalStoragePurged(AuthIndex, NetworkCurve, DkgIndex),
DkgRoundsCleared(AuthIndex, NetworkCurve),
DkgRoundAlreadyPassed(u8, AuthIndex, NetworkCurve),
DkgRoundWaitingPeriod(u8, AuthIndex, NetworkCurve),
@ -18,27 +21,41 @@ pub enum ExodusOk<NetworkCurve> {
DkgRoundMaxSignersNarrowed(u8, AuthIndex, AuthIndex, AuthIndex, NetworkCurve),
}
impl<NetworkCurve> core::fmt::Display for ExodusOk<NetworkCurve>
impl<NetworkCurve, NetworkId> core::fmt::Debug for ExodusOk<NetworkCurve, NetworkId>
where
NetworkCurve: core::fmt::Display
NetworkCurve: core::fmt::Display,
NetworkId: UniqueSaturatedInto<u64> + Copy,
{
fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
match self {
ExodusOk::ExodusEmptyRequests(auth_index, curve) => write!(fmt, "No EXODUS requests found by authority #{auth_index} for curve {curve}."),
ExodusOk::ExodusWaitingState(auth_index, curve) => write!(fmt, "Waiting for EXODUS requests to be processed by authority #{auth_index} for curve {curve}."),
ExodusOk::ExodusNonceCommitted(exodus, roast, auth_index, curve) => write!(fmt, "Nonce committed for EXODUS {exodus:?} on ROAST session {roast:?} by authority #{auth_index} for curve {curve}."),
ExodusOk::ExodusGroupCommitted(exodus, roast, auth_index, curve) => write!(fmt, "Aggregated nonce and binding factor proof committed for EXODUS {exodus:?} on ROAST session {roast:?} by authority #{auth_index} for curve {curve}."),
ExodusOk::ExodusSignedPartially(exodus, roast, auth_index, curve) => write!(fmt, "Partial signature created for EXODUS {exodus:?} on ROAST session {roast:?} by authority #{auth_index} for curve {curve}."),
ExodusOk::ExodusVerifyingShareMissed(auth_index, dkg_index, curve) => write!(fmt, "EXODUS Round no verifying share for authority #{auth_index} based on DKG session #{dkg_index} for curve {curve}."),
ExodusOk::ExodusRequestsFinished(auth_index, curve, success_count, failed_count) => {
write!(fmt, "EXODUS requests processing round completed by authority #{auth_index} for curve {curve}: {success_count} tasks succeeded, {failed_count} tasks failed.")
}
ExodusOk::ExodusEmptyRequests(auth_index, curve) => write!(fmt, "No EXODUS requests found for authority #{auth_index} on curve {curve}."),
ExodusOk::ExodusWaitingState(auth_index, curve) => write!(fmt, "Waiting for EXODUS requests to be processed by authority #{auth_index} on curve {curve}."),
ExodusOk::ExodusNonceCommitted(exodus, network_id, roast, auth_index, curve) => {
let network_id_u64: u64 = (*network_id).unique_saturated_into();
write!(fmt, "Nonce committed for EXODUS {exodus:?} on ROAST session {roast:?} by authority #{auth_index} for curve {curve} on network #{network_id_u64}.")
},
ExodusOk::ExodusGroupCommitted(exodus, network_id, roast, auth_index, curve) => {
let network_id_u64: u64 = (*network_id).unique_saturated_into();
write!(fmt, "Aggregated nonce and binding factor proof committed for EXODUS {exodus:?} on ROAST session {roast:?} by authority #{auth_index} for curve {curve} on network #{network_id_u64}.")
},
ExodusOk::ExodusSignedPartially(exodus, network_id, roast, auth_index, curve) => {
let network_id_u64: u64 = (*network_id).unique_saturated_into();
write!(fmt, "Partial signature created for EXODUS {exodus:?} on ROAST session {roast:?} by authority #{auth_index} for curve {curve} on network #{network_id_u64}.")
},
ExodusOk::ExodusVerifyingShareMissed(auth_index, dkg_index, curve) => write!(fmt, "No verifying share found for authority #{auth_index} in EXODUS round based on DKG session #{dkg_index} for curve {curve}."),
ExodusOk::DkgNotPartOfRound(round, auth_index, curve) => write!(fmt, "DKG authority #{auth_index} for network curve {curve} is not part of Round{round}."),
ExodusOk::DkgRoundRegistered(round, auth_index, curve) => write!(fmt, "DKG Round{round} registered by authority #{auth_index} for network curve {curve}."),
ExodusOk::DkgPreviousPurged(auth_index, curve) => write!(fmt, "Storage prepared for new DKG session by authority #{auth_index} for network curve {curve}."),
ExodusOk::DkgNotPartOfRound(round, auth_index, curve) => write!(fmt, "DKG authority #{auth_index} is not part of Round {round} for network curve {curve}."),
ExodusOk::DkgRoundRegistered(round, auth_index, curve) => write!(fmt, "DKG Round {round} registered by authority #{auth_index} for network curve {curve}."),
ExodusOk::DkgPreviousPurged(auth_index, curve, removed_count) => write!(fmt, "Storage prepared for a new DKG session by authority #{auth_index} for network curve {curve}. {removed_count} entries removed."),
ExodusOk::DkgLocalStoragePurged(auth_index, curve, dkg_index) => write!(fmt, "Storage for DKG session #{dkg_index} cleared by authority #{auth_index} for network curve {curve}."),
ExodusOk::DkgRoundsCleared(auth_index, curve) => write!(fmt, "DKG Rounds cleared from storage by authority #{auth_index} for network curve {curve}."),
ExodusOk::DkgRoundAlreadyPassed(round, auth_index, curve) => write!(fmt, "DKG Round{round} already done by authority #{auth_index} for network curve {curve}."),
ExodusOk::DkgRoundWaitingPeriod(round, auth_index, curve) => write!(fmt, "DKG Round{round} waiting to be applied by authority #{auth_index} for network curve {curve}."),
ExodusOk::DkgRoundNothingStored(round, auth_index, curve) => write!(fmt, "DKG nothing stored from Round{round} by authority #{auth_index} for network curve {curve}."),
ExodusOk::DkgRoundMaxSignersNarrowed(round, auth_index, prev_max, curr_max, curve) => write!(fmt, "DKG Round{round} narrowed max_signers from {prev_max} to {curr_max} by {auth_index} for network curve {curve}."),
ExodusOk::DkgRoundAlreadyPassed(round, auth_index, curve) => write!(fmt, "DKG Round {round} already completed by authority #{auth_index} for network curve {curve}."),
ExodusOk::DkgRoundWaitingPeriod(round, auth_index, curve) => write!(fmt, "DKG Round {round} is waiting to be applied by authority #{auth_index} for network curve {curve}."),
ExodusOk::DkgRoundNothingStored(round, auth_index, curve) => write!(fmt, "No data stored for DKG Round {round} by authority #{auth_index} for network curve {curve}."),
ExodusOk::DkgRoundMaxSignersNarrowed(round, auth_index, prev_max, curr_max, curve) => write!(fmt, "DKG Round {round} narrowed max_signers from {prev_max} to {curr_max} by authority #{auth_index} for network curve {curve}."),
}
}
}

View File

@ -16,7 +16,7 @@
//! Autogenerated weights for `ghost_exodus`
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0
//! DATE: 2026-09-25, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! DATE: 2026-10-29, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! WORST CASE MAP SIZE: `1000000`
//! HOSTNAME: `ghostown`, CPU: `Intel(R) Core(TM) i3-2310M CPU @ 2.10GHz`
//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("casper-dev")`, DB CACHE: 1024
@ -63,34 +63,30 @@ pub trait WeightInfo {
/// Weight functions for `ghost_exodus`.
impl WeightInfo for () {
/// Storage: `GhostNetworks::NetworkCurves` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkCurves` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostNetworks::NetworkTypes` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkTypes` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationDkgState` (r:1 w:1)
/// Proof: `GhostExodus::QualificationDkgState` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::QualificationAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusActivity` (r:1 w:1)
/// Proof: `GhostExodus::ExodusActivity` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::Round0Packages` (r:0 w:1)
/// Proof: `GhostExodus::Round0Packages` (`max_values`: None, `max_size`: None, mode: `Measured`)
fn register_round0_package() -> Weight {
// Proof Size summary in bytes:
// Measured: `351`
// Estimated: `3816`
// Minimum execution time: 240_813_000 picoseconds.
Weight::from_parts(243_523_000, 0)
.saturating_add(Weight::from_parts(0, 3816))
// Measured: `365`
// Estimated: `3830`
// Minimum execution time: 240_843_000 picoseconds.
Weight::from_parts(243_855_000, 0)
.saturating_add(Weight::from_parts(0, 3830))
.saturating_add(RocksDbWeight::get().reads(4))
.saturating_add(RocksDbWeight::get().writes(3))
.saturating_add(RocksDbWeight::get().writes(2))
}
/// Storage: `GhostNetworks::NetworkCurves` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkCurves` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostNetworks::NetworkTypes` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkTypes` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationDkgState` (r:1 w:0)
/// Proof: `GhostExodus::QualificationDkgState` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::QualificationAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusActivity` (r:1 w:1)
/// Proof: `GhostExodus::ExodusActivity` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::Round0Packages` (r:1 w:0)
/// Proof: `GhostExodus::Round0Packages` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::Round1Packages` (r:1 w:1)
@ -98,119 +94,109 @@ impl WeightInfo for () {
/// The range of component `c` is `[4, 300]`.
fn register_round1_package(c: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `391 + c * (32 ±0)`
// Estimated: `3856 + c * (32 ±0)`
// Minimum execution time: 1_356_791_000 picoseconds.
Weight::from_parts(1_221_760_215, 0)
.saturating_add(Weight::from_parts(0, 3856))
// Standard Error: 16_481
.saturating_add(Weight::from_parts(33_163_393, 0).saturating_mul(c.into()))
.saturating_add(RocksDbWeight::get().reads(6))
.saturating_add(RocksDbWeight::get().writes(2))
// Measured: `405 + c * (32 ±0)`
// Estimated: `3870 + c * (32 ±0)`
// Minimum execution time: 1_349_108_000 picoseconds.
Weight::from_parts(1_224_956_354, 0)
.saturating_add(Weight::from_parts(0, 3870))
// Standard Error: 14_930
.saturating_add(Weight::from_parts(33_204_477, 0).saturating_mul(c.into()))
.saturating_add(RocksDbWeight::get().reads(5))
.saturating_add(RocksDbWeight::get().writes(1))
.saturating_add(Weight::from_parts(0, 32).saturating_mul(c.into()))
}
/// Storage: `GhostNetworks::NetworkCurves` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkCurves` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostNetworks::NetworkTypes` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkTypes` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationDkgState` (r:1 w:0)
/// Proof: `GhostExodus::QualificationDkgState` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::QualificationAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusActivity` (r:1 w:1)
/// Proof: `GhostExodus::ExodusActivity` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::Round2Packages` (r:1 w:1)
/// Proof: `GhostExodus::Round2Packages` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// The range of component `c` is `[1, 37]`.
/// The range of component `c` is `[1, 300]`.
fn register_encrypted_round2_packages(c: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `317 + c * (257 ±0)`
// Estimated: `3783 + c * (257 ±0)`
// Minimum execution time: 254_590_000 picoseconds.
Weight::from_parts(258_091_105, 0)
.saturating_add(Weight::from_parts(0, 3783))
// Standard Error: 25_082
.saturating_add(Weight::from_parts(6_595_878, 0).saturating_mul(c.into()))
.saturating_add(RocksDbWeight::get().reads(5))
.saturating_add(RocksDbWeight::get().writes(2))
.saturating_add(Weight::from_parts(0, 257).saturating_mul(c.into()))
// Measured: `332 + c * (32 ±0)`
// Estimated: `3797 + c * (32 ±0)`
// Minimum execution time: 245_305_000 picoseconds.
Weight::from_parts(253_863_353, 0)
.saturating_add(Weight::from_parts(0, 3797))
// Standard Error: 3_527
.saturating_add(Weight::from_parts(810_623, 0).saturating_mul(c.into()))
.saturating_add(RocksDbWeight::get().reads(4))
.saturating_add(RocksDbWeight::get().writes(1))
.saturating_add(Weight::from_parts(0, 32).saturating_mul(c.into()))
}
/// Storage: `GhostNetworks::NetworkCurves` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkCurves` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostNetworks::NetworkTypes` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkTypes` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationDkgState` (r:1 w:0)
/// Proof: `GhostExodus::QualificationDkgState` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::QualificationAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusActivity` (r:1 w:1)
/// Proof: `GhostExodus::ExodusActivity` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::Complaints` (r:1 w:1)
/// Proof: `GhostExodus::Complaints` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// The range of component `c` is `[1, 37]`.
/// The range of component `c` is `[1, 300]`.
fn register_round3_complaints(c: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `317 + c * (257 ±0)`
// Estimated: `3783 + c * (257 ±0)`
// Minimum execution time: 240_888_000 picoseconds.
Weight::from_parts(245_480_975, 0)
.saturating_add(Weight::from_parts(0, 3783))
// Standard Error: 13_733
.saturating_add(Weight::from_parts(371_161, 0).saturating_mul(c.into()))
.saturating_add(RocksDbWeight::get().reads(5))
.saturating_add(RocksDbWeight::get().writes(2))
.saturating_add(Weight::from_parts(0, 257).saturating_mul(c.into()))
// Measured: `332 + c * (32 ±0)`
// Estimated: `3797 + c * (32 ±0)`
// Minimum execution time: 238_391_000 picoseconds.
Weight::from_parts(238_937_380, 0)
.saturating_add(Weight::from_parts(0, 3797))
// Standard Error: 1_789
.saturating_add(Weight::from_parts(39_316, 0).saturating_mul(c.into()))
.saturating_add(RocksDbWeight::get().reads(4))
.saturating_add(RocksDbWeight::get().writes(1))
.saturating_add(Weight::from_parts(0, 32).saturating_mul(c.into()))
}
/// Storage: `GhostNetworks::NetworkCurves` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkCurves` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostNetworks::NetworkTypes` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkTypes` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationDkgState` (r:1 w:0)
/// Proof: `GhostExodus::QualificationDkgState` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::QualificationAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusActivity` (r:1 w:1)
/// Proof: `GhostExodus::ExodusActivity` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::Justifications` (r:1 w:1)
/// Proof: `GhostExodus::Justifications` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// The range of component `c` is `[1, 37]`.
/// The range of component `c` is `[1, 300]`.
fn register_round4_justifications(c: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `317 + c * (257 ±0)`
// Estimated: `3783 + c * (257 ±0)`
// Minimum execution time: 251_521_000 picoseconds.
Weight::from_parts(253_053_636, 0)
.saturating_add(Weight::from_parts(0, 3783))
// Standard Error: 14_691
.saturating_add(Weight::from_parts(4_101_872, 0).saturating_mul(c.into()))
.saturating_add(RocksDbWeight::get().reads(5))
.saturating_add(RocksDbWeight::get().writes(2))
.saturating_add(Weight::from_parts(0, 257).saturating_mul(c.into()))
// Measured: `332 + c * (32 ±0)`
// Estimated: `3797 + c * (32 ±0)`
// Minimum execution time: 245_916_000 picoseconds.
Weight::from_parts(251_070_206, 0)
.saturating_add(Weight::from_parts(0, 3797))
// Standard Error: 2_152
.saturating_add(Weight::from_parts(508_508, 0).saturating_mul(c.into()))
.saturating_add(RocksDbWeight::get().reads(4))
.saturating_add(RocksDbWeight::get().writes(1))
.saturating_add(Weight::from_parts(0, 32).saturating_mul(c.into()))
}
/// Storage: `GhostNetworks::NetworkCurves` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkCurves` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostNetworks::NetworkTypes` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkTypes` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationDkgState` (r:1 w:0)
/// Proof: `GhostExodus::QualificationDkgState` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::QualificationAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusActivity` (r:1 w:1)
/// Proof: `GhostExodus::ExodusActivity` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::Verifications` (r:1 w:1)
/// Proof: `GhostExodus::Verifications` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::VerifyingKeyConsensus` (r:1 w:1)
/// Proof: `GhostExodus::VerifyingKeyConsensus` (`max_values`: None, `max_size`: None, mode: `Measured`)
fn register_round5_verifying_package() -> Weight {
// Proof Size summary in bytes:
// Measured: `448`
// Estimated: `3913`
// Minimum execution time: 265_187_000 picoseconds.
Weight::from_parts(269_124_000, 0)
.saturating_add(Weight::from_parts(0, 3913))
.saturating_add(RocksDbWeight::get().reads(6))
.saturating_add(RocksDbWeight::get().writes(3))
// Measured: `462`
// Estimated: `3927`
// Minimum execution time: 271_654_000 picoseconds.
Weight::from_parts(286_394_000, 0)
.saturating_add(Weight::from_parts(0, 3927))
.saturating_add(RocksDbWeight::get().reads(5))
.saturating_add(RocksDbWeight::get().writes(2))
}
/// Storage: `GhostNetworks::NetworkCurves` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkCurves` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostNetworks::NetworkTypes` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkTypes` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationDkgState` (r:1 w:0)
/// Proof: `GhostExodus::QualificationDkgState` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::QualificationAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusActivity` (r:1 w:1)
/// Proof: `GhostExodus::ExodusActivity` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::VerifyingKeyConsensus` (r:1 w:0)
/// Proof: `GhostExodus::VerifyingKeyConsensus` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::VerifyingShares` (r:1 w:1)
@ -221,18 +207,18 @@ impl WeightInfo for () {
fn register_round6_public_share_package(c: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `0 + c * (770 ±0)`
// Estimated: `1551 + c * (1019 ±27)`
// Minimum execution time: 284_558_000 picoseconds.
Weight::from_parts(284_117_484, 0)
.saturating_add(Weight::from_parts(0, 1551))
// Standard Error: 74_361
.saturating_add(Weight::from_parts(3_551_271, 0).saturating_mul(c.into()))
.saturating_add(RocksDbWeight::get().reads(7))
.saturating_add(RocksDbWeight::get().writes(3))
// Estimated: `1698 + c * (1019 ±27)`
// Minimum execution time: 283_863_000 picoseconds.
Weight::from_parts(286_691_978, 0)
.saturating_add(Weight::from_parts(0, 1698))
// Standard Error: 85_685
.saturating_add(Weight::from_parts(2_854_037, 0).saturating_mul(c.into()))
.saturating_add(RocksDbWeight::get().reads(6))
.saturating_add(RocksDbWeight::get().writes(2))
.saturating_add(Weight::from_parts(0, 1019).saturating_mul(c.into()))
}
/// Storage: `GhostNetworks::NetworkCurves` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkCurves` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostNetworks::NetworkTypes` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkTypes` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationDkgState` (r:1 w:0)
/// Proof: `GhostExodus::QualificationDkgState` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ActiveDkgAuthorities` (r:1 w:0)
@ -243,24 +229,22 @@ impl WeightInfo for () {
/// Proof: `GhostExodus::ExodusRequests` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::RoastSessions` (r:1 w:1)
/// Proof: `GhostExodus::RoastSessions` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusActivity` (r:1 w:1)
/// Proof: `GhostExodus::ExodusActivity` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::RoastSessionStates` (r:1 w:1)
/// Proof: `GhostExodus::RoastSessionStates` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::NonceCommitments` (r:0 w:1)
/// Proof: `GhostExodus::NonceCommitments` (`max_values`: None, `max_size`: None, mode: `Measured`)
fn register_nonce_commitment() -> Weight {
// Proof Size summary in bytes:
// Measured: `627`
// Estimated: `4092`
// Minimum execution time: 315_066_000 picoseconds.
Weight::from_parts(318_561_000, 0)
.saturating_add(Weight::from_parts(0, 4092))
// Measured: `633`
// Estimated: `4098`
// Minimum execution time: 369_666_000 picoseconds.
Weight::from_parts(373_163_000, 0)
.saturating_add(Weight::from_parts(0, 4098))
.saturating_add(RocksDbWeight::get().reads(8))
.saturating_add(RocksDbWeight::get().writes(5))
.saturating_add(RocksDbWeight::get().writes(4))
}
/// Storage: `GhostNetworks::NetworkCurves` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkCurves` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostNetworks::NetworkTypes` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkTypes` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationDkgState` (r:1 w:0)
/// Proof: `GhostExodus::QualificationDkgState` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ActiveDkgAuthorities` (r:1 w:0)
@ -269,8 +253,6 @@ impl WeightInfo for () {
/// Proof: `GhostExodus::ActiveAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::RoastSessions` (r:1 w:0)
/// Proof: `GhostExodus::RoastSessions` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusActivity` (r:1 w:1)
/// Proof: `GhostExodus::ExodusActivity` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusRequests` (r:1 w:0)
/// Proof: `GhostExodus::ExodusRequests` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::GroupCommitters` (r:1 w:1)
@ -281,16 +263,16 @@ impl WeightInfo for () {
/// Proof: `GhostExodus::GroupCommitmentsConsensus` (`max_values`: None, `max_size`: None, mode: `Measured`)
fn register_group_commitment() -> Weight {
// Proof Size summary in bytes:
// Measured: `992`
// Estimated: `4457`
// Minimum execution time: 338_644_000 picoseconds.
Weight::from_parts(343_954_000, 0)
.saturating_add(Weight::from_parts(0, 4457))
.saturating_add(RocksDbWeight::get().reads(10))
.saturating_add(RocksDbWeight::get().writes(4))
// Measured: `1032`
// Estimated: `4495`
// Minimum execution time: 339_569_000 picoseconds.
Weight::from_parts(341_788_000, 0)
.saturating_add(Weight::from_parts(0, 4495))
.saturating_add(RocksDbWeight::get().reads(9))
.saturating_add(RocksDbWeight::get().writes(3))
}
/// Storage: `GhostNetworks::NetworkCurves` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkCurves` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostNetworks::NetworkTypes` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkTypes` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationDkgState` (r:1 w:0)
/// Proof: `GhostExodus::QualificationDkgState` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ActiveDkgAuthorities` (r:1 w:0)
@ -299,8 +281,6 @@ impl WeightInfo for () {
/// Proof: `GhostExodus::ActiveAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::RoastSessions` (r:1 w:1)
/// Proof: `GhostExodus::RoastSessions` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusActivity` (r:1 w:1)
/// Proof: `GhostExodus::ExodusActivity` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::GroupCommitmentsConsensus` (r:1 w:1)
/// Proof: `GhostExodus::GroupCommitmentsConsensus` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusRequests` (r:1 w:1)
@ -323,13 +303,13 @@ impl WeightInfo for () {
/// Proof: `GhostExodus::CounterForExodusRequests` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
fn register_signature_share() -> Weight {
// Proof Size summary in bytes:
// Measured: `11299`
// Estimated: `14764`
// Minimum execution time: 3_968_938_000 picoseconds.
Weight::from_parts(3_975_088_000, 0)
.saturating_add(Weight::from_parts(0, 14764))
.saturating_add(RocksDbWeight::get().reads(15))
.saturating_add(RocksDbWeight::get().writes(9))
// Measured: `11337`
// Estimated: `14802`
// Minimum execution time: 4_046_065_000 picoseconds.
Weight::from_parts(4_059_064_000, 0)
.saturating_add(Weight::from_parts(0, 14802))
.saturating_add(RocksDbWeight::get().reads(14))
.saturating_add(RocksDbWeight::get().writes(8))
}
/// Storage: `GhostNetworks::Networks` (r:1 w:0)
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
@ -339,17 +319,17 @@ impl WeightInfo for () {
/// Proof: `GhostNetworks::NetworkImbalance` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationDkgState` (r:1 w:0)
/// Proof: `GhostExodus::QualificationDkgState` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::CurrentExodus` (r:1 w:1)
/// Proof: `GhostExodus::CurrentExodus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::CurrentExodusSessions` (r:1 w:1)
/// Proof: `GhostExodus::CurrentExodusSessions` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusRequests` (r:1 w:1)
/// Proof: `GhostExodus::ExodusRequests` (`max_values`: None, `max_size`: None, mode: `Measured`)
fn register_evm_bridge_out_exodus() -> Weight {
// Proof Size summary in bytes:
// Measured: `448`
// Estimated: `3913`
// Minimum execution time: 191_374_000 picoseconds.
Weight::from_parts(192_991_000, 0)
.saturating_add(Weight::from_parts(0, 3913))
// Measured: `457`
// Estimated: `3922`
// Minimum execution time: 195_417_000 picoseconds.
Weight::from_parts(196_842_000, 0)
.saturating_add(Weight::from_parts(0, 3922))
.saturating_add(RocksDbWeight::get().reads(7))
.saturating_add(RocksDbWeight::get().writes(5))
}

View File

@ -1,6 +1,6 @@
[package]
name = "ghost-governance"
version = "0.3.10"
version = "0.3.11"
description = "Full-chain and cross-chain governance pallet with early adopter share claims"
license.workspace = true
authors.workspace = true

View File

@ -79,14 +79,21 @@ fn claim_happy_path() {
let users = vec![user_1.clone(), user_2.clone(), user_3.clone()];
let max_index = 2u8;
let tmp_package_probe = ClaimPackage::<Test> {
shares: user_1.allowed_shares,
merkle_proof: Default::default(),
token_id: user_1.token_id,
index: 0u32,
};
let size_hint = tmp_package_probe.get_preimage(&user_1.evm_address, network_id).as_ref().len();
let raw_values_complex = vec![(0u8, &user_1), (1u8, &user_2), (2u8, &user_3)];
let merkle_tree = generate_tree::<SubstrateKeccakHasher, u8, _, _, _>(
let merkle_tree = generate_tree::<SubstrateKeccakHasher, u8, _, _, ()>(
max_index,
size_hint,
raw_values_complex,
|item: (u8, &NewTestUser)| -> Result<(usize, Vec<u8>), ()> {
let (index, u) = item;
|(index, u), buffer| {
let tmp_package = ClaimPackage::<Test> {
shares: u.allowed_shares,
merkle_proof: Default::default(),
@ -94,9 +101,12 @@ fn claim_happy_path() {
index: index as u32
};
Ok((index as usize, tmp_package.get_preimage(&u.evm_address, network_id).to_vec()))
let preimage_bytes = tmp_package.get_preimage(&u.evm_address, network_id);
buffer.extend_from_slice(preimage_bytes.as_ref());
Ok(index as usize)
}
).unwrap();
).ok().unwrap();
let merkle_root = *merkle_tree.last().unwrap();
@ -116,7 +126,9 @@ fn claim_happy_path() {
activated_shares: 0,
});
let proof = generate_proof::<SubstrateKeccakHasher, u8>(&merkle_tree, max_index, slot_index as u8);
let proof = generate_proof::<SubstrateKeccakHasher, u8, ()>(&merkle_tree, max_index, slot_index as u8)
.ok()
.unwrap();
let claim_package = ClaimPackage::<Test> {
shares: current_user.allowed_shares,
@ -205,21 +217,33 @@ fn partial_claims_happy_path() {
let max_index = 0u8;
let raw_values_complex = vec![(0u8, &current_user)];
let merkle_tree = generate_tree::<SubstrateKeccakHasher, u8, _, _, _>(
let tmp_package_probe = ClaimPackage::<Test> {
shares: current_user.allowed_shares,
merkle_proof: Default::default(),
token_id: current_user.token_id,
index: 0u32,
};
let size_hint = tmp_package_probe.get_preimage(&current_user.evm_address, network_id).as_ref().len();
let merkle_tree = generate_tree::<SubstrateKeccakHasher, u8, _, _, ()>(
max_index,
size_hint,
raw_values_complex,
|item: (u8, &NewTestUser)| -> Result<(usize, Vec<u8>), ()> {
let (index, u) = item;
|(index, u), buffer| {
let tmp_package = ClaimPackage::<Test> {
shares: u.allowed_shares,
merkle_proof: Default::default(),
token_id: u.token_id,
index: index as u32
};
Ok((index as usize, tmp_package.get_preimage(&u.evm_address, network_id).to_vec()))
let preimage_bytes = tmp_package.get_preimage(&u.evm_address, network_id);
buffer.extend_from_slice(preimage_bytes.as_ref());
Ok(index as usize)
}
).unwrap();
).ok().unwrap();
let total_shares = 1_000_000;
let merkle_root = *merkle_tree.last().unwrap();
@ -240,7 +264,10 @@ fn partial_claims_happy_path() {
setup_governor(&substrate_account_b);
setup_governor(&substrate_account_c);
let proof = generate_proof::<SubstrateKeccakHasher, u8>(&merkle_tree, max_index, 0u8);
let proof = generate_proof::<SubstrateKeccakHasher, u8, ()>(&merkle_tree, max_index, 0u8)
.ok()
.unwrap();
let claim_package = ClaimPackage {
shares: current_user.allowed_shares,
merkle_proof: BoundedVec::try_from(proof).unwrap(),

View File

@ -16,7 +16,7 @@
//! Autogenerated weights for `ghost_governance`
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0
//! DATE: 2026-09-25, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! DATE: 2026-10-29, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! WORST CASE MAP SIZE: `1000000`
//! HOSTNAME: `ghostown`, CPU: `Intel(R) Core(TM) i3-2310M CPU @ 2.10GHz`
//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("casper-dev")`, DB CACHE: 1024
@ -67,11 +67,11 @@ impl WeightInfo for () {
// Proof Size summary in bytes:
// Measured: `283`
// Estimated: `3748`
// Minimum execution time: 229_254_000 picoseconds.
Weight::from_parts(229_992_093, 0)
// Minimum execution time: 224_852_000 picoseconds.
Weight::from_parts(225_347_733, 0)
.saturating_add(Weight::from_parts(0, 3748))
// Standard Error: 25_680
.saturating_add(Weight::from_parts(1_874_433, 0).saturating_mul(p.into()))
// Standard Error: 21_234
.saturating_add(Weight::from_parts(1_581_800, 0).saturating_mul(p.into()))
.saturating_add(RocksDbWeight::get().reads(5))
.saturating_add(RocksDbWeight::get().writes(5))
}

View File

@ -1,6 +1,6 @@
[package]
name = "ghost-helpers"
version = "0.0.11"
version = "0.0.16"
description = "Cryptographic utility suite for custom runtimes: optimized Bitmaps, UTXO parsing, Merkle Tree proofs, and Hash Chain components."
license.workspace = true
authors.workspace = true

View File

@ -2,8 +2,8 @@ use codec::{Decode, Encode};
use num_traits::PrimInt;
use scale_info::TypeInfo;
use sp_runtime::traits::UniqueSaturatedInto;
use sp_arithmetic::traits::AtLeast8BitUnsigned;
use sp_runtime::{traits::UniqueSaturatedInto, RuntimeDebug};
use sp_std::{cmp::{min, max}, marker::PhantomData, vec, vec::Vec};
use ghost_traits::bounded_bitmap as traits;
@ -33,7 +33,7 @@ pub fn validate_bitmap_sizes<B: traits::BoundedBitmapMetadata>(authorities: u32)
);
}
#[derive(Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]
#[derive(Encode, Decode, TypeInfo, MaxEncodedLen)]
#[scale_info(skip_type_params(MaxChunks))]
pub struct BoundedBitmap<Inner, MaxChunks>
where
@ -43,6 +43,19 @@ where
active_bits_count: u32,
}
impl<Inner: core::fmt::Debug, MaxChunks> core::fmt::Debug for BoundedBitmap<Inner, MaxChunks>
where
MaxChunks: frame_support::traits::Get<u32>,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let slice: &[Inner] = &self.data;
f.debug_struct("BoundedBitmap")
.field("data", &slice)
.field("active_bits_count", &self.active_bits_count)
.finish()
}
}
impl<Inner, MaxChunks> BoundedBitmap<Inner, MaxChunks>
where
MaxChunks: Get<u32>,
@ -54,6 +67,15 @@ where
Inner: BitAndAssign + BitOrAssign + BitXorAssign,
Inner: Shl<usize, Output = Inner> + Shr<usize, Output = Inner>,
{
pub fn inner_as_ref(&self) -> &[u8] {
let slice: &[Inner] = self.data.as_ref();
let byte_len = slice.len() * core::mem::size_of::<Inner>();
unsafe {
core::slice::from_raw_parts(slice.as_ptr() as *const u8, byte_len)
}
}
fn combine<F>(&self, rhs: &Self, op: F) -> Self
where
Inner: Copy,
@ -146,41 +168,6 @@ where
new_bitmap
}
fn from_bitmask<Outer>(values: &Vec<Outer>) -> Self
where
Outer: AtLeast8BitUnsigned + PrimInt,
{
let value_bits = inner_bits::<Outer>();
let n = values.iter().enumerate().rev()
.find(|(_, &val)| val != Outer::zero())
.map(|(idx, &val)| {
let active_bits = value_bits - (val.leading_zeros() as usize);
idx.saturating_mul(value_bits).saturating_add(active_bits)
})
.unwrap_or(0);
let mut new_bitmap = Self::empty(n);
for (value_index, value) in values.iter().enumerate() {
for bit_index in 0..value_bits {
let bit_mask = Outer::one() << bit_index;
if (*value & bit_mask) == Outer::zero() {
continue;
}
let index = value_index
.saturating_mul(value_bits)
.saturating_add(bit_index);
if let None = new_bitmap.insert(index as u32) {
return new_bitmap;
}
}
}
new_bitmap
}
fn all_ones<N>(n: N) -> Self
where
N: UniqueSaturatedInto<usize>,
@ -307,6 +294,35 @@ where
})
}
fn count_ones_before<N>(&self, index: N) -> Option<N>
where
usize: UniqueSaturatedInto<N>,
N: UniqueSaturatedInto<usize>,
{
let index: usize = index.unique_saturated_into();
if index >= self.active_bits_count as usize { return None; }
let shift = inner_shift::<Inner>();
let element_index = index >> shift;
let bit_index = index & ((1 << shift) - 1);
let current_element = self.data.get(element_index)?;
let lower_bits_mask = (Inner::one() << bit_index) - Inner::one();
let bits_before = *current_element & lower_bits_mask;
self.data.get(0..element_index)?
.iter()
.try_fold(0usize, |acc, &element| {
acc.checked_add(element.count_ones() as usize)
})
.and_then(|popcount| {
popcount.checked_add(bits_before.count_ones() as usize)
})
.map(|total| total.unique_saturated_into())
}
fn count_ones<N>(&self) -> N
where
u32: UniqueSaturatedInto<N>,
@ -425,6 +441,14 @@ where
self.highest_bit()
}
fn count_ones_before<N>(&self, index: N) -> Option<N>
where
usize: UniqueSaturatedInto<N>,
N: UniqueSaturatedInto<usize>,
{
self.count_ones_before(index)
}
fn count_ones<N>(&self) -> N
where
u32: UniqueSaturatedInto<N>,
@ -476,13 +500,6 @@ where
Self::empty_from(bitmap_ref)
}
fn from_bitmask<Outer>(values_ref: &Vec<Outer>) -> Self
where
Outer: AtLeast8BitUnsigned + PrimInt,
{
Self::from_bitmask(values_ref)
}
fn from_vec<Outer>(values_ref: &Vec<Outer>) -> Self
where
Outer: AtLeast8BitUnsigned + TryInto<usize> + PartialEq + Copy,
@ -1099,50 +1116,72 @@ mod tests {
}
#[test]
fn test_from_bitmask_where_each_u8_is_a_bit() {
let byte1 = 77u8;
let byte2 = 42u8;
let byte3 = 69u8;
fn test_count_ones_before_basic() {
let w = <$inner_type>::BITS as usize;
let idx = |x: usize| -> $inner_type {
x.try_into().ok().expect("bit index fits inner type")
};
let raw_bits: Vec<u8> = vec![byte1, byte2, byte3];
let bitmap = TestBitmap::from_bitmask(&raw_bits);
let mut indices: Vec<$inner_type> = vec![
0u8.into(),
1u8.into(),
3u8.into()
];
let count_ones = raw_bits.iter().map(|b| b.count_ones()).sum::<u32>();
let filled_bits = (raw_bits.len() as u32 - 1) * 8;
let expected_bits = filled_bits + (8 - byte3.leading_zeros());
indices.extend([idx(w), idx(w + 1), idx(w + 3)] as [$inner_type; 3]);
let bitmap = TestBitmap::from_vec(&indices);
assert_eq!(bitmap.active_bits_count(), expected_bits);
assert_eq!(bitmap.count_ones::<u32>(), count_ones);
assert_eq!(bitmap.count_ones_before(0), Some(0));
assert_eq!(bitmap.count_ones_before(1), Some(1));
assert_eq!(bitmap.count_ones_before(2), Some(2));
assert_eq!(bitmap.count_ones_before(3), Some(2));
assert_eq!(bitmap.count_ones_before(4), Some(3));
assert_eq!(bitmap.count_ones_before(w), Some(3));
// 77 - 10110010
assert!(bitmap.contains(0u32));
assert!(!bitmap.contains(1u32));
assert!(bitmap.contains(2u32));
assert!(bitmap.contains(3u32));
assert!(!bitmap.contains(4u32));
assert!(!bitmap.contains(5u32));
assert!(bitmap.contains(6u32));
assert!(!bitmap.contains(7u32));
assert_eq!(bitmap.count_ones_before(w + 1), Some(4));
assert_eq!(bitmap.count_ones_before(w + 4), Some(6));
assert_eq!(bitmap.count_ones_before(2 * w), Some(6));
}
// 42 - 01010100
assert!(!bitmap.contains(8u32));
assert!(bitmap.contains(9u32));
assert!(!bitmap.contains(10u32));
assert!(bitmap.contains(11u32));
assert!(!bitmap.contains(12u32));
assert!(bitmap.contains(13u32));
assert!(!bitmap.contains(14u32));
assert!(!bitmap.contains(15u32));
#[test]
fn test_count_ones_before_crossing_elements() {
let w = <$inner_type>::BITS as usize;
let idx = |x: usize| -> $inner_type {
x.try_into().ok().expect("bit index fits inner type")
};
// 69 - 10100010
assert!(bitmap.contains(16u32));
assert!(!bitmap.contains(17u32));
assert!(bitmap.contains(18u32));
assert!(!bitmap.contains(19u32));
assert!(!bitmap.contains(20u32));
assert!(!bitmap.contains(21u32));
assert!(bitmap.contains(22u32));
assert!(!bitmap.contains(23u32));
let mut bitmap = TestBitmap::empty(2 * w + 1);
for i in [0usize, 1, w, w + 1, w + 2] {
bitmap.insert(idx(i));
}
let bits_per_element = core::mem::size_of::<$inner_type>() * 8;
assert_eq!(bitmap.count_ones_before(bits_per_element), Some(2));
assert_eq!(bitmap.count_ones_before(bits_per_element + 1), Some(3));
assert_eq!(bitmap.count_ones_before(bits_per_element + 3), Some(5));
assert_eq!(bitmap.count_ones_before(2 * bits_per_element), Some(5));
assert_eq!(bitmap.count_ones_before(2 * bits_per_element + 1), None);
}
#[test]
fn test_count_ones_before_bounds_and_overflow() {
let w = <$inner_type>::BITS as usize;
let idx = |x: usize| -> $inner_type {
x.try_into().ok().expect("bit index fits inner type")
};
let mut bitmap = TestBitmap::empty(w);
for i in 0..w {
bitmap.insert(idx(i));
}
let bits_per_element = (core::mem::size_of::<$inner_type>() * 8) as u32;
assert_eq!(bitmap.count_ones_before(bits_per_element - 1), Some(bits_per_element - 1));
assert_eq!(bitmap.count_ones_before(bits_per_element), None);
assert_eq!(bitmap.count_ones_before(10000u32), None);
}
}
};

View File

@ -1,19 +1,5 @@
use ghost_traits::hashing::GhostHasher;
fn combine_values<H, V>(left: V, right: V) -> H::Hash
where
H: GhostHasher,
V: AsRef<[u8]>,
{
let hash_len = H::hash_len();
let mut combined = sp_std::vec![0u8; hash_len * 2];
combined[..hash_len].copy_from_slice(left.as_ref());
combined[hash_len..].copy_from_slice(right.as_ref());
H::hash(&combined)
}
pub fn cumulative_hash<H, S>(raw_values: S) -> H::Hash
where
H: GhostHasher,
@ -24,7 +10,7 @@ where
.into_iter()
.fold(H::empty(), |acc_hash, raw_value| {
let hashed_value = H::hash(raw_value.as_ref());
combine_values::<H, _>(acc_hash, hashed_value)
H::combine_hashes(acc_hash, hashed_value)
})
}
@ -47,7 +33,7 @@ where
iter.fold(current_hash, |acc_hash, raw_value| {
let next_hash = H::from_slice(raw_value.as_ref());
combine_values::<H, _>(acc_hash, next_hash)
H::combine_hashes(acc_hash, next_hash)
})
}
@ -94,45 +80,14 @@ where
mod tests {
use super::*;
use sp_core::H256;
use sp_io::hashing::{blake2_256, sha2_256};
use sp_std::vec::Vec;
pub struct TestBlake2Hasher;
impl GhostHasher for TestBlake2Hasher {
type Hash = H256;
type HashBytes = [u8; 32];
fn hash(data: &[u8]) -> Self::Hash {
H256::from(blake2_256(data))
}
fn empty() -> Self::Hash {
H256::zero()
}
fn hash_len() -> usize {
H256::len_bytes()
}
}
pub struct TestSha2Hasher;
impl GhostHasher for TestSha2Hasher {
type Hash = H256;
type HashBytes = [u8; 32];
fn hash(data: &[u8]) -> Self::Hash {
let first_pass: [u8; 32] = sha2_256(data);
let second_pass: [u8; 32] = sha2_256(&first_pass);
H256::from(second_pass)
}
fn hash_len() -> usize {
H256::len_bytes()
}
fn empty() -> Self::Hash {
H256::zero()
}
}
use crate::{
SubstrateBlake2Hasher, SubstrateKeccakHasher, UtxoSha2Hasher,
};
fn mock_sequential_hash(header: &[u8; 80]) -> H256 {
sequential_hash::<TestSha2Hasher, _>(core::iter::once(header))
sequential_hash::<UtxoSha2Hasher, _>(core::iter::once(header))
}
fn create_header(prev_hash: [u8; 32]) -> [u8; 80] {
@ -141,89 +96,101 @@ mod tests {
header
}
#[test]
fn test_generate_empty_values() {
let empty_list: Vec<Vec<u8>> = sp_std::vec![];
let result = cumulative_hash::<TestBlake2Hasher, _>(empty_list);
assert_eq!(H256::zero(), result);
macro_rules! run_hasher_tests {
($hasher_type:ty, $mod_name:ident) => {
mod $mod_name {
use super::*;
#[test]
fn test_generate_empty_values() {
let empty_list: Vec<Vec<u8>> = sp_std::vec![];
let result = cumulative_hash::<$hasher_type, _>(empty_list);
assert_eq!(H256::zero(), result);
}
#[test]
fn test_cumulative_hash_determinism_and_avalanche() {
let dataset_1 = sp_std::vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]];
let dataset_2 = sp_std::vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]];
let dataset_3 = sp_std::vec![vec![9, 2, 3], vec![4, 5, 6], vec![7, 8, 9]];
let hash_1 = cumulative_hash::<$hasher_type, _>(dataset_1);
let hash_2 = cumulative_hash::<$hasher_type, _>(dataset_2);
let hash_3 = cumulative_hash::<$hasher_type, _>(dataset_3);
assert_eq!(hash_1, hash_2);
assert_ne!(hash_1, hash_3);
assert_ne!(hash_2, hash_3);
}
#[test]
fn test_cumulative_hash_order_sensitive() {
let dataset_forward = sp_std::vec![sp_std::vec![1, 1, 1], sp_std::vec![2, 2, 2]];
let dataset_reversed = sp_std::vec![sp_std::vec![2, 2, 2], sp_std::vec![1, 1, 1]];
let hash_forward = cumulative_hash::<$hasher_type, _>(dataset_forward);
let hash_reversed = cumulative_hash::<$hasher_type, _>(dataset_reversed);
assert_ne!(hash_forward, hash_reversed);
}
#[test]
fn test_cumulative_hash_with_slices() {
let slice_data: &[&[u8]] = &[&[1, 2, 3], &[4, 5, 6]];
let hash_from_slice = cumulative_hash::<$hasher_type, _>(slice_data.iter());
assert_ne!(hash_from_slice, H256::zero());
}
#[test]
fn test_sequential_hash_empty() {
let empty_list: Vec<Vec<u8>> = sp_std::vec![];
let result = sequential_hash::<$hasher_type, _>(empty_list);
assert_eq!(H256::zero(), result);
}
#[test]
fn test_sequential_hash_single_element() {
let single_item = sp_std::vec![sp_std::vec![1, 2, 3]];
let result = sequential_hash::<$hasher_type, _>(single_item);
let expected = <$hasher_type>::hash(&[1, 2, 3]);
assert_eq!(result, expected);
}
#[test]
fn test_sequential_hash_determinism_and_order() {
let data_1 = sp_std::vec![sp_std::vec![1], sp_std::vec![2], sp_std::vec![3]];
let data_2 = sp_std::vec![sp_std::vec![1], sp_std::vec![2], sp_std::vec![3]];
let data_reversed = sp_std::vec![sp_std::vec![3], sp_std::vec![2], sp_std::vec![1]];
let hash_1 = sequential_hash::<$hasher_type, _>(data_1);
let hash_2 = sequential_hash::<$hasher_type, _>(data_2);
let hash_reversed = sequential_hash::<$hasher_type, _>(data_reversed);
assert_eq!(hash_1, hash_2);
assert_ne!(hash_1, hash_reversed);
}
#[test]
fn test_sequential_vs_cumulative_hash() {
let dataset = sp_std::vec![sp_std::vec![1, 2, 3], sp_std::vec![4, 5, 6]];
let seq_hash = sequential_hash::<$hasher_type, _>(dataset.clone());
let cum_hash = cumulative_hash::<$hasher_type, _>(dataset);
assert_ne!(seq_hash, cum_hash);
}
#[test]
fn test_sequential_hash_with_slices() {
let slice_data: &[&[u8]] = &[&[10, 20], &[30, 40]];
let result = sequential_hash::<$hasher_type, _>(slice_data.iter());
assert_ne!(result, H256::zero());
}
}
}
}
#[test]
fn test_cumulative_hash_determinism_and_avalanche() {
let dataset_1 = sp_std::vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]];
let dataset_2 = sp_std::vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]];
let dataset_3 = sp_std::vec![vec![9, 2, 3], vec![4, 5, 6], vec![7, 8, 9]];
let hash_1 = cumulative_hash::<TestBlake2Hasher, _>(dataset_1);
let hash_2 = cumulative_hash::<TestBlake2Hasher, _>(dataset_2);
let hash_3 = cumulative_hash::<TestBlake2Hasher, _>(dataset_3);
assert_eq!(hash_1, hash_2);
assert_ne!(hash_1, hash_3);
assert_ne!(hash_2, hash_3);
}
#[test]
fn test_cumulative_hash_order_sensitive() {
let dataset_forward = sp_std::vec![sp_std::vec![1, 1, 1], sp_std::vec![2, 2, 2]];
let dataset_reversed = sp_std::vec![sp_std::vec![2, 2, 2], sp_std::vec![1, 1, 1]];
let hash_forward = cumulative_hash::<TestBlake2Hasher, _>(dataset_forward);
let hash_reversed = cumulative_hash::<TestBlake2Hasher, _>(dataset_reversed);
assert_ne!(hash_forward, hash_reversed);
}
#[test]
fn test_cumulative_hash_with_slices() {
let slice_data: &[&[u8]] = &[&[1, 2, 3], &[4, 5, 6]];
let hash_from_slice = cumulative_hash::<TestBlake2Hasher, _>(slice_data.iter());
assert_ne!(hash_from_slice, H256::zero());
}
#[test]
fn test_sequential_hash_empty() {
let empty_list: Vec<Vec<u8>> = sp_std::vec![];
let result = sequential_hash::<TestBlake2Hasher, _>(empty_list);
assert_eq!(H256::zero(), result);
}
#[test]
fn test_sequential_hash_single_element() {
let single_item = sp_std::vec![sp_std::vec![1, 2, 3]];
let result = sequential_hash::<TestBlake2Hasher, _>(single_item);
let expected = TestBlake2Hasher::hash(&[1, 2, 3]);
assert_eq!(result, expected);
}
#[test]
fn test_sequential_hash_determinism_and_order() {
let data_1 = sp_std::vec![sp_std::vec![1], sp_std::vec![2], sp_std::vec![3]];
let data_2 = sp_std::vec![sp_std::vec![1], sp_std::vec![2], sp_std::vec![3]];
let data_reversed = sp_std::vec![sp_std::vec![3], sp_std::vec![2], sp_std::vec![1]];
let hash_1 = sequential_hash::<TestBlake2Hasher, _>(data_1);
let hash_2 = sequential_hash::<TestBlake2Hasher, _>(data_2);
let hash_reversed = sequential_hash::<TestBlake2Hasher, _>(data_reversed);
assert_eq!(hash_1, hash_2);
assert_ne!(hash_1, hash_reversed);
}
#[test]
fn test_sequential_vs_cumulative_hash() {
let dataset = sp_std::vec![sp_std::vec![1, 2, 3], sp_std::vec![4, 5, 6]];
let seq_hash = sequential_hash::<TestBlake2Hasher, _>(dataset.clone());
let cum_hash = cumulative_hash::<TestBlake2Hasher, _>(dataset);
assert_ne!(seq_hash, cum_hash);
}
#[test]
fn test_sequential_hash_with_slices() {
let slice_data: &[&[u8]] = &[&[10, 20], &[30, 40]];
let result = sequential_hash::<TestBlake2Hasher, _>(slice_data.iter());
assert_ne!(result, H256::zero());
}
run_hasher_tests!(SubstrateBlake2Hasher, blake2_hashing_tests);
run_hasher_tests!(SubstrateKeccakHasher, keccak_hashing_tests);
run_hasher_tests!(UtxoSha2Hasher, sha2_hashing_tests);
#[test]
fn test_verify_in_current_block_with_empty_ancestry() {
@ -234,7 +201,7 @@ mod tests {
trusted_bytes.reverse();
let trusted_top_block_hash = H256::from(trusted_bytes);
let result = verify_hash_ancestry::<TestSha2Hasher, &[[u8; 80]]>(
let result = verify_hash_ancestry::<UtxoSha2Hasher, &[[u8; 80]]>(
&tx_header,
&[],
trusted_top_block_hash,
@ -256,7 +223,7 @@ mod tests {
let trusted_top_block_hash = H256::from(trusted_bytes);
let ancestry = [header_101];
let result = verify_hash_ancestry::<TestSha2Hasher, &[[u8; 80]]>(
let result = verify_hash_ancestry::<UtxoSha2Hasher, &[[u8; 80]]>(
&tx_header_100,
&ancestry,
trusted_top_block_hash,
@ -284,7 +251,7 @@ mod tests {
let trusted_top_block_hash = H256::from(trusted_bytes);
let ancestry = [header_101, header_102, header_103];
let result = verify_hash_ancestry::<TestSha2Hasher, &[[u8; 80]]>(
let result = verify_hash_ancestry::<UtxoSha2Hasher, &[[u8; 80]]>(
&header_100,
&ancestry,
trusted_top_block_hash,
@ -305,7 +272,7 @@ mod tests {
let trusted_top_block_hash = H256::from(trusted_bytes);
let ancestry = [broken_header_101];
let result = verify_hash_ancestry::<TestSha2Hasher, &[[u8; 80]]>(
let result = verify_hash_ancestry::<UtxoSha2Hasher, &[[u8; 80]]>(
&header_100,
&ancestry,
trusted_top_block_hash,

452
pallets/helpers/src/lcg.rs Normal file
View File

@ -0,0 +1,452 @@
use sp_runtime::traits::UniqueSaturatedInto;
macro_rules! generate_perfect_slot_mapper {
(
struct_name: $struct_name:ident,
inner_type: $inner_type:ty,
init_rng: $init_rng:expr,
golden_ratio: $golden_ratio:expr,
lcg_multiplier: $lcg_multiplier:expr,
lcg_increment: $lcg_increment:expr,
) => {
pub struct $struct_name;
impl $struct_name {
const TOTAL_BITS: usize = core::mem::size_of::<$inner_type>() * 8;
fn get_exact_iterations(items_count: usize) -> Option<usize> {
let table_size = Self::TOTAL_BITS;
if items_count <= 1 { return Some(1); }
if items_count > table_size { return None; }
let density_pct = (items_count * 100) / table_size;
match table_size {
16 => match density_pct {
0 => Some(1),
1..=5 => Some(1), // max 0 items
6..=10 => Some(1), // max 1 items
11..=15 => Some(2), // max 2 items
16..=20 => Some(2), // max 3 items
21..=25 => Some(3), // max 4 items
26..=30 => Some(3), // max 4 items
31..=35 => Some(5), // max 5 items
36..=40 => Some(8), // max 6 items
41..=45 => Some(13), // max 7 items
46..=50 => Some(24), // max 8 items
51..=55 => Some(24), // max 8 items
56..=60 => Some(49), // max 9 items
61..=65 => Some(112), // max 10 items
66..=70 => Some(301), // max 11 items
71..=75 => Some(966), // max 12 items
76..=80 => Some(966), // max 12 items
81..=85 => Some(3868), // max 13 items
86..=90 => Some(20633), // max 14 items
91..=95 => Some(165075), // max 15 items
_ => None,
},
32 => match density_pct {
0 => Some(1),
1..=5 => Some(1), // max 1 items
6..=10 => Some(2), // max 3 items
11..=15 => Some(2), // max 4 items
16..=20 => Some(4), // max 6 items
21..=25 => Some(7), // max 8 items
26..=30 => Some(9), // max 9 items
31..=35 => Some(20), // max 11 items
36..=40 => Some(31), // max 12 items
41..=45 => Some(85), // max 14 items
46..=50 => Some(287), // max 16 items
51..=55 => Some(575), // max 17 items
56..=60 => Some(2807), // max 19 items
61..=65 => Some(6912), // max 20 items
66..=70 => Some(53627), // max 22 items
71..=75 => Some(610170), // max 24 items
_ => None,
},
64 => match density_pct {
0 => Some(1),
1..=5 => Some(1), // max 3 items
6..=10 => Some(2), // max 6 items
11..=15 => Some(4), // max 9 items
16..=20 => Some(8), // max 12 items
21..=25 => Some(22), // max 16 items
26..=30 => Some(58), // max 19 items
31..=35 => Some(180), // max 22 items
36..=40 => Some(686), // max 25 items
41..=45 => Some(3285), // max 28 items
46..=50 => Some(38995), // max 32 items
51..=55 => Some(343497), // max 35 items
_ => None,
},
128 => match density_pct {
0 => Some(1),
1..=5 => Some(2), // max 6 items
6..=10 => Some(4), // max 12 items
11..=15 => Some(11), // max 19 items
16..=20 => Some(36), // max 25 items
21..=25 => Some(207), // max 32 items
26..=30 => Some(1368), // max 38 items
31..=35 => Some(13426), // max 44 items
36..=40 => Some(331105), // max 51 items
_ => None,
},
_ => None,
}
}
pub fn get_bits_metadata() -> Option<($inner_type, $inner_type)> {
let table_size = Self::TOTAL_BITS;
let bits_needed = table_size.trailing_zeros() as usize;
let bit_shift_usize = table_size.checked_sub(bits_needed)?;
let bit_shift = bit_shift_usize as $inner_type;
let bit_mask = (table_size - 1) as $inner_type;
Some((bit_shift, bit_mask))
}
pub fn get_lcg_xor_mask<V>(values: &[V]) -> Option<$inner_type>
where
V: UniqueSaturatedInto<$inner_type> + Copy,
{
let table_size = Self::TOTAL_BITS;
if values.len() > table_size { return None; }
let max_iterations = Self::get_exact_iterations(values.len())?;
let (bit_shift, bit_mask) = Self::get_bits_metadata()?;
let mut lcg_rng_state: $inner_type = $init_rng;
let mut xor_key: $inner_type = lcg_rng_state;
let mut slots_taken = [false; Self::TOTAL_BITS];
for _ in 0..max_iterations {
slots_taken.fill(false);
let mut collision_found = false;
for value in values {
let slot_index = Self::get_lcg_based_slot_index(
bit_mask,
bit_shift,
xor_key,
value
);
if slot_index >= table_size || slots_taken[slot_index] {
collision_found = true;
break;
}
slots_taken[slot_index] = true;
}
if !collision_found { return Some(xor_key); }
lcg_rng_state = lcg_rng_state
.wrapping_mul($lcg_multiplier)
.wrapping_add($lcg_increment);
xor_key = lcg_rng_state;
}
None
}
#[inline(always)]
pub fn get_lcg_based_slot_index<V>(
bit_mask: $inner_type,
bit_shift: $inner_type,
xor_key: $inner_type,
value: &V
) -> usize
where
V: UniqueSaturatedInto<$inner_type> + Copy,
{
let converted_value: $inner_type = (*value).unique_saturated_into();
let knuth_hash = (converted_value ^ xor_key).wrapping_mul($golden_ratio);
let shifted_hash = knuth_hash.checked_shr(bit_shift as u32).unwrap_or(knuth_hash);
let slot_index_converted = shifted_hash & bit_mask;
slot_index_converted as usize
}
}
}
}
generate_perfect_slot_mapper!(
struct_name: PerfectSlotMapperU16,
inner_type: u16,
init_rng: 0x1234,
golden_ratio: 0x9E37, // Computed with: 2^16 * ( (sqrt(5) - 1) / 2)
lcg_multiplier: 25173, // Reference: Peter Grogono, Programming in Pascal, Section 4.5.
lcg_increment: 13849, // http://engr.case.edu/merat_francis/eeap282f97/labs/Random_Lab.pdf
);
generate_perfect_slot_mapper!(
struct_name: PerfectSlotMapperU32,
inner_type: u32,
init_rng: 0x12345678,
golden_ratio: 0x9E3779B9, // Computd with: 2^32 * ( (sqrt(5) - 1) / 2)
lcg_multiplier: 1664525, // Numerical Recipes `ranqd1`, Chapter 7.1,
lcg_increment: 1013904223, // An Even Quicker Generator, Eq. 7.1.6
);
generate_perfect_slot_mapper!(
struct_name: PerfectSlotMapperU64,
inner_type: u64,
init_rng: 0x123456789ABCDEF,
golden_ratio: 0x9E3779B97F4A7C15, // Taken from `splitmix64`, this generator is specifically used in PostgreSQL
lcg_multiplier: 6364136223846793005, // Newlib / Musl libc 64-bit LCG multiplier
lcg_increment: 1, // https://sourceware.org/git/?p=newlib-cygwin.git;a=blob;f=newlib/libc/stdlib/rand.c
);
generate_perfect_slot_mapper!(
struct_name: PerfectSlotMapperU128,
inner_type: u128,
init_rng: 0x123456789ABCDEF0123456789ABCDEF0,
golden_ratio: 0x9E3779B97F4A7C15F39CC0605CEDC834, // Computd with: 2^128 * ( (sqrt(5) - 1) / 2) with python
lcg_multiplier: 0x2360ED051FC65DA44385DF649FCCF645, // 2549297995355413924ULL,4865540595714422341ULL
lcg_increment: 0x5851F42D4C957F2D14057B7EF767814F, // 6364136223846793005ULL,1442695040888963407ULL
// https://github.com/imneme/pcg-cpp/blob/master/include/pcg_random.hpp
);
#[cfg(test)]
mod perfect_mapper_tests {
use super::*;
macro_rules! generate_mapper_test {
($mod_name:ident, $struct_name:ident, $init_rng:expr, $inner_type:ty) => {
mod $mod_name {
use super::*;
#[test]
fn test_standard_pack_20_percentages() {
const TABLE_SIZE: usize = $struct_name::TOTAL_BITS;
const ITEMS_COUNT: usize = (TABLE_SIZE * 20) / 100;
let mut values = [0 as $inner_type; TABLE_SIZE];
for i in 0..ITEMS_COUNT {
values[i] = ((i as $inner_type).wrapping_mul(999)) ^ 0xABC;
}
let filled_values = &values[0..ITEMS_COUNT];
let mask = $struct_name::get_lcg_xor_mask(filled_values);
assert!(mask.is_some());
let xor_key = mask.unwrap();
let bits_needed = TABLE_SIZE.trailing_zeros() as $inner_type;
let bit_mask = (TABLE_SIZE - 1) as $inner_type;
let bit_shift = (TABLE_SIZE as $inner_type) - bits_needed;
let mut slots = vec![false; TABLE_SIZE];
for val in filled_values {
let idx = $struct_name::get_lcg_based_slot_index(
bit_mask,
bit_shift,
xor_key,
val
);
assert!(idx < TABLE_SIZE);
assert!(!slots[idx]);
slots[idx] = true;
}
}
#[test]
fn test_heavy_math() {
const TABLE_SIZE: usize = $struct_name::TOTAL_BITS;
let max_val = <$inner_type>::MAX;
let one_val = 1 as $inner_type;
let mut block_pattern = 0 as $inner_type;
let mut noise_pattern = 0 as $inner_type;
let total_bytes = core::mem::size_of::<$inner_type>();
let mut i = 0;
while i < total_bytes {
let byte_val = if i % 2 == 0 { 0x55 } else { 0xAA };
noise_pattern = (noise_pattern << 8) | (byte_val as $inner_type);
let block_val = if i < total_bytes / 3 {
0xEE
} else if i < (total_bytes * 2) / 3 {
0xFF
} else {
0xAA
};
block_pattern = (block_pattern << 8) | (block_val as $inner_type);
i += 1;
}
let center_bit_shift = (TABLE_SIZE / 2) as u32;
let center_bit_pattern = (1 as $inner_type)
.checked_shl(center_bit_shift)
.unwrap_or(1);
let values: [$inner_type; 6] = [
$init_rng,
noise_pattern,
max_val,
one_val,
center_bit_pattern,
block_pattern
];
let mask = $struct_name::get_lcg_xor_mask(&values);
assert!(mask.is_some());
}
#[test]
fn test_single_element() {
const TABLE_SIZE: usize = $struct_name::TOTAL_BITS;
let single_value = [420 as $inner_type];
let mask = $struct_name::get_lcg_xor_mask(&single_value);
assert!(mask.is_some());
let xor_key = mask.unwrap();
let bits_needed = TABLE_SIZE.trailing_zeros() as $inner_type;
let bit_mask = (TABLE_SIZE - 1) as $inner_type;
let bit_shift = (TABLE_SIZE as $inner_type) - bits_needed;
let idx = $struct_name::get_lcg_based_slot_index(
bit_mask,
bit_shift,
xor_key,
&single_value[0]
);
assert!(idx < TABLE_SIZE);
}
#[test]
fn test_invalid_bounds_returns_none() {
const TABLE_SIZE: usize = $struct_name::TOTAL_BITS;
let oversized_values = [1 as $inner_type; TABLE_SIZE];
let mask = $struct_name::get_lcg_xor_mask(&oversized_values);
assert!(mask.is_none());
}
#[test]
fn test_top_25_evm_chains_by_tvl() {
const TABLE_SIZE: usize = $struct_name::TOTAL_BITS;
let evm_network_ids: [u64; 25] = [
1, // Ethereum Mainnet
8453, // Base
56, // BNB Smart Chain
42161, // Arbitrum One
10143, // Monad
137, // Polygon PoS
43114, // Avalanche C-Chain
10, // OP Mainnet
57073, // Ink
14, // Flare Network
196, // X Layer
25, // Cronos
5000, // Mantle
100, // Gnosis Chain
59144, // Linea
534352, // Scroll
324, // zkSync Era
146, // Sonic (Fantom)
1116, // Core
42220, // Celo
1088, // Metis
592, // Astar
1284, // Moonbeam
98865, // Plume Network
60808, // BOB (Build on Bitcoin)
];
let mask = $struct_name::get_lcg_xor_mask(&evm_network_ids);
if let Some(xor_key) = mask {
let mut slots_check = [false; TABLE_SIZE];
let bits_needed = TABLE_SIZE.trailing_zeros() as $inner_type;
let bit_mask = (TABLE_SIZE - 1) as $inner_type;
let bit_shift = (TABLE_SIZE as $inner_type) - bits_needed;
for chain_id in &evm_network_ids {
let slot_idx = $struct_name::get_lcg_based_slot_index(
bit_mask,
bit_shift,
xor_key,
chain_id
);
assert!(slot_idx < TABLE_SIZE);
assert!(!slots_check[slot_idx]);
slots_check[slot_idx] = true;
}
} else {
assert!(TABLE_SIZE <= 32);
}
}
#[test]
fn test_top_25_evm_testnet_chains() {
const TABLE_SIZE: usize = $struct_name::TOTAL_BITS;
let evm_network_ids: [u64; 10] = [
63, // Mordor (Ethereum Classic Testnet)
11155111, // Ethereum Sepolia
80002, // Polygon Amoy
421614, // Arbitrum Sepolia
84532, // Base Sepolia
11155420, // Optimism Sepolia
59141, // Linea Sepolia
534351, // Scroll Sepolia
80084, // Berachain Bartio
98864, // Plume Testnet
];
let mask = $struct_name::get_lcg_xor_mask(&evm_network_ids);
if let Some(xor_key) = mask {
let mut slots_check = [false; TABLE_SIZE];
let bits_needed = TABLE_SIZE.trailing_zeros() as $inner_type;
let bit_mask = (TABLE_SIZE - 1) as $inner_type;
let bit_shift = (TABLE_SIZE as $inner_type) - bits_needed;
for chain_id in &evm_network_ids {
let slot_idx = $struct_name::get_lcg_based_slot_index(
bit_mask,
bit_shift,
xor_key,
chain_id
);
assert!(slot_idx < TABLE_SIZE);
assert!(!slots_check[slot_idx]);
slots_check[slot_idx] = true;
}
} else {
assert!(TABLE_SIZE == 16);
}
}
}
}
}
generate_mapper_test!(u16_mapper, PerfectSlotMapperU16, 0x1234, u16);
generate_mapper_test!(u32_mapper, PerfectSlotMapperU32, 0x12345678, u32);
generate_mapper_test!(u64_mapper, PerfectSlotMapperU64, 0x123456789ABCDEF, u64);
generate_mapper_test!(u128_mapper, PerfectSlotMapperU128, 0x123456789ABCDEF0123456789ABCDEF0, u128);
}

View File

@ -3,12 +3,14 @@
use ghost_traits::hashing::GhostHasher;
use sp_core::H256;
use sp_io::hashing::{blake2_256, keccak_256, sha2_256};
use sp_runtime::{Perbill, traits::{Saturating, UniqueSaturatedInto}};
pub mod bounded_bitmap;
pub mod hash_chain;
pub mod merkle_tree;
pub mod networks;
pub mod utxo;
pub mod lcg;
pub type AuthIndexU8 = u8;
pub type AuthIndexU16 = u16;
@ -48,53 +50,101 @@ where
value - f
}
pub struct SubstrateBlake2Hasher;
impl GhostHasher for SubstrateBlake2Hasher {
type Hash = H256;
type HashBytes = [u8; 32];
fn hash(data: &[u8]) -> Self::Hash {
H256::from(blake2_256(data))
}
fn hash_len() -> usize {
H256::len_bytes()
}
fn empty() -> Self::Hash {
H256::zero()
}
pub fn get_submission_probability<B>(
participants_count: usize,
values_count: usize,
blocks_left: B
) -> Perbill
where
B: UniqueSaturatedInto<u32>,
{
// y = min(1, (1/6) + (A-C)/2x)
let a: u32 = participants_count.unique_saturated_into();
let c: u32 = values_count.unique_saturated_into();
let x: u32 = blocks_left.unique_saturated_into();
if a == 0 { return Perbill::zero(); }
let part_1 = Perbill::from_percent(6);
let part_2 = if x > 0 {
let numerator = a.saturating_sub(c);
let denominator = x.saturating_mul(2);
Perbill::from_rational(numerator, denominator)
} else {
if c < a { Perbill::one() }
else { Perbill::zero() }
};
part_1.saturating_add(part_2)
}
pub struct SubstrateKeccakHasher;
impl GhostHasher for SubstrateKeccakHasher {
type Hash = H256;
type HashBytes = [u8; 32];
fn hash(data: &[u8]) -> Self::Hash {
H256::from(keccak_256(data))
}
fn hash_len() -> usize {
H256::len_bytes()
}
fn empty() -> Self::Hash {
H256::zero()
}
macro_rules! impl_ghost_hasher {
(
struct $name:ident;
hash_type: $hash_type:ty;
hash_bytes_len: $hash_bytes_len:expr;
fn hash($data:ident) -> $hash_res:expr
) => {
pub struct $name;
impl GhostHasher for $name {
type Hash = $hash_type;
type HashBytes = [u8; $hash_bytes_len];
fn hash($data: &[u8]) -> Self::Hash {
$hash_res
}
fn hash_len() -> usize {
$hash_bytes_len
}
fn empty() -> Self::Hash {
<$hash_type>::zero()
}
fn combine_hashes(left: Self::Hash, right: Self::Hash) -> Self::Hash {
let mut combined = [0u8; $hash_bytes_len * 2];
combined[..$hash_bytes_len].copy_from_slice(left.as_ref());
combined[$hash_bytes_len..].copy_from_slice(right.as_ref());
Self::hash(&combined)
}
fn combine_hashes_prefixed(p: u8, l: Self::Hash, r: Self::Hash) -> Self::Hash {
let mut combined = [p; $hash_bytes_len * 2 + 1];
combined[1..1 + $hash_bytes_len].copy_from_slice(l.as_ref());
combined[1 + $hash_bytes_len..].copy_from_slice(r.as_ref());
Self::hash(&combined)
}
}
};
}
pub struct UtxoSha2Hasher;
impl GhostHasher for UtxoSha2Hasher {
type Hash = H256;
type HashBytes = [u8; 32];
fn hash(data: &[u8]) -> Self::Hash {
impl_ghost_hasher!(
struct SubstrateBlake2Hasher;
hash_type: H256;
hash_bytes_len: 32;
fn hash(data) -> H256::from(blake2_256(data))
);
impl_ghost_hasher!(
struct SubstrateKeccakHasher;
hash_type: H256;
hash_bytes_len: 32;
fn hash(data) -> H256::from(keccak_256(data))
);
impl_ghost_hasher!(
struct UtxoSha2Hasher;
hash_type: H256;
hash_bytes_len: 32;
fn hash(data) -> {
let first_pass: [u8; 32] = sha2_256(data);
let second_pass: [u8; 32] = sha2_256(&first_pass);
H256::from(second_pass)
}
fn hash_len() -> usize {
H256::len_bytes()
}
fn empty() -> Self::Hash {
H256::zero()
}
}
);
#[cfg(test)]
mod tests {
@ -134,4 +184,38 @@ mod tests {
assert_eq!(get_byzantium_quorum_threshold(4u64), 3u64);
assert_eq!(get_byzantium_quorum_threshold(4usize), 3usize);
}
#[test]
fn test_base_probability_when_plenty_of_time() {
let prob = get_submission_probability(100, 0, 10000u32);
assert!(prob > Perbill::from_percent(6));
assert!(prob < Perbill::from_percent(7));
}
#[test]
fn test_urgency_increases_as_blocks_shrink() {
let prob_far = get_submission_probability(20, 10, 10u32);
assert_eq!(prob_far, Perbill::from_percent(56));
let prob_close = get_submission_probability(20, 10, 2u32);
assert_eq!(prob_close, Perbill::one());
}
#[test]
fn test_deadline_reached_but_votes_missing() {
let prob = get_submission_probability(10, 5, 0u32);
assert_eq!(prob, Perbill::one());
}
#[test]
fn test_all_votes_collected() {
let prob = get_submission_probability(10, 10, 5u32);
assert_eq!(prob, Perbill::from_percent(6));
}
#[test]
fn test_zero_participants_edge_case() {
let prob = get_submission_probability(0, 0, 5u32);
assert_eq!(prob, Perbill::zero());
}
}

File diff suppressed because it is too large Load Diff

View File

@ -3,6 +3,8 @@ use sp_std::vec::Vec;
const MAX_INPUTS: usize = 3;
const MAX_OUTPUTS: usize = 4;
const MAX_FLAGS_LEN: usize = 8;
const MAX_HASHES_LEN: usize = 16;
const MAX_SCRIPT_LEN: usize = 512;
pub const MAX_TX_LEN: usize = 4096;
@ -27,20 +29,17 @@ pub struct Transaction<H: GhostHasher> {
lock_time: u32,
}
pub struct Header<H: GhostHasher> {
pub merkle_root: H::Hash,
pub tx_count: u32,
pub tree_hashes: Vec<H::Hash>,
pub flags: Vec<u8>,
}
pub struct Header<H: GhostHasher>(sp_std::marker::PhantomData<H>);
impl<H: GhostHasher> Header<H> {
fn calc_tree_width(&self, h: u32) -> u32 {
(self.tx_count + (1 << h) - 1) >> h
fn calc_tree_width(tx_count: u32, h: u32) -> u32 {
(tx_count + (1 << h) - 1) >> h
}
fn traverse(
&self,
tx_count: u32,
flags: &[u8],
tree_hashes: &[H::Hash],
height: u32,
pos: u32,
bits_used: &mut u32,
@ -51,19 +50,19 @@ impl<H: GhostHasher> Header<H> {
let byte_idx = (*bits_used / 8) as usize;
let bit_idx = (*bits_used % 8) as u8;
if byte_idx >= self.flags.len() {
if byte_idx >= flags.len() {
return Err(ParseError::InvalidVarint(VarintError::BufferTooShort));
}
let parent_of_match = ((self.flags[byte_idx] >> bit_idx) & 1) == 1;
let parent_of_match = ((flags[byte_idx] >> bit_idx) & 1) == 1;
*bits_used += 1;
if height == 0 || !parent_of_match {
if *hash_used as usize >= self.tree_hashes.len() {
if *hash_used as usize >= tree_hashes.len() {
return Err(ParseError::InvalidLength);
}
let curr_hash = self.tree_hashes[*hash_used as usize];
let curr_hash = tree_hashes[*hash_used as usize];
*hash_used += 1;
if height == 0 && parent_of_match && curr_hash == target_txid {
@ -73,7 +72,10 @@ impl<H: GhostHasher> Header<H> {
return Ok(curr_hash);
}
let left = self.traverse(
let left = Self::traverse(
tx_count,
flags,
tree_hashes,
height - 1,
pos * 2,
bits_used,
@ -82,8 +84,11 @@ impl<H: GhostHasher> Header<H> {
target_txid,
)?;
let right = if pos * 2 + 1 < self.calc_tree_width(height - 1) {
let right = self.traverse(
let right = if pos * 2 + 1 < Self::calc_tree_width(tx_count, height - 1) {
let right = Self::traverse(
tx_count,
flags,
tree_hashes,
height - 1,
pos * 2 + 1,
bits_used,
@ -109,17 +114,83 @@ impl<H: GhostHasher> Header<H> {
Ok(current_node_hash)
}
pub fn extract_proof(&self, target_txid: H::Hash) -> Result<H::Hash, ParseError> {
pub fn verify_utxo_merkle_proof(
txproof_bytes: &[u8],
target_txid: H::Hash,
) -> Result<(), ParseError> {
if txproof_bytes.len() < 85 {
return Err(ParseError::InvalidLength);
}
let header_bytes = &txproof_bytes[..80];
let mut bytes = &txproof_bytes[80..];
let mut merkle_root = [0u8; 32];
merkle_root.copy_from_slice(&header_bytes[36..68]);
if bytes.len() < 4 {
return Err(ParseError::InvalidLength);
}
let tx_count = u32::from_le_bytes(
bytes[..4]
.try_into()
.map_err(|_| ParseError::TypeConversionFailed)?,
);
bytes = &bytes[4..];
let hash_count = read_varint(&mut bytes)?;
if hash_count > MAX_HASHES_LEN {
return Err(ParseError::InvalidLength);
}
let hash_count_bytes = hash_count
.checked_mul(32)
.ok_or(ParseError::TypeConversionFailed)?;
if bytes.len() < hash_count_bytes {
return Err(ParseError::InvalidLength);
}
let tree_hashes_bytes = &bytes[..hash_count_bytes];
bytes = &bytes[hash_count_bytes..];
let tree_hashes: Vec<H::Hash> = tree_hashes_bytes
.chunks_exact(32)
.map(|chunk| H::from_slice(chunk))
.collect();
let flags_count = read_varint(&mut bytes)?;
if flags_count > MAX_FLAGS_LEN {
return Err(ParseError::InvalidLength);
}
if bytes.len() < flags_count {
return Err(ParseError::InvalidLength);
}
let flags = bytes[..flags_count].to_vec();
bytes = &bytes[flags_count..];
if !bytes.is_empty() {
return Err(ParseError::InvalidLength);
}
let mut bits_used = 0u32;
let mut hash_used = 0u32;
let mut matched_index = None;
let mut max_height = 0;
while (1 << max_height) < self.tx_count {
while (1 << max_height) < tx_count {
max_height += 1;
}
let computed_root = self.traverse(
let computed_root = Self::traverse(
tx_count,
&flags,
&tree_hashes,
max_height,
0,
&mut bits_used,
@ -128,19 +199,23 @@ impl<H: GhostHasher> Header<H> {
target_txid,
)?;
if hash_used as usize != self.tree_hashes.len() {
if hash_used as usize != tree_hashes.len() {
return Err(ParseError::InvalidLength);
}
if (bits_used + 7) / 8 != self.flags.len() as u32 {
if (bits_used + 7) / 8 != flags.len() as u32 {
return Err(ParseError::InvalidLength);
}
if matched_index.is_some() {
Ok(computed_root)
} else {
Err(ParseError::TransactionNotFoundInProof)
if matched_index.is_none() {
return Err(ParseError::TransactionNotFoundInProof);
}
if computed_root != H::from_slice(&merkle_root) {
return Err(ParseError::InvalidMerkleRoot);
}
Ok(())
}
}
@ -357,6 +432,9 @@ fn read_varint(bytes: &mut &[u8]) -> Result<usize, VarintError> {
.try_into()
.map_err(|_| VarintError::TypeConversionFailed)?,
);
if val < 0xfd {
return Err(VarintError::TypeConversionFailed);
}
*bytes = &bytes[2..];
Ok(val as usize)
}
@ -369,6 +447,9 @@ fn read_varint(bytes: &mut &[u8]) -> Result<usize, VarintError> {
.try_into()
.map_err(|_| VarintError::TypeConversionFailed)?,
);
if val < 0x10000 {
return Err(VarintError::TypeConversionFailed);
}
*bytes = &bytes[4..];
Ok(val as usize)
}
@ -381,6 +462,9 @@ fn read_varint(bytes: &mut &[u8]) -> Result<usize, VarintError> {
.try_into()
.map_err(|_| VarintError::TypeConversionFailed)?,
);
if val < 0x100000000 {
return Err(VarintError::TypeConversionFailed);
}
*bytes = &bytes[8..];
Ok(val as usize)
}
@ -392,6 +476,7 @@ fn read_varint(bytes: &mut &[u8]) -> Result<usize, VarintError> {
pub enum ParseError {
InvalidLength,
ExceededMaxLimits,
InvalidMerkleRoot,
TypeConversionFailed,
TransactionNotFoundInProof,
InvalidVarint(VarintError),
@ -525,60 +610,3 @@ impl<'a, H: GhostHasher> TryFrom<&'a [u8]> for Transaction<H> {
})
}
}
impl<'a, H: GhostHasher> TryFrom<&'a [u8]> for Header<H> {
type Error = ParseError;
fn try_from(mut bytes: &[u8]) -> Result<Self, Self::Error> {
if bytes.len() < 85 {
return Err(ParseError::InvalidLength);
}
let header_bytes = &bytes[..80];
bytes = &bytes[80..];
let mut merkle_root = [0u8; 32];
merkle_root.copy_from_slice(&header_bytes[36..68]);
if bytes.len() < 4 {
return Err(ParseError::InvalidLength);
}
let tx_count = u32::from_le_bytes(
bytes[..4]
.try_into()
.map_err(|_| ParseError::TypeConversionFailed)?,
);
bytes = &bytes[4..];
let hash_count = read_varint(&mut bytes)?;
if bytes.len() < hash_count * 32 {
return Err(ParseError::InvalidLength);
}
let tree_hashes_bytes = &bytes[..hash_count * 32];
bytes = &bytes[hash_count * 32..];
let tree_hashes: Vec<H::Hash> = tree_hashes_bytes
.chunks_exact(32)
.map(|chunk| H::from_slice(chunk))
.collect();
let flags_count = read_varint(&mut bytes)?;
if bytes.len() < flags_count {
return Err(ParseError::InvalidLength);
}
let flags = bytes[..flags_count].to_vec();
bytes = &bytes[flags_count..];
if !bytes.is_empty() {
return Err(ParseError::InvalidLength);
}
Ok(Self {
merkle_root: H::from_slice(&merkle_root),
tx_count,
tree_hashes,
flags,
})
}
}

View File

@ -1,6 +1,6 @@
[package]
name = "ghost-networks"
version = "0.2.10"
version = "0.2.12"
description = "Registry and lifecycle management for external network metadata and cryptographic profiles."
license.workspace = true
authors.workspace = true

View File

@ -197,17 +197,21 @@ pub mod module {
StorageValue<_, BoundedVec<T::NetworkId, T::MaxNetworks>, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn network_curves)]
pub type NetworkCurves<T: Config> = StorageMap<
#[pallet::getter(fn network_types)]
pub type NetworkTypes<T: Config> = StorageMap<
_,
Twox64Concat, NetworkCurve,
BoundedBTreeMap<NetworkType, u32, T::MaxNetworks>,
BoundedBTreeMap<
NetworkType,
BoundedVec<T::NetworkId, T::MaxNetworks>,
T::MaxNetworks,
>,
ValueQuery,
>;
#[pallet::storage]
#[pallet::getter(fn network_curves_vec)]
pub type NetworkCurvesVec<T: Config> =
#[pallet::storage]
#[pallet::getter(fn network_curves)]
pub type NetworkCurves<T: Config> =
StorageValue<_, BoundedVec<NetworkCurve, T::MaxNetworks>, ValueQuery>;
#[pallet::storage]
@ -429,24 +433,26 @@ impl<T: Config> Pallet<T> {
modified_indexes.try_push(chain_id.clone())
.map_err(|_| Error::<T>::TooManyNetworks)?;
let mut modified_curves_map = NetworkCurves::<T>::get(network_curve);
if let Some(counter) = modified_curves_map.get_mut(&network_type) {
*counter = counter.saturating_add(1);
let mut modified_types = NetworkTypes::<T>::get(network_curve);
if let Some(chain_ids) = modified_types.get_mut(&network_type) {
chain_ids.try_push(chain_id).map_err(|_| Error::<T>::TooManyNetworks)?;
} else {
modified_curves_map.try_insert(network_type, 1u32)
let mut new_ids_vec = BoundedVec::<T::NetworkId, T::MaxNetworks>::default();
new_ids_vec.try_push(chain_id).map_err(|_| Error::<T>::TooManyNetworks)?;
modified_types.try_insert(network_type, new_ids_vec)
.map_err(|_| Error::<T>::TooManyNetworks)?;
}
let mut modified_curves_vec = NetworkCurvesVec::<T>::get();
if !modified_curves_vec.contains(&network_curve) {
modified_curves_vec.try_push(network_curve)
let mut modified_curves = NetworkCurves::<T>::get();
if !modified_curves.contains(&network_curve) {
modified_curves.try_push(network_curve)
.map_err(|_| Error::<T>::TooManyNetworks)?;
}
Networks::<T>::insert(&chain_id, network);
NetworkIndexes::<T>::put(modified_indexes);
NetworkCurves::<T>::insert(network_curve, modified_curves_map);
NetworkCurvesVec::<T>::put(modified_curves_vec);
NetworkTypes::<T>::insert(network_curve, modified_types);
NetworkCurves::<T>::put(modified_curves);
Self::deposit_event(Event::<T>::NetworkRegistered { chain_id });
Ok(())
@ -460,38 +466,38 @@ impl<T: Config> Pallet<T> {
let network_curve = network.curve;
let network_type = network.r#type;
let mut modified_curves_map = NetworkCurves::<T>::get(network_curve);
let mut modified_types = NetworkTypes::<T>::get(network_curve);
let counter = modified_curves_map
let chain_ids = modified_types
.get_mut(&network_type)
.ok_or(Error::<T>::NetworkTypeDoesNotExist)?;
*counter = counter.saturating_sub(1);
chain_ids.retain(|id| id != &chain_id);
let mut should_remove_curve_from_vec = false;
if *counter == 0 {
modified_curves_map.remove(&network_type);
if modified_curves_map.is_empty() {
should_remove_curve_from_vec = true;
let mut should_remove_curve = false;
if chain_ids.is_empty() {
modified_types.remove(&network_type);
if modified_types.is_empty() {
should_remove_curve = true;
}
}
let mut modified_indexes = NetworkIndexes::<T>::get();
modified_indexes.retain(|id| id != &chain_id);
let mut modified_curves_vec = NetworkCurvesVec::<T>::get();
if should_remove_curve_from_vec {
modified_curves_vec.retain(|curve| curve != &network_curve);
let mut modified_curves = NetworkCurves::<T>::get();
if should_remove_curve {
modified_curves.retain(|curve| curve != &network_curve);
}
Networks::<T>::remove(&chain_id);
NetworkIndexes::<T>::put(modified_indexes);
NetworkCurvesVec::<T>::put(modified_curves_vec);
NetworkCurves::<T>::put(modified_curves);
if modified_curves_map.is_empty() {
NetworkCurves::<T>::remove(network_curve);
if modified_types.is_empty() {
NetworkTypes::<T>::remove(network_curve);
} else {
NetworkCurves::<T>::insert(network_curve, modified_curves_map);
NetworkTypes::<T>::insert(network_curve, modified_types);
}
Self::deposit_event(Event::<T>::NetworkRemoved { chain_id });
@ -625,15 +631,46 @@ impl<T: Config> Pallet<T> {
chain_id: T::NetworkId,
network_type: NetworkType,
) -> DispatchResult {
Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
let network = maybe_network.as_mut().ok_or(Error::<T>::NetworkDoesNotExist)?;
network.r#type = network_type;
Ok(())
})?;
let mut network = Networks::<T>::get(&chain_id)
.ok_or(Error::<T>::NetworkDoesNotExist)?;
let network_curve = network.curve;
let prev_type = network.r#type;
if prev_type == network_type {
return Ok(());
}
let mut modified_types = NetworkTypes::<T>::get(network_curve);
let prev_chain_ids = modified_types
.get_mut(&prev_type)
.ok_or(Error::<T>::NetworkTypeDoesNotExist)?;
prev_chain_ids.retain(|id| id != &chain_id);
if prev_chain_ids.is_empty() {
modified_types.remove(&prev_type);
}
if let Some(chain_ids) = modified_types.get_mut(&network_type) {
chain_ids.try_push(chain_id).map_err(|_| Error::<T>::TooManyNetworks)?;
} else {
let mut new_ids_vec = BoundedVec::<T::NetworkId, T::MaxNetworks>::default();
new_ids_vec.try_push(chain_id).map_err(|_| Error::<T>::TooManyNetworks)?;
modified_types.try_insert(network_type, new_ids_vec)
.map_err(|_| Error::<T>::TooManyNetworks)?;
}
network.r#type = network_type;
Networks::<T>::insert(&chain_id, network);
NetworkTypes::<T>::insert(network_curve, modified_types);
Self::deposit_event(Event::<T>::NetworkTypeUpdated {
chain_id,
network_type,
});
Ok(())
}
@ -652,50 +689,53 @@ impl<T: Config> Pallet<T> {
return Ok(());
}
let mut modified_prev_map = NetworkCurves::<T>::get(prev_curve);
let counter = modified_prev_map
let mut modified_prev_types = NetworkTypes::<T>::get(prev_curve);
let prev_chain_ids = modified_prev_types
.get_mut(&network_type)
.ok_or(Error::<T>::NetworkTypeDoesNotExist)?;
*counter = counter.saturating_sub(1);
prev_chain_ids.retain(|id| id != &chain_id);
let mut should_remove_prev_from_vec = false;
if *counter == 0 {
modified_prev_map.remove(&network_type);
if modified_prev_map.is_empty() {
should_remove_prev_from_vec = true;
let mut should_remove_prev_curve = false;
if prev_chain_ids.is_empty() {
modified_prev_types.remove(&network_type);
if modified_prev_types.is_empty() {
should_remove_prev_curve = true;
}
}
let mut modified_curr_map = NetworkCurves::<T>::get(network_curve);
if let Some(counter) = modified_curr_map.get_mut(&network_type) {
*counter = counter.saturating_add(1);
let mut modified_curr_types = NetworkTypes::<T>::get(network_curve);
if let Some(chain_ids) = modified_curr_types.get_mut(&network_type) {
chain_ids.try_push(chain_id)
.map_err(|_| Error::<T>::TooManyNetworks)?;
} else {
modified_curr_map.try_insert(network_type, 1u32)
let mut new_ids_vec = BoundedVec::<T::NetworkId, T::MaxNetworks>::default();
new_ids_vec.try_push(chain_id).map_err(|_| Error::<T>::TooManyNetworks)?;
modified_curr_types.try_insert(network_type, new_ids_vec)
.map_err(|_| Error::<T>::TooManyNetworks)?;
}
let mut modified_curves_vec = NetworkCurvesVec::<T>::get();
let mut modified_curves = NetworkCurves::<T>::get();
if should_remove_prev_from_vec {
modified_curves_vec.retain(|curve| curve != &prev_curve);
if should_remove_prev_curve {
modified_curves.retain(|curve| curve != &prev_curve);
}
if !modified_curves_vec.contains(&network_curve) {
modified_curves_vec.try_push(network_curve)
if !modified_curves.contains(&network_curve) {
modified_curves.try_push(network_curve)
.map_err(|_| Error::<T>::TooManyNetworks)?;
}
network.curve = network_curve;
Networks::<T>::insert(&chain_id, network);
NetworkCurves::<T>::insert(network_curve, modified_curr_map);
NetworkCurvesVec::<T>::put(modified_curves_vec);
NetworkTypes::<T>::insert(network_curve, modified_curr_types);
NetworkCurves::<T>::put(modified_curves);
if modified_prev_map.is_empty() {
NetworkCurves::<T>::remove(prev_curve);
if modified_prev_types.is_empty() {
NetworkTypes::<T>::remove(prev_curve);
} else {
NetworkCurves::<T>::insert(prev_curve, modified_prev_map);
NetworkTypes::<T>::insert(prev_curve, modified_prev_types);
}
Self::deposit_event(Event::<T>::NetworkCurveUpdated {
@ -761,9 +801,12 @@ impl<T: Config> NetworkDataBasicHandler for Pallet<T> {
type NetworkId = T::NetworkId;
type NetworkType = NetworkType;
type NetworkCurve = NetworkCurve;
type MaxNetworks = T::MaxNetworks;
}
impl<T: Config> NetworkDataInspectHandler<NetworkData> for Pallet<T> {
type TypesItem = BoundedVec<T::NetworkId, T::MaxNetworks>;
fn count() -> u32 {
NetworkIndexes::<T>::decode_len()
.map(|len| len as u32)
@ -775,7 +818,7 @@ impl<T: Config> NetworkDataInspectHandler<NetworkData> for Pallet<T> {
}
fn curve_exists(curve: &Self::NetworkCurve) -> bool {
NetworkCurves::<T>::contains_key(curve)
NetworkTypes::<T>::contains_key(curve)
}
fn get(n: &Self::NetworkId) -> Option<NetworkData> {
@ -804,15 +847,17 @@ impl<T: Config> NetworkDataInspectHandler<NetworkData> for Pallet<T> {
}
fn iter_curves() -> impl Iterator<Item = Self::NetworkCurve> {
NetworkCurvesVec::<T>::get().into_iter()
NetworkCurves::<T>::get().into_iter()
}
fn iter_types_by_curve(
curve: &Self::NetworkCurve
) -> impl Iterator<Item = Self::NetworkType> {
NetworkCurves::<T>::get(curve)
.into_iter()
.map(|(network_type, _)| network_type)
) -> impl Iterator<Item = (Self::NetworkType, Self::TypesItem)> {
let network_types = NetworkTypes::<T>::get(curve);
network_types.into_iter().map(|(network_type, network_ids)| {
(network_type, network_ids)
})
}
}
@ -833,23 +878,25 @@ impl<T: Config> NetworkDataMutateHandler<NetworkData, BalanceOf<T>> for Pallet<T
.map(|network| network.incoming_share)
.ok_or(())?;
GatekeeperAmounts::<T>::try_mutate(network_id, |gatekeeper_amount| -> Result<(), ()> {
*gatekeeper_amount = gatekeeper_amount.checked_add(&amount).ok_or(())?;
Ok(())
})?;
let increased_gatekeeper_amount = GatekeeperAmounts::<T>::get(network_id)
.checked_add(&amount)
.ok_or(())?;
let curve_share = Perbill::from_parts(incoming_share).mul_ceil(amount);
let final_amount = amount.saturating_sub(curve_share);
NetworkImbalance::<T>::try_mutate(|imbalance_state| -> Result<(), ()> {
imbalance_state.incoming = imbalance_state.incoming
.checked_add(&final_amount)
.ok_or(())?;
imbalance_state.curve_share = imbalance_state.curve_share
.checked_add(&curve_share)
.ok_or(())?;
Ok(())
})?;
let mut imbalance_state = NetworkImbalance::<T>::get();
imbalance_state.incoming = imbalance_state.incoming
.checked_add(&final_amount)
.ok_or(())?;
imbalance_state.curve_share = imbalance_state.curve_share
.checked_add(&curve_share)
.ok_or(())?;
GatekeeperAmounts::<T>::insert(network_id, increased_gatekeeper_amount);
NetworkImbalance::<T>::put(imbalance_state);
Ok(final_amount)
}
@ -862,23 +909,25 @@ impl<T: Config> NetworkDataMutateHandler<NetworkData, BalanceOf<T>> for Pallet<T
.map(|network| network.outgoing_share)
.ok_or(())?;
GatekeeperAmounts::<T>::try_mutate(network_id, |gatekeeper_amount| -> Result<(), ()> {
*gatekeeper_amount = gatekeeper_amount.checked_sub(&amount).ok_or(())?;
Ok(())
})?;
let decreased_gatekeeper_amount = GatekeeperAmounts::<T>::get(network_id)
.checked_sub(&amount)
.ok_or(())?;
let curve_share = Perbill::from_parts(outgoing_share).mul_ceil(amount);
let final_amount = amount.saturating_sub(curve_share);
NetworkImbalance::<T>::try_mutate(|imbalance_state| -> Result<(), ()> {
imbalance_state.outgoing = imbalance_state.outgoing
.checked_add(&final_amount)
.ok_or(())?;
imbalance_state.curve_share = imbalance_state.curve_share
.checked_add(&curve_share)
.ok_or(())?;
Ok(())
})?;
let mut imbalance_state = NetworkImbalance::<T>::get();
imbalance_state.outgoing = imbalance_state.outgoing
.checked_add(&final_amount)
.ok_or(())?;
imbalance_state.curve_share = imbalance_state.curve_share
.checked_add(&curve_share)
.ok_or(())?;
GatekeeperAmounts::<T>::insert(network_id, decreased_gatekeeper_amount);
NetworkImbalance::<T>::put(imbalance_state);
Ok(final_amount)
}

View File

@ -45,8 +45,8 @@ fn register_and_check_network(chain_id: u32, network: NetworkData) {
));
assert_eq!(Networks::<Test>::get(chain_id), Some(network));
assert_eq!(NetworkIndexes::<Test>::get(), vec![chain_id]);
assert!(NetworkCurves::<Test>::contains_key(&network_curve));
assert!(NetworkCurvesVec::<Test>::get().contains(&network_curve));
assert!(NetworkTypes::<Test>::contains_key(&network_curve));
assert!(NetworkCurves::<Test>::get().contains(&network_curve));
}
#[test]
@ -55,8 +55,8 @@ fn could_add_network_from_authority() {
let (chain_id, network) = prepare_network_data(None, None);
assert_eq!(Networks::<Test>::get(chain_id), None);
assert_eq!(NetworkIndexes::<Test>::get(), vec![]);
assert!(!NetworkCurves::<Test>::contains_key(&CURRENT_CURVE));
assert!(!NetworkCurvesVec::<Test>::get().contains(&CURRENT_CURVE));
assert!(!NetworkTypes::<Test>::contains_key(&CURRENT_CURVE));
assert!(!NetworkCurves::<Test>::get().contains(&CURRENT_CURVE));
assert_ok!(GhostNetworks::register_network(
RuntimeOrigin::signed(RegistererAccount::get()),
@ -68,8 +68,8 @@ fn could_add_network_from_authority() {
));
assert_eq!(Networks::<Test>::get(chain_id), Some(network));
assert_eq!(NetworkIndexes::<Test>::get(), vec![chain_id]);
assert!(NetworkCurves::<Test>::contains_key(&CURRENT_CURVE));
assert!(NetworkCurvesVec::<Test>::get().contains(&CURRENT_CURVE));
assert!(NetworkTypes::<Test>::contains_key(&CURRENT_CURVE));
assert!(NetworkCurves::<Test>::get().contains(&CURRENT_CURVE));
});
}
@ -79,8 +79,8 @@ fn could_not_add_network_from_random_account() {
let (chain_id, network) = prepare_network_data(None, None);
assert_eq!(Networks::<Test>::get(chain_id), None);
assert_eq!(NetworkIndexes::<Test>::get(), vec![]);
assert!(!NetworkCurves::<Test>::contains_key(&CURRENT_CURVE));
assert!(!NetworkCurvesVec::<Test>::get().contains(&CURRENT_CURVE));
assert!(!NetworkTypes::<Test>::contains_key(&CURRENT_CURVE));
assert!(!NetworkCurves::<Test>::get().contains(&CURRENT_CURVE));
assert_err!(
GhostNetworks::register_network(
@ -108,8 +108,8 @@ fn could_not_add_network_from_random_account() {
);
assert_eq!(Networks::<Test>::get(chain_id), None);
assert_eq!(NetworkIndexes::<Test>::get(), vec![]);
assert!(!NetworkCurves::<Test>::contains_key(&CURRENT_CURVE));
assert!(!NetworkCurvesVec::<Test>::get().contains(&CURRENT_CURVE));
assert!(!NetworkTypes::<Test>::contains_key(&CURRENT_CURVE));
assert!(!NetworkCurves::<Test>::get().contains(&CURRENT_CURVE));
});
}
@ -351,10 +351,10 @@ fn could_update_network_curve_from_authority_account() {
let (chain_id, network) = prepare_network_data(None, None);
register_and_check_network(chain_id, network.clone());
assert!(!NetworkCurves::<Test>::contains_key(&new_curve));
assert!(NetworkCurves::<Test>::contains_key(&CURRENT_CURVE));
assert!(!NetworkCurvesVec::<Test>::get().contains(&new_curve));
assert!(NetworkCurvesVec::<Test>::get().contains(&CURRENT_CURVE));
assert!(!NetworkTypes::<Test>::contains_key(&new_curve));
assert!(NetworkTypes::<Test>::contains_key(&CURRENT_CURVE));
assert!(!NetworkCurves::<Test>::get().contains(&new_curve));
assert!(NetworkCurves::<Test>::get().contains(&CURRENT_CURVE));
assert_ok!(GhostNetworks::update_network_curve(
RuntimeOrigin::signed(UpdaterAccount::get()),
@ -370,10 +370,10 @@ fn could_update_network_curve_from_authority_account() {
let mut final_network = network.clone();
final_network.r#curve = new_curve;
assert!(NetworkCurves::<Test>::contains_key(&new_curve));
assert!(!NetworkCurves::<Test>::contains_key(&CURRENT_CURVE));
assert!(NetworkCurvesVec::<Test>::get().contains(&new_curve));
assert!(!NetworkCurvesVec::<Test>::get().contains(&CURRENT_CURVE));
assert!(NetworkTypes::<Test>::contains_key(&new_curve));
assert!(!NetworkTypes::<Test>::contains_key(&CURRENT_CURVE));
assert!(NetworkCurves::<Test>::get().contains(&new_curve));
assert!(!NetworkCurves::<Test>::get().contains(&CURRENT_CURVE));
assert_eq!(Networks::<Test>::get(chain_id), Some(final_network.clone()));
assert_ne!(network, final_network);
@ -1050,8 +1050,8 @@ fn could_remove_network_from_authority_account() {
));
assert_eq!(Networks::<Test>::get(chain_id), None);
assert_eq!(NetworkIndexes::<Test>::get(), vec![]);
assert!(!NetworkCurves::<Test>::contains_key(&CURRENT_CURVE));
assert!(!NetworkCurvesVec::<Test>::get().contains(&CURRENT_CURVE));
assert!(!NetworkTypes::<Test>::contains_key(&CURRENT_CURVE));
assert!(!NetworkCurves::<Test>::get().contains(&CURRENT_CURVE));
});
}
@ -1077,8 +1077,8 @@ fn could_not_remove_network_from_random_account() {
);
assert_eq!(Networks::<Test>::get(chain_id), Some(network));
assert_eq!(NetworkIndexes::<Test>::get(), vec![chain_id]);
assert!(NetworkCurves::<Test>::contains_key(&CURRENT_CURVE));
assert!(NetworkCurvesVec::<Test>::get().contains(&CURRENT_CURVE));
assert!(NetworkTypes::<Test>::contains_key(&CURRENT_CURVE));
assert!(NetworkCurves::<Test>::get().contains(&CURRENT_CURVE));
});
}
@ -1093,8 +1093,8 @@ fn could_not_remove_non_existent_network() {
);
assert_eq!(Networks::<Test>::get(chain_id), None);
assert_eq!(NetworkIndexes::<Test>::get(), vec![]);
assert!(!NetworkCurves::<Test>::contains_key(&CURRENT_CURVE));
assert!(!NetworkCurvesVec::<Test>::get().contains(&CURRENT_CURVE));
assert!(!NetworkTypes::<Test>::contains_key(&CURRENT_CURVE));
assert!(!NetworkCurves::<Test>::get().contains(&CURRENT_CURVE));
});
}

View File

@ -1,6 +1,6 @@
[package]
name = "ghost-traits"
version = "0.4.6"
version = "0.4.10"
description = "Shared traits including `GhostHasher`, `NetworkDataBasicHandler`, `BoundedBTreeMap`, `MerkleTree` and more."
license.workspace = true
authors.workspace = true

View File

@ -20,6 +20,10 @@ pub trait BoundedBitmapReader {
fn highest_bit<N>(&self) -> Option<N>
where
usize: UniqueSaturatedInto<N>;
fn count_ones_before<N>(&self, index: N) -> Option<N>
where
N: UniqueSaturatedInto<usize>,
usize: UniqueSaturatedInto<N>;
fn count_ones<N>(&self) -> N
where
u32: UniqueSaturatedInto<N>;
@ -40,9 +44,6 @@ pub trait BoundedBitmapGenerator {
where
N: UniqueSaturatedInto<usize>;
fn empty_from(bitmap: &Self) -> Self;
fn from_bitmask<Outer>(values: &Vec<Outer>) -> Self
where
Outer: AtLeast8BitUnsigned + num_traits::PrimInt;
fn from_vec<Outer>(values: &Vec<Outer>) -> Self
where
Outer: AtLeast8BitUnsigned + TryInto<usize> + PartialEq + Copy;

View File

@ -6,7 +6,14 @@ use sp_std::{
vec::Vec,
};
pub trait IdentifierConverter<A, E> {
pub trait CiphersuiteSizes<A, E> {
fn header_bytes_len(&self) -> usize;
fn element_bytes_len(&self) -> usize;
fn signature_bytes_len(&self) -> usize;
fn scalar_bytes_len(&self) -> usize;
}
pub trait IdentifierConverter<A, E>: CiphersuiteSizes<A, E> {
fn non_zero_index(index: A) -> Result<A, E>;
fn convert_identifier_to_index(bytes: &[u8]) -> Result<A, E>;
}
@ -15,11 +22,6 @@ pub trait FlexibleRoundOptimizedSchnorrThresholdSignature<A, E>: MerkleTreeBuild
type Packages<'a>;
type NoncePackages<'a>;
fn header_bytes_len(&self) -> usize;
fn element_bytes_len(&self) -> usize;
fn signature_bytes_len(&self) -> usize;
fn scalar_bytes_len(&self) -> usize;
fn verify_signature_share(
&self,
i: A,
@ -33,6 +35,7 @@ pub trait FlexibleRoundOptimizedSchnorrThresholdSignature<A, E>: MerkleTreeBuild
vk: &[u8],
m: &[u8],
) -> Result<(), E>;
fn is_nonce_valid_point(&self, b: &[u8]) -> Result<(), E>;
fn is_signature_valid(&self, vk: &[u8], r: &[u8], z: &[u8], m: &[u8]) -> Result<(), E>;
fn accumulate_signature_scalar(&self, ss: &[u8], sh: &[u8]) -> Result<Vec<u8>, E>;
@ -91,13 +94,14 @@ pub trait MerkleTreeBuilder<A, E>: IdentifierConverter<A, E> {
type Hash;
fn build_merkle_tree<C, V, F>(
size_hint: usize,
verifying_shares: &BTreeMap<Identifier<C>, V>,
serialize_fn: F,
) -> Result<Vec<Self::Hash>, E>
where
C: frost_core::Ciphersuite,
<<C as Ciphersuite>::Group as Group>::Field: frost_core::Field,
F: Fn(&V) -> Result<Vec<u8>, E>;
F: Fn(&V, &mut Vec<u8>) -> Result<(), E>;
fn generate_merkle_proof_from_tree<C, V, F>(
verifying_shares: &BTreeMap<Identifier<C>, V>,

View File

@ -21,6 +21,13 @@ pub trait GhostHasher {
bytes
}
fn combine_hashes(left: Self::Hash, right: Self::Hash) -> Self::Hash;
fn combine_hashes_prefixed(
prefix: u8,
left: Self::Hash,
right: Self::Hash,
) -> Self::Hash;
fn hash(data: &[u8]) -> Self::Hash;
fn empty() -> Self::Hash;
fn hash_len() -> usize;

View File

@ -1,4 +1,7 @@
use frame_support::{pallet_prelude::*, storage::PrefixIterator};
use frame_support::{
pallet_prelude::*,
storage::PrefixIterator,
};
use sp_std::vec::Vec;
use sp_runtime::{
offchain::http::PendingRequest,
@ -7,14 +10,14 @@ use sp_runtime::{
};
pub trait NetworkDataBasicHandler {
type NetworkCurve: Parameter
type NetworkCurve: Parameter
+ Member
+ Default
+ Copy
+ TypeInfo
+ MaybeSerializeDeserialize
+ MaxEncodedLen;
type NetworkType: Parameter
type NetworkType: Parameter
+ Member
+ Default
+ Copy
@ -29,9 +32,12 @@ pub trait NetworkDataBasicHandler {
+ TypeInfo
+ MaybeSerializeDeserialize
+ MaxEncodedLen;
type MaxNetworks: Get<u32>;
}
pub trait NetworkDataInspectHandler<Network>: NetworkDataBasicHandler {
type TypesItem: AsRef<[Self::NetworkId]>;
fn count() -> u32;
fn contains_key(n: &Self::NetworkId) -> bool;
fn curve_exists(c: &Self::NetworkCurve) -> bool;
@ -40,7 +46,9 @@ pub trait NetworkDataInspectHandler<Network>: NetworkDataBasicHandler {
fn iter() -> PrefixIterator<(Self::NetworkId, Network)>;
fn iter_indexes() -> impl Iterator<Item = Self::NetworkId>;
fn iter_curves() -> impl Iterator<Item = Self::NetworkCurve>;
fn iter_types_by_curve(n: &Self::NetworkCurve) -> impl Iterator<Item = Self::NetworkType>;
fn iter_types_by_curve(
c: &Self::NetworkCurve
) -> impl Iterator<Item = (Self::NetworkType, Self::TypesItem)>;
}
pub trait NetworkDataMutateHandler<Network, Balance>: NetworkDataInspectHandler<Network> {

View File

@ -1,6 +1,6 @@
[package]
name = "ghost-weaver"
version = "0.0.14"
version = "0.0.27"
description = "Weaving a secure cryptographic tapestry across different external chains."
license.workspace = true
authors.workspace = true

View File

@ -35,6 +35,9 @@ fn prepare_pallet<T: Config>(
T::NetworkDataHandler::register(network_id, network_data)
.expect("network should be valid for insertion");
let current_block = T::BlockNumberProvider::current_block_number();
Pallet::<T>::on_initialize(current_block);
(network_id, authority, 0)
}
@ -53,6 +56,7 @@ mod benchmarks {
let block_attestation = AttestationContext::default()
.with_network_id(network_id)
.with_authority_index(authority_index)
.with_current_block(1337u16.into())
.build_block_attestation(420);
let signature = authority
@ -73,21 +77,38 @@ mod benchmarks {
fn weave_weft(
a: Linear<1, { T::MaxAuthorities::get() as u32 }>
) -> Result<(), BenchmarkError> {
let weaving_session = 420;
let next_session = weaving_session + 1;
let weaving_session: WeavingSession = 420;
let next_session: WeavingSession = weaving_session + 1;
let root_hash = H256::repeat_byte(0x69);
let (network_id, authority, authority_index) = prepare_pallet::<T>(a, None);
let tapestry_key = (network_id, weaving_session, next_session, root_hash);
TapestryStrands::<T>::mutate(&tapestry_key, |tapestry| {
(0..a).for_each(|i| {
let index = i as AuthIndex;
if index == authority_index { return; }
tapestry.insert(index);
});
let next_session_bytes = next_session.to_be_bytes();
let weaving_session_bytes = weaving_session.to_be_bytes();
let metadata: &[&[u8]] = &[
root_hash.as_ref(),
next_session_bytes.as_ref(),
weaving_session_bytes.as_ref(),
];
let tapestry_strand_key =
sequential_hash::<SubstrateBlake2Hasher, _>(metadata);
let mut bitmap = WeaversBitmap::<T>::empty(a);
(0..a).for_each(|i| {
let index = i as AuthIndex;
if index == authority_index { return; }
bitmap.insert(index);
});
let mut tapestry_map = BoundedBTreeMap::<_, _, T::MaxAuthorities>::default();
tapestry_map
.try_insert(tapestry_strand_key.clone(), bitmap)
.map_err(|_| BenchmarkError::Stop("Failed to insert bitmap into benchmark map"))?;
TapestryStrands::<T>::insert(&network_id, tapestry_map);
let current_block = T::BlockNumberProvider::current_block_number();
let dummy_draft = TapestryDraftBuilder::default()
.with_current_block(current_block)
@ -101,24 +122,31 @@ mod benchmarks {
.with_network_id(network_id)
.with_weaving_session(weaving_session)
.with_authority_index(authority_index)
.with_current_block(1337u16.into())
.build_hash_attestation(root_hash, next_session);
let signature = authority
.sign(&hash_attestation.encode())
.expect("Signature should be created");
let tapestry_strand_key = (network_id, weaving_session, next_session, root_hash);
let weaving_state = WeavingStates::<T>::get(&network_id);
assert_eq!(weaving_state.hash, H256::default());
assert_eq!(weaving_state.next_session, 0);
assert_eq!(weaving_state.count, 0);
assert!(!TapestryStrands::<T>::get(&tapestry_strand_key).contains(authority_index));
assert_eq!(
TapestryStrands::<T>::get(&tapestry_strand_key).count_ones::<u32>(),
a - 1
);
let tapestry_strand = TapestryStrands::<T>::get(&network_id);
let maybe_tapestry_bitmap = tapestry_strand.get(&tapestry_strand_key);
let authority_does_not_exists = maybe_tapestry_bitmap
.map(|bitmap| !bitmap.contains(authority_index))
.ok_or(BenchmarkError::Stop("NetworkId should be registered"))?;
let tapestry_participants = maybe_tapestry_bitmap
.map(|bitmap| bitmap.count_ones::<u32>())
.ok_or(BenchmarkError::Stop("NetworkId should be registered"))?;
assert!(authority_does_not_exists);
assert_eq!(tapestry_participants, a - 1);
#[extrinsic_call]
_(RawOrigin::None, hash_attestation, signature);
@ -128,11 +156,19 @@ mod benchmarks {
assert_eq!(new_weaving_state.next_session, next_session);
assert_eq!(new_weaving_state.count, a as AuthIndex);
assert!(TapestryStrands::<T>::get(&tapestry_strand_key).contains(authority_index));
assert_eq!(
TapestryStrands::<T>::get(&tapestry_strand_key).count_ones::<u32>(),
a
);
let tapestry_strand = TapestryStrands::<T>::get(&network_id);
let maybe_tapestry_bitmap = tapestry_strand.get(&tapestry_strand_key);
let authority_exists = maybe_tapestry_bitmap
.map(|bitmap| bitmap.contains(authority_index))
.ok_or(BenchmarkError::Stop("NetworkId should be registered"))?;
let tapestry_participants = maybe_tapestry_bitmap
.map(|bitmap| bitmap.count_ones::<u32>())
.ok_or(BenchmarkError::Stop("NetworkId should be registered"))?;
assert!(authority_exists);
assert_eq!(tapestry_participants, a);
Ok(())
}
@ -320,9 +356,12 @@ mod benchmarks {
let first_sha = sp_io::hashing::sha2_256(&last_header[..80]);
let mut native_le = sp_io::hashing::sha2_256(&first_sha);
native_le.reverse();
let dynamic_trusted_root = H256::from(native_le);
LoomStates::<T>::insert(&network_id, &session, dynamic_trusted_root);
let at_block = 420;
let dynamic_trusted_root = H256::from(native_le);
let loom_state = LoomState { at_block, root_hash: dynamic_trusted_root };
LoomStates::<T>::insert(&network_id, &session, loom_state);
let utxo_thread_proof = UtxoThreadProof::new(
gettxproof_bytes,
@ -360,8 +399,11 @@ mod benchmarks {
let root_string =
hex::decode("07f4a5ba4750d9ddfc080429287cbcbde7464aa2f8ae13feaade0dcb45ca6129")
.expect("EVM Root hex should be valid");
let at_block = 420;
let expected_root = H256::from_slice(&root_string);
LoomStates::<T>::insert(&network_id, &session, expected_root);
let loom_state = LoomState { at_block, root_hash: expected_root };
LoomStates::<T>::insert(&network_id, &session, loom_state);
let receiver_32: [u8; 32] =
hex::decode("0ae3ba32f8555fe3031461e2cb6feeadb67029ef7490b982824fc90751a4cb3e")

View File

@ -68,13 +68,14 @@ fn parse_pending_requests<'a, R: Copy + Into<RequestId> + 'static>(
) -> impl Iterator<Item = NetworkResponseType> + 'a {
PendingRequest::try_wait_all(pending_requests, Some(deadline))
.into_iter()
.filter_map(move |wait_result| {
.enumerate()
.filter_map(move |(idx, wait_result)| {
let http_result = wait_result
.inspect_err(|_| {
log::error!(
target: LOG_TARGET,
"🕸️ Error during request preparation: {:?}",
WeavingError::<u32>::DeadlineReached,
"🕸️ Weaver: RPC request [index {}] timed out (deadline reached).",
idx
)
})
.ok()?;
@ -83,7 +84,8 @@ fn parse_pending_requests<'a, R: Copy + Into<RequestId> + 'static>(
.inspect_err(|err| {
log::error!(
target: LOG_TARGET,
"🕸️ Error during response parsing: {:?}",
"🕸️ Weaver: RPC request [index {}] failed with HTTP error: {:?}",
idx,
err,
)
})

View File

@ -22,23 +22,32 @@ use sp_runtime::{
storage::StorageValueRef,
storage_lock::{StorageLock, Time},
},
traits::{BlockNumberProvider, UniqueSaturatedInto},
RuntimeAppPublic,
traits::{
TrailingZeroInput, BlockNumberProvider, Saturating,
UniqueSaturatedInto,
},
RuntimeAppPublic, Perbill, PerThing,
};
use ghost_helpers::{
bounded_bitmap::{validate_bitmap_sizes, BoundedBitmap},
get_byzantium_quorum_threshold,
networks::{NetworkData, NetworkType},
hash_chain::sequential_hash,
get_byzantium_quorum_threshold, get_submission_probability,
SubstrateBlake2Hasher,
};
use ghost_traits::{
bounded_bitmap::{BoundedBitmapReader, BoundedBitmapWriter},
bounded_bitmap::{
BoundedBitmapReader, BoundedBitmapWriter,
BoundedBitmapGenerator,
},
networks::{
NetworkDataBasicHandler, NetworkDataInspectHandler, NetworkDataMutateHandler,
NetworkRpcResolver,
NetworkDataBasicHandler, NetworkDataInspectHandler,
NetworkDataMutateHandler, NetworkRpcResolver,
},
};
pub mod migrations;
mod errors;
mod impls;
mod types;
@ -78,8 +87,8 @@ const LOG_TARGET: &str = "runtime::ghost-weaver";
const DB_PREFIX: &[u8] = b"ghost-weaver::";
const LOCK_BLOCK_EXPIRATION: u64 = 20;
const MIN_LOCK_GUARD_PERIOD: u64 = 15_000;
const FETCH_TIMEOUT_PERIOD: u64 = 3_000;
const MIN_LOCK_GUARD_PERIOD: u64 = 60_000;
const FETCH_TIMEOUT_PERIOD: u64 = 5_000;
type AuthIndex = ghost_helpers::AuthIndexU16;
type BitmapChunk = ghost_helpers::BitmapChunkU32;
@ -99,7 +108,7 @@ type WeaversBitmap<T> = BoundedBitmap<BitmapChunk, <T as Config>::MaxAuthorities
pub mod pallet {
use super::*;
const STORAGE_VERSION: StorageVersion = StorageVersion::new(0);
const STORAGE_VERSION: StorageVersion = StorageVersion::new(2);
#[pallet::pallet]
#[pallet::storage_version(STORAGE_VERSION)]
@ -170,6 +179,7 @@ pub mod pallet {
InvalidParticipantAttestation,
NonExistentNetworkId,
TooManyAttestations,
TooManyTapestries,
TimeWentBackwards,
WeavingIsActive,
WeavingIsInactive,
@ -182,6 +192,16 @@ pub mod pallet {
CouldNotRegisterIncoming,
}
#[pallet::storage]
#[pallet::getter(fn network_attested_blocks)]
pub(super) type NetworkAttestedBlocks<T: Config> = StorageDoubleMap<
_,
Twox64Concat, NetworkIdOf<T>,
Twox64Concat, AuthIndex,
BlockNumberFor<T>,
ValueQuery,
>;
#[pallet::storage]
#[pallet::getter(fn current_weaving_session)]
pub(super) type CurrentWeavingSession<T: Config> =
@ -202,21 +222,16 @@ pub mod pallet {
_,
Twox64Concat, NetworkIdOf<T>,
Twox64Concat, WeavingSession,
H256,
LoomState,
ValueQuery,
>;
#[pallet::storage]
#[pallet::getter(fn tapestry_strands)]
pub(super) type TapestryStrands<T: Config> = StorageNMap<
pub(super) type TapestryStrands<T: Config> = StorageMap<
_,
(
NMapKey<Twox64Concat, NetworkIdOf<T>>,
NMapKey<Twox64Concat, WeavingSession>,
NMapKey<Twox64Concat, WeavingSession>,
NMapKey<Twox64Concat, H256>,
),
WeaversBitmap<T>,
Twox64Concat, NetworkIdOf<T>,
BoundedBTreeMap<H256, WeaversBitmap<T>, T::MaxAuthorities>,
ValueQuery,
>;
@ -288,7 +303,12 @@ pub mod pallet {
);
return;
}
LoomStates::<T>::insert(&data.0, data.1, data.2);
let initial_loom_state = LoomState {
at_block: 0u16.into(),
root_hash: data.2,
};
LoomStates::<T>::insert(&data.0, data.1, initial_loom_state);
});
if !self.authorities.is_empty() {
@ -330,26 +350,23 @@ pub mod pallet {
let external_block = block_attestation.external_block;
let current_authorities_count = Authorities::<T>::decode_len().
unwrap_or_default();
NetworkAttestations::<T>::try_mutate(
&network_id,
|attestations| -> DispatchResult {
if attestations.is_empty() {
attestations.bounded_resize(current_authorities_count, None);
}
let index = authority_index as usize;
ensure!(
(authority_index as usize) < attestations.len(),
Error::<T>::TooManyAttestations,
);
attestations[authority_index as usize] = Some(external_block);
ensure!(index < attestations.len(), Error::<T>::TooManyAttestations);
attestations[index] = Some(external_block);
Ok(())
})?;
let current_block = T::BlockNumberProvider::current_block_number();
NetworkAttestedBlocks::<T>::insert(
&network_id, &authority_index,
current_block
);
Self::deposit_event(Event::<T>::BlockAttestated {
network_id,
authority_index,
@ -383,29 +400,65 @@ pub mod pallet {
Error::<T>::WeavingIsInactive,
);
let authorities_len = Authorities::<T>::decode_len()
.unwrap_or(0);
let root_hash = hash_attestation.root_hash;
let next_session = hash_attestation.next_session;
let weaving_session = hash_attestation.weaving_session();
let next_session_bytes = next_session.to_be_bytes();
let weaving_session_bytes = weaving_session.to_be_bytes();
let metadata: &[&[u8]] = &[
root_hash.as_ref(),
next_session_bytes.as_ref(),
weaving_session_bytes.as_ref(),
];
let tapestry_strand_key =
sequential_hash::<SubstrateBlake2Hasher, _>(metadata);
let weavers_count = TapestryStrands::<T>::try_mutate(
&(network_id, weaving_session, next_session, root_hash),
&network_id,
|tapestry_strand| -> Result<AuthIndex, DispatchError> {
if !tapestry_strand.contains_key(&tapestry_strand_key) {
let empty_bitmap = WeaversBitmap::<T>::empty(authorities_len);
tapestry_strand
.try_insert(tapestry_strand_key, empty_bitmap)
.map_err(|_| Error::<T>::TooManyTapestries)?;
}
let tapestry_bitmap = tapestry_strand
.get_mut(&tapestry_strand_key)
.ok_or(Error::<T>::TooManyTapestries)?;
ensure!(
!tapestry_strand.contains(authority_index),
!tapestry_bitmap.contains(authority_index),
Error::<T>::AuthorityPartOfTrapestry,
);
tapestry_strand.insert(authority_index);
Ok(tapestry_strand.count_ones::<AuthIndex>())
tapestry_bitmap.insert(authority_index);
Ok(tapestry_bitmap.count_ones::<AuthIndex>())
},
)?;
WeavingStates::<T>::mutate(&network_id, |weaving_state| {
if weaving_state.count < weavers_count {
*weaving_state = WeavingState::new(root_hash, next_session, weavers_count);
*weaving_state = WeavingState::new(
root_hash,
next_session,
weavers_count
);
}
});
let current_block = T::BlockNumberProvider::current_block_number();
NetworkAttestedBlocks::<T>::insert(
&network_id, &authority_index,
current_block
);
Self::deposit_event(Event::<T>::HashAttestated {
authority_index,
next_session,
@ -438,7 +491,7 @@ pub mod pallet {
let network_data = T::NetworkDataHandler::get(&network_id)
.ok_or(Error::<T>::NonExistentNetworkId)?;
let root_hash = LoomStates::<T>::get(&network_id, &session);
let state = LoomStates::<T>::get(&network_id, &session);
let pulled_thread_key = thread_proof.get_unique_key(session, network_id);
ensure!(
@ -447,7 +500,7 @@ pub mod pallet {
);
let (receiver_account, amount) = thread_proof
.verify_thread_proof(root_hash, network_id, &network_data.gatekeeper)
.verify_thread_proof(state.root_hash, network_id, &network_data.gatekeeper)
.ok_or(Error::<T>::InvalidMerkleProof)?;
let receiver_bytes: &[u8; 32] = receiver_account.as_ref();
@ -504,11 +557,13 @@ pub mod pallet {
let current_session = CurrentWeavingSession::<T>::get(&network_id);
let loom_state_changed =
LoomStates::<T>::mutate(&network_id, &current_session, |loom_state| {
if *loom_state != weaving_state.hash && threshold_reached {
*loom_state = weaving_state.hash;
return true;
}
false
if !threshold_reached { return false; }
let hash_changed = loom_state.root_hash != weaving_state.hash;
loom_state.at_block = tapestry_draft.median;
if hash_changed { loom_state.root_hash = weaving_state.hash; }
hash_changed
});
if loom_state_changed && weaving_state.next_session != current_session {
@ -518,8 +573,10 @@ pub mod pallet {
CurrentWeavingSession::<T>::insert(&network_id, next_session);
}
weight.saturating_accrue(T::DbWeight::get().writes(1));
weight.saturating_accrue(T::DbWeight::get().writes(3));
TapestryDrafts::<T>::remove(&network_id);
WeavingStates::<T>::remove(&network_id);
NetworkAttestations::<T>::remove(&network_id);
}
}
None => {
@ -532,9 +589,11 @@ pub mod pallet {
weight.saturating_accrue(T::DbWeight::get().reads(1));
let attestation_len = NetworkAttestations::<T>::decode_len(&network_id);
if attestation_len.unwrap_or(0) > len {
weight.saturating_accrue(T::DbWeight::get().writes(1));
NetworkAttestations::<T>::remove(&network_id);
if attestation_len.unwrap_or(0) != len {
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
NetworkAttestations::<T>::mutate(&network_id, |attestations| {
attestations.bounded_resize(len, None);
});
return weight;
}
@ -562,8 +621,9 @@ pub mod pallet {
.with_median_external(median_block)
.build();
TapestryStrands::<T>::remove(&network_id);
TapestryDrafts::<T>::insert(&network_id, tapestry_draft);
weight.saturating_accrue(T::DbWeight::get().writes(1));
weight.saturating_accrue(T::DbWeight::get().writes(2));
}
}
}
@ -575,7 +635,7 @@ pub mod pallet {
if sp_io::offchain::is_validator() {
log::warn!(target: LOG_TARGET, "🕸️ Weaver started at block #{:?}", now);
match Self::start_weaving(now) {
Ok(_) => log::warn!(target: LOG_TARGET, "🕸️ Weaver finished gracefully at block #{:?}", now),
Ok(msg) => log::warn!(target: LOG_TARGET, "🕸️ Weaver finished gracefully at block #{:?}: {:?}", now, msg),
Err(e) => log::warn!(target: LOG_TARGET, "🕸️ Weaver failed at block #{:?}: {:?}", now, e),
}
}
@ -616,6 +676,21 @@ pub mod pallet {
.propagate(true)
.build()
}
Call::pull_thread {
network_id,
session,
thread_proof,
} => {
let thread_context =
Self::validate_thread(network_id, session, thread_proof)?;
ValidTransaction::with_tag_prefix("WeaverThread")
.priority(T::UnsignedPriority::get())
.and_provides(thread_context.to_fixed_bytes())
.longevity(LOCK_BLOCK_EXPIRATION)
.propagate(true)
.build()
}
_ => InvalidTransaction::Call.into(),
}
}
@ -623,6 +698,31 @@ pub mod pallet {
}
impl<T: Config> Pallet<T> {
fn validate_thread(
network_id: &NetworkIdOf<T>,
session: &WeavingSession,
thread_proof: &ThreadProof<BalanceOf<T>>,
) -> Result<H256, InvalidTransaction> {
let network_data = T::NetworkDataHandler::get(&network_id)
.ok_or(InvalidTransaction::BadProof)?;
let root_hash = LoomStates::<T>::get(&network_id, &session).root_hash;
if root_hash == Default::default() {
return Err(InvalidTransaction::BadProof);
}
thread_proof
.verify_thread_proof(root_hash, *network_id, &network_data.gatekeeper)
.ok_or(InvalidTransaction::BadProof)?;
let pulled_thread_key = thread_proof.get_unique_key(*session, *network_id);
if PulledThreads::<T>::contains_key(&pulled_thread_key) {
return Err(InvalidTransaction::Stale);
}
Ok(pulled_thread_key)
}
fn validate_attestation_signature<A>(
attestation: &A,
signature: &<T::AuthorityId as RuntimeAppPublic>::Signature,
@ -632,15 +732,40 @@ impl<T: Config> Pallet<T> {
A: Encode,
{
let authority_index = attestation.authority_index();
let attestation_block = attestation.block();
let network_id = attestation.network_id();
if !T::NetworkDataHandler::contains_key(&network_id) {
return Err(InvalidTransaction::BadProof);
}
let current_attestation_len =
NetworkAttestations::<T>::decode_len(&network_id).unwrap_or(0);
if authority_index as usize >= current_attestation_len {
return Err(InvalidTransaction::BadSigner.into());
}
if DisabledAuthorities::<T>::get().contains(authority_index) {
return Err(InvalidTransaction::BadSigner);
}
if T::BlockNumberProvider::current_block_number() <= attestation_block {
return Err(InvalidTransaction::BadProof);
let attestation_block = attestation.block();
let current_block = T::BlockNumberProvider::current_block_number();
let lock: BlockNumberFor<T> = LOCK_BLOCK_EXPIRATION.unique_saturated_into();
let block_expiration = attestation_block.saturating_add(lock);
if current_block >= block_expiration {
return Err(InvalidTransaction::Stale);
}
let last_attested = NetworkAttestedBlocks::<T>::get(
&network_id,
&authority_index
);
let cooling_off_until = last_attested.saturating_add(lock);
if attestation_block < cooling_off_until {
return Err(InvalidTransaction::Stale);
}
Authorities::<T>::get()
@ -653,10 +778,6 @@ impl<T: Config> Pallet<T> {
.ok_or(InvalidTransaction::BadSigner)
})?;
if !T::NetworkDataHandler::contains_key(&network_id) {
return Err(InvalidTransaction::BadProof);
}
Ok((authority_index, network_id))
}
@ -671,7 +792,7 @@ impl<T: Config> Pallet<T> {
let lock_period_key = Self::create_storage_key(b"network-period", &network_id_encoded);
let lock_period = Self::read_offchain_storage(&lock_period_key, &MIN_LOCK_GUARD_PERIOD);
let lock_until = rt_offchain::Duration::from_millis(lock_period);
let lock_until = rt_offchain::Duration::from_millis(lock_period.max(MIN_LOCK_GUARD_PERIOD));
let network_lock_key = Self::create_storage_key(b"network-lock-", &network_id_encoded);
let mut network_lock = StorageLock::<Time>::with_deadline(&network_lock_key, lock_until);
@ -709,14 +830,31 @@ impl<T: Config> Pallet<T> {
return Err(WeavingError::NoEndpointsAvailable(network_id));
}
let check_probability = |values_count: usize, blocks_left: BlockNumberFor<T>| -> bool {
let total_authorities = Authorities::<T>::decode_len().unwrap_or(1);
let submission_probability = get_submission_probability(
total_authorities,
values_count,
blocks_left,
);
let seed = sp_io::offchain::random_seed();
let randomness = <u32>::decode(&mut TrailingZeroInput::new(seed.as_ref()))
.expect("input is padded with zeroes; qed");
let random_roll = Perbill::from_parts(randomness % Perbill::ACCURACY);
random_roll > submission_probability
};
match TapestryDrafts::<T>::get(&network_id) {
Some(tapestry_draft) => Self::try_request_and_submit_hash(
network_id,
current_block,
tapestry_draft.median,
tapestry_draft,
deadline,
&network_data,
&rpc_endpoints,
check_probability,
),
None => Self::try_request_and_submit_block(
network_id,
@ -724,6 +862,7 @@ impl<T: Config> Pallet<T> {
deadline,
&network_data,
&rpc_endpoints,
check_probability,
),
}
}
@ -731,13 +870,22 @@ impl<T: Config> Pallet<T> {
fn try_request_and_submit_hash(
network_id: NetworkIdOf<T>,
current_block: BlockNumberFor<T>,
target_block: ExternalBlockNumber,
tapestry_draft: TapestryDraft<BlockNumberFor<T>>,
deadline: rt_offchain::Timestamp,
network_data: &NetworkData,
rpc_endpoints: &Vec<Vec<u8>>,
check_probability: impl Fn(usize, BlockNumberFor<T>) -> bool,
) -> WeavingResult<T, WeavingOk<NetworkIdOf<T>>> {
let weaving_session = CurrentWeavingSession::<T>::get(&network_id);
let block_until = tapestry_draft.until.saturating_sub(current_block);
let weaving_count = WeavingStates::<T>::get(&network_id).count as usize;
if check_probability(weaving_count, block_until) {
return Ok(WeavingOk::Skipped(network_id));
}
let target_block = tapestry_draft.median;
let request_id = current_block.unique_saturated_into();
let request_body = network_data.r#type.get_hash_request_body(
@ -770,8 +918,19 @@ impl<T: Config> Pallet<T> {
network_id,
))?;
let tapestry_key = (network_id, weaving_session, first_next_session, first_hash);
let tapestry_strand = TapestryStrands::<T>::get(&tapestry_key);
let next_session_bytes = first_next_session.to_be_bytes();
let weaving_session_bytes = weaving_session.to_be_bytes();
let metadata: &[&[u8]] = &[
first_hash.as_ref(),
next_session_bytes.as_ref(),
weaving_session_bytes.as_ref(),
];
let tapestry_strand_key =
sequential_hash::<SubstrateBlake2Hasher, _>(metadata);
let tapestry_strand = TapestryStrands::<T>::get(&network_id);
let context = AttestationContext::default()
.with_weaving_session(weaving_session)
@ -779,7 +938,12 @@ impl<T: Config> Pallet<T> {
.with_network_id(network_id);
Self::local_authorities().for_each(|(authority_index, authority_key)| {
if tapestry_strand.contains(authority_index) {
let already_part_of_tapestry_strand = tapestry_strand
.get(&tapestry_strand_key)
.map(|bitmap| bitmap.contains(authority_index))
.unwrap_or(false);
if already_part_of_tapestry_strand {
log::error!(
target: LOG_TARGET,
"🕸️ Weft already weaved from authority #{:?} for network #{:?}.",
@ -789,6 +953,23 @@ impl<T: Config> Pallet<T> {
return;
}
let last_attested =
NetworkAttestedBlocks::<T>::get(&network_id, &authority_index);
let lock: BlockNumberFor<T> = LOCK_BLOCK_EXPIRATION.unique_saturated_into();
let cooling_off_until = last_attested.saturating_add(lock);
if current_block < cooling_off_until {
log::error!(
target: LOG_TARGET,
"🕸️ Weft is cooling for authority #{:?} for network #{:?} blocks left {:?}.",
authority_index,
network_id,
cooling_off_until.saturating_sub(current_block)
);
return;
}
let hash_attestation = context
.with_authority_index(authority_index)
.build_hash_attestation(first_hash, first_next_session);
@ -831,7 +1012,20 @@ impl<T: Config> Pallet<T> {
deadline: rt_offchain::Timestamp,
network_data: &NetworkData,
rpc_endpoints: &Vec<Vec<u8>>,
check_probability: impl Fn(usize, BlockNumberFor<T>) -> bool,
) -> WeavingResult<T, WeavingOk<NetworkIdOf<T>>> {
let next_attempt_block = NextConsensusAttemptBlocks::<T>::get(&network_id);
let block_until = next_attempt_block.saturating_sub(current_block);
let attestations_count = NetworkAttestations::<T>::get(&network_id)
.iter()
.flatten()
.count();
if check_probability(attestations_count, block_until) {
return Ok(WeavingOk::Skipped(network_id));
}
let network_id_encoded = network_id.encode();
let distance_key = Self::create_storage_key(b"distance-", &network_id_encoded);
let block_deviation =
@ -850,6 +1044,7 @@ impl<T: Config> Pallet<T> {
let total_blocks = block_numbers.len();
let weaving_threshold = get_byzantium_quorum_threshold(total_blocks);
let parsed_blocks_count = total_blocks.unique_saturated_into();
let median_block = Self::validate_block_numbers_consensus(
&mut block_numbers,
@ -870,6 +1065,23 @@ impl<T: Config> Pallet<T> {
.with_network_id(network_id);
Self::local_authorities().for_each(|(authority_index, authority_key)| {
let last_attested =
NetworkAttestedBlocks::<T>::get(&network_id, &authority_index);
let lock: BlockNumberFor<T> = LOCK_BLOCK_EXPIRATION.unique_saturated_into();
let cooling_off_until = last_attested.saturating_add(lock);
if current_block < cooling_off_until {
log::error!(
target: LOG_TARGET,
"🕸️ Attestation is cooling for authority #{:?} for network #{:?} blocks left {:?}.",
authority_index,
network_id,
cooling_off_until.saturating_sub(current_block)
);
return;
}
let block_attestation = context
.with_authority_index(authority_index)
.build_block_attestation(finalized_block);
@ -898,7 +1110,7 @@ impl<T: Config> Pallet<T> {
}
});
Ok(WeavingOk::Block(finalized_block, network_id))
Ok(WeavingOk::Block(finalized_block, parsed_blocks_count, network_id))
}
fn prepare_pending_requests(
@ -1042,12 +1254,7 @@ impl<T: Config> Pallet<T> {
local_authorities
.binary_search(&authority)
.ok()
.map(|location| {
(
index.unique_saturated_into(),
local_authorities[location].clone(),
)
})
.map(|_| (index as AuthIndex, authority))
})
}

View File

@ -0,0 +1,18 @@
pub mod v1;
pub mod v2;
pub type MigrateV0ToV1<T> = frame_support::migrations::VersionedMigration<
0,
1,
v1::RemoveAttestedBlocksStorage<T>,
crate::Pallet::<T>,
<T as frame_system::Config>::DbWeight,
>;
pub type MigrateV1ToV2<T> = frame_support::migrations::VersionedMigration<
1,
2,
v2::UpdateTapestryStrandsStorageLayout<T>,
crate::Pallet::<T>,
<T as frame_system::Config>::DbWeight,
>;

View File

@ -0,0 +1,33 @@
use frame_support::{
traits::{Get, UncheckedOnRuntimeUpgrade},
weights::Weight,
};
use core::marker::PhantomData;
use crate::{Config, LOG_TARGET};
pub struct RemoveAttestedBlocksStorage<T>(PhantomData<T>);
impl<T: Config> UncheckedOnRuntimeUpgrade for RemoveAttestedBlocksStorage<T> {
fn on_runtime_upgrade() -> Weight {
let clear_key = frame_support::storage::storage_prefix(
b"GhostWeaver",
b"AttestedBlocks"
);
let removal_results = frame_support::storage::unhashed::clear_prefix(
&clear_key,
None,
None
);
let killed_keys = removal_results.unique;
log::info!(
target: LOG_TARGET,
"🧹 Success: Purged deprecated AttestedBlocks storage. Wiped {} keys from Trie.",
killed_keys
);
T::DbWeight::get().reads_writes(1, killed_keys as u64)
}
}

View File

@ -0,0 +1,33 @@
use frame_support::{
traits::{Get, UncheckedOnRuntimeUpgrade},
weights::Weight,
};
use core::marker::PhantomData;
use crate::{Config, LOG_TARGET};
pub struct UpdateTapestryStrandsStorageLayout<T>(PhantomData<T>);
impl<T: Config> UncheckedOnRuntimeUpgrade for UpdateTapestryStrandsStorageLayout<T> {
fn on_runtime_upgrade() -> Weight {
let clear_key = frame_support::storage::storage_prefix(
b"GhostWeaver",
b"TapestryStrands"
);
let removal_results = frame_support::storage::unhashed::clear_prefix(
&clear_key,
None,
None
);
let killed_keys = removal_results.unique;
log::info!(
target: LOG_TARGET,
"🧹 Success: Old StorageNMap for TapestryStrands wiped. Wiped {} keys from Trie.",
killed_keys
);
T::DbWeight::get().reads_writes(1, killed_keys as u64)
}
}

View File

@ -1,6 +1,6 @@
#![cfg(test)]
use crate::sr25519::AuthorityPair;
use crate::{LOCK_BLOCK_EXPIRATION, sr25519::AuthorityPair};
use crate::mock::*;
use crate::pallet::*;
@ -76,10 +76,11 @@ fn should_successfully_compute_median_and_start_weaving_round() {
let networks = vec![(network_id, network_data, Default::default())];
new_test_ext(authorities, networks).execute_with(|| {
System::set_block_number(2);
GhostWeaver::on_initialize(LOCK_BLOCK_EXPIRATION + 1);
System::set_block_number(LOCK_BLOCK_EXPIRATION + 2);
let context = AttestationContext::default()
.with_current_block(1)
.with_current_block(LOCK_BLOCK_EXPIRATION + 1)
.with_network_id(network_id);
let att1 = context.with_authority_index(0).build_block_attestation(100);
@ -96,12 +97,12 @@ fn should_successfully_compute_median_and_start_weaving_round() {
assert!(GhostWeaver::tapestry_drafts(network_id).is_none());
System::set_block_number(3);
GhostWeaver::on_initialize(3);
System::set_block_number(LOCK_BLOCK_EXPIRATION + 3);
GhostWeaver::on_initialize(LOCK_BLOCK_EXPIRATION + 3);
let round = GhostWeaver::tapestry_drafts(network_id);
assert!(round.is_some());
assert_eq!(round.unwrap().until, 3 + WeavingDelay::get());
assert_eq!(round.unwrap().until, LOCK_BLOCK_EXPIRATION + 3 + WeavingDelay::get());
});
}
@ -120,10 +121,11 @@ fn should_apply_cooldown_delay_on_consensus_failure() {
let networks = vec![(network_id, network_data, Default::default())];
new_test_ext(authorities, networks).execute_with(|| {
System::set_block_number(10);
GhostWeaver::on_initialize(LOCK_BLOCK_EXPIRATION + 9);
System::set_block_number(LOCK_BLOCK_EXPIRATION + 10);
let context = AttestationContext::default()
.with_current_block(9)
.with_current_block(LOCK_BLOCK_EXPIRATION + 9)
.with_network_id(network_id);
let att1 = context.with_authority_index(0).build_block_attestation(100);
@ -138,20 +140,20 @@ fn should_apply_cooldown_delay_on_consensus_failure() {
assert!(GhostWeaver::extend_warp(RuntimeOrigin::none(), att2, sig2).is_ok());
assert!(GhostWeaver::extend_warp(RuntimeOrigin::none(), att3, sig3).is_ok());
System::set_block_number(11);
GhostWeaver::on_initialize(11);
System::set_block_number(LOCK_BLOCK_EXPIRATION + 11);
GhostWeaver::on_initialize(LOCK_BLOCK_EXPIRATION + 11);
let cooldown_block = GhostWeaver::next_consensus_attempt_blocks(network_id);
assert_eq!(cooldown_block, 14);
assert_eq!(cooldown_block, LOCK_BLOCK_EXPIRATION + 14);
TapestryDrafts::<TestRuntime>::remove(network_id);
System::set_block_number(12);
GhostWeaver::on_initialize(12);
System::set_block_number(LOCK_BLOCK_EXPIRATION + 12);
GhostWeaver::on_initialize(LOCK_BLOCK_EXPIRATION+ 12);
assert!(TapestryDrafts::<TestRuntime>::get(network_id).is_none());
System::set_block_number(14);
GhostWeaver::on_initialize(14);
System::set_block_number(LOCK_BLOCK_EXPIRATION + 14);
GhostWeaver::on_initialize(LOCK_BLOCK_EXPIRATION + 14);
assert!(TapestryDrafts::<TestRuntime>::get(network_id).is_some());
});
}
@ -168,10 +170,11 @@ fn validate_unsigned_should_prevent_mempool_dos_via_tags() {
let networks = vec![(network_id, network_data, Default::default())];
new_test_ext(authorities, networks).execute_with(|| {
System::set_block_number(2);
GhostWeaver::on_initialize(LOCK_BLOCK_EXPIRATION + 1);
System::set_block_number(LOCK_BLOCK_EXPIRATION + 2);
let att = AttestationContext::default()
.with_current_block(1)
.with_current_block(LOCK_BLOCK_EXPIRATION + 1)
.with_network_id(network_id)
.with_authority_index(0)
.build_block_attestation(100);
@ -184,7 +187,9 @@ fn validate_unsigned_should_prevent_mempool_dos_via_tags() {
signature: valid_sig,
};
let validity = GhostWeaver::validate_unsigned(TransactionSource::External, &call);
let validity =
GhostWeaver::validate_unsigned(TransactionSource::External, &call);
assert!(validity.is_ok());
let valid_info = validity.unwrap();
@ -199,7 +204,9 @@ fn validate_unsigned_should_prevent_mempool_dos_via_tags() {
signature: invalid_sig,
};
let spam_validity = GhostWeaver::validate_unsigned(TransactionSource::External, &spam_call);
let spam_validity =
GhostWeaver::validate_unsigned(TransactionSource::External, &spam_call);
assert_eq!(
spam_validity,
Err(TransactionValidityError::Invalid(InvalidTransaction::BadSigner).into())
@ -240,6 +247,9 @@ fn offchain_worker_should_fetch_block_and_submit_unsigned_tx() {
ext.register_extension(TransactionPoolExt::new(pool));
ext.execute_with(|| {
GhostWeaver::on_initialize(LOCK_BLOCK_EXPIRATION + 1);
GhostWeaver::on_initialize(LOCK_BLOCK_EXPIRATION + 2);
let network_id_encoded = evm_network_id.encode();
let mut endpoint_key = b"ghost-weaver::".to_vec();
@ -252,6 +262,7 @@ fn offchain_worker_should_fetch_block_and_submit_unsigned_tx() {
let target_endpoint = b"http://localhost:8545".to_vec();
let rpc_endpoints: Vec<Vec<u8>> = vec![target_endpoint.clone()];
sp_io::offchain::local_storage_set(
sp_core::offchain::StorageKind::PERSISTENT,
&endpoint_key,
@ -288,8 +299,8 @@ fn offchain_worker_should_fetch_block_and_submit_unsigned_tx() {
("Accept".into(), "application/json".into()),
("Content-Type".into(), "application/json".into()),
],
body: b"{\"id\":1,\"jsonrpc\":\"2.0\",\"method\":\"getblockcount\"}".to_vec(),
response: Some(b"{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":960127}".to_vec()),
body: b"{\"id\":21,\"jsonrpc\":\"2.0\",\"method\":\"getblockcount\"}".to_vec(),
response: Some(b"{\"jsonrpc\":\"2.0\",\"id\":21,\"result\":960127}".to_vec()),
sent: true,
..Default::default()
});
@ -303,17 +314,16 @@ fn offchain_worker_should_fetch_block_and_submit_unsigned_tx() {
("X-Custom-Provider".into(), "GhostWeaverNode".into()),
("Authorization".into(), "Bearer new-api-token-123".into()),
],
body: b"{\"id\":2,\"jsonrpc\":\"2.0\",\"method\":\"eth_blockNumber\"}".to_vec(),
response: Some(b"{\"jsonrpc\":\"2.0\",\"id\":2,\"result\":\"0x1873a84\"}".to_vec()),
body: b"{\"id\":22,\"jsonrpc\":\"2.0\",\"method\":\"eth_blockNumber\"}".to_vec(),
response: Some(b"{\"jsonrpc\":\"2.0\",\"id\":22,\"result\":\"0x1873a84\"}".to_vec()),
sent: true,
..Default::default()
});
}
ext.execute_with(|| {
System::set_block_number(1u64);
GhostWeaver::offchain_worker(1u64);
System::set_block_number(2u64);
System::set_block_number(LOCK_BLOCK_EXPIRATION + 1u64);
GhostWeaver::offchain_worker(LOCK_BLOCK_EXPIRATION + 1u64);
let state = pool_state.read();
assert_eq!(state.transactions.len(), 1);
@ -334,14 +344,13 @@ fn offchain_worker_should_fetch_block_and_submit_unsigned_tx() {
assert_eq!(curr_utxo_block, 960127);
assert!(TapestryDrafts::<TestRuntime>::get(&utxo_network_id).is_none());
GhostWeaver::on_initialize(3u64);
GhostWeaver::on_initialize(LOCK_BLOCK_EXPIRATION + 3u64);
assert!(TapestryDrafts::<TestRuntime>::get(&utxo_network_id).is_some());
});
ext.execute_with(|| {
System::set_block_number(2u64);
GhostWeaver::offchain_worker(2u64);
System::set_block_number(3u64);
System::set_block_number(LOCK_BLOCK_EXPIRATION + 2u64);
GhostWeaver::offchain_worker(LOCK_BLOCK_EXPIRATION + 2u64);
let state = pool_state.read();
assert_eq!(state.transactions.len(), 2);
@ -362,7 +371,8 @@ fn offchain_worker_should_fetch_block_and_submit_unsigned_tx() {
assert_eq!(curr_evm_block, 25639556);
assert!(TapestryDrafts::<TestRuntime>::get(&evm_network_id).is_none());
GhostWeaver::on_initialize(4u64);
GhostWeaver::on_initialize(LOCK_BLOCK_EXPIRATION + 3u64);
GhostWeaver::on_initialize(LOCK_BLOCK_EXPIRATION + 4u64);
assert!(TapestryDrafts::<TestRuntime>::get(&evm_network_id).is_some());
});
}
@ -372,6 +382,9 @@ fn offchain_worker_should_fetch_hash_and_submit_unsigned_tx() {
let evm_network_id = 1u32;
let utxo_network_id = 69u32;
let median_utxo_block = 960129;
let median_evm_block = 25639556;
let pair = AuthorityPair::from_string("//Alice", None).expect("Should be valid seed");
let new_evm_network_data = NetworkDataBuilder::default()
@ -406,11 +419,12 @@ fn offchain_worker_should_fetch_hash_and_submit_unsigned_tx() {
ext.register_extension(TransactionPoolExt::new(pool));
ext.execute_with(|| {
GhostWeaver::on_initialize(LOCK_BLOCK_EXPIRATION + 1);
GhostWeaver::on_initialize(LOCK_BLOCK_EXPIRATION + 2);
let tapestry_draft = TapestryDraftBuilder::<crate::ExternalBlockNumber, u32>::default();
let evm_tapestry_draft = tapestry_draft.with_median_external(25639556).build();
let utxo_tapestry_draft = tapestry_draft.with_median_external(960129).build();
let evm_tapestry_draft = tapestry_draft.with_median_external(median_evm_block).build();
let utxo_tapestry_draft = tapestry_draft.with_median_external(median_utxo_block).build();
CurrentWeavingSession::<TestRuntime>::insert(&evm_network_id, evm_session);
CurrentWeavingSession::<TestRuntime>::insert(&utxo_network_id, utxo_session);
@ -466,8 +480,8 @@ fn offchain_worker_should_fetch_hash_and_submit_unsigned_tx() {
("Accept".into(), "application/json".into()),
("Content-Type".into(), "application/json".into()),
],
body: b"{\"id\":1,\"jsonrpc\":\"2.0\",\"method\":\"getblockhash\",\"params\":[960129]}".to_vec(),
response: Some(b"{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":\"0000000000000000000091c72779c83d76dfa8149e950f3a5287377a9708d5c1\"}".to_vec()),
body: b"{\"id\":21,\"jsonrpc\":\"2.0\",\"method\":\"getblockhash\",\"params\":[960129]}".to_vec(),
response: Some(b"{\"jsonrpc\":\"2.0\",\"id\":21,\"result\":\"0000000000000000000091c72779c83d76dfa8149e950f3a5287377a9708d5c1\"}".to_vec()),
sent: true,
..Default::default()
});
@ -482,17 +496,16 @@ fn offchain_worker_should_fetch_hash_and_submit_unsigned_tx() {
("Authorization".into(), "Bearer new-api-token-123".into()),
],
// Match the EXACT field order: data first, then to
body: b"{\"id\":2,\"jsonrpc\":\"2.0\",\"method\":\"eth_call\",\"params\":[{\"data\":\"0x00000000000000000000000000000000000000000000000000000000000000450000000000000000000000000000000000000000000000000000000001873a84\",\"to\":\"0x02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9\"},\"latest\"]}".to_vec(),
response: Some(b"{\"jsonrpc\":\"2.0\",\"id\":2,\"result\":\"0xf72b9ef0b66c8cc3952ffd11dc0b85f3b02e6edfa9236da400138f2fcba4668d0000000000000000000000000000000000000000000000000000000000000046\"}".to_vec()),
body: b"{\"id\":22,\"jsonrpc\":\"2.0\",\"method\":\"eth_call\",\"params\":[{\"data\":\"0x00000000000000000000000000000000000000000000000000000000000000450000000000000000000000000000000000000000000000000000000001873a84\",\"to\":\"0x02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9\"},\"latest\"]}".to_vec(),
response: Some(b"{\"jsonrpc\":\"2.0\",\"id\":22,\"result\":\"0xf72b9ef0b66c8cc3952ffd11dc0b85f3b02e6edfa9236da400138f2fcba4668d0000000000000000000000000000000000000000000000000000000000000046\"}".to_vec()),
sent: true,
..Default::default()
});
}
ext.execute_with(|| {
System::set_block_number(1u64);
GhostWeaver::offchain_worker(1u64);
System::set_block_number(2u64);
System::set_block_number(LOCK_BLOCK_EXPIRATION + 1u64);
GhostWeaver::offchain_worker(LOCK_BLOCK_EXPIRATION + 1u64);
let state = pool_state.read();
assert_eq!(state.transactions.len(), 1);
@ -518,14 +531,15 @@ fn offchain_worker_should_fetch_hash_and_submit_unsigned_tx() {
GhostWeaver::on_initialize(3u64);
let curr_state = LoomStates::<TestRuntime>::get(&utxo_network_id, &utxo_session);
assert_eq!(prev_state, H256::default());
assert_eq!(curr_state, block_hash);
assert_eq!(prev_state.root_hash, H256::default());
assert_eq!(curr_state.root_hash, block_hash);
assert_eq!(prev_state.at_block, 0);
assert_eq!(curr_state.at_block, median_utxo_block);
});
ext.execute_with(|| {
System::set_block_number(2u64);
GhostWeaver::offchain_worker(2u64);
System::set_block_number(3u64);
System::set_block_number(LOCK_BLOCK_EXPIRATION + 2u64);
GhostWeaver::offchain_worker(LOCK_BLOCK_EXPIRATION + 2u64);
let state = pool_state.read();
assert_eq!(state.transactions.len(), 2);
@ -542,17 +556,20 @@ fn offchain_worker_should_fetch_hash_and_submit_unsigned_tx() {
assert_eq!(curr_weaving_state.next_session, evm_session + 1);
let bytes = hex::decode("f72b9ef0b66c8cc3952ffd11dc0b85f3b02e6edfa9236da400138f2fcba4668d")
.expect("UTXO block hash should be valid");
.expect("EVM block hash should be valid");
let block_hash = H256::from_slice(&bytes);
assert_eq!(curr_weaving_state.hash, block_hash);
let prev_state = LoomStates::<TestRuntime>::get(&evm_network_id, &evm_session);
GhostWeaver::on_initialize(4u64);
GhostWeaver::on_initialize(LOCK_BLOCK_EXPIRATION + 3u64);
GhostWeaver::on_initialize(LOCK_BLOCK_EXPIRATION + 4u64);
let curr_state = LoomStates::<TestRuntime>::get(&evm_network_id, &evm_session);
assert_eq!(prev_state, H256::default());
assert_eq!(curr_state, block_hash);
assert_eq!(prev_state.root_hash, H256::default());
assert_eq!(curr_state.root_hash, block_hash);
assert_eq!(prev_state.at_block, 0);
assert_eq!(curr_state.at_block, median_evm_block);
});
}
@ -635,14 +652,18 @@ fn should_successfully_pull_evm_thread_and_mint_currency() {
];
new_test_ext(authorities, networks).execute_with(|| {
GhostWeaver::on_initialize(1);
// https://sepolia.etherscan.io/address/0x7fec11e652f4d54e8cf11c4b52c360380332f2bd#readContract
// getRoot: session 0, atBlock 11716130
let session = 0;
let root_string =
hex::decode("07f4a5ba4750d9ddfc080429287cbcbde7464aa2f8ae13feaade0dcb45ca6129")
.expect("Root hex should be valid");
let expected_root = H256::from_slice(&root_string);
LoomStates::<TestRuntime>::insert(&network_id, &session, expected_root);
let expected_loom_state = LoomState { at_block: 420, root_hash: expected_root };
LoomStates::<TestRuntime>::insert(&network_id, &session, expected_loom_state);
let mut total_issuance = 0;
@ -755,7 +776,8 @@ fn should_successfully_pull_utxo_thread_and_mint_currency() {
.expect("Bitcoin Root hex should be valid");
let expected_root = H256::from_slice(&block_hash);
LoomStates::<TestRuntime>::insert(&network_id, &session, expected_root);
let expected_loom_state = LoomState { at_block: 420, root_hash: expected_root };
LoomStates::<TestRuntime>::insert(&network_id, &session, expected_loom_state);
let expected_receiver_bytes = hex::decode("1657090d02985f0cdb41436fe2d5a83a3acb1995d963a698e7e619e927bdcc5e").unwrap();
let mut receiver_raw = [0u8; 32];
@ -801,6 +823,117 @@ fn should_successfully_pull_utxo_thread_and_mint_currency() {
});
}
#[test]
fn should_successfully_pull_utxo_thread_and_reject_nominal_varint_encoding() {
let pair = AuthorityPair::from_string("//Alice", None).expect("Should be valid seed");
let incoming_share = 0;
let network_id = 69;
let network_data = NetworkDataBuilder::default()
.with_network_type(NetworkType::Utxo)
.with_incoming_share(incoming_share)
.with_gatekeeper(
// Transaction: https://blockstream.info/tx/4a3fdd9ebb30b9c6df05d4199f8474b2f0c795d1f40a58d0d73342fc666b4a2e
hex::decode("4635b1794a22bfbe6c5c5eba17b693f4aaf0e348")
.expect("Bytes32 from UTXO should be valid"),
)
.build();
let authorities = vec![pair.clone()];
let networks = vec![(network_id, network_data, Default::default())];
// Real world tx at block #481838
// Block https://blockstream.info/block/0000000000000000011bdccb39e19e942a60edffdccd9779653c5b46b337904a
// Transaction: https://blockstream.info/tx/4a3fdd9ebb30b9c6df05d4199f8474b2f0c795d1f40a58d0d73342fc666b4a2e
let payload = (
// curl --data-binary '{"jsonrpc": "2.0", "id": "test-proof", "method": "gettxoutproof", "params": [["4a3fdd9ebb30b9c6df05d4199f8474b2f0c795d1f40a58d0d73342fc666b4a2e"], "0000000000000000011bdccb39e19e942a60edffdccd9779653c5b46b337904a"]}' -H 'Content-Type: application/json' https://bitcoin-rpc.publicnode.com
hex::decode("00000020375a14cfcf5730daa8ff8d7a5bd2cf59fba797e289c2e0000000000000000000529cd017146361b57e6b7353b16d8d38d32f92ee842039dd779d35bfbfa2f3e53f649e59e93c01187900ea9fb40600000cfbd7b718845d123b1ae1a7fd3bce9c97acba097dc912c4ac67a920395db07ba4eef685960869ba70e0ca38890565c97dc11f1d7ae538e7b509069f81e38eff1a2e4a6b66fc4233d7d0580af4d195c7f0b274849f19d405dfc6b930bb9edd3f4a4c1f1e9fa3a336c50e8ea16039e4db4bef7ff5946e3a019937bdf626c2336d0e3926a83a575218f84e6dc55ffb9d0997a41e4b4d5425a85f22160c9f52362a95021a573ff96bb429bd80a8ec9dcb899971553efe91b6a0fe57c4694c3e8da8dc7f1babd514f2746da1ed06f08664baed853c334e0a3917d0c859b19ee14f448f77e624822070db3b606d5092027ca9ecf7e0243e6c151081021ebb3ddcdd28c1b8f9fdcffebd1962590477675f3d29d52602a1e7e941f772a3bbff42cf2eb6e74a1124bf424f767e294ae6253a21602c7c6b0381743dd6dcd57b025983e0e34375b24f67ac8beb00713d2f70594bbf797d5d94c061cb9965e6458f2f017f983d7ce37361fa13fda372f595dc1f22895ab93339457c72c480fed1c4d1164a242303ff3600").expect("Bytes32 from UTXO should be valid"),
// curl --data-binary '{"jsonrpc": "2.0", "id": "test-proof", "method": "getrawtransaction", "params": ["4a3fdd9ebb30b9c6df05d4199f8474b2f0c795d1f40a58d0d73342fc666b4a2e", false]}' -H 'Content-Type: application/json' https://bitcoin-rpc.publicnode.com
hex::decode("010000000299a43fae581f80baf44e021ddf7b11995f184db282d6e48c22248b9fe79c43bb010000008b483045022100e9db01784dcd879c65d66127c2d52a4f9244321d15796893d6dccadaa1146e3d02203d9bb80f8df8b66692d3d9517f7cbb898d7cc40a30b175432322e1956c9b973a0141048097e4f8bb8ee104d5c2ece11d42f7e4719df8ddba459ecadecaba87d233c81a3c14830371392dd8ed030ef970450c0b7af48d9db4ba20c046896481602eb9dbffffffffb3fd05bc6000a5a0384e9a613d452e9307568df0a964907068e00403a0a0e09e020000006a47304402205bb1cbad4b29421648c1e22cac32a56aa06c933fe655559823fffb71ffebbc0b022021648d970560aa5b2031560b52a0fd06305995a58a457663324c18c1a9b0bd0b8121020d6e50b2660af27933c42bc1395fe93df90ffac5e2a989f6a134919fb8cf8075ffffffff030000000000000000296a2769642b5bb666ac1657090d02985f0cdb41436fe2d5a83a3acb1995d963a698e7e619e927bdcc5e7c150000000000001976a914c1c69d91a5719a23ba2094d89a4df3eadfa4aef788acefea2309000000001976a9144635b1794a22bfbe6c5c5eba17b693f4aaf0e34888ac00000000").expect("Bytes32 from UTXO should be valid"),
vec![
// curl --data-binary '{"jsonrpc": "2.0", "id": "test-proof", "method": "getblockheader", "params": ["000000000000000000e7da64065cc23d5c5a5aa402f088aaaf37c5fe1c3b6b92", false]}' -H 'Content-Type: application/json' https://bitcoin-rpc.publicnode.com
hex::decode("000000204a9037b3465b3c657997cddcffed602a949ee139cbdc1b010000000000000000669c3e2472384bc95993f154aada38d6b2113d8d32069e32e2724ba40915a1802a669e59e93c011801c8c735").expect("Bytes32 from UTXO should be valid"),
// curl --data-binary '{"jsonrpc": "2.0", "id": "test-proof", "method": "getblockheader", "params": ["00000000000000000063da6d9362c0ac3b87877d1c5ad7afd5bd706571d5b393", false]}' -H 'Content-Type: application/json' https://bitcoin-rpc.publicnode.com
hex::decode("02000020926b3b1cfec537afaa88f002a45a5a5c3dc25c0664dae7000000000000000000af0f270bf3f868d47089f395197809c360f1506636c960d96afed68bbd72d0b4496d9e59e93c01183ac94b67").expect("Bytes32 from UTXO should be valid"),
// curl --data-binary '{"jsonrpc": "2.0", "id": "test-proof", "method": "getblockheader", "params": ["000000000000000000ec6063eb743366264368be7ff5021727682cb518963026", false]}' -H 'Content-Type: application/json' https://bitcoin-rpc.publicnode.com
hex::decode("0000002093b3d5716570bdd5afd75a1c7d87873bacc062936dda630000000000000000009ab437e7c66b10a59b67127f290d24f381aba4e03d5e16c062a356eea4d608dea56d9e59e93c0118264f310e").expect("Bytes32 from UTXO should be valid"),
],
);
let encodings = vec![
vec![0x02], // canonical
vec![0xFD, 0x02, 0x00], // non-minimal 3 bytes
vec![0xFE, 0x02, 0x00, 0x00, 0x00], // non-minimal 5 bytes
vec![0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // non-minimal 9 bytes
];
new_test_ext(authorities, networks).execute_with(|| {
let session = 0;
let block_hash =
hex::decode("000000000000000000ec6063eb743366264368be7ff5021727682cb518963026")
.expect("Bitcoin Root hex should be valid");
let expected_root = H256::from_slice(&block_hash);
let expected_loom_state = LoomState { at_block: 420, root_hash: expected_root };
LoomStates::<TestRuntime>::insert(&network_id, &session, expected_loom_state);
let expected_receiver_bytes = hex::decode("1657090d02985f0cdb41436fe2d5a83a3acb1995d963a698e7e619e927bdcc5e").unwrap();
let mut receiver_raw = [0u8; 32];
receiver_raw.copy_from_slice(&expected_receiver_bytes);
let receiver_account = sp_runtime::AccountId32::from(receiver_raw);
let transfer_amount: u64 = 153348847;
let mut expected_pure_amount: u64 = 0;
for (idx, enc) in encodings.iter().enumerate() {
let mut variant = Vec::with_capacity(payload.1.len() + enc.len() - 1);
variant.extend_from_slice(&payload.1[0..4]);
variant.extend_from_slice(&enc);
variant.extend_from_slice(&payload.1[5..]);
let utxo_thread_proof = UtxoThreadProof::new(payload.0.clone(), variant, payload.2.clone());
let thread_info = ThreadProof::UtxoThreadProof(utxo_thread_proof);
let pulled_thread_key = thread_info.get_unique_key(session, network_id);
assert_eq!(Balances::free_balance(&receiver_account), expected_pure_amount);
assert!(!PulledThreads::<TestRuntime>::contains_key(
&pulled_thread_key
));
let pull_result = GhostWeaver::pull_thread(
RuntimeOrigin::none(),
network_id,
session,
thread_info
);
if idx == 0 {
assert!(pull_result.is_ok());
assert!(PulledThreads::<TestRuntime>::contains_key(&pulled_thread_key));
let curve_share = Perbill::from_parts(incoming_share).mul_ceil(transfer_amount);
expected_pure_amount += transfer_amount.saturating_sub(curve_share);
assert_eq!(Balances::free_balance(&receiver_account), expected_pure_amount);
System::assert_last_event(RuntimeEvent::GhostWeaver(Event::ThreadPulled {
pulled_thread_key,
network_id,
receiver: receiver_account.clone(),
amount: transfer_amount,
}));
} else {
assert!(pull_result.is_err());
assert!(!PulledThreads::<TestRuntime>::contains_key(&pulled_thread_key));
assert_eq!(Balances::free_balance(&receiver_account), expected_pure_amount);
}
}
assert_eq!(Balances::free_balance(&receiver_account), transfer_amount);
});
}
#[test]
fn on_initialize_should_finalize_session_when_timeout_is_reached() {
let pair1 = AuthorityPair::from_string("//Alice", None).expect("Should be valid seed");
@ -817,7 +950,9 @@ fn on_initialize_should_finalize_session_when_timeout_is_reached() {
let networks = vec![(network_id, new_network_data, 0u64)];
new_test_ext(authorities, networks).execute_with(|| {
let current_substrate_block = 10;
let current_substrate_block = LOCK_BLOCK_EXPIRATION + 10;
GhostWeaver::on_initialize(current_substrate_block);
System::set_block_number(current_substrate_block + 1);
let tapestry_draft = TapestryDraftBuilder::default()
@ -865,8 +1000,96 @@ fn on_initialize_should_finalize_session_when_timeout_is_reached() {
GhostWeaver::on_initialize(current_substrate_block);
let saved_loom = LoomStates::<TestRuntime>::get(&network_id, current_session);
assert_ne!(saved_loom, target_hash);
assert_eq!(saved_loom, saved_loom);
assert_ne!(saved_loom.root_hash, target_hash);
assert_eq!(saved_loom.at_block, 0);
let updated_session = CurrentWeavingSession::<TestRuntime>::get(&network_id);
assert_eq!(updated_session, current_session);
});
}
#[test]
fn on_initialize_should_update_at_block_when_threshold_reached_with_same_hash() {
let pair1 = AuthorityPair::from_string("//Alice", None).expect("Should be valid seed");
let pair2 = AuthorityPair::from_string("//Bob", None).expect("Should be valid seed");
let pair3 = AuthorityPair::from_string("//Charlie", None).expect("Should be valid seed");
let network_id = 1;
let new_network_data = NetworkDataBuilder::default()
.with_network_type(NetworkType::Evm)
.build();
let authorities = vec![pair1.clone(), pair2.clone(), pair3.clone()];
let networks = vec![(network_id, new_network_data, 0u64)];
new_test_ext(authorities, networks).execute_with(|| {
let current_substrate_block = LOCK_BLOCK_EXPIRATION + 10;
let current_session = CurrentWeavingSession::<TestRuntime>::get(&network_id);
let target_hash = H256::repeat_byte(0xAA);
LoomStates::<TestRuntime>::insert(&network_id, &current_session, crate::LoomState {
root_hash: target_hash,
at_block: 0,
});
System::set_block_number(current_substrate_block);
GhostWeaver::on_initialize(current_substrate_block);
let tapestry_draft = TapestryDraftBuilder::default()
.with_current_block(current_substrate_block)
.with_block_delay(5u32)
.with_median_external(100)
.build();
TapestryDrafts::<TestRuntime>::insert(&network_id, tapestry_draft);
let attestation = AttestationContext::default()
.with_current_block(current_substrate_block)
.with_network_id(network_id)
.with_weaving_session(current_session);
let hash_att1 = attestation
.with_authority_index(0)
.build_hash_attestation(target_hash, current_session);
let hash_att2 = attestation
.with_authority_index(1)
.build_hash_attestation(target_hash, current_session);
let hash_att3 = attestation
.with_authority_index(2)
.build_hash_attestation(target_hash, current_session);
assert!(GhostWeaver::weave_weft(
RuntimeOrigin::none(),
hash_att1,
pair1.sign(&hash_att1.encode())
)
.is_ok());
assert!(GhostWeaver::weave_weft(
RuntimeOrigin::none(),
hash_att2,
pair2.sign(&hash_att2.encode())
)
.is_ok());
assert!(GhostWeaver::weave_weft(
RuntimeOrigin::none(),
hash_att3,
pair3.sign(&hash_att3.encode())
)
.is_ok());
let state_before = WeavingStates::<TestRuntime>::get(&network_id);
assert_eq!(state_before.count, 3);
assert_eq!(state_before.hash, target_hash);
GhostWeaver::on_initialize(current_substrate_block);
let saved_loom = LoomStates::<TestRuntime>::get(&network_id, current_session);
assert_eq!(saved_loom.root_hash, target_hash);
assert_eq!(saved_loom.at_block, 100);
let updated_session = CurrentWeavingSession::<TestRuntime>::get(&network_id);
assert_eq!(updated_session, current_session);

View File

@ -14,7 +14,6 @@ use ghost_helpers::{
merkle_tree::verify_tree_proof,
SubstrateBlake2Hasher, SubstrateKeccakHasher, UtxoSha2Hasher,
};
use ghost_traits::hashing::GhostHasher;
use crate::{ExternalBlockNumber, ThreadId, WeavingSession};
@ -37,22 +36,43 @@ const PROTOCOL_PREFIX: [u8; 7] = [0x69, 0x64, 0x2b, 0x5b, 0xb6, 0x66, 0xac];
const PROTOCOL_PREFIX: [u8; 7] = [0x47, 0x48, 0x53, 0x54, 0x74, 0x6F, 0x3A];
pub enum WeavingOk<NetworkId> {
Block(ExternalBlockNumber, NetworkId),
Skipped(NetworkId),
Block(ExternalBlockNumber, u32, NetworkId),
Hash(ExternalBlockNumber, H256, WeavingSession, NetworkId),
}
impl<NetworkId> core::fmt::Display for WeavingOk<NetworkId>
impl<NetworkId> core::fmt::Debug for WeavingOk<NetworkId>
where
NetworkId: core::fmt::Debug,
{
fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
match self {
Self::Block(block, network_id) => write!(fmt, "Verified block height #{:?} found for network: {:?}.", block, network_id),
Self::Hash(block, hash, next_session, network_id) => write!(fmt, "Extracted Merkle Root {:?} where next session is {:?}; from block #{:?} for network: {:?}.", hash, next_session, block, network_id),
Self::Skipped(network_id) => write!(fmt, "Submission step skipped for network: {:?} based on probability distribution.", network_id),
Self::Block(block, count, network_id) => write!(fmt, "Verified block height #{:?} for network: {:?} based on {} parsed requests.", block, network_id, count),
Self::Hash(block, hash, next_session, network_id) => write!(fmt, "Extracted Merkle Root {:?} (next session is {:?}) from block #{:?} for network: {:?}.", hash, next_session, block, network_id),
}
}
}
#[derive(
Default,
RuntimeDebug,
Clone,
Copy,
Eq,
PartialEq,
Ord,
PartialOrd,
Encode,
Decode,
TypeInfo,
MaxEncodedLen,
)]
pub struct LoomState {
pub at_block: ExternalBlockNumber,
pub root_hash: H256,
}
#[derive(
Default,
RuntimeDebug,
@ -350,8 +370,10 @@ where
{
if EVM_MERKLE_DEPTH as usize != self.proof.len() { return None; }
let preimage_hash = self.get_preimage_hash(network_id)?;
let proof_valid = verify_tree_proof::<SubstrateKeccakHasher, _>(
&self.get_preimage_slice(network_id).to_fixed_bytes(),
&preimage_hash.to_fixed_bytes(),
self.proof.as_ref(),
root_hash,
self.proof_index,
@ -362,22 +384,17 @@ where
Some((self.receiver.clone(), self.amount))
}
fn get_preimage_slice<NetworkId>(&self, network_id: NetworkId) -> H256
fn get_preimage_hash<NetworkId>(&self, network_id: NetworkId) -> Option<H256>
where
NetworkId: Copy + Clone + Eq + PartialEq + UniqueSaturatedInto<u64>,
{
let slot_index_usize: usize = self.slot_index.unique_saturated_into();
let mut slot_values: Vec<H256> = self.slots.to_vec();
match slot_values.get_mut(slot_index_usize) {
Some(slot) => {
*slot = self.compute_arguments_hash(network_id);
cumulative_hash::<SubstrateKeccakHasher, _>(slot_values)
}
None => {
SubstrateKeccakHasher::empty()
}
}
self.slots.get(slot_index_usize)
.filter(|&&provided_hash| {
provided_hash == self.compute_arguments_hash(network_id)
})
.map(|_| cumulative_hash::<SubstrateKeccakHasher, _>(self.slots.iter()))
}
pub fn get_unique_key<NetworkId>(
@ -483,7 +500,7 @@ impl UtxoThreadProof {
pub fn verify_thread_proof<Balance>(
&self,
root_hash: H256,
block_hash: H256,
gatekeeper_ref: &[u8],
) -> Option<(AccountId32, Balance)>
where
@ -498,7 +515,7 @@ impl UtxoThreadProof {
let is_chain_valid = verify_hash_ancestry::<UtxoSha2Hasher, _>(
header_bytes,
self.ancestry_headers.iter(),
root_hash,
block_hash,
);
if !is_chain_valid { return None; }
@ -507,13 +524,11 @@ impl UtxoThreadProof {
self.getrawtransaction_bytes.as_ref()
).ok()?;
let header = Header::<UtxoSha2Hasher>::try_from(
self.gettxproof_bytes.as_ref(),
).ok()?;
let txid_preimage = transaction.compute_txid()?;
let root_hash = header.extract_proof(txid_preimage).ok()?;
if root_hash != header.merkle_root { return None; }
Header::<UtxoSha2Hasher>::verify_utxo_merkle_proof(
self.gettxproof_bytes.as_ref(),
txid_preimage,
).ok()?;
#[cfg(not(any(test, feature = "runtime-benchmarks")))]
let destination_script = Transaction::<UtxoSha2Hasher>::convert_into_p2tr(gatekeeper_ref)?;

View File

@ -16,7 +16,7 @@
//! Autogenerated weights for `ghost_weaver`
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0
//! DATE: 2026-09-25, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! DATE: 2026-10-29, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! WORST CASE MAP SIZE: `1000000`
//! HOSTNAME: `ghostown`, CPU: `Intel(R) Core(TM) i3-2310M CPU @ 2.10GHz`
//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("casper-dev")`, DB CACHE: 1024
@ -67,16 +67,16 @@ impl WeightInfo for () {
/// The range of component `a` is `[1, 300]`.
fn extend_warp(a: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `223 + a * (32 ±0)`
// Estimated: `3687 + a * (32 ±0)`
// Minimum execution time: 212_751_000 picoseconds.
Weight::from_parts(215_022_916, 0)
.saturating_add(Weight::from_parts(0, 3687))
// Standard Error: 1_319
.saturating_add(Weight::from_parts(53_379, 0).saturating_mul(a.into()))
.saturating_add(RocksDbWeight::get().reads(5))
.saturating_add(RocksDbWeight::get().writes(1))
.saturating_add(Weight::from_parts(0, 32).saturating_mul(a.into()))
// Measured: `274 + a * (33 ±0)`
// Estimated: `3736 + a * (33 ±0)`
// Minimum execution time: 229_711_000 picoseconds.
Weight::from_parts(233_206_680, 0)
.saturating_add(Weight::from_parts(0, 3736))
// Standard Error: 1_554
.saturating_add(Weight::from_parts(66_095, 0).saturating_mul(a.into()))
.saturating_add(RocksDbWeight::get().reads(6))
.saturating_add(RocksDbWeight::get().writes(2))
.saturating_add(Weight::from_parts(0, 33).saturating_mul(a.into()))
}
/// Storage: `GhostWeaver::DisabledAuthorities` (r:1 w:0)
/// Proof: `GhostWeaver::DisabledAuthorities` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
@ -93,16 +93,16 @@ impl WeightInfo for () {
/// The range of component `a` is `[1, 300]`.
fn weave_weft(a: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `518 + a * (32 ±0)`
// Estimated: `3982 + a * (32 ±0)`
// Minimum execution time: 212_751_000 picoseconds.
Weight::from_parts(246_374_350, 0)
.saturating_add(Weight::from_parts(0, 3982))
// Measured: `569 + a * (33 ±0)`
// Estimated: `4031 + a * (33 ±0)`
// Minimum execution time: 262_809_000 picoseconds.
Weight::from_parts(267_769_707, 0)
.saturating_add(Weight::from_parts(0, 4031))
// Standard Error: 1_865
.saturating_add(Weight::from_parts(38_601, 0).saturating_mul(a.into()))
.saturating_add(RocksDbWeight::get().reads(6))
.saturating_add(RocksDbWeight::get().writes(2))
.saturating_add(Weight::from_parts(0, 32).saturating_mul(a.into()))
.saturating_add(Weight::from_parts(39_109, 0).saturating_mul(a.into()))
.saturating_add(RocksDbWeight::get().reads(8))
.saturating_add(RocksDbWeight::get().writes(3))
.saturating_add(Weight::from_parts(0, 33).saturating_mul(a.into()))
}
/// Storage: `GhostNetworks::Networks` (r:1 w:0)
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
@ -119,12 +119,12 @@ impl WeightInfo for () {
/// The range of component `a` is `[1, 144]`.
fn pull_utxo_thread(a: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `449`
// Estimated: `3914`
// Minimum execution time: 217_880_000 picoseconds.
Weight::from_parts(217_119_201, 0)
// Standard Error: 4_368
.saturating_add(Weight::from_parts(3_445_491, 0).saturating_mul(a.into()))
// Measured: `500`
// Estimated: `3965`
// Minimum execution time: 217_481_000 picoseconds.
Weight::from_parts(216_492_234, 0)
// Standard Error: 4_414
.saturating_add(Weight::from_parts(3_376_569, 0).saturating_mul(a.into()))
.saturating_add(RocksDbWeight::get().reads(6))
.saturating_add(RocksDbWeight::get().writes(4))
}
@ -142,11 +142,11 @@ impl WeightInfo for () {
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
fn pull_evm_thread() -> Weight {
// Proof Size summary in bytes:
// Measured: `449`
// Estimated: `3914`
// Minimum execution time: 183_413_000 picoseconds.
Weight::from_parts(186_271_000, 0)
.saturating_add(Weight::from_parts(0, 3914))
// Measured: `500`
// Estimated: `3965`
// Minimum execution time: 176_861_000 picoseconds.
Weight::from_parts(177_861_000, 0)
.saturating_add(Weight::from_parts(0, 3965))
.saturating_add(RocksDbWeight::get().reads(6))
.saturating_add(RocksDbWeight::get().writes(4))
}

View File

@ -1,6 +1,6 @@
[package]
name = "casper-runtime"
version = "4.0.14"
version = "4.0.20"
build = "build.rs"
description = "Runtime of the Casper Network"
edition.workspace = true
@ -75,6 +75,7 @@ ghost-networks = { workspace = true }
ghost-governance = { workspace = true }
ghost-weaver = { workspace = true }
ghost-exodus = { workspace = true }
ghost-exodus-runtime-api = { workspace = true }
ghost-sudo = { workspace = true }
ghost-traits = { workspace = true }
ghost-helpers = { workspace = true }
@ -188,6 +189,7 @@ std = [
"ghost-governance/std",
"ghost-weaver/std",
"ghost-exodus/std",
"ghost-exodus-runtime-api/std",
"ghost-sudo/std",
"casper-runtime-constants/std",
"runtime-common/std",

View File

@ -69,6 +69,7 @@ pub use frame_system::Call as SystemCall;
pub use pallet_balances::Call as BalancesCall;
pub use pallet_election_provider_multi_phase::{Call as EPMCall, GeometricDepositBase};
use ghost_helpers::networks::{NetworkCurve, NetworkType};
use ghost_weaver::sr25519::AuthorityId as GhostWeaverId;
use ghost_exodus::sr25519::AuthorityId as GhostExodusId;
@ -123,7 +124,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
spec_name: create_runtime_str!("casper"),
impl_name: create_runtime_str!("casper-svengali"),
authoring_version: 0,
spec_version: 7,
spec_version: 14,
impl_version: 0,
apis: RUNTIME_API_VERSIONS,
transaction_version: 1,
@ -906,6 +907,7 @@ parameter_types! {
pub ExodusUnsignedPriority: TransactionPriority =
Perbill::from_percent(90) * TransactionPriority::MAX;
pub MaxExodusRequestCount: u32 = 32;
pub MaxAuthoritiesChunks: u32 = 32;
pub WeavingDelay: u64 = 50;
pub AttestationDelay: u64 = 25;
@ -949,6 +951,7 @@ impl ghost_exodus::Config for Runtime {
type UnsignedPriority = ExodusUnsignedPriority;
type MaxAuthorities = MaxActiveValidators;
type MaxAuthoritiesChunks = MaxAuthoritiesChunks;
type MaxExodusRequestCount = MaxExodusRequestCount;
type DkgRoundPeriod = ExodusDkgRoundPeriod;
type RemovalLimit = ExodusRemovalLimit;
@ -1032,7 +1035,10 @@ pub type SignedExtra = (
/// All migrations that will run on the next runtime upgrade.
/// Should be cleared after release.
pub type Migrations = ();
pub type Migrations = (
ghost_weaver::migrations::MigrateV0ToV1<Runtime>,
ghost_weaver::migrations::MigrateV1ToV2<Runtime>,
);
/// Unchecked extrinsic type as expected by this runtime.
pub type UncheckedExtrinsic =
@ -1156,6 +1162,39 @@ sp_api::impl_runtime_apis! {
}
}
impl ghost_exodus_runtime_api::ExodusApi<Block> for Runtime {
fn get_evm_rotation_metadata(
dkg_index: u64,
network_type: NetworkType,
) -> ghost_exodus_runtime_api::PreparedEvmMessage {
GhostExodus::api_get_evm_rotation(dkg_index, network_type)
}
fn get_evm_bridge_metadata(
dkg_index: u64,
network_id: u64,
session: u64,
) -> ghost_exodus_runtime_api::PreparedEvmMessage {
GhostExodus::api_get_evm_bridge(dkg_index, network_id, session)
}
fn get_evm_governance_metadata(
dkg_index: u64,
network_id: u64,
session: u64,
) -> ghost_exodus_runtime_api::PreparedEvmMessage {
GhostExodus::api_get_evm_governance(dkg_index, network_id, session)
}
fn pending_exodus_request_count() -> u32 {
GhostExodus::api_pending_exodus_request_count()
}
fn is_qualification_ongoing(network_curve: NetworkCurve) -> bool {
GhostExodus::api_is_qualification_ongoing(network_curve)
}
}
impl pallet_staking_runtime_api::StakingApi<Block, Balance, AccountId> for Runtime {
fn nominations_quota(balance: Balance) -> u32 {
Staking::api_nominations_quota(balance)

View File

@ -16,7 +16,7 @@
//! Autogenerated weights for `ghost_exodus`
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0
//! DATE: 2026-09-25, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! DATE: 2026-10-29, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! WORST CASE MAP SIZE: `1000000`
//! HOSTNAME: `ghostown`, CPU: `Intel(R) Core(TM) i3-2310M CPU @ 2.10GHz`
//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("casper-dev")`, DB CACHE: 1024
@ -46,252 +46,232 @@ use core::marker::PhantomData;
/// Weight functions for `ghost_exodus`.
pub struct WeightInfo<T>(PhantomData<T>);
impl<T: frame_system::Config> ghost_exodus::WeightInfo for WeightInfo<T> {
/// Storage: `GhostNetworks::NetworkCurves` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkCurves` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostNetworks::NetworkTypes` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkTypes` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationDkgState` (r:1 w:1)
/// Proof: `GhostExodus::QualificationDkgState` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::Round0Packages` (r:1 w:1)
/// Proof: `GhostExodus::Round0Packages` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::QualificationAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusActivity` (r:1 w:1)
/// Proof: `GhostExodus::ExodusActivity` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::Round0Packages` (r:0 w:1)
/// Proof: `GhostExodus::Round0Packages` (`max_values`: None, `max_size`: None, mode: `Measured`)
fn register_round0_package() -> Weight {
// Proof Size summary in bytes:
// Measured: `351`
// Estimated: `3816`
// Minimum execution time: 240_813_000 picoseconds.
Weight::from_parts(243_523_000, 0)
.saturating_add(Weight::from_parts(0, 3816))
// Measured: `365`
// Estimated: `3830`
// Minimum execution time: 240_843_000 picoseconds.
Weight::from_parts(243_855_000, 0)
.saturating_add(Weight::from_parts(0, 3830))
.saturating_add(T::DbWeight::get().reads(4))
.saturating_add(T::DbWeight::get().writes(3))
.saturating_add(T::DbWeight::get().writes(2))
}
/// Storage: `GhostNetworks::NetworkCurves` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkCurves` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostNetworks::NetworkTypes` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkTypes` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationDkgState` (r:1 w:0)
/// Proof: `GhostExodus::QualificationDkgState` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::QualificationAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusActivity` (r:1 w:1)
/// Proof: `GhostExodus::ExodusActivity` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::Round0Packages` (r:1 w:0)
/// Proof: `GhostExodus::Round0Packages` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::Round1Packages` (r:1 w:1)
/// Proof: `GhostExodus::Round1Packages` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::Round0Packages` (r:1 w:0)
/// Proof: `GhostExodus::Round0Packages` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// The range of component `c` is `[4, 300]`.
fn register_round1_package(c: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `391 + c * (32 ±0)`
// Estimated: `3856 + c * (32 ±0)`
// Minimum execution time: 1_356_791_000 picoseconds.
Weight::from_parts(1_221_760_215, 0)
.saturating_add(Weight::from_parts(0, 3856))
// Standard Error: 16_481
.saturating_add(Weight::from_parts(33_163_393, 0).saturating_mul(c.into()))
.saturating_add(T::DbWeight::get().reads(6))
.saturating_add(T::DbWeight::get().writes(2))
// Measured: `405 + c * (32 ±0)`
// Estimated: `3870 + c * (32 ±0)`
// Minimum execution time: 1_349_108_000 picoseconds.
Weight::from_parts(1_224_956_354, 0)
.saturating_add(Weight::from_parts(0, 3870))
// Standard Error: 14_930
.saturating_add(Weight::from_parts(33_204_477, 0).saturating_mul(c.into()))
.saturating_add(T::DbWeight::get().reads(5))
.saturating_add(T::DbWeight::get().writes(1))
.saturating_add(Weight::from_parts(0, 32).saturating_mul(c.into()))
}
/// Storage: `GhostNetworks::NetworkCurves` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkCurves` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostNetworks::NetworkTypes` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkTypes` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationDkgState` (r:1 w:0)
/// Proof: `GhostExodus::QualificationDkgState` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::QualificationAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusActivity` (r:1 w:1)
/// Proof: `GhostExodus::ExodusActivity` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::Round2Packages` (r:1 w:1)
/// Proof: `GhostExodus::Round2Packages` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// The range of component `c` is `[1, 37]`.
/// Storage: `GhostExodus::QualificationAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::QualificationAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// The range of component `c` is `[1, 300]`.
fn register_encrypted_round2_packages(c: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `317 + c * (257 ±0)`
// Estimated: `3783 + c * (257 ±0)`
// Minimum execution time: 254_590_000 picoseconds.
Weight::from_parts(258_091_105, 0)
.saturating_add(Weight::from_parts(0, 3783))
// Standard Error: 25_082
.saturating_add(Weight::from_parts(6_595_878, 0).saturating_mul(c.into()))
.saturating_add(T::DbWeight::get().reads(5))
.saturating_add(T::DbWeight::get().writes(2))
.saturating_add(Weight::from_parts(0, 257).saturating_mul(c.into()))
// Measured: `332 + c * (32 ±0)`
// Estimated: `3797 + c * (32 ±0)`
// Minimum execution time: 245_305_000 picoseconds.
Weight::from_parts(253_863_353, 0)
.saturating_add(Weight::from_parts(0, 3797))
// Standard Error: 3_527
.saturating_add(Weight::from_parts(810_623, 0).saturating_mul(c.into()))
.saturating_add(T::DbWeight::get().reads(4))
.saturating_add(T::DbWeight::get().writes(1))
.saturating_add(Weight::from_parts(0, 32).saturating_mul(c.into()))
}
/// Storage: `GhostNetworks::NetworkCurves` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkCurves` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostNetworks::NetworkTypes` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkTypes` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationDkgState` (r:1 w:0)
/// Proof: `GhostExodus::QualificationDkgState` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::QualificationAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusActivity` (r:1 w:1)
/// Proof: `GhostExodus::ExodusActivity` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::Complaints` (r:1 w:1)
/// Proof: `GhostExodus::Complaints` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// The range of component `c` is `[1, 37]`.
/// Storage: `GhostExodus::QualificationAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::QualificationAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// The range of component `c` is `[1, 300]`.
fn register_round3_complaints(c: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `317 + c * (257 ±0)`
// Estimated: `3783 + c * (257 ±0)`
// Minimum execution time: 240_888_000 picoseconds.
Weight::from_parts(245_480_975, 0)
.saturating_add(Weight::from_parts(0, 3783))
// Standard Error: 13_733
.saturating_add(Weight::from_parts(371_161, 0).saturating_mul(c.into()))
.saturating_add(T::DbWeight::get().reads(5))
.saturating_add(T::DbWeight::get().writes(2))
.saturating_add(Weight::from_parts(0, 257).saturating_mul(c.into()))
// Measured: `332 + c * (32 ±0)`
// Estimated: `3797 + c * (32 ±0)`
// Minimum execution time: 238_391_000 picoseconds.
Weight::from_parts(242_937_380, 0)
.saturating_add(Weight::from_parts(0, 3797))
// Standard Error: 1_789
.saturating_add(Weight::from_parts(39_316, 0).saturating_mul(c.into()))
.saturating_add(T::DbWeight::get().reads(4))
.saturating_add(T::DbWeight::get().writes(1))
.saturating_add(Weight::from_parts(0, 32).saturating_mul(c.into()))
}
/// Storage: `GhostNetworks::NetworkCurves` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkCurves` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostNetworks::NetworkTypes` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkTypes` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationDkgState` (r:1 w:0)
/// Proof: `GhostExodus::QualificationDkgState` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::QualificationAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusActivity` (r:1 w:1)
/// Proof: `GhostExodus::ExodusActivity` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::Justifications` (r:1 w:1)
/// Proof: `GhostExodus::Justifications` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// The range of component `c` is `[1, 37]`.
/// Storage: `GhostExodus::QualificationAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::QualificationAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// The range of component `c` is `[1, 300]`.
fn register_round4_justifications(c: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `317 + c * (257 ±0)`
// Estimated: `3783 + c * (257 ±0)`
// Minimum execution time: 251_521_000 picoseconds.
Weight::from_parts(253_053_636, 0)
.saturating_add(Weight::from_parts(0, 3783))
// Standard Error: 14_691
.saturating_add(Weight::from_parts(4_101_872, 0).saturating_mul(c.into()))
.saturating_add(T::DbWeight::get().reads(5))
.saturating_add(T::DbWeight::get().writes(2))
.saturating_add(Weight::from_parts(0, 257).saturating_mul(c.into()))
// Measured: `332 + c * (32 ±0)`
// Estimated: `3797 + c * (32 ±0)`
// Minimum execution time: 245_916_000 picoseconds.
Weight::from_parts(251_070_206, 0)
.saturating_add(Weight::from_parts(0, 3797))
// Standard Error: 2_152
.saturating_add(Weight::from_parts(508_508, 0).saturating_mul(c.into()))
.saturating_add(T::DbWeight::get().reads(4))
.saturating_add(T::DbWeight::get().writes(1))
.saturating_add(Weight::from_parts(0, 32).saturating_mul(c.into()))
}
/// Storage: `GhostNetworks::NetworkCurves` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkCurves` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostNetworks::NetworkTypes` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkTypes` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationDkgState` (r:1 w:0)
/// Proof: `GhostExodus::QualificationDkgState` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::QualificationAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusActivity` (r:1 w:1)
/// Proof: `GhostExodus::ExodusActivity` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::Verifications` (r:1 w:1)
/// Proof: `GhostExodus::Verifications` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::VerifyingKeyConsensus` (r:1 w:1)
/// Proof: `GhostExodus::VerifyingKeyConsensus` (`max_values`: None, `max_size`: None, mode: `Measured`)
fn register_round5_verifying_package() -> Weight {
// Proof Size summary in bytes:
// Measured: `448`
// Estimated: `3913`
// Minimum execution time: 265_187_000 picoseconds.
Weight::from_parts(269_124_000, 0)
.saturating_add(Weight::from_parts(0, 3913))
.saturating_add(T::DbWeight::get().reads(6))
.saturating_add(T::DbWeight::get().writes(3))
}
/// Storage: `GhostNetworks::NetworkCurves` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkCurves` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationDkgState` (r:1 w:0)
/// Proof: `GhostExodus::QualificationDkgState` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::QualificationAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusActivity` (r:1 w:1)
/// Proof: `GhostExodus::ExodusActivity` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::Verifications` (r:1 w:1)
/// Proof: `GhostExodus::Verifications` (`max_values`: None, `max_size`: None, mode: `Measured`)
fn register_round5_verifying_package() -> Weight {
// Proof Size summary in bytes:
// Measured: `462`
// Estimated: `3927`
// Minimum execution time: 271_654_000 picoseconds.
Weight::from_parts(286_394_000, 0)
.saturating_add(Weight::from_parts(0, 3927))
.saturating_add(T::DbWeight::get().reads(5))
.saturating_add(T::DbWeight::get().writes(2))
}
/// Storage: `GhostNetworks::NetworkTypes` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkTypes` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationDkgState` (r:1 w:0)
/// Proof: `GhostExodus::QualificationDkgState` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::VerifyingSharesParticipants` (r:1 w:1)
/// Proof: `GhostExodus::VerifyingSharesParticipants` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::QualificationAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::VerifyingKeyConsensus` (r:1 w:0)
/// Proof: `GhostExodus::VerifyingKeyConsensus` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::VerifyingShares` (r:1 w:1)
/// Proof: `GhostExodus::VerifyingShares` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::VerifyingSharesParticipants` (r:1 w:1)
/// Proof: `GhostExodus::VerifyingSharesParticipants` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// The range of component `c` is `[1, 9]`.
fn register_round6_public_share_package(c: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `0 + c * (770 ±0)`
// Estimated: `1551 + c * (1019 ±27)`
// Minimum execution time: 284_558_000 picoseconds.
Weight::from_parts(284_117_484, 0)
.saturating_add(Weight::from_parts(0, 1551))
// Standard Error: 74_361
.saturating_add(Weight::from_parts(3_551_271, 0).saturating_mul(c.into()))
.saturating_add(T::DbWeight::get().reads(7))
.saturating_add(T::DbWeight::get().writes(3))
// Estimated: `1698 + c * (1019 ±27)`
// Minimum execution time: 283_863_000 picoseconds.
Weight::from_parts(286_691_978, 0)
.saturating_add(Weight::from_parts(0, 1698))
// Standard Error: 85_685
.saturating_add(Weight::from_parts(2_854_037, 0).saturating_mul(c.into()))
.saturating_add(T::DbWeight::get().reads(6))
.saturating_add(T::DbWeight::get().writes(2))
.saturating_add(Weight::from_parts(0, 1019).saturating_mul(c.into()))
}
/// Storage: `GhostNetworks::NetworkCurves` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkCurves` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostNetworks::NetworkTypes` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkTypes` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationDkgState` (r:1 w:0)
/// Proof: `GhostExodus::QualificationDkgState` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ActiveDkgAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::ActiveDkgAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ActiveAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::ActiveAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusRequests` (r:1 w:1)
/// Proof: `GhostExodus::ExodusRequests` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::RoastSessions` (r:1 w:1)
/// Proof: `GhostExodus::RoastSessions` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusActivity` (r:1 w:1)
/// Proof: `GhostExodus::ExodusActivity` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::NonceCommitments` (r:1 w:1)
/// Proof: `GhostExodus::NonceCommitments` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ActiveAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::ActiveAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::RoastSessionStates` (r:1 w:1)
/// Proof: `GhostExodus::RoastSessionStates` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::NonceCommitments` (r:0 w:1)
/// Proof: `GhostExodus::NonceCommitments` (`max_values`: None, `max_size`: None, mode: `Measured`)
fn register_nonce_commitment() -> Weight {
// Proof Size summary in bytes:
// Measured: `627`
// Estimated: `4092`
// Minimum execution time: 315_066_000 picoseconds.
Weight::from_parts(318_561_000, 0)
.saturating_add(Weight::from_parts(0, 4092))
// Measured: `633`
// Estimated: `4098`
// Minimum execution time: 369_666_000 picoseconds.
Weight::from_parts(373_163_000, 0)
.saturating_add(Weight::from_parts(0, 4098))
.saturating_add(T::DbWeight::get().reads(8))
.saturating_add(T::DbWeight::get().writes(5))
.saturating_add(T::DbWeight::get().writes(4))
}
/// Storage: `GhostNetworks::NetworkCurves` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkCurves` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostNetworks::NetworkTypes` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkTypes` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationDkgState` (r:1 w:0)
/// Proof: `GhostExodus::QualificationDkgState` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ActiveDkgAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::ActiveDkgAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ActiveAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::ActiveAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::RoastSessions` (r:1 w:0)
/// Proof: `GhostExodus::RoastSessions` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusActivity` (r:1 w:1)
/// Proof: `GhostExodus::ExodusActivity` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::RoastSessionStates` (r:1 w:1)
/// Proof: `GhostExodus::RoastSessionStates` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ActiveAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::ActiveAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusRequests` (r:1 w:0)
/// Proof: `GhostExodus::ExodusRequests` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::GroupCommitters` (r:1 w:1)
/// Proof: `GhostExodus::GroupCommitters` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::RoastSessionStates` (r:1 w:1)
/// Proof: `GhostExodus::RoastSessionStates` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::GroupCommitmentsConsensus` (r:1 w:1)
/// Proof: `GhostExodus::GroupCommitmentsConsensus` (`max_values`: None, `max_size`: None, mode: `Measured`)
fn register_group_commitment() -> Weight {
// Proof Size summary in bytes:
// Measured: `992`
// Estimated: `4457`
// Minimum execution time: 338_644_000 picoseconds.
Weight::from_parts(343_954_000, 0)
.saturating_add(Weight::from_parts(0, 4457))
.saturating_add(T::DbWeight::get().reads(10))
.saturating_add(T::DbWeight::get().writes(4))
// Measured: `1030`
// Estimated: `4495`
// Minimum execution time: 339_569_000 picoseconds.
Weight::from_parts(341_788_000, 0)
.saturating_add(Weight::from_parts(0, 4495))
.saturating_add(T::DbWeight::get().reads(9))
.saturating_add(T::DbWeight::get().writes(3))
}
/// Storage: `GhostNetworks::NetworkCurves` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkCurves` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostNetworks::NetworkTypes` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkTypes` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationDkgState` (r:1 w:0)
/// Proof: `GhostExodus::QualificationDkgState` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ActiveDkgAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::ActiveDkgAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ActiveAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::ActiveAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::RoastSessions` (r:1 w:1)
/// Proof: `GhostExodus::RoastSessions` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusActivity` (r:1 w:1)
/// Proof: `GhostExodus::ExodusActivity` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::RoastSessionStates` (r:1 w:1)
/// Proof: `GhostExodus::RoastSessionStates` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ActiveAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::ActiveAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::GroupCommitmentsConsensus` (r:1 w:1)
/// Proof: `GhostExodus::GroupCommitmentsConsensus` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusRequests` (r:1 w:1)
/// Proof: `GhostExodus::ExodusRequests` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusSignedGovernance` (r:1 w:1)
/// Proof: `GhostExodus::ExodusSignedGovernance` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::RoastSessionStates` (r:1 w:1)
/// Proof: `GhostExodus::RoastSessionStates` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::NonceCommitments` (r:1 w:0)
/// Proof: `GhostExodus::NonceCommitments` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::VerifyingShares` (r:1 w:0)
@ -306,13 +286,13 @@ impl<T: frame_system::Config> ghost_exodus::WeightInfo for WeightInfo<T> {
/// Proof: `GhostExodus::GroupCommitters` (`max_values`: None, `max_size`: None, mode: `Measured`)
fn register_signature_share() -> Weight {
// Proof Size summary in bytes:
// Measured: `11299`
// Estimated: `14764`
// Minimum execution time: 3_968_938_000 picoseconds.
Weight::from_parts(3_975_088_000, 0)
.saturating_add(Weight::from_parts(0, 14764))
.saturating_add(T::DbWeight::get().reads(15))
.saturating_add(T::DbWeight::get().writes(9))
// Measured: `11337`
// Estimated: `14802`
// Minimum execution time: 4_046_065_000 picoseconds.
Weight::from_parts(4_059_064_000, 0)
.saturating_add(Weight::from_parts(0, 14802))
.saturating_add(T::DbWeight::get().reads(14))
.saturating_add(T::DbWeight::get().writes(8))
}
/// Storage: `GhostExodus::CounterForExodusRequests` (r:1 w:1)
/// Proof: `GhostExodus::CounterForExodusRequests` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
@ -324,17 +304,17 @@ impl<T: frame_system::Config> ghost_exodus::WeightInfo for WeightInfo<T> {
/// Proof: `GhostNetworks::NetworkImbalance` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationDkgState` (r:1 w:0)
/// Proof: `GhostExodus::QualificationDkgState` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::CurrentExodus` (r:1 w:1)
/// Proof: `GhostExodus::CurrentExodus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::CurrentExodusSessions` (r:1 w:1)
/// Proof: `GhostExodus::CurrentExodusSessions` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusRequests` (r:1 w:1)
/// Proof: `GhostExodus::ExodusRequests` (`max_values`: None, `max_size`: None, mode: `Measured`)
fn register_evm_bridge_out_exodus() -> Weight {
// Proof Size summary in bytes:
// Measured: `448`
// Estimated: `3913`
// Minimum execution time: 191_374_000 picoseconds.
Weight::from_parts(192_991_000, 0)
.saturating_add(Weight::from_parts(0, 3913))
// Measured: `457`
// Estimated: `3922`
// Minimum execution time: 195_417_000 picoseconds.
Weight::from_parts(196_842_000, 0)
.saturating_add(Weight::from_parts(0, 3922))
.saturating_add(T::DbWeight::get().reads(7))
.saturating_add(T::DbWeight::get().writes(5))
}

View File

@ -16,7 +16,7 @@
//! Autogenerated weights for `ghost_governance`
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0
//! DATE: 2026-09-25, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! DATE: 2026-10-29, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! WORST CASE MAP SIZE: `1000000`
//! HOSTNAME: `ghostown`, CPU: `Intel(R) Core(TM) i3-2310M CPU @ 2.10GHz`
//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("casper-dev")`, DB CACHE: 1024
@ -61,11 +61,11 @@ impl<T: frame_system::Config> ghost_governance::WeightInfo for WeightInfo<T> {
// Proof Size summary in bytes:
// Measured: `283`
// Estimated: `3748`
// Minimum execution time: 229_254_000 picoseconds.
Weight::from_parts(229_992_093, 0)
// Minimum execution time: 224_852_000 picoseconds.
Weight::from_parts(225_347_733, 0)
.saturating_add(Weight::from_parts(0, 3748))
// Standard Error: 25_680
.saturating_add(Weight::from_parts(1_874_433, 0).saturating_mul(p.into()))
// Standard Error: 21_234
.saturating_add(Weight::from_parts(1_581_800, 0).saturating_mul(p.into()))
.saturating_add(T::DbWeight::get().reads(5))
.saturating_add(T::DbWeight::get().writes(5))
}

View File

@ -16,7 +16,7 @@
//! Autogenerated weights for `ghost_weaver`
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0
//! DATE: 2026-09-25, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! DATE: 2026-10-29, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! WORST CASE MAP SIZE: `1000000`
//! HOSTNAME: `ghostown`, CPU: `Intel(R) Core(TM) i3-2310M CPU @ 2.10GHz`
//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("casper-dev")`, DB CACHE: 1024
@ -46,34 +46,40 @@ use core::marker::PhantomData;
/// Weight functions for `ghost_weaver`.
pub struct WeightInfo<T>(PhantomData<T>);
impl<T: frame_system::Config> ghost_weaver::WeightInfo for WeightInfo<T> {
/// Storage: `GhostWeaver::DisabledAuthorities` (r:1 w:0)
/// Proof: `GhostWeaver::DisabledAuthorities` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `GhostWeaver::Authorities` (r:1 w:0)
/// Proof: `GhostWeaver::Authorities` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `GhostWeaver::AttestedBlocks` (r:1 w:1)
/// Proof: `GhostWeaver::AttestedBlocks` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostWeaver::NetworkAttestations` (r:1 w:1)
/// Proof: `GhostWeaver::NetworkAttestations` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostWeaver::DisabledAuthorities` (r:1 w:0)
/// Proof: `GhostWeaver::DisabledAuthorities` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `GhostNetworks::Networks` (r:1 w:0)
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostWeaver::TapestryDrafts` (r:1 w:0)
/// Proof: `GhostWeaver::TapestryDrafts` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostWeaver::NetworkAttestations` (r:1 w:1)
/// Proof: `GhostWeaver::NetworkAttestations` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// The range of component `a` is `[1, 300]`.
fn extend_warp(a: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `223 + a * (32 ±0)`
// Estimated: `3687 + a * (32 ±0)`
// Minimum execution time: 212_751_000 picoseconds.
Weight::from_parts(215_022_916, 0)
.saturating_add(Weight::from_parts(0, 3687))
// Standard Error: 1_319
.saturating_add(Weight::from_parts(53_379, 0).saturating_mul(a.into()))
.saturating_add(T::DbWeight::get().reads(5))
.saturating_add(T::DbWeight::get().writes(1))
.saturating_add(Weight::from_parts(0, 32).saturating_mul(a.into()))
// Measured: `274 + a * (33 ±0)`
// Estimated: `3736 + a * (33 ±0)`
// Minimum execution time: 229_711_000 picoseconds.
Weight::from_parts(235_206_680, 0)
.saturating_add(Weight::from_parts(0, 3736))
// Standard Error: 1_554
.saturating_add(Weight::from_parts(66_905, 0).saturating_mul(a.into()))
.saturating_add(T::DbWeight::get().reads(6))
.saturating_add(T::DbWeight::get().writes(2))
.saturating_add(Weight::from_parts(0, 33).saturating_mul(a.into()))
}
/// Storage: `GhostWeaver::DisabledAuthorities` (r:1 w:0)
/// Proof: `GhostWeaver::DisabledAuthorities` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `GhostWeaver::Authorities` (r:1 w:0)
/// Proof: `GhostWeaver::Authorities` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `GhostWeaver::AttestedBlocks` (r:1 w:1)
/// Proof: `GhostWeaver::AttestedBlocks` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostWeaver::NetworkAttestations` (r:1 w:0)
/// Proof: `GhostWeaver::NetworkAttestations` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostWeaver::DisabledAuthorities` (r:1 w:0)
/// Proof: `GhostWeaver::DisabledAuthorities` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `GhostNetworks::Networks` (r:1 w:0)
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostWeaver::TapestryDrafts` (r:1 w:0)
@ -85,16 +91,16 @@ impl<T: frame_system::Config> ghost_weaver::WeightInfo for WeightInfo<T> {
/// The range of component `a` is `[1, 300]`.
fn weave_weft(a: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `518 + a * (32 ±0)`
// Estimated: `3982 + a * (32 ±0)`
// Minimum execution time: 243_006_000 picoseconds.
Weight::from_parts(246_374_350, 0)
.saturating_add(Weight::from_parts(0, 3982))
// Standard Error: 1_865
.saturating_add(Weight::from_parts(38_601, 0).saturating_mul(a.into()))
.saturating_add(T::DbWeight::get().reads(6))
.saturating_add(T::DbWeight::get().writes(2))
.saturating_add(Weight::from_parts(0, 32).saturating_mul(a.into()))
// Measured: `569 + a * (33 ±0)`
// Estimated: `4031 + a * (33 ±0)`
// Minimum execution time: 262_809_000 picoseconds.
Weight::from_parts(267_769_707, 0)
.saturating_add(Weight::from_parts(0, 4031))
// Standard Error: 2_375
.saturating_add(Weight::from_parts(39_109, 0).saturating_mul(a.into()))
.saturating_add(T::DbWeight::get().reads(8))
.saturating_add(T::DbWeight::get().writes(3))
.saturating_add(Weight::from_parts(0, 33).saturating_mul(a.into()))
}
/// Storage: `GhostNetworks::Networks` (r:1 w:0)
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
@ -111,13 +117,13 @@ impl<T: frame_system::Config> ghost_weaver::WeightInfo for WeightInfo<T> {
/// The range of component `a` is `[1, 144]`.
fn pull_utxo_thread(a: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `449`
// Estimated: `3914`
// Minimum execution time: 217_880_000 picoseconds.
Weight::from_parts(217_119_201, 0)
.saturating_add(Weight::from_parts(0, 3914))
// Standard Error: 5_460
.saturating_add(Weight::from_parts(3_445_491, 0).saturating_mul(a.into()))
// Measured: `500`
// Estimated: `3965`
// Minimum execution time: 217_481_000 picoseconds.
Weight::from_parts(216_492_234, 0)
.saturating_add(Weight::from_parts(0, 3965))
// Standard Error: 4_414
.saturating_add(Weight::from_parts(3_376_569, 0).saturating_mul(a.into()))
.saturating_add(T::DbWeight::get().reads(6))
.saturating_add(T::DbWeight::get().writes(4))
}
@ -135,11 +141,11 @@ impl<T: frame_system::Config> ghost_weaver::WeightInfo for WeightInfo<T> {
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
fn pull_evm_thread() -> Weight {
// Proof Size summary in bytes:
// Measured: `449`
// Estimated: `3914`
// Minimum execution time: 183_413_000 picoseconds.
Weight::from_parts(186_271_000, 0)
.saturating_add(Weight::from_parts(0, 3914))
// Measured: `500`
// Estimated: `3965`
// Minimum execution time: 176_861_000 picoseconds.
Weight::from_parts(177_918_000, 0)
.saturating_add(Weight::from_parts(0, 3965))
.saturating_add(T::DbWeight::get().reads(6))
.saturating_add(T::DbWeight::get().writes(4))
}

View File

@ -20,10 +20,11 @@ MORDOR_RPC=(
HOODI_RPC=(
"https://rpc.hoodi.ethpandaops.io"
"https://0xrpc.io/hoodi"
"https://ethereum-hoodi.gateway.tatum.io"
"https://rpc.sentio.xyz/hoodi"
)
HOODI_GATEKEEPER="0xc9D5f50e17eeEAF39E0D0cDbbcF4B04FA4A4Bbd6"
show_help() {
cat << EOF
Usage: $(basename "$0") [OPTIONS]
@ -58,20 +59,66 @@ spinner() {
tput cnorm
}
pad32() {
local h="$1"
h="${h#0x}"
printf "%064s" "$h" | tr ' ' '0'
}
build_calldata() {
local selector="$1"
local at_block="$2"
local s_hex
local b_hex
s_hex=$(printf "%x" 0)
b_hex=$(printf "%x" "$at_block")
echo "0x${selector#0x}$(pad32 "$s_hex")$(pad32 "$b_hex")"
}
decode_get_root() {
local res_json="$1"
local hex
hex=$(echo "$res_json" | grep -o '"result":"[^"]*' | cut -d'"' -f4)
if [[ -z "$hex" || "$hex" == "null" ]]; then
echo "ERROR"
return
fi
hex="${hex#0x}"
if (( ${#hex} < 128 )); then
echo "ERROR"
return
fi
local word1="${hex:0:64}"
local word2="${hex:64:64}"
local num
num=$(printf "%d" "0x$word2" 2>/dev/null)
[[ -z "$num" ]] && num="$word2"
echo "0x${word1} ${num}"
}
check_network() {
local name=$1
shift
local gatekeeper=$2
shift 2
local rpcs=("$@")
local target_dir="${OUT_DIR:-/tmp}"
local tmp_file="${target_dir}/$(date +%s)_${name,,}"
local root_width=66
local sess_width=6
local max_len=0
for e in "${rpcs[@]}"; do (( ${#e} > max_len )) && max_len=${#e}; done
[[ $max_len -lt 20 ]] && max_len=20
local total_width=$(( max_len + 32 ))
local total_width=$(( max_len + 95 ))
local line=$(printf "%.0s-" $(seq 1 $total_width))
local header_fmt="| %-${max_len}s | %-12s | %-10s |\n"
local header_fmt="| %-${max_len}s | %-10s | %-${root_width}s | %-${sess_width}s |\n"
(
for endpoint in "${rpcs[@]}"; do
@ -80,11 +127,15 @@ check_network() {
hex=$(echo "$res_b" | grep -o '"result":"[^"]*' | cut -d'"' -f4)
[[ -n "$hex" && "$hex" != "null" ]] && blk=$(printf "%d" "$hex") || blk="ERROR"
res_l=$(curl -s -X POST -H "Content-Type: application/json" --max-time 10 \
--data '{"jsonrpc":"2.0","method":"eth_getLogs","params":[{"fromBlock":"0x1","toBlock":"0x2"}],"id":1}' "$endpoint")
echo "$res_l" | grep -q '"result"' && ev="YES" || ev="NO"
root="N/A"
if [[ -n "$blk" && "$blk" != "ERROR" ]]; then
calldata=$(build_calldata "$SELECTOR" "$blk")
payload=$(printf '{"jsonrpc":"2.0","method":"eth_call","params":[{"to":"%s","data":"%s"},"latest"],"id":1}' "$gatekeeper" "$calldata")
res_c=$(curl -s -X POST -H "Content-Type: application/json" --max-time 10 --data "$payload" "$endpoint")
root=$(decode_get_root "$res_c")
fi
echo "$endpoint|$blk|$ev"
echo "$endpoint|$blk|$root"
done
) > "$tmp_file" &
@ -94,10 +145,17 @@ check_network() {
spinner $worker_pid "$name"
local table_output="$line\n"
table_output+=$(printf "$header_fmt" "Endpoint" "Block" "Events")"\n"
table_output+=$(printf "$header_fmt" "Endpoint" "Block" "Merkle Root" "Session")"\n"
table_output+="$line\n"
while IFS="|" read -r url blk ev; do
table_output+=$(printf "$header_fmt" "$url" "$blk" "$ev")"\n"
while IFS="|" read -r url blk root; do
if [[ "$root" == *" "* ]]; then
hash="${root%% *}"
sess="${root##* }"
else
hash="$root"
sess="-"
fi
table_output+=$(printf "$header_fmt" "$url" "$blk" "$hash" "$sess")"\n"
done < "$tmp_file"
table_output+="$line"
@ -111,6 +169,7 @@ check_network() {
CHOSEN_CHAIN=""
BASE_OUT_DIR=""
CUSTOM_RPCS=""
SELECTOR="d4be7935"
while [[ $# -gt 0 ]]; do
case "$1" in
@ -132,14 +191,14 @@ if [[ -n "$CUSTOM_RPCS" ]]; then
check_network "Custom" "${USER_RPC_ARRAY[@]}"
else
if [ "$CHOSEN_CHAIN" == "sepolia" ]; then
check_network "Sepolia" "${SEPOLIA_RPC[@]}"
check_network "Sepolia" "$SEPOLIA_GATEKEEPER" "${HOODI_RPC[@]}"
elif [ "$CHOSEN_CHAIN" == "mordor" ]; then
check_network "Mordor" "${MORDOR_RPC[@]}"
check_network "Mordor" "$MORDOR_GATEKEEPER" "${HOODI_RPC[@]}"
elif [ "$CHOSEN_CHAIN" == "hoodi" ]; then
check_network "Hoodi" "${HOODI_RPC[@]}"
check_network "Hoodi" "$HOODI_GATEKEEPER" "${HOODI_RPC[@]}"
else
check_network "Sepolia" "${SEPOLIA_RPC[@]}"
check_network "Mordor" "${MORDOR_RPC[@]}"
check_network "Hoodi" "${HOODI_RPC[@]}"
check_network "Sepolia" "$SEPOLIA_GATEKEEPER" "${SEPOLIA_RPC[@]}"
check_network "Mordor" "$MORDOR_GATEKEEPER" "${MORDOR_RPC[@]}"
check_network "Hoodi" "$HOODI_GATEKEEPER" "${HOODI_RPC[@]}"
fi
fi

View File

@ -9,6 +9,7 @@ UNIT_FILE=false
SKIP_BUILD=false
SET_ENVIRONMENT=false
VALIDATE=false
FIX_WEAVER_KEYSTORE=false
EXECUTABLE_PATH="/usr/bin/"
SPECIFICATION_PATH="/etc/ghost/"
BASE_PATH="/var/lib/ghost"
@ -120,7 +121,8 @@ help() {
echo -e "-s, --base-path\n\tPath to the folder with chain database ('/var/lib/ghost' is default)."
echo -e "-c, --specification-path\n\tPath to specification ('/etc/ghost' is default)."
echo -e "-n, --specification-name\n\tSpecification name to be used ('casper' is default)."
echo -e "-g, --generate-genesis\n\tGenerate keys to participate in the genesis file."
echo -e "--generate-genesis\n\tGenerate keys to participate in the genesis file."
echo -e "--fix-weaver-keystore\n\tFix keystore prefixes (wevr -> weav) to ensure the offchain worker can find the key."
echo -e "--hard-reset-i-know-what-im-doing\n\tWill completely remove ledger along with associated session keys."
echo -e "-h, --help\n\tPrints help information."
}
@ -192,9 +194,12 @@ while [ $# -gt 0 ]; do
--hard-reset-i-know-what-im-doing)
HARD_RESET=true
;;
--generate-genesis|-g)
--generate-genesis)
GENERATE_GENESIS=true
;;
--fix-weaver-keystore)
FIX_WEAVER_KEYSTORE=true
;;
--help|-h)
help
exit 0
@ -460,7 +465,7 @@ if [ $CHECK_KEYS = true ]; then
rpc_endpoint="${rpc_endpoint:-localhost:9945}"
fi
for type in $(echo audi babe gran wevr exds); do
for type in $(echo audi babe gran weav exds); do
echo "[+] parsing session key for [$type]"
scheme="sr25519"
if [ $type = "gran" ]; then
@ -528,6 +533,51 @@ if [ $UNIT_FILE = true ]; then
fi
fi
if [ $FIX_WEAVER_KEYSTORE = true ]; then
echo "[+] Starting keystore prefix fix (wevr -> weav)..."
KEYSTORE_DIR="${BASE_PATH%/}/chains/${SPECIFICATION_NAME}_staging_testnet/keystore"
echo "[+] Requesting sudo privileges to modify keystore files..."
sudo -v
if ! sudo test -d "$KEYSTORE_DIR"; then
echo "[-] Keystore directory not found at: $KEYSTORE_DIR"
echo "[-] Please check your --base-path and --specification-name settings."
exit 1
fi
WRONG_PREFIX="77657672" # "wevr"
CORRECT_PREFIX="77656176" # "weav"
CHANGES_MADE=0
while IFS= read -r file; do
if sudo test -f "$file"; then
filename=$(basename "$file")
if [[ "$filename" == "$WRONG_PREFIX"* ]]; then
new_filename="${CORRECT_PREFIX}${filename#$WRONG_PREFIX}"
new_file_path="$KEYSTORE_DIR/$new_filename"
if sudo test -f "$new_file_path"; then
echo "[!] WARNING: File with correct prefix already exists: $new_filename. Skipping."
else
sudo mv "$file" "$new_file_path"
echo "[+] Renamed: $filename -> $new_filename"
CHANGES_MADE=1
fi
fi
fi
done < <(sudo find "$KEYSTORE_DIR" -maxdepth 1 -type f)
if [ $CHANGES_MADE -eq 1 ]; then
echo "[+] Keystore fix applied successfully."
echo "[+] IMPORTANT: You must restart the ghost-node service for changes to take effect."
echo "[+] Run: sudo systemctl restart ghost-node"
else
echo "[+] No files with the wrong 'weav' prefix were found. Everything is fine."
fi
fi
if [ "$GENERATE_GENESIS" = true ]; then
echo "[+] Generating keys for genesis configuration..."
@ -561,7 +611,7 @@ if [ "$GENERATE_GENESIS" = true ]; then
BABE_HEX=$(ghost key inspect --scheme=sr25519 "$SESSION_SEED//babe" | grep "Public key (hex)" | awk '{print $4}' | sed 's/^0x//')
GRAN_HEX=$(ghost key inspect --scheme=ed25519 "$SESSION_SEED//gran" | grep "Public key (hex)" | awk '{print $4}' | sed 's/^0x//')
AUDI_HEX=$(ghost key inspect --scheme=sr25519 "$SESSION_SEED//audi" | grep "Public key (hex)" | awk '{print $4}' | sed 's/^0x//')
WEVR_HEX=$(ghost key inspect --scheme=sr25519 "$SESSION_SEED//wevr" | grep "Public key (hex)" | awk '{print $4}' | sed 's/^0x//')
WEAV_HEX=$(ghost key inspect --scheme=sr25519 "$SESSION_SEED//weav" | grep "Public key (hex)" | awk '{print $4}' | sed 's/^0x//')
EXDS_HEX=$(ghost key inspect --scheme=sr25519 "$SESSION_SEED//exds" | grep "Public key (hex)" | awk '{print $4}' | sed 's/^0x//')
STASH_SS58=$(ghost key inspect --scheme=sr25519 "$STASH_SEED" | grep "SS58 Address" | awk '{print $3}')
@ -575,7 +625,7 @@ if [ "$GENERATE_GENESIS" = true ]; then
echo " .with_babe_id(BabeId::unchecked_from(hex!(\"$BABE_HEX\")))"
echo " .with_grandpa_id(GrandpaId::unchecked_from(hex!(\"$GRAN_HEX\")))"
echo " .with_authority_id(AuthorityDiscoveryId::unchecked_from(hex!(\"$AUDI_HEX\")))"
echo " .with_weaver_id(WeaverId::unchecked_from(hex!(\"$WEVR_HEX\")))"
echo " .with_weaver_id(WeaverId::unchecked_from(hex!(\"$WEAV_HEX\")))"
echo " .with_exodus_id(ExodusId::unchecked_from(hex!(\"$EXDS_HEX\")))"
echo " .build(),"
echo ""