Skip to content

Commit 59292f9

Browse files
authored
fix: metadata extrinsic front-run attack, rework benchmarks (#927)
1 parent 10fde57 commit 59292f9

File tree

12 files changed

+176
-104
lines changed

12 files changed

+176
-104
lines changed

demo/runtime/src/lib.rs

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -509,26 +509,35 @@ impl pallet_block_production_log::benchmarking::BenchmarkHelper<BlockAuthor>
509509
pub struct PalletBlockProducerMetadataBenchmarkHelper;
510510

511511
#[cfg(feature = "runtime-benchmarks")]
512-
impl pallet_block_producer_metadata::benchmarking::BenchmarkHelper<BlockProducerMetadataType>
513-
for PalletBlockProducerMetadataBenchmarkHelper
512+
impl
513+
pallet_block_producer_metadata::benchmarking::BenchmarkHelper<
514+
BlockProducerMetadataType,
515+
AccountId,
516+
> for PalletBlockProducerMetadataBenchmarkHelper
514517
{
518+
fn genesis_utxo() -> UtxoId {
519+
Sidechain::genesis_utxo()
520+
}
521+
515522
fn metadata() -> BlockProducerMetadataType {
516523
BlockProducerMetadataType {
517524
url: "https://cool.stuff/spo.json".try_into().unwrap(),
518525
hash: SizedByteString::from([0; 32]),
519526
}
520527
}
528+
521529
fn cross_chain_pub_key() -> sidechain_domain::CrossChainPublicKey {
522530
sidechain_domain::CrossChainPublicKey(
523531
hex_literal::hex!("020a1091341fe5664bfa1782d5e04779689068c916b04cb365ec3153755684d9a1")
524532
.to_vec(),
525533
)
526534
}
527-
fn upsert_cross_chain_signature() -> sidechain_domain::CrossChainSignature {
528-
sidechain_domain::CrossChainSignature(hex_literal::hex!("0892ab398baf72fb90c7e90147caea9b5aa642f082e8573877aba34aeb618c7e3c4a904ef5c12bdb6560d665a71584ea266279ba686ff7e4f886ca75e357fdff").to_vec())
529-
}
530-
fn delete_cross_chain_signature() -> sidechain_domain::CrossChainSignature {
531-
sidechain_domain::CrossChainSignature(hex_literal::hex!("e891b42327fc5202f258b080d0a3f33d9a292693840dee5fa5e46033fe0b059b682597be65ac5678c182a65b46b621aaadcfb0811155f54e8d99c9e4394a1fe9").to_vec())
535+
536+
fn cross_chain_sign_key() -> pallet_block_producer_metadata::benchmarking::SecretKey {
537+
pallet_block_producer_metadata::benchmarking::SecretKey::from_slice(&hex_literal::hex!(
538+
"cb6df9de1efca7a3998a8ead4e02159d5fa99c3e0d4fd6432667390bb4726854"
539+
))
540+
.unwrap()
532541
}
533542

534543
fn upsert_valid_before() -> u64 {

e2e-tests/src/partner_chains_node/node.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,13 @@ def sign_address_association(self, genesis_utxo: str, partner_chain_address, sta
4141
logging.error(f"Could not parse response of sign-address-association cmd: {result}")
4242
raise e
4343

44-
def sign_block_producer_metadata_upsert(self, genesis_utxo, metadata_file, cross_chain_signing_key):
45-
return self.sign_block_producer_metadata_operation(genesis_utxo, metadata_file, cross_chain_signing_key)
44+
def sign_block_producer_metadata_upsert(self, genesis_utxo, metadata_file, cross_chain_signing_key, partner_chain_account):
45+
return self.sign_block_producer_metadata_operation(genesis_utxo, metadata_file, cross_chain_signing_key, partner_chain_account)
4646

47-
def sign_block_producer_metadata_delete(self, genesis_utxo, cross_chain_signing_key):
48-
return self.sign_block_producer_metadata_operation(genesis_utxo, None, cross_chain_signing_key)
47+
def sign_block_producer_metadata_delete(self, genesis_utxo, cross_chain_signing_key, partner_chain_account):
48+
return self.sign_block_producer_metadata_operation(genesis_utxo, None, cross_chain_signing_key, partner_chain_account)
4949

50-
def sign_block_producer_metadata_operation(self, genesis_utxo, metadata_file, cross_chain_signing_key):
50+
def sign_block_producer_metadata_operation(self, genesis_utxo, metadata_file, cross_chain_signing_key, partner_chain_account):
5151
cross_chain_signing_key = cross_chain_signing_key.to_string().hex()
5252

5353
sign_block_producer_metadata_cmd = " ".join([
@@ -56,7 +56,8 @@ def sign_block_producer_metadata_operation(self, genesis_utxo, metadata_file, cr
5656
(f"upsert" if metadata_file is not None else "delete"),
5757
f"--genesis-utxo {genesis_utxo}",
5858
(f"--metadata-file {metadata_file}" if metadata_file is not None else ""),
59-
f"--cross-chain-signing-key {cross_chain_signing_key}"
59+
f"--cross-chain-signing-key {cross_chain_signing_key}",
60+
f"--partner-chain-account {partner_chain_account}"
6061
])
6162

6263
result = self.run_command.exec(sign_block_producer_metadata_cmd)

e2e-tests/src/substrate_api.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -685,14 +685,14 @@ def _effective_in_mc_epoch(self):
685685
def sign_address_association(self, genesis_utxo, address, stake_signing_key):
686686
return self.partner_chains_node.sign_address_association(genesis_utxo, address, stake_signing_key)
687687

688-
def sign_block_producer_metadata_upsert(self, genesis_utxo, metadata_file, cross_chain_signing_key):
688+
def sign_block_producer_metadata_upsert(self, genesis_utxo, metadata_file, cross_chain_signing_key, partner_chain_account):
689689
return self.partner_chains_node.sign_block_producer_metadata_upsert(
690-
genesis_utxo, metadata_file, cross_chain_signing_key
690+
genesis_utxo, metadata_file, cross_chain_signing_key, partner_chain_account
691691
)
692692

693-
def sign_block_producer_metadata_delete(self, genesis_utxo, cross_chain_signing_key):
693+
def sign_block_producer_metadata_delete(self, genesis_utxo, cross_chain_signing_key, partner_chain_account):
694694
return self.partner_chains_node.sign_block_producer_metadata_delete(
695-
genesis_utxo, cross_chain_signing_key
695+
genesis_utxo, cross_chain_signing_key, partner_chain_account
696696
)
697697

698698
@long_running_function

e2e-tests/tests/committee/test_blocks.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def test_block_producer_can_update_their_metadata(genesis_utxo, api: BlockchainA
1818
}
1919
metadata_filepath = write_file(api.partner_chains_node.run_command, metadata)
2020

21-
signature = api.sign_block_producer_metadata_upsert(genesis_utxo, metadata_filepath, skey)
21+
signature = api.sign_block_producer_metadata_upsert(genesis_utxo, metadata_filepath, skey, get_wallet.address)
2222
assert signature.signature, "Signature is empty"
2323
assert signature.cross_chain_pub_key == f"0x{vkey_hex}"
2424

@@ -41,7 +41,7 @@ def test_block_producer_can_update_their_metadata(genesis_utxo, api: BlockchainA
4141
"hash": "0x0000000000000000000000000000000000000000000000000000000000000002",
4242
}
4343
metadata_filepath = write_file(api.partner_chains_node.run_command, metadata)
44-
signature = api.sign_block_producer_metadata_upsert(genesis_utxo, metadata_filepath, skey)
44+
signature = api.sign_block_producer_metadata_upsert(genesis_utxo, metadata_filepath, skey, get_wallet.address)
4545
assert signature.signature, "Signature is empty"
4646
assert signature.cross_chain_pub_key == f"0x{vkey_hex}"
4747

@@ -72,7 +72,7 @@ def test_block_producer_can_delete_their_metadata(genesis_utxo, api: BlockchainA
7272
}
7373
metadata_filepath = write_file(api.partner_chains_node.run_command, metadata)
7474

75-
signature = api.sign_block_producer_metadata_upsert(genesis_utxo, metadata_filepath, skey)
75+
signature = api.sign_block_producer_metadata_upsert(genesis_utxo, metadata_filepath, skey, get_wallet.address)
7676
assert signature.signature, "Signature is empty"
7777
assert signature.cross_chain_pub_key == f"0x{vkey_hex}"
7878

@@ -90,7 +90,7 @@ def test_block_producer_can_delete_their_metadata(genesis_utxo, api: BlockchainA
9090
assert rpc_metadata == metadata, "RPC did not return block producer metadata or it is incorrect"
9191

9292
logger.info("Starting delete")
93-
signature = api.sign_block_producer_metadata_delete(genesis_utxo, skey)
93+
signature = api.sign_block_producer_metadata_delete(genesis_utxo, skey, get_wallet.address)
9494
assert signature.signature, "Signature is empty"
9595
assert signature.cross_chain_pub_key == f"0x{vkey_hex}"
9696

toolkit/block-producer-metadata/pallet/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ sp-core = { workspace = true, optional = true }
2424
sp-block-producer-metadata = { workspace = true }
2525
sp-runtime = { workspace = true, optional = true }
2626
sp-io = { workspace = true, optional = true }
27+
k256 = { workspace = true, optional = true }
2728

2829
[dev-dependencies]
2930
sp-core = { workspace = true }
@@ -33,6 +34,7 @@ hex-literal = { workspace = true }
3334
k256 = { workspace = true }
3435
pallet-balances = { workspace = true }
3536
pretty_assertions = { workspace = true }
37+
sp-block-producer-metadata = { workspace = true, features = ["std"] }
3638

3739
[features]
3840
default = ["std"]
@@ -57,4 +59,5 @@ runtime-benchmarks = [
5759
"hex-literal",
5860
"sp-core",
5961
"sp-io",
62+
"k256",
6063
]

toolkit/block-producer-metadata/pallet/src/benchmarking.rs

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,24 @@
77
//!
88
//! ```rust
99
//! use sidechain_domain::byte_string::*;
10-
//! use sidechain_domain::{ CrossChainSignature, CrossChainPublicKey };
11-
//! use sp_core::ConstU32;
10+
//! use sidechain_domain::{ CrossChainSignature, CrossChainPublicKey, UtxoId };
11+
//! use sp_core::{ ConstU32, Encode };
1212
//! use hex_literal::hex;
13+
//! use sp_runtime::AccountId32;
1314
//!
15+
//! #[derive(Encode)]
1416
//! struct BlockProducerMetadata {
1517
//! pub url: BoundedString<ConstU32<512>>,
1618
//! pub hash: SizedByteString<32>,
1719
//! }
1820
//!
1921
//! struct ExampleBenchmarkHelper;
2022
//!
21-
//! impl pallet_block_producer_metadata::benchmarking::BenchmarkHelper<BlockProducerMetadata> for ExampleBenchmarkHelper {
23+
//! impl pallet_block_producer_metadata::benchmarking::BenchmarkHelper<BlockProducerMetadata, AccountId32> for ExampleBenchmarkHelper {
24+
//! fn genesis_utxo() -> UtxoId {
25+
//! UtxoId::new([1;32], 0)
26+
//! }
27+
//!
2228
//! fn metadata() -> BlockProducerMetadata {
2329
//! BlockProducerMetadata {
2430
//! url: BoundedString::try_from("https://cool.stuff/spo.json").unwrap(),
@@ -28,11 +34,8 @@
2834
//! fn cross_chain_pub_key() -> CrossChainPublicKey {
2935
//! CrossChainPublicKey(hex!("020a1091341fe5664bfa1782d5e04779689068c916b04cb365ec3153755684d9a1").to_vec())
3036
//! }
31-
//! fn upsert_cross_chain_signature() -> sidechain_domain::CrossChainSignature {
32-
//! CrossChainSignature(hex!("810854f5bd1d06dc8583ebd58ff4877dddb1646511edb10afd021f716bf51a8e617353b6c5d5f92a2005e2c3c24b782a6f74132d6b54251854cce186c981862c").to_vec())
33-
//! }
34-
//! fn delete_cross_chain_signature() -> sidechain_domain::CrossChainSignature {
35-
//! CrossChainSignature(hex!("5c1a701c8adffdf53a371409a24cc6c2d778a4c65c2c105c5fccfc5eeb69e3fa59bd723e7c10893f53fcfdfff8c02954f2230953cb9596119c11d4a9a29564c5").to_vec())
37+
//! fn cross_chain_sign_key() -> k256::SecretKey {
38+
//! k256::SecretKey::from_slice(&[2;32]).unwrap()
3639
//! }
3740
//! fn upsert_valid_before() -> u64 {
3841
//! 100_000_000
@@ -65,26 +68,49 @@
6568
use super::*;
6669
use frame_benchmarking::v2::*;
6770
use frame_system::RawOrigin;
71+
pub use k256::SecretKey;
6872
use sidechain_domain::*;
6973

7074
/// Helper trait for injecting mock values for use in benchmarks
71-
pub trait BenchmarkHelper<BlockProducerMetadata> {
75+
pub trait BenchmarkHelper<BlockProducerMetadata: Encode, AccountId: Encode> {
76+
/// Should return the chain's genesis utxo
77+
fn genesis_utxo() -> UtxoId;
7278
/// Should return mock metadata
7379
fn metadata() -> BlockProducerMetadata;
7480
/// Should return mock cross-chain pubkey
7581
fn cross_chain_pub_key() -> CrossChainPublicKey;
82+
/// Should return mock cross-chain signing key
83+
fn cross_chain_sign_key() -> SecretKey;
7684
/// Should return mock cross-chain signature for upsert operation
7785
///
7886
/// This signature must match the cross-chain pubkey returned by `cross_chain_pub_key` and be a valid
7987
/// signature of [MetadataSignedMessage] created using values returned by `metadata` and `cross_chain_pub_key`
8088
/// and the genesis UTXO used for benchmarks.
81-
fn upsert_cross_chain_signature() -> CrossChainSignature;
89+
fn upsert_cross_chain_signature(owner: AccountId) -> CrossChainSignature {
90+
MetadataSignedMessage {
91+
cross_chain_pub_key: Self::cross_chain_pub_key(),
92+
metadata: Some(Self::metadata()),
93+
genesis_utxo: Self::genesis_utxo(),
94+
valid_before: Self::upsert_valid_before(),
95+
owner,
96+
}
97+
.sign_with_key(&Self::cross_chain_sign_key())
98+
}
8299

83100
/// Should return mock cross-chain signature for delete operation
84101
///
85102
/// This signature must match the cross-chain pubkey returned by `cross_chain_pub_key` and be a valid
86103
/// for the genesis UTXO used for benchmarks.
87-
fn delete_cross_chain_signature() -> CrossChainSignature;
104+
fn delete_cross_chain_signature(owner: AccountId) -> CrossChainSignature {
105+
MetadataSignedMessage {
106+
cross_chain_pub_key: Self::cross_chain_pub_key(),
107+
metadata: None::<BlockProducerMetadata>,
108+
genesis_utxo: Self::genesis_utxo(),
109+
valid_before: Self::delete_valid_before(),
110+
owner,
111+
}
112+
.sign_with_key(&Self::cross_chain_sign_key())
113+
}
88114

89115
/// Should return the valid-before value for the upsert
90116
fn upsert_valid_before() -> u64;
@@ -103,13 +129,15 @@ mod benchmarks {
103129
fn upsert_metadata() {
104130
let metadata = T::BenchmarkHelper::metadata();
105131
let cross_chain_pub_key = T::BenchmarkHelper::cross_chain_pub_key();
106-
let cross_chain_signature = T::BenchmarkHelper::upsert_cross_chain_signature();
107132
let valid_before = T::BenchmarkHelper::upsert_valid_before();
108133

109134
// Create an account and fund it with sufficient balance
110135
let caller: T::AccountId = account("caller", 0, 0);
111136
let _ = T::Currency::mint_into(&caller, T::HoldAmount::get() * 2u32.into());
112137

138+
let cross_chain_signature =
139+
T::BenchmarkHelper::upsert_cross_chain_signature(caller.clone());
140+
113141
#[extrinsic_call]
114142
_(
115143
RawOrigin::Signed(caller),
@@ -124,13 +152,15 @@ mod benchmarks {
124152
fn delete_metadata() {
125153
let metadata = T::BenchmarkHelper::metadata();
126154
let cross_chain_pub_key = T::BenchmarkHelper::cross_chain_pub_key();
127-
let cross_chain_signature = T::BenchmarkHelper::delete_cross_chain_signature();
128155
let valid_before = T::BenchmarkHelper::delete_valid_before();
129156

130157
let caller: T::AccountId = account("caller", 0, 0);
131158
let _ =
132159
T::Currency::hold(&HoldReason::MetadataDeposit.into(), &caller, T::HoldAmount::get());
133160

161+
let cross_chain_signature =
162+
T::BenchmarkHelper::delete_cross_chain_signature(caller.clone());
163+
134164
BlockProducerMetadataStorage::<T>::insert(
135165
cross_chain_pub_key.hash(),
136166
(metadata, caller.clone(), T::HoldAmount::get()),

toolkit/block-producer-metadata/pallet/src/lib.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ pub mod pallet {
183183

184184
/// Helper providing mock values for use in benchmarks
185185
#[cfg(feature = "runtime-benchmarks")]
186-
type BenchmarkHelper: benchmarking::BenchmarkHelper<Self::BlockProducerMetadata>;
186+
type BenchmarkHelper: benchmarking::BenchmarkHelper<Self::BlockProducerMetadata, Self::AccountId>;
187187
}
188188

189189
/// Storage mapping from block producers to their metadata, owner account and deposit amount
@@ -248,6 +248,7 @@ pub mod pallet {
248248
metadata: Some(metadata.clone()),
249249
genesis_utxo,
250250
valid_before,
251+
owner: origin_account.clone(),
251252
};
252253

253254
let is_valid_signature =
@@ -297,11 +298,12 @@ pub mod pallet {
297298
let origin_account = ensure_signed(origin)?;
298299

299300
let genesis_utxo = T::genesis_utxo();
300-
let metadata_message = MetadataSignedMessage::<T::BlockProducerMetadata> {
301+
let metadata_message = MetadataSignedMessage::<T::BlockProducerMetadata, T::AccountId> {
301302
cross_chain_pub_key: cross_chain_pub_key.clone(),
302303
metadata: None,
303304
genesis_utxo,
304305
valid_before,
306+
owner: origin_account.clone(),
305307
};
306308
let cross_chain_key_hash = cross_chain_pub_key.hash();
307309
let is_valid_signature =

0 commit comments

Comments
 (0)