Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jumpstart network #918

Merged
merged 27 commits into from
Jul 11, 2024
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ At the moment this project **does not** adhere to
- Add `blake2` as built in hash function and make `HashingAlgorithm` non-exhaustive ([#881](https://github.com/entropyxyz/entropy-core/pull/881))
- Add sort to subgroup signer selection ([#900](https://github.com/entropyxyz/entropy-core/pull/900))
- Create four node Docker Compose chainspec ([#902](https://github.com/entropyxyz/entropy-core/pull/902))
- Jumpstart network ([#918](https://github.com/entropyxyz/entropy-core/pull/918))
HCastano marked this conversation as resolved.
Show resolved Hide resolved

### Changed
- Move TSS mnemonic out of keystore ([#853](https://github.com/entropyxyz/entropy-core/pull/853))
Expand Down
Binary file modified crates/client/entropy_metadata.scale
Binary file not shown.
2 changes: 2 additions & 0 deletions crates/shared/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ lazy_static! {
pub static ref DEVICE_KEY_CONFIG_TYPE: Vec<u8> = vec![123, 34, 36, 115, 99, 104, 101, 109, 97, 34, 58, 34, 104, 116, 116, 112, 58, 47, 47, 106, 115, 111, 110, 45, 115, 99, 104, 101, 109, 97, 46, 111, 114, 103, 47, 100, 114, 97, 102, 116, 45, 48, 55, 47, 115, 99, 104, 101, 109, 97, 35, 34, 44, 34, 116, 105, 116, 108, 101, 34, 58, 34, 85, 115, 101, 114, 67, 111, 110, 102, 105, 103, 34, 44, 34, 100, 101, 115, 99, 114, 105, 112, 116, 105, 111, 110, 34, 58, 34, 74, 83, 79, 78, 45, 100, 101, 115, 101, 114, 105, 97, 108, 105, 122, 97, 98, 108, 101, 32, 115, 116, 114, 117, 99, 116, 32, 116, 104, 97, 116, 32, 119, 105, 108, 108, 32, 98, 101, 32, 117, 115, 101, 100, 32, 116, 111, 32, 100, 101, 114, 105, 118, 101, 32, 116, 104, 101, 32, 112, 114, 111, 103, 114, 97, 109, 45, 74, 83, 79, 78, 32, 105, 110, 116, 101, 114, 102, 97, 99, 101, 46, 32, 78, 111, 116, 101, 32, 104, 111, 119, 32, 116, 104, 105, 115, 32, 117, 115, 101, 115, 32, 74, 83, 79, 78, 45, 110, 97, 116, 105, 118, 101, 32, 116, 121, 112, 101, 115, 32, 111, 110, 108, 121, 46, 34, 44, 34, 116, 121, 112, 101, 34, 58, 34, 111, 98, 106, 101, 99, 116, 34, 44, 34, 112, 114, 111, 112, 101, 114, 116, 105, 101, 115, 34, 58, 123, 34, 101, 99, 100, 115, 97, 95, 112, 117, 98, 108, 105, 99, 95, 107, 101, 121, 115, 34, 58, 123, 34, 100, 101, 115, 99, 114, 105, 112, 116, 105, 111, 110, 34, 58, 34, 98, 97, 115, 101, 54, 52, 45, 101, 110, 99, 111, 100, 101, 100, 32, 99, 111, 109, 112, 114, 101, 115, 115, 101, 100, 32, 112, 111, 105, 110, 116, 32, 40, 51, 51, 45, 98, 121, 116, 101, 41, 32, 69, 67, 68, 83, 65, 32, 112, 117, 98, 108, 105, 99, 32, 107, 101, 121, 115, 44, 32, 40, 101, 103, 46, 32, 92, 34, 65, 53, 55, 50, 100, 113, 111, 117, 101, 53, 79, 121, 119, 89, 47, 52, 56, 100, 116, 121, 116, 81, 105, 109, 76, 57, 87, 79, 48, 100, 112, 83, 79, 98, 97, 70, 98, 65, 120, 111, 69, 87, 87, 57, 92, 34, 41, 34, 44, 34, 116, 121, 112, 101, 34, 58, 91, 34, 97, 114, 114, 97, 121, 34, 44, 34, 110, 117, 108, 108, 34, 93, 44, 34, 105, 116, 101, 109, 115, 34, 58, 123, 34, 116, 121, 112, 101, 34, 58, 34, 115, 116, 114, 105, 110, 103, 34, 125, 125, 44, 34, 101, 100, 50, 53, 53, 49, 57, 95, 112, 117, 98, 108, 105, 99, 95, 107, 101, 121, 115, 34, 58, 123, 34, 116, 121, 112, 101, 34, 58, 91, 34, 97, 114, 114, 97, 121, 34, 44, 34, 110, 117, 108, 108, 34, 93, 44, 34, 105, 116, 101, 109, 115, 34, 58, 123, 34, 116, 121, 112, 101, 34, 58, 34, 115, 116, 114, 105, 110, 103, 34, 125, 125, 44, 34, 115, 114, 50, 53, 53, 49, 57, 95, 112, 117, 98, 108, 105, 99, 95, 107, 101, 121, 115, 34, 58, 123, 34, 116, 121, 112, 101, 34, 58, 91, 34, 97, 114, 114, 97, 121, 34, 44, 34, 110, 117, 108, 108, 34, 93, 44, 34, 105, 116, 101, 109, 115, 34, 58, 123, 34, 116, 121, 112, 101, 34, 58, 34, 115, 116, 114, 105, 110, 103, 34, 125, 125, 125, 125];
// Device key aux data struct seralized by generate types in programs repo
pub static ref DEVICE_KEY_AUX_DATA_TYPE: Vec<u8> = vec![123, 34, 36, 115, 99, 104, 101, 109, 97, 34, 58, 34, 104, 116, 116, 112, 58, 47, 47, 106, 115, 111, 110, 45, 115, 99, 104, 101, 109, 97, 46, 111, 114, 103, 47, 100, 114, 97, 102, 116, 45, 48, 55, 47, 115, 99, 104, 101, 109, 97, 35, 34, 44, 34, 116, 105, 116, 108, 101, 34, 58, 34, 65, 117, 120, 68, 97, 116, 97, 34, 44, 34, 100, 101, 115, 99, 114, 105, 112, 116, 105, 111, 110, 34, 58, 34, 74, 83, 79, 78, 32, 114, 101, 112, 114, 101, 115, 101, 110, 116, 97, 116, 105, 111, 110, 32, 111, 102, 32, 116, 104, 101, 32, 97, 117, 120, 105, 108, 105, 97, 114, 121, 32, 100, 97, 116, 97, 34, 44, 34, 116, 121, 112, 101, 34, 58, 34, 111, 98, 106, 101, 99, 116, 34, 44, 34, 114, 101, 113, 117, 105, 114, 101, 100, 34, 58, 91, 34, 99, 111, 110, 116, 101, 120, 116, 34, 44, 34, 112, 117, 98, 108, 105, 99, 95, 107, 101, 121, 34, 44, 34, 112, 117, 98, 108, 105, 99, 95, 107, 101, 121, 95, 116, 121, 112, 101, 34, 44, 34, 115, 105, 103, 110, 97, 116, 117, 114, 101, 34, 93, 44, 34, 112, 114, 111, 112, 101, 114, 116, 105, 101, 115, 34, 58, 123, 34, 99, 111, 110, 116, 101, 120, 116, 34, 58, 123, 34, 100, 101, 115, 99, 114, 105, 112, 116, 105, 111, 110, 34, 58, 34, 84, 104, 101, 32, 99, 111, 110, 116, 101, 120, 116, 32, 102, 111, 114, 32, 116, 104, 101, 32, 115, 105, 103, 110, 97, 116, 117, 114, 101, 32, 111, 110, 108, 121, 32, 110, 101, 101, 100, 101, 100, 32, 105, 110, 32, 115, 114, 50, 53, 53, 49, 57, 32, 115, 105, 103, 110, 97, 116, 117, 114, 101, 32, 116, 121, 112, 101, 34, 44, 34, 116, 121, 112, 101, 34, 58, 34, 115, 116, 114, 105, 110, 103, 34, 125, 44, 34, 112, 117, 98, 108, 105, 99, 95, 107, 101, 121, 34, 58, 123, 34, 100, 101, 115, 99, 114, 105, 112, 116, 105, 111, 110, 34, 58, 34, 98, 97, 115, 101, 54, 52, 45, 101, 110, 99, 111, 100, 101, 100, 32, 112, 117, 98, 108, 105, 99, 32, 107, 101, 121, 34, 44, 34, 116, 121, 112, 101, 34, 58, 34, 115, 116, 114, 105, 110, 103, 34, 125, 44, 34, 112, 117, 98, 108, 105, 99, 95, 107, 101, 121, 95, 116, 121, 112, 101, 34, 58, 123, 34, 100, 101, 115, 99, 114, 105, 112, 116, 105, 111, 110, 34, 58, 34, 92, 34, 101, 99, 100, 115, 97, 92, 34, 44, 32, 92, 34, 101, 100, 50, 53, 53, 49, 57, 92, 34, 44, 32, 92, 34, 115, 114, 50, 53, 53, 49, 57, 92, 34, 34, 44, 34, 116, 121, 112, 101, 34, 58, 34, 115, 116, 114, 105, 110, 103, 34, 125, 44, 34, 115, 105, 103, 110, 97, 116, 117, 114, 101, 34, 58, 123, 34, 100, 101, 115, 99, 114, 105, 112, 116, 105, 111, 110, 34, 58, 34, 98, 97, 115, 101, 54, 52, 45, 101, 110, 99, 111, 100, 101, 100, 32, 115, 105, 103, 110, 97, 116, 117, 114, 101, 34, 44, 34, 116, 121, 112, 101, 34, 58, 34, 115, 116, 114, 105, 110, 103, 34, 125, 125, 125];
// Network parent key
pub static ref NETWORK_PARENT_KEY: H256 = H256::zero();
}

pub const SIGNING_PARTY_SIZE: usize = 2;
Expand Down
49 changes: 36 additions & 13 deletions crates/threshold-signature-server/src/user/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ use entropy_programs_runtime::{Config as ProgramConfig, Runtime, SignatureReques
use entropy_protocol::ValidatorInfo;
use entropy_protocol::{KeyParams, SigningSessionInfo};
use entropy_shared::{
types::KeyVisibility, HashingAlgorithm, OcwMessageDkg, X25519PublicKey, SIGNING_PARTY_SIZE,
types::KeyVisibility, HashingAlgorithm, OcwMessageDkg, X25519PublicKey, NETWORK_PARENT_KEY,
SIGNING_PARTY_SIZE,
};
use futures::{
channel::mpsc,
Expand All @@ -46,7 +47,7 @@ use futures::{
use num::{bigint::BigInt, FromPrimitive, Num, ToPrimitive};
use parity_scale_codec::{Decode, DecodeAll, Encode};
use serde::{Deserialize, Serialize};
use sp_core::crypto::AccountId32;
use sp_core::{crypto::AccountId32, H256};
use subxt::{
backend::legacy::LegacyRpcMethods,
ext::sp_core::{crypto::Ss58Codec, sr25519, sr25519::Signature, Pair},
Expand Down Expand Up @@ -142,6 +143,11 @@ pub async fn sign_tx(
.number;

check_stale(user_sig_req.block_number, block_number).await?;
// Probably impossible but block signing from parent key anyways
if user_sig_req.signature_verifying_key == NETWORK_PARENT_KEY.encode() {
return Err(UserErr::NoSigningFromMasterKey);
}

let user_details =
get_registered_details(&api, &rpc, user_sig_req.signature_verifying_key.clone()).await?;
check_hash_pointer_out_of_bounds(&user_sig_req.hash, user_details.programs_data.0.len())?;
Expand Down Expand Up @@ -307,7 +313,6 @@ async fn setup_dkg(
app_state: AppState,
) -> Result<(), UserErr> {
tracing::debug!("Preparing to execute DKG");

let subgroup = get_subgroup(&api, rpc, signer.account_id()).await?;
let stash_address = get_stash_address(&api, rpc, signer.account_id()).await?;
let mut addresses_in_subgroup = return_all_addresses_of_subgroup(&api, rpc, subgroup).await?;
Expand All @@ -325,21 +330,32 @@ async fn setup_dkg(
.try_into()
.map_err(|_| UserErr::AddressConversionError("Invalid Length".to_string()))?;
let sig_request_address = SubxtAccountId32(*address_slice);
let user_details =
get_registering_user_details(&api, &sig_request_address.clone(), rpc).await?;
let key_visibility = if sig_request_account == NETWORK_PARENT_KEY.encode() {
KeyVisibility::Public
} else {
let user_details =
get_registering_user_details(&api, &sig_request_address.clone(), rpc).await?;
user_details.key_visibility.0
};

let key_share = do_dkg(
&data.validators_info,
&signer,
x25519_secret_key,
&app_state.listener_state,
sig_request_address.clone(),
*user_details.key_visibility,
key_visibility,
data.block_number,
)
.await?;
let verifying_key = key_share.verifying_key().to_encoded_point(true).as_bytes().to_vec();
let string_verifying_key = hex::encode(verifying_key.clone()).to_string();
let string_verifying_key = if sig_request_account == NETWORK_PARENT_KEY.encode() {
hex::encode(*NETWORK_PARENT_KEY)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One suggestion: instead of overwriting the parent's verifying key to be 0x00 we could
have a separate "forbidden key" entry, PARENT_VERIFYING_KEY, which points to the parent
verifying key.

For those checks you mention, we could achieve them the same way, but doing an extra
lookup.

That way we keep the verifying key entries consistent.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure at this point tho im in favour of opening an issue

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah an issue is fine

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

} else {
hex::encode(verifying_key.clone())
}
.to_string();

let serialized_key_share = key_serialize(&key_share)
.map_err(|_| UserErr::KvSerialize("Kv Serialize Error".to_string()))?;

Expand Down Expand Up @@ -505,12 +521,19 @@ pub async fn confirm_registered(
// TODO fire and forget, or wait for in block maybe Ddos error
// TODO: Understand this better, potentially use sign_and_submit_default
// or other method under sign_and_*
let registration_tx = entropy::tx().registry().confirm_register(
who,
subgroup,
entropy::runtime_types::bounded_collections::bounded_vec::BoundedVec(verifying_key),
);
submit_transaction(api, rpc, signer, &registration_tx, Some(nonce)).await?;

if who.encode() == NETWORK_PARENT_KEY.encode() {
let jump_start_request = entropy::tx().registry().confirm_jump_start(subgroup);
submit_transaction(api, rpc, signer, &jump_start_request, Some(nonce)).await?;
} else {
let confirm_register_request = entropy::tx().registry().confirm_register(
who,
subgroup,
entropy::runtime_types::bounded_collections::bounded_vec::BoundedVec(verifying_key),
);
submit_transaction(api, rpc, signer, &confirm_register_request, Some(nonce)).await?;
}

Ok(())
}

Expand Down
2 changes: 2 additions & 0 deletions crates/threshold-signature-server/src/user/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ pub enum UserErr {
EncryptionOrAuthentication(#[from] EncryptedSignedMessageErr),
#[error("Custom hash choice out of bounds")]
CustomHashOutOfBounds,
#[error("No signing from parent key")]
NoSigningFromMasterKey,
JesseAbram marked this conversation as resolved.
Show resolved Hide resolved
#[error("Listener: {0}")]
Listener(#[from] entropy_protocol::errors::ListenerErr),
#[error("Error creating sr25519 keypair from seed: {0}")]
Expand Down
100 changes: 100 additions & 0 deletions crates/threshold-signature-server/src/user/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ use entropy_protocol::{
use entropy_shared::{
HashingAlgorithm, KeyVisibility, OcwMessageDkg, DAVE_VERIFYING_KEY, DEFAULT_VERIFYING_KEY,
DEFAULT_VERIFYING_KEY_NOT_REGISTERED, DEVICE_KEY_HASH, EVE_VERIFYING_KEY, FERDIE_VERIFYING_KEY,
NETWORK_PARENT_KEY,
};
use entropy_testing_utils::{
chain_api::{
Expand Down Expand Up @@ -362,6 +363,18 @@ async fn test_sign_tx_no_chain() {
for res in test_user_custom_hash_out_of_bounds {
assert_eq!(res.unwrap().text().await.unwrap(), "Custom hash choice out of bounds");
}

generic_msg.block_number = rpc.chain_get_header(None).await.unwrap().unwrap().number;
generic_msg.signature_verifying_key = NETWORK_PARENT_KEY.0.to_vec();
let test_user_sign_with_parent_key = submit_transaction_requests(
vec![validator_ips_and_keys[1].clone()],
generic_msg.clone(),
one,
)
.await;
for res in test_user_sign_with_parent_key {
assert_eq!(res.unwrap().text().await.unwrap(), "No signing from parent key");
}
clean_tests();
}

Expand Down Expand Up @@ -774,6 +787,81 @@ async fn test_store_share() {
clean_tests();
}

#[tokio::test]
#[serial]
async fn test_jumpstart_network() {
initialize_test_logger().await;
clean_tests();

let alice = AccountKeyring::Alice;

let cxt = test_context_stationary().await;
let (_validator_ips, _validator_ids, _) =
spawn_testing_validators(Some(DEFAULT_VERIFYING_KEY.to_vec()), false, false).await;
let api = get_api(&cxt.node_proc.ws_url).await.unwrap();
let rpc = get_rpc(&cxt.node_proc.ws_url).await.unwrap();

let client = reqwest::Client::new();

let block_number = rpc.chain_get_header(None).await.unwrap().unwrap().number + 1;
let validators_info = vec![
entropy_shared::ValidatorInfo {
ip_address: b"127.0.0.1:3001".to_vec(),
x25519_public_key: X25519_PUBLIC_KEYS[0],
tss_account: TSS_ACCOUNTS[0].clone().encode(),
},
entropy_shared::ValidatorInfo {
ip_address: b"127.0.0.1:3002".to_vec(),
x25519_public_key: X25519_PUBLIC_KEYS[1],
tss_account: TSS_ACCOUNTS[1].clone().encode(),
},
];
let onchain_user_request = OcwMessageDkg {
sig_request_accounts: vec![H256::zero().encode()],
block_number,
validators_info,
};

put_jumpstart_request_on_chain(&api, &rpc, &alice).await;

run_to_block(&rpc, block_number + 1).await;

// succeeds
let user_registration_response = client
.post("http://127.0.0.1:3002/user/new")
.body(onchain_user_request.clone().encode())
.send()
.await
.unwrap();

assert_eq!(user_registration_response.text().await.unwrap(), "");
// wait for jump start event check that key exists in kvdb
for _ in 0..45 {
std::thread::sleep(std::time::Duration::from_millis(1000));
let block_hash = rpc.chain_get_block_hash(None).await.unwrap();
let events = EventsClient::new(api.clone()).at(block_hash.unwrap()).await.unwrap();
let jump_start_event = events.find::<entropy::registry::events::FinishedNetworkJumpStart>();
for _event in jump_start_event.flatten() {
break;
}
}

let get_query = UnsafeQuery::new(hex::encode(H256::zero()), [].to_vec()).to_json();
// check get key before registration to see if key gets replaced
let response_key = client
.post("http://127.0.0.1:3001/unsafe/get")
.header("Content-Type", "application/json")
.body(get_query.clone())
.send()
.await
.unwrap();
// check to make sure keyshare is correct
let key_share: Option<KeyShare<KeyParams>> =
entropy_kvdb::kv_manager::helpers::deserialize(&response_key.bytes().await.unwrap());
assert_eq!(key_share.is_some(), true);
clean_tests();
}

#[tokio::test]
#[serial]
async fn test_return_addresses_of_subgroup() {
Expand Down Expand Up @@ -1053,6 +1141,18 @@ pub async fn put_register_request_on_chain(
submit_transaction(api, rpc, &sig_req_account, &registering_tx, None).await.unwrap();
}

pub async fn put_jumpstart_request_on_chain(
api: &OnlineClient<EntropyConfig>,
rpc: &LegacyRpcMethods<EntropyConfig>,
sig_req_keyring: &Sr25519Keyring,
) {
let sig_req_account =
PairSigner::<EntropyConfig, sp_core::sr25519::Pair>::new(sig_req_keyring.pair());

let registering_tx = entropy::tx().registry().jump_start_network();
submit_transaction(api, rpc, &sig_req_account, &registering_tx, None).await.unwrap();
}

#[tokio::test]
#[serial]
async fn test_sign_tx_user_participates() {
Expand Down
61 changes: 61 additions & 0 deletions pallets/registry/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,67 @@ pub fn add_non_syncing_validators<T: Config>(
}

benchmarks! {
jump_start_network {

let sig_req_account: T::AccountId = whitelisted_caller();
let balance = <T as pallet_staking_extension::Config>::Currency::minimum_balance() * 100u32.into();
let _ = <T as pallet_staking_extension::Config>::Currency::make_free_balance_be(&sig_req_account, balance);

}: _(RawOrigin::Signed(sig_req_account.clone()))
verify {
assert_last_event::<T>(Event::StartedNetworkJumpStart().into());
}

confirm_jump_start_done {
let c in 0 .. SIG_PARTIES as u32;
let sig_req_account: T::AccountId = whitelisted_caller();
let validator_account: T::AccountId = whitelisted_caller();
let threshold_account: T::AccountId = whitelisted_caller();

let sig_party_size = MaxValidators::<T>::get() / SIG_PARTIES as u32;
// add validators and a registering user
for i in 0..SIG_PARTIES {
let validators = add_non_syncing_validators::<T>(sig_party_size, 0, i as u8);
<ThresholdToStash<T>>::insert(&threshold_account, &validators[i]);
}
<JumpStartProgress<T>>::put(JumpStartDetails {
jump_start_status: JumpStartStatus::InProgress(0),
confirmations: vec![1],
});


let balance = <T as pallet_staking_extension::Config>::Currency::minimum_balance() * 100u32.into();
let _ = <T as pallet_staking_extension::Config>::Currency::make_free_balance_be(&threshold_account, balance);
}: confirm_jump_start(RawOrigin::Signed(threshold_account), 0)
verify {
assert_last_event::<T>(Event::<T>::FinishedNetworkJumpStart().into());
}

confirm_jump_start_confirm {
let c in 0 .. SIG_PARTIES as u32;
let sig_req_account: T::AccountId = whitelisted_caller();
let validator_account: T::AccountId = whitelisted_caller();
let threshold_account: T::AccountId = whitelisted_caller();

let sig_party_size = MaxValidators::<T>::get() / SIG_PARTIES as u32;
// add validators and a registering user
for i in 0..SIG_PARTIES {
let validators = add_non_syncing_validators::<T>(sig_party_size, 0, i as u8);
<ThresholdToStash<T>>::insert(&threshold_account, &validators[i]);
}
<JumpStartProgress<T>>::put(JumpStartDetails {
jump_start_status: JumpStartStatus::InProgress(0),
confirmations: vec![],
});


let balance = <T as pallet_staking_extension::Config>::Currency::minimum_balance() * 100u32.into();
let _ = <T as pallet_staking_extension::Config>::Currency::make_free_balance_be(&threshold_account, balance);
}: confirm_jump_start(RawOrigin::Signed(threshold_account), 0)
verify {
assert_last_event::<T>(Event::<T>::JumpStartConfirmation(0).into());
}

register {
let p in 1 .. T::MaxProgramHashes::get();
let program = vec![0u8];
Expand Down
Loading