Skip to content
Snippets Groups Projects
main.rs 22.2 KiB
Newer Older
mod cache;
mod indexer;

Pascal Engélibert's avatar
Pascal Engélibert committed
use anyhow::{anyhow, Result};
Éloïs's avatar
Éloïs committed
use clap::Parser;
use codec::Encode;
use futures::join;
Éloïs's avatar
Éloïs committed
use sp_core::{
    crypto::{AccountId32, DeriveJunction, Pair as _, Ss58Codec},
Éloïs's avatar
Éloïs committed
    sr25519::Pair,
};
use std::collections::BTreeMap;
use std::str::FromStr;
Pascal Engélibert's avatar
Pascal Engélibert committed
use subxt::ext::sp_runtime::MultiAddress;
use subxt::tx::{BaseExtrinsicParamsBuilder, PairSigner};
Éloïs's avatar
Éloïs committed

#[subxt::subxt(runtime_metadata_path = "res/metadata.scale")]
pub mod gdev_300 {}
Éloïs's avatar
Éloïs committed

Pascal Engélibert's avatar
Pascal Engélibert committed
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>;
}
Éloïs's avatar
Éloïs committed

#[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)
    }
}

Éloïs's avatar
Éloïs committed
#[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,
Éloïs's avatar
Éloïs committed
    /// Secret key or BIP39 mnemonic
    /// (eventually followed by derivation path)
    #[clap(short, long)]
    secret: Option<String>,
    /// Address
    #[clap(short, long)]
    address: Option<String>,
Éloïs's avatar
Éloïs committed
    /// Websocket RPC endpoint
    #[clap(short, long, default_value = "ws://localhost:9944")]
Éloïs's avatar
Éloïs committed
    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,
    },
Éloïs's avatar
Éloïs committed
    /// Generate a revocation document for the provided account
    GenRevocDoc,
    OneshotBalance {
        account: sp_core::crypto::AccountId32,
    },
Éloïs's avatar
Éloïs committed
    #[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 },
Pascal Engélibert's avatar
Pascal Engélibert committed
    SudoSetKey {
        new_key: sp_core::crypto::AccountId32,
    },
    Transfer {
        balance: u64,
        dest: sp_core::crypto::AccountId32,
        #[clap(short = 'k')]
        keep_alive: bool,
    },
Éloïs's avatar
Éloïs committed
}

#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<()> {
    env_logger::init();

    let args = Args::parse();

    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),
    };
Éloïs's avatar
Éloïs committed

Pascal Engélibert's avatar
Pascal Engélibert committed
    let client = Client::new().await.unwrap();
Éloïs's avatar
Éloïs committed

    let gql_client = reqwest::Client::builder()
        .user_agent("gcli/0.1.0")
        .build()?;

    if let Some(account_id) = &account_id {
Pascal Engélibert's avatar
Pascal Engélibert committed
        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);
    }
Éloïs's avatar
Éloïs committed

    match args.subcommand {
        Subcommand::CreateOneshot { balance, dest } => {
            let pair = pair.expect("This subcommand needs a secret.");

Pascal Engélibert's avatar
Pascal Engélibert committed
            client
                .tx()
                .sign_and_submit_then_watch(
Pascal Engélibert's avatar
Pascal Engélibert committed
                    &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.");

Pascal Engélibert's avatar
Pascal Engélibert committed
            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.");

Pascal Engélibert's avatar
Pascal Engélibert committed
            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 } => {
Pascal Engélibert's avatar
Pascal Engélibert committed
            let parent_hash = client
                .storage()
Pascal Engélibert's avatar
Pascal Engélibert committed
                .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(
Pascal Engélibert's avatar
Pascal Engélibert committed
                &client,
                if args.no_indexer {
                    None
                } else {
                    Some((&gql_client, &args.indexer))
                },
            );

            // Rotate keys
            let mut must_rotate_keys_before_iter = client
                .storage()
Pascal Engélibert's avatar
Pascal Engélibert committed
                .iter(
                    gdev_300::storage()
                        .authority_members()
                        .must_rotate_keys_before(0),
                    10,
                    Some(parent_hash),
                )
                .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()
Pascal Engélibert's avatar
Pascal Engélibert committed
                .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()
Pascal Engélibert's avatar
Pascal Engélibert committed
                .iter(
                    gdev_300::storage()
                        .smiths_cert()
                        .storage_certs_removable_on(0),
                    10,
                    Some(parent_hash),
                )
                .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()
Pascal Engélibert's avatar
Pascal Engélibert committed
                .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()
Pascal Engélibert's avatar
Pascal Engélibert committed
                .iter(
                    gdev_300::storage()
                        .smiths_membership()
                        .memberships_expire_on(0),
                    10,
                    Some(parent_hash),
                )
                .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,
                        );
                    }
                }
            }
        }
        Subcommand::GenRevocDoc => {
Pascal Engélibert's avatar
Pascal Engélibert committed
            gen_revoc_doc(&client, &pair.expect("This subcommand needs a secret.")).await?
        Subcommand::OneshotBalance { account } => {
            logs::info!(
                "{}",
Pascal Engélibert's avatar
Pascal Engélibert committed
                client
                    .storage()
                    .fetch(
                        &gdev_300::storage()
                            .oneshot_account()
                            .oneshot_accounts(&account),
                        None
                    )
                    .await?
                    .unwrap_or(0)
            );
        }
Éloïs's avatar
Éloïs committed
        Subcommand::Repart {
            target,
            actual_repart,
        } => {
            let pair = pair.expect("This subcommand needs a secret.");

Éloïs's avatar
Éloïs committed
            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())
Éloïs's avatar
Éloïs committed
                    .await?
                    .wait_for_in_block()
                    .await?;
                signer.increment_nonce();*/

Pascal Engélibert's avatar
Pascal Engélibert committed
                if let Some(pair_i_account) = client
Éloïs's avatar
Éloïs committed
                    .storage()
Pascal Engélibert's avatar
Pascal Engélibert committed
                    .fetch(
                        &gdev_300::storage()
                            .system()
                            .account(&pair_i.public().into()),
                        None,
                    )
                    .await?
                {
                    logs::info!("account //{} balance: {}", i, pair_i_account.data.free);
                }
Éloïs's avatar
Éloïs committed
            }
        }
        Subcommand::SpamRoll { actual_repart } => {
            let pair = pair.expect("This subcommand needs a secret.");

            let mut pairs =
Pascal Engélibert's avatar
Pascal Engélibert committed
                Vec::<(PairSigner<GdevConfig, Pair>, AccountId32)>::with_capacity(actual_repart);
Éloïs's avatar
Éloïs committed
            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();
Pascal Engélibert's avatar
Pascal Engélibert committed
                    let watcher = client
Éloïs's avatar
Éloïs committed
                        .tx()
Pascal Engélibert's avatar
Pascal Engélibert committed
                        .sign_and_submit_then_watch(
                            &gdev_300::tx()
                                .balances()
                                .transfer(MultiAddress::Id(dest), 1),
                            &pairs[i].0,
                            BaseExtrinsicParamsBuilder::new(),
                        )
Éloïs's avatar
Éloïs committed
                        .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();
Pascal Engélibert's avatar
Pascal Engélibert committed
                let watcher = client
Éloïs's avatar
Éloïs committed
                    .tx()
                    .sign_and_submit_then_watch(
Pascal Engélibert's avatar
Pascal Engélibert committed
                        &gdev_300::tx()
                            .balances()
                            .transfer(MultiAddress::Id(dest), 1),
                        &pairs[actual_repart - 1].0,
                        BaseExtrinsicParamsBuilder::new(),
                    )
Éloïs's avatar
Éloïs committed
                    .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?;
                }
            }
        }
Pascal Engélibert's avatar
Pascal Engélibert committed
        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.");

            if keep_alive {
Pascal Engélibert's avatar
Pascal Engélibert committed
                client
                    .tx()
                    .sign_and_submit_then_watch(
Pascal Engélibert's avatar
Pascal Engélibert committed
                        &gdev_300::tx().balances().transfer(dest.into(), balance),
                        &PairSigner::new(pair),
                        BaseExtrinsicParamsBuilder::new(),
                    )
                    .await?;
            } else {
Pascal Engélibert's avatar
Pascal Engélibert committed
                client
                    .tx()
                    .sign_and_submit_then_watch(
Pascal Engélibert's avatar
Pascal Engélibert committed
                        &gdev_300::tx()
                            .balances()
                            .transfer_keep_alive(dest.into(), balance),
                        &PairSigner::new(pair),
                        BaseExtrinsicParamsBuilder::new(),
                    )
                    .await?;
            }
        }
Éloïs's avatar
Éloïs committed
    }

    Ok(())
}

Pascal Engélibert's avatar
Pascal Engélibert committed
async fn gen_revoc_doc(api: &Client, pair: &Pair) -> Result<()> {
Éloïs's avatar
Éloïs committed
    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();
Pascal Engélibert's avatar
Pascal Engélibert committed
    let payload = (b"revo", genesis_hash, idty_index).encode();
Éloïs's avatar
Éloïs committed
    let signature = pair.sign(&payload);

Pascal Engélibert's avatar
Pascal Engélibert committed
    println!("0x{}", hex::encode(signature));
Éloïs's avatar
Éloïs committed

    Ok(())
}