Newer
Older
crypto::{AccountId32, DeriveJunction, Pair as _, Ss58Codec},
use subxt::ext::sp_runtime::MultiAddress;
use subxt::tx::{BaseExtrinsicParamsBuilder, PairSigner};
pub type Client = subxt::OnlineClient<GdevConfig>;
pub enum GdevConfig {}
impl subxt::config::Config for GdevConfig {
type Index = u32;
type BlockNumber = u32;
type Hash = sp_core::H256;
type Hashing = subxt::ext::sp_runtime::traits::BlakeTwo256;
type AccountId = subxt::ext::sp_runtime::AccountId32;
type Address = subxt::ext::sp_runtime::MultiAddress<Self::AccountId, u32>;
type Header = subxt::ext::sp_runtime::generic::Header<
Self::BlockNumber,
subxt::ext::sp_runtime::traits::BlakeTwo256,
>;
type Signature = subxt::ext::sp_runtime::MultiSignature;
type Extrinsic = subxt::ext::sp_runtime::OpaqueExtrinsic;
type ExtrinsicParams = subxt::tx::BaseExtrinsicParams<Self, Tip>;
}
#[derive(Copy, Clone, Debug, Default, Encode)]
pub struct Tip {
#[codec(compact)]
tip: u64,
}
impl Tip {
pub fn new(amount: u64) -> Self {
Tip { tip: amount }
}
}
impl From<u64> for Tip {
fn from(n: u64) -> Self {
Self::new(n)
}
}
#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
struct Args {
#[clap(subcommand)]
pub subcommand: Subcommand,
/// Indexer URL
#[clap(short, long, default_value = "https://idx.gdev.cgeek.fr/v1/graphql")]
indexer: String,
/// Do not use indexer
#[clap(long)]
no_indexer: bool,
/// Secret key or BIP39 mnemonic
/// (eventually followed by derivation path)
#[clap(short, long)]
secret: Option<String>,
/// Address
#[clap(short, long)]
address: Option<String>,
#[clap(short, long, default_value = "ws://localhost:9944")]
url: String,
}
#[derive(Debug, clap::Subcommand)]
pub enum Subcommand {
CreateOneshot {
balance: u64,
dest: sp_core::crypto::AccountId32,
},
ConsumeOneshot {
dest: sp_core::crypto::AccountId32,
#[clap(long = "oneshot")]
dest_oneshot: bool,
},
ConsumeOneshotWithRemaining {
balance: u64,
dest: sp_core::crypto::AccountId32,
#[clap(long = "one")]
dest_oneshot: bool,
remaining_to: sp_core::crypto::AccountId32,
#[clap(long = "rem-one")]
remaining_to_oneshot: bool,
},
/// List upcoming expirations that require an action
Expire {
/// Show certs that expire within less than this number of blocks
#[clap(short, long, default_value_t = 100800)]
blocks: u32,
/// Show authorities that should rotate keys within less than this number of sessions
#[clap(short, long, default_value_t = 100)]
sessions: u32,
},
/// Generate a revocation document for the provided account
GenRevocDoc,
OneshotBalance {
account: sp_core::crypto::AccountId32,
},
#[clap(hide = true)]
Repart {
// Number of transactions per block to target
target: u32,
#[clap(short = 'o', long = "old-repart")]
// Old/actual repartition
actual_repart: Option<u32>,
},
#[clap(hide = true)]
SpamRoll { actual_repart: usize },
SudoSetKey {
new_key: sp_core::crypto::AccountId32,
},
Transfer {
balance: u64,
dest: sp_core::crypto::AccountId32,
#[clap(short = 'k')]
keep_alive: bool,
},
}
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<()> {
env_logger::init();
let args = Args::parse();
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
let (account_id, pair) = match (&args.address, &args.secret) {
(Some(address), Some(secret)) => {
let pair = Pair::from_string(secret, None)
.map_err(|_| anyhow!("Invalid secret {}", secret))?;
let address = sp_core::crypto::AccountId32::from_string(address)
.map_err(|_| anyhow!("Invalid address {}", address))?;
assert_eq!(
address,
pair.public().into(),
"Secret and address do not match."
);
(Some(pair.public().into()), Some(pair))
}
(None, Some(secret)) => {
let pair = Pair::from_string(secret, None)
.map_err(|_| anyhow!("Invalid secret {}", secret))?;
(Some(pair.public().into()), Some(pair))
}
(Some(address), None) => (
Some(
sp_core::crypto::AccountId32::from_str(address)
.map_err(|_| anyhow!("Invalid address {}", address))?,
),
None,
),
(None, None) => (None, None),
};
let gql_client = reqwest::Client::builder()
.user_agent("gcli/0.1.0")
.build()?;
let account = client
.storage()
.fetch(&gdev_300::storage().system().account(account_id), None)
.await?
.expect("Cannot fetch account");
logs::info!("Account free balance: {}", account.data.free);
}
Subcommand::CreateOneshot { balance, dest } => {
let pair = pair.expect("This subcommand needs a secret.");
&gdev_300::tx()
.oneshot_account()
.create_oneshot_account(dest.into(), balance),
&PairSigner::new(pair),
BaseExtrinsicParamsBuilder::new(),
)
.await?;
}
Subcommand::ConsumeOneshot { dest, dest_oneshot } => {
let pair = pair.expect("This subcommand needs a secret.");
let number = client
.storage()
.fetch(&gdev_300::storage().system().number(), None)
.await?
.unwrap();
client.tx()
.sign_and_submit_then_watch(&gdev_300::tx()
.oneshot_account()
.consume_oneshot_account(
number,
if dest_oneshot {
gdev_300::runtime_types::pallet_oneshot_account::types::Account::Oneshot(
dest.into(),
)
} else {
gdev_300::runtime_types::pallet_oneshot_account::types::Account::Normal(
dest.into(),
)
},
&PairSigner::new(pair),
BaseExtrinsicParamsBuilder::new(),
)
.await?;
}
Subcommand::ConsumeOneshotWithRemaining {
balance,
dest,
dest_oneshot,
remaining_to,
remaining_to_oneshot,
} => {
let pair = pair.expect("This subcommand needs a secret.");
let number = client
.storage()
.fetch(&gdev_300::storage().system().number(), None)
.await?
.unwrap();
client.tx()
.sign_and_submit_then_watch(&gdev_300::tx()
.oneshot_account()
.consume_oneshot_account_with_remaining(
number,
if dest_oneshot {
gdev_300::runtime_types::pallet_oneshot_account::types::Account::Oneshot(
dest.into(),
)
} else {
gdev_300::runtime_types::pallet_oneshot_account::types::Account::Normal(
dest.into(),
)
},
if remaining_to_oneshot {
gdev_300::runtime_types::pallet_oneshot_account::types::Account::Oneshot(
remaining_to.into(),
)
} else {
gdev_300::runtime_types::pallet_oneshot_account::types::Account::Normal(
remaining_to.into(),
)
},
balance,
&PairSigner::new(pair),
BaseExtrinsicParamsBuilder::new(),
)
.await?;
}
Subcommand::Expire { blocks, sessions } => {
.fetch(&gdev_300::storage().system().parent_hash(), None)
.await?
.unwrap();
let addr_current_block = gdev_300::storage().system().number();
let addr_current_session = gdev_300::storage().session().current_index();
let (current_block, current_session) = join!(
client
.storage()
.fetch(&addr_current_block, Some(parent_hash)),
client
.storage()
.fetch(&addr_current_session, Some(parent_hash),)
);
let current_block = current_block?.unwrap();
let current_session = current_session?.unwrap();
let end_block = current_block + blocks;
let end_session = current_session + sessions;
let mut identity_cache = cache::IdentityCache::new(
if args.no_indexer {
None
} else {
Some((&gql_client, &args.indexer))
},
);
// Rotate keys
let mut must_rotate_keys_before_iter = client
.storage()
.iter(
gdev_300::storage()
.authority_members()
.must_rotate_keys_before(0),
10,
Some(parent_hash),
)
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
.await?;
let mut must_rotate_keys_before = BTreeMap::new();
while let Some((k, v)) = must_rotate_keys_before_iter.next().await? {
let session_index = u32::from_le_bytes(k.as_ref()[40..44].try_into().unwrap());
if session_index < end_session {
must_rotate_keys_before.insert(session_index - current_session, v);
}
}
println!("\nAuthority members:");
for (sessions_left, identity_ids) in must_rotate_keys_before {
println!("Must rotate keys before {} sessions:", sessions_left);
for identity_id in identity_ids {
println!(
" {} ({})",
identity_cache
.fetch_identity(identity_id, parent_hash)
.await
.unwrap_or_else(|_| "?".into()),
identity_id
);
}
}
// Certifications
let mut basic_certs_iter = client
.storage()
.iter(
gdev_300::storage().cert().storage_certs_removable_on(0),
10,
Some(parent_hash),
)
.await?;
let mut basic_certs = BTreeMap::new();
while let Some((k, v)) = basic_certs_iter.next().await? {
let block_number = u32::from_le_bytes(k.as_ref()[40..44].try_into().unwrap());
if block_number < end_block {
basic_certs.insert(block_number - current_block, v);
}
}
let mut smith_certs_iter = client
.storage()
.iter(
gdev_300::storage()
.smiths_cert()
.storage_certs_removable_on(0),
10,
Some(parent_hash),
)
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
.await?;
let mut smith_certs = BTreeMap::new();
while let Some((k, v)) = smith_certs_iter.next().await? {
let block_number = u32::from_le_bytes(k.as_ref()[40..44].try_into().unwrap());
if block_number < end_block {
smith_certs.insert(block_number - current_block, v);
}
}
for (title, certs) in [
("Certifications", basic_certs),
("Smith certifications", smith_certs),
] {
println!("\n{}:", title);
for (blocks_left, certs) in certs {
println!("{} blocks before expiration:", blocks_left);
for (issuer_id, receiver_id) in certs {
println!(
" {} ({}) -> {} ({})",
identity_cache
.fetch_identity(issuer_id, parent_hash)
.await
.unwrap_or_else(|_| "?".into()),
issuer_id,
identity_cache
.fetch_identity(receiver_id, parent_hash)
.await
.unwrap_or_else(|_| "?".into()),
receiver_id,
);
}
}
}
// Memberships
let mut basic_membership_iter = client
.storage()
.iter(
gdev_300::storage().membership().memberships_expire_on(0),
10,
Some(parent_hash),
)
.await?;
let mut basic_memberships = BTreeMap::new();
while let Some((k, v)) = basic_membership_iter.next().await? {
let block_number = u32::from_le_bytes(k.as_ref()[40..44].try_into().unwrap());
if block_number < end_block {
basic_memberships.insert(block_number - current_block, v);
}
}
let mut smith_membership_iter = client
.storage()
.iter(
gdev_300::storage()
.smiths_membership()
.memberships_expire_on(0),
10,
Some(parent_hash),
)
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
.await?;
let mut smith_memberships = BTreeMap::new();
while let Some((k, v)) = smith_membership_iter.next().await? {
let block_number = u32::from_le_bytes(k.as_ref()[40..44].try_into().unwrap());
if block_number < end_block {
smith_memberships.insert(block_number - current_block, v);
}
}
for (title, memberships) in [
("Memberships", basic_memberships),
("Smith memberships", smith_memberships),
] {
println!("\n{}:", title);
for (blocks_left, membership) in memberships {
println!("{} blocks before expiration:", blocks_left);
for identity_id in membership {
println!(
" {} ({})",
identity_cache
.fetch_identity(identity_id, parent_hash)
.await
.unwrap_or_else(|_| "?".into()),
identity_id,
);
}
}
}
}
gen_revoc_doc(&client, &pair.expect("This subcommand needs a secret.")).await?
Subcommand::OneshotBalance { account } => {
logs::info!(
"{}",
client
.storage()
.fetch(
&gdev_300::storage()
.oneshot_account()
.oneshot_accounts(&account),
None
)
let pair = pair.expect("This subcommand needs a secret.");
let mut pairs = Vec::new();
for i in actual_repart.unwrap_or_default()..target {
let pair_i = pair
.derive(std::iter::once(DeriveJunction::hard::<u32>(i)), None)
.map_err(|_| anyhow!("Fail to derive //{}", i))?
.0;
pairs.push((i, pair_i));
}
for (i, pair_i) in &pairs {
/*let _ = api
.tx()
.balances()
.transfer(MultiAddress::Id(pair_i.public().into()), 501)?
.sign_and_submit_then_watch(&signer, BaseExtrinsicParamsBuilder::new())
.await?
.wait_for_in_block()
.await?;
signer.increment_nonce();*/
.fetch(
&gdev_300::storage()
.system()
.account(&pair_i.public().into()),
None,
)
.await?
{
logs::info!("account //{} balance: {}", i, pair_i_account.data.free);
}
let pair = pair.expect("This subcommand needs a secret.");
Vec::<(PairSigner<GdevConfig, Pair>, AccountId32)>::with_capacity(actual_repart);
for i in 0..actual_repart {
let pair_i = pair
.derive(std::iter::once(DeriveJunction::hard::<u32>(i as u32)), None)
.map_err(|_| anyhow!("Fail to derive //{}", i))?
.0;
let account_id_i = pair_i.public().into();
pairs.push((PairSigner::new(pair_i), account_id_i));
}
loop {
let mut watchers = Vec::with_capacity(actual_repart);
for i in 0..(actual_repart - 1) {
let dest: AccountId32 = pairs[i + 1].1.clone();
.sign_and_submit_then_watch(
&gdev_300::tx()
.balances()
.transfer(MultiAddress::Id(dest), 1),
&pairs[i].0,
BaseExtrinsicParamsBuilder::new(),
)
.await?;
pairs[i].0.increment_nonce();
logs::info!("send 1 cent from //{} to //{}", i, i + 1);
watchers.push(watcher);
}
let dest: AccountId32 = pairs[0].1.clone();
&gdev_300::tx()
.balances()
.transfer(MultiAddress::Id(dest), 1),
&pairs[actual_repart - 1].0,
BaseExtrinsicParamsBuilder::new(),
)
.await?;
pairs[actual_repart - 1].0.increment_nonce();
logs::info!("send 1 cent from //{} to //0", actual_repart - 1);
watchers.push(watcher);
// Wait all transactions
for watcher in watchers {
watcher.wait_for_in_block().await?;
}
}
}
Subcommand::SudoSetKey { new_key } => {
let pair = pair.expect("This subcommand needs a secret.");
client
.tx()
.sign_and_submit_then_watch(
&gdev_300::tx().sudo().set_key(new_key.into()),
&PairSigner::new(pair),
BaseExtrinsicParamsBuilder::new(),
)
.await?;
}
Subcommand::Transfer {
balance,
dest,
keep_alive,
} => {
let pair = pair.expect("This subcommand needs a secret.");
&gdev_300::tx().balances().transfer(dest.into(), balance),
&PairSigner::new(pair),
BaseExtrinsicParamsBuilder::new(),
)
.await?;
} else {
&gdev_300::tx()
.balances()
.transfer_keep_alive(dest.into(), balance),
&PairSigner::new(pair),
BaseExtrinsicParamsBuilder::new(),
)
.await?;
}
}
async fn gen_revoc_doc(api: &Client, pair: &Pair) -> Result<()> {
let account_id: sp_core::crypto::AccountId32 = pair.public().into();
let addr_idty_index = gdev_300::storage()
.identity()
.identity_index_of(&account_id);
let addr_block_hash = gdev_300::storage().system().block_hash(0);
let (idty_index, genesis_hash) = join!(
api.storage().fetch(&addr_idty_index, None,),
api.storage().fetch(&addr_block_hash, None)
);
let idty_index = idty_index?.unwrap();
let genesis_hash = genesis_hash?.unwrap();
let payload = (b"revo", genesis_hash, idty_index).encode();