Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • 1000i100-test
  • 105_gitlab_container_registry
  • cgeek/issue-297-cpu
  • ci_cache
  • debug/podman
  • elois-compose-metrics
  • elois-duniter-storage
  • elois-smoldot
  • feature/dc-dump
  • feature/distance-rule
  • feature/show_milestone
  • fix-252
  • gdev-800-tests
  • hugo-release/runtime-701
  • hugo-tmp-dockerfile-cache
  • hugo/195-doc
  • hugo/195-graphql-schema
  • hugo/distance-precompute
  • hugo/endpoint-gossip
  • hugo/tmp-0.9.1
  • master
  • network/gdev-800
  • network/gdev-802
  • network/gdev-803
  • network/gdev-900
  • pini-check-password
  • release/client-800.2
  • release/hugo-chainspec-gdev5
  • release/poka-chainspec-gdev5
  • release/poka-chainspec-gdev5-pini-docker
  • release/runtime-100
  • release/runtime-200
  • release/runtime-300
  • release/runtime-400
  • release/runtime-401
  • release/runtime-500
  • release/runtime-600
  • release/runtime-700
  • release/runtime-701
  • release/runtime-800
  • tests/distance-with-oracle
  • tuxmain/anonymous-tx
  • tuxmain/benchmark-distance
  • update-docker-compose-rpc-squid-names
  • gdev-800
  • gdev-800-0.8.0
  • gdev-802
  • gdev-803
  • gdev-900-0.10.0
  • gdev-900-0.10.1
  • gdev-900-0.9.0
  • gdev-900-0.9.1
  • gdev-900-0.9.2
  • runtime-100
  • runtime-101
  • runtime-102
  • runtime-103
  • runtime-104
  • runtime-105
  • runtime-200
  • runtime-201
  • runtime-300
  • runtime-301
  • runtime-302
  • runtime-303
  • runtime-400
  • runtime-401
  • runtime-500
  • runtime-600
  • runtime-700
  • runtime-701
  • runtime-800
  • runtime-800-backup
  • runtime-800-bis
  • runtime-801
  • v0.1.0
  • v0.2.0
  • v0.3.0
  • v0.4.0
  • v0.4.1
80 results

Target

Select target project
  • nodes/rust/duniter-v2s
  • llaq/lc-core-substrate
  • pini-gh/duniter-v2s
  • vincentux/duniter-v2s
  • mildred/duniter-v2s
  • d0p1/duniter-v2s
  • bgallois/duniter-v2s
  • Nicolas80/duniter-v2s
8 results
Select Git revision
  • archive_upgrade_polkadot_v0.9.42
  • david-wot-scenarios-cucumber
  • distance
  • elois-ci-binary-release
  • elois-compose-metrics
  • elois-duniter-storage
  • elois-fix-85
  • elois-fix-idty-post-genesis
  • elois-fix-sufficients-change-owner-key
  • elois-opti-cert
  • elois-remove-renewable-period
  • elois-revoc-with-old-key
  • elois-rework-certs
  • elois-smish-members-cant-change-or-rem-idty
  • elois-smoldot
  • elois-substrate-v0.9.23
  • elois-technical-commitee
  • hugo-gtest
  • hugo-remove-duniter-account
  • hugo-rework-genesis
  • hugo-tmp
  • jrx/workspace_tomls
  • master
  • no-bootnodes
  • pallet-benchmark
  • release/poka-chainspec-gdev5
  • release/poka-chainspec-gdev5-pini-docker
  • release/runtime-100
  • release/runtime-200
  • release/runtime-300
  • release/runtime-400
  • test-gen-new-owner-key-msg
  • ts-types
  • ud-time-64
  • upgrade_polkadot_v0.9.42
  • runtime-100
  • runtime-101
  • runtime-102
  • runtime-103
  • runtime-104
  • runtime-105
  • runtime-200
  • runtime-201
  • runtime-300
  • runtime-301
  • runtime-302
  • runtime-303
  • runtime-400
  • v0.1.0
  • v0.2.0
  • v0.3.0
  • v0.4.0
52 results
Show changes
Showing
with 421886 additions and 96712 deletions
[package]
authors.workspace = true
build = "build.rs"
description = "Crypto-currency software (based on Substrate framework) to operate Ğ1 libre currency"
edition.workspace = true
homepage.workspace = true
license.workspace = true
name = "duniter"
repository.workspace = true
version = "0.8.0"
default-run = "duniter"
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[[bin]]
bench = false
name = "duniter"
path = "src/main.rs"
[features]
default = ["distance-oracle", "gdev"]
gdev = ["gdev-runtime", "std"]
g1 = ["g1-runtime", "std"]
constant-fees = [
"common-runtime/constant-fees",
"g1-runtime/constant-fees",
"gdev-runtime/constant-fees",
"gtest-runtime/constant-fees",
]
gtest = ["gtest-runtime", "std"]
embed = []
native = []
runtime-benchmarks = [
"common-runtime/runtime-benchmarks",
"dc-distance?/runtime-benchmarks",
"frame-benchmarking-cli/runtime-benchmarks",
"frame-benchmarking/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"g1-runtime/runtime-benchmarks",
"gdev-runtime/runtime-benchmarks",
"gtest-runtime/runtime-benchmarks",
"pallet-grandpa/runtime-benchmarks",
"pallet-oneshot-account/runtime-benchmarks",
"pallet-im-online/runtime-benchmarks",
"pallet-treasury/runtime-benchmarks",
"pallet-transaction-payment/runtime-benchmarks",
"sc-client-db/runtime-benchmarks",
"sc-service/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
]
try-runtime = [
"runtime-benchmarks",
"common-runtime/try-runtime",
"dc-distance?/try-runtime",
"distance-oracle?/try-runtime",
"frame-system/try-runtime",
"g1-runtime/try-runtime",
"gdev-runtime/try-runtime",
"gtest-runtime/try-runtime",
"pallet-grandpa/try-runtime",
"pallet-oneshot-account/try-runtime",
"pallet-im-online/try-runtime",
"pallet-transaction-payment/try-runtime",
"pallet-treasury/try-runtime",
"sp-distance/try-runtime",
"sp-membership/try-runtime",
"sp-runtime/try-runtime",
]
std = [
"bs58/std",
"common-runtime/std",
"dc-distance/std",
"distance-oracle?/std",
"frame-benchmarking/std",
"frame-system/std",
"futures/std",
"g1-runtime/std",
"gdev-runtime/std",
"gtest-runtime/std",
"hex/std",
"log/std",
"num-format/std",
"pallet-grandpa/std",
"pallet-oneshot-account/std",
"pallet-im-online/std",
"pallet-transaction-payment-rpc-runtime-api/std",
"pallet-transaction-payment/std",
"pallet-treasury/std",
"sc-executor/std",
"serde/std",
"serde_json/std",
"sp-api/std",
"sp-authority-discovery/std",
"sp-block-builder/std",
"sp-consensus-babe/std",
"sp-consensus-grandpa/std",
"sp-core/std",
"sp-distance/std",
"sp-inherents/std",
"sp-io/std",
"sp-keystore/std",
"sp-membership/std",
"sp-offchain/std",
"sp-runtime/std",
"sp-session/std",
"sp-storage/std",
"sp-timestamp/std",
"sp-transaction-pool/std",
"sp-transaction-storage-proof/std",
"sp-trie/std",
]
distance-oracle = ["dep:distance-oracle"]
[dependencies]
async-io = { workspace = true }
bs58 = { workspace = true }
clap = { workspace = true, features = ["derive"] }
clap_complete = { workspace = true }
frame-benchmarking = { workspace = true }
frame-benchmarking-cli = { workspace = true }
frame-system = { workspace = true }
frame-metadata-hash-extension = { workspace = true, default-features = true }
futures = { workspace = true, features = ["compat"] }
hex = { workspace = true }
jsonrpsee = { workspace = true, features = ["server"] }
log = { workspace = true }
memmap2 = { workspace = true }
num-format = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
serde_yaml = { workspace = true }
codec = { workspace = true }
array-bytes = { workspace = true }
parking_lot = { workspace = true }
tokio = { workspace = true, features = ["rt-multi-thread"] }
# Local
common-runtime = { workspace = true }
g1-runtime = { workspace = true, optional = true }
gdev-runtime = { workspace = true, optional = true }
gtest-runtime = { workspace = true, optional = true }
distance-oracle = { workspace = true, optional = true }
dc-distance = { workspace = true, optional = true }
pallet-oneshot-account = { workspace = true, optional = true }
# Substrate
pallet-grandpa = { workspace = true, default-features = true }
pallet-im-online = { workspace = true, default-features = true }
pallet-transaction-payment = { workspace = true, default-features = true }
pallet-transaction-payment-rpc = { workspace = true, default-features = true }
pallet-transaction-payment-rpc-runtime-api = { workspace = true, default-features = true }
pallet-treasury = { workspace = true, default-features = true }
sc-basic-authorship = { workspace = true, default-features = true }
sc-chain-spec = { workspace = true, default-features = true }
sc-client-api = { workspace = true, default-features = true }
sc-client-db = { workspace = true, default-features = true }
sc-consensus = { workspace = true, default-features = true }
sc-rpc = { workspace = true, default-features = true }
sc-consensus-babe = { workspace = true, default-features = true }
sc-consensus-babe-rpc = { workspace = true, default-features = true }
sc-consensus-grandpa = { workspace = true, default-features = true }
sc-consensus-grandpa-rpc = { workspace = true, default-features = true }
sc-consensus-manual-seal = { workspace = true, default-features = true }
sc-executor = { workspace = true, default-features = true }
sc-keystore = { workspace = true, default-features = true }
sc-network = { workspace = true, default-features = true }
sc-network-sync = { workspace = true, default-features = true }
sc-offchain = { workspace = true, default-features = true }
sc-rpc-api = { workspace = true, default-features = true }
sc-telemetry = { workspace = true, default-features = true }
sc-transaction-pool = { workspace = true, default-features = true }
sc-transaction-pool-api = { workspace = true, default-features = true }
sc-utils = { workspace = true, default-features = true }
sp-api = { workspace = true, default-features = true }
sp-authority-discovery = { workspace = true, default-features = true }
sp-block-builder = { workspace = true, default-features = true }
sp-blockchain = { workspace = true, default-features = true }
sp-consensus = { workspace = true, default-features = true }
sp-consensus-babe = { workspace = true, default-features = true }
sp-consensus-grandpa = { workspace = true, default-features = true }
sp-core = { workspace = true, default-features = true }
sp-distance = { workspace = true, default-features = true }
sp-inherents = { workspace = true, default-features = true }
sp-io = { workspace = true, default-features = true }
sp-keyring = { workspace = true, default-features = true }
sp-keystore = { workspace = true, default-features = true }
sp-membership = { workspace = true, default-features = true }
sp-offchain = { workspace = true, default-features = true }
sp-runtime = { workspace = true, default-features = true }
sp-session = { workspace = true, default-features = true }
sp-storage = { workspace = true, default-features = true }
sp-timestamp = { workspace = true, default-features = true }
sp-transaction-pool = { workspace = true, default-features = true }
sp-transaction-storage-proof = { workspace = true, default-features = true }
substrate-frame-rpc-system = { workspace = true, default-features = true }
[dev-dependencies]
sc-network-test = { workspace = true, default-features = true }
async-trait = { version = "0.1.79" }
env_logger = "0.10.2"
async-channel = "2.3.1"
[build-dependencies]
substrate-build-script-utils = { workspace = true, default-features = true }
# Dependencies for specific targets
[target.'cfg(any(target_arch="x86_64", target_arch="aarch64"))'.dependencies]
sc-cli = { workspace = true, default-features = true }
sc-service = { workspace = true, default-features = true }
sp-trie = { workspace = true, default-features = true }
[package.metadata.deb]
maintainer-scripts = "../resources/debian"
systemd-units = [
{ unit-name = "duniter-mirror", enable = false },
{ unit-name = "duniter-smith", enable = false },
{ unit-name = "distance-oracle", enable = false },
]
assets = [
[
"../resources/debian/env_file",
"/etc/duniter/env_file",
"0640",
],
[
"../target/release/duniter",
"/usr/bin/duniter2",
"755",
],
]
[package.metadata.generate-rpm]
assets = [
{ source = "../target/release/duniter", dest = "/usr/bin/duniter2", mode = "755" },
{ source = "../resources/debian/duniter.sysusers", dest = "/usr/lib/sysusers.d/duniter.conf", mode = "0644" },
{ source = "../resources/debian/env_file", dest = "/etc/duniter/env_file", config = true, mode = "0640" },
{ source = "../LICENSE", dest = "/usr/share/licenses/duniter/LICENSE" },
{ source = "../resources/debian/duniter-mirror.service", dest = "/usr/lib/systemd/system/duniter-mirror.service", mode = "0644" },
{ source = "../resources/debian/duniter-smith.service", dest = "/usr/lib/systemd/system/duniter-smith.service", mode = "0644" },
{ source = "../resources/debian/duniter-smith.service", dest = "/usr/lib/systemd/system/distance-oracle.service", mode = "0644" },
]
\ No newline at end of file
# Duniter Node
You can find the autogenerated documentation at: [https://doc-duniter-org.ipns.pagu.re/duniter/index.html](https://doc-duniter-org.ipns.pagu.re/duniter/index.html).
Source diff could not be displayed: it is too large. Options to address this: view the blob.
name: "ĞDev"
id: "gdev"
chainType: "Live"
bootNodes:
- "/dns/gdev.cgeek.fr/tcp/30334/p2p/12D3KooWN7QhcPbTZgNMnS7AUZh3ZfnM43VdVKqy4JbAEp5AJh4f"
- "/dns/gdev.coinduf.eu/tcp/30333/p2p/12D3KooWFseA3B66eBzj4NY5ng3Lb2U3VPnKCi3iXYGYUSAahEw7"
telemetryEndpoints:
- ["/dns/telemetry.polkadot.io/tcp/443/x-parity-wss/%2Fsubmit%2F", 0]
properties:
tokenDecimals: 2
tokenSymbol: "ĞD"
Source diff could not be displayed: it is too large. Options to address this: view the blob.
name: "ĞTest"
id: "gtest"
chainType: "Live"
bootNodes: []
telemetryEndpoints:
- ["/dns/telemetry.polkadot.io/tcp/443/x-parity-wss/%2Fsubmit%2F", 0]
properties:
tokenDecimals: 2
tokenSymbol: "ĞT"
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU Affero General Public License
// along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>. // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
// Common to all Duniter blockchains
pub mod gen_genesis_data; pub mod gen_genesis_data;
#[cfg(feature = "g1")] #[cfg(feature = "g1")]
...@@ -23,15 +24,12 @@ pub mod gdev; ...@@ -23,15 +24,12 @@ pub mod gdev;
#[cfg(feature = "gtest")] #[cfg(feature = "gtest")]
pub mod gtest; pub mod gtest;
use common_runtime::{AccountId, IdtyIndex, Signature}; use common_runtime::{AccountId, Signature};
use sp_core::{Pair, Public}; use sp_core::{Pair, Public};
use sp_runtime::traits::{IdentifyAccount, Verify}; use sp_runtime::traits::{IdentifyAccount, Verify};
use std::collections::BTreeMap;
pub type AccountPublic = <Signature as Verify>::Signer; pub type AccountPublic = <Signature as Verify>::Signer;
pub const NAMES: [&str; 6] = ["Alice", "Bob", "Charlie", "Dave", "Eve", "Ferdie"];
/// Generate a crypto pair from seed. /// Generate a crypto pair from seed.
pub fn get_from_seed<TPublic: Public>(seed: &str) -> <TPublic::Pair as Pair>::Public { pub fn get_from_seed<TPublic: Public>(seed: &str) -> <TPublic::Pair as Pair>::Public {
TPublic::Pair::from_string(&format!("//{}", seed), None) TPublic::Pair::from_string(&format!("//{}", seed), None)
...@@ -39,14 +37,6 @@ pub fn get_from_seed<TPublic: Public>(seed: &str) -> <TPublic::Pair as Pair>::Pu ...@@ -39,14 +37,6 @@ pub fn get_from_seed<TPublic: Public>(seed: &str) -> <TPublic::Pair as Pair>::Pu
.public() .public()
} }
/*/// Generate an account ID from pair.
pub fn get_account_id_from_pair<TPublic: Public>(pair: TPublic::Pair) -> AccountId
where
AccountPublic: From<<TPublic::Pair as Pair>::Public>,
{
AccountPublic::from(pair.public()).into_account()
}*/
/// Generate an account ID from seed. /// Generate an account ID from seed.
pub fn get_account_id_from_seed<TPublic: Public>(seed: &str) -> AccountId pub fn get_account_id_from_seed<TPublic: Public>(seed: &str) -> AccountId
where where
...@@ -54,24 +44,3 @@ where ...@@ -54,24 +44,3 @@ where
{ {
AccountPublic::from(get_from_seed::<TPublic>(seed)).into_account() AccountPublic::from(get_from_seed::<TPublic>(seed)).into_account()
} }
fn clique_wot(
initial_identities_len: usize,
) -> BTreeMap<IdtyIndex, BTreeMap<IdtyIndex, Option<common_runtime::BlockNumber>>> {
let mut certs_by_issuer = BTreeMap::new();
for i in 1..=initial_identities_len {
certs_by_issuer.insert(
i as IdtyIndex,
(1..=initial_identities_len)
.filter_map(|j| {
if i != j {
Some((j as IdtyIndex, None))
} else {
None
}
})
.collect(),
);
}
certs_by_issuer
}
...@@ -14,8 +14,231 @@ ...@@ -14,8 +14,231 @@
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU Affero General Public License
// along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>. // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
pub type ChainSpec = sc_service::GenericChainSpec<g1_runtime::GenesisConfig>; use super::*;
use crate::chain_spec::gen_genesis_data::{
AuthorityKeys, CommonParameters, GenesisIdentity, SessionKeysProvider,
};
use common_runtime::{constants::*, entities::IdtyData, GenesisIdty};
use g1_runtime::{
opaque::SessionKeys, pallet_universal_dividend, parameters, Runtime, RuntimeGenesisConfig,
WASM_BINARY,
};
use sc_service::ChainType;
use serde::Deserialize;
use sp_core::{sr25519, Get};
use std::{env, fs};
pub fn development_chain_spec() -> Result<ChainSpec, String> { pub type ChainSpec = sc_service::GenericChainSpec;
todo!()
#[derive(Default, Clone, Deserialize)]
// No parameters for G1 (unlike GDev)
struct GenesisParameters {}
const TOKEN_DECIMALS: usize = 2;
const TOKEN_SYMBOL: &str = "Ğ";
static EXISTENTIAL_DEPOSIT: u64 = parameters::ExistentialDeposit::get();
struct G1SKP;
impl SessionKeysProvider<SessionKeys> for G1SKP {
fn session_keys(keys: &AuthorityKeys) -> SessionKeys {
let cloned = keys.clone();
SessionKeys {
grandpa: cloned.1,
babe: cloned.2,
im_online: cloned.3,
authority_discovery: cloned.4,
}
}
}
fn get_parameters(_parameters_from_file: &Option<GenesisParameters>) -> CommonParameters {
CommonParameters {
currency_name: TOKEN_SYMBOL.to_string(),
decimals: TOKEN_DECIMALS,
babe_epoch_duration: parameters::EpochDuration::get(),
babe_expected_block_time: parameters::ExpectedBlockTime::get(),
babe_max_authorities: parameters::MaxAuthorities::get(),
timestamp_minimum_period: parameters::MinimumPeriod::get(),
balances_existential_deposit: parameters::ExistentialDeposit::get(),
authority_members_max_authorities: parameters::MaxAuthorities::get(),
grandpa_max_authorities: parameters::MaxAuthorities::get(),
universal_dividend_max_past_reevals:
<Runtime as pallet_universal_dividend::Config>::MaxPastReeval::get(),
universal_dividend_square_money_growth_rate: parameters::SquareMoneyGrowthRate::get(),
universal_dividend_ud_creation_period: parameters::UdCreationPeriod::get() as u64,
universal_dividend_ud_reeval_period: parameters::UdReevalPeriod::get() as u64,
wot_first_issuable_on: parameters::WotFirstCertIssuableOn::get(),
wot_min_cert_for_membership: parameters::WotMinCertForMembership::get(),
wot_min_cert_for_create_idty_right: parameters::WotMinCertForCreateIdtyRight::get(),
identity_confirm_period: parameters::ConfirmPeriod::get(),
identity_change_owner_key_period: parameters::ChangeOwnerKeyPeriod::get(),
identity_idty_creation_period: parameters::IdtyCreationPeriod::get(),
identity_autorevocation_period: parameters::AutorevocationPeriod::get(),
identity_deletion_period: parameters::DeletionPeriod::get(),
membership_membership_period: parameters::MembershipPeriod::get(),
membership_membership_renewal_period: parameters::MembershipRenewalPeriod::get(),
cert_max_by_issuer: parameters::MaxByIssuer::get(),
cert_min_received_cert_to_be_able_to_issue_cert:
parameters::MinReceivedCertToBeAbleToIssueCert::get(),
cert_validity_period: parameters::ValidityPeriod::get(),
distance_min_accessible_referees: parameters::MinAccessibleReferees::get(),
distance_max_depth: parameters::MaxRefereeDistance::get(),
smith_sub_wot_min_cert_for_membership: parameters::SmithWotMinCertForMembership::get(),
smith_inactivity_max_duration: parameters::SmithInactivityMaxDuration::get(),
smith_cert_max_by_issuer: parameters::SmithMaxByIssuer::get(),
cert_cert_period: parameters::CertPeriod::get(),
treasury_spend_period: <Runtime as pallet_treasury::Config>::SpendPeriod::get(),
}
}
/// generate local network chainspects
pub fn local_testnet_config(
initial_authorities_len: usize,
initial_smiths_len: usize,
initial_identities_len: usize,
) -> Result<ChainSpec, String> {
Ok(ChainSpec::builder(
&get_wasm_binary().ok_or_else(|| "Development wasm not available".to_string())?,
None,
)
.with_name("Ğ1 Local Testnet")
.with_id("g1_local")
.with_chain_type(ChainType::Local)
.with_genesis_config_patch({
let genesis_data =
gen_genesis_data::generate_genesis_data_for_local_chain::<_, _, SessionKeys, G1SKP>(
// Initial authorities len
initial_authorities_len,
// Initial smiths len,
initial_smiths_len,
// Initial identities len
initial_identities_len,
EXISTENTIAL_DEPOSIT,
None,
// Sudo account
get_account_id_from_seed::<sr25519::Public>("Alice"),
get_parameters,
)
.expect("Genesis Data must be buildable");
genesis_data_to_g1_genesis_conf(genesis_data)
})
.with_properties(
serde_json::json!({
"tokenDecimals": TOKEN_DECIMALS,
"tokenSymbol": TOKEN_SYMBOL,
})
.as_object()
.expect("must be a map")
.clone(),
)
.build())
}
/// custom genesis
fn genesis_data_to_g1_genesis_conf(
genesis_data: super::gen_genesis_data::GenesisData<GenesisParameters, SessionKeys>,
) -> serde_json::Value {
let super::gen_genesis_data::GenesisData {
accounts,
treasury_balance,
certs_by_receiver,
first_ud,
first_ud_reeval,
identities,
initial_authorities,
initial_monetary_mass,
memberships,
parameters: _,
common_parameters: _,
session_keys_map,
initial_smiths,
sudo_key,
technical_committee_members,
ud,
} = genesis_data;
serde_json::json!({
"account": {
"accounts": accounts,
"treasuryBalance": treasury_balance,
},
"authorityMembers": {
"initialAuthorities": initial_authorities,
},
"balances": {
"totalIssuance": initial_monetary_mass,
},
"babe": {
"epochConfig": Some(BABE_GENESIS_EPOCH_CONFIG),
},
"session": {
"keys": session_keys_map
.into_iter()
.map(|(account_id, session_keys)| (account_id.clone(), account_id, session_keys))
.collect::<Vec<_>>(),
},
"sudo": { "key": sudo_key },
"technicalCommittee": {
"members": technical_committee_members,
},
"quota": {
"identities": identities.iter().map(|i| i.idty_index).collect::<Vec<_>>(),
},
"identity": {
"identities": identities
.into_iter()
.map(
|GenesisIdentity {
idty_index,
name,
owner_key,
status,
next_scheduled,
}| GenesisIdty {
index: idty_index,
name: common_runtime::IdtyName::from(name.as_str()),
value: common_runtime::IdtyValue {
data: IdtyData {
first_eligible_ud: match status {
// Only members are eligible to UD.
// The first claimable UD has the minimum index (1).
common_runtime::IdtyStatus::Member => g1_runtime::pallet_universal_dividend::FirstEligibleUd::min(),
_ => g1_runtime::pallet_universal_dividend::FirstEligibleUd(None),
}
},
next_creatable_identity_on: 0,
old_owner_key: None,
owner_key,
next_scheduled,
status,
},
},
)
.collect::<Vec<GenesisIdty<g1_runtime::Runtime>>>(),
},
"certification": {
"applyCertPeriodAtGenesis": false,
"certsByReceiver": certs_by_receiver,
},
"membership": { "memberships": memberships },
"smithMembers": { "initialSmiths": initial_smiths},
"universalDividend": {
"firstReeval": first_ud_reeval,
"firstUd": first_ud,
"initialMonetaryMass": initial_monetary_mass,
"ud": ud,
},
})
}
/// Get the WASM bytes either from filesytem (`WASM_FILE` env variable giving the path to the wasm blob)
/// or else get the one compiled from source code.
/// Goal: allow to provide the WASM built with srtool, which is reproductible.
fn get_wasm_binary() -> Option<Vec<u8>> {
let wasm_bytes_from_file = if let Ok(file_path) = env::var("WASM_FILE") {
Some(fs::read(file_path).unwrap_or_else(|e| panic!("Could not read wasm file: {}", e)))
} else {
None
};
wasm_bytes_from_file.or_else(|| WASM_BINARY.map(|bytes| bytes.to_vec()))
} }
...@@ -15,119 +15,107 @@ ...@@ -15,119 +15,107 @@
// along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>. // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
use super::*; use super::*;
use common_runtime::constants::*; use crate::chain_spec::gen_genesis_data::{
use common_runtime::entities::IdtyData; AuthorityKeys, CommonParameters, GenesisIdentity, SessionKeysProvider,
use common_runtime::*; };
use common_runtime::{constants::*, entities::IdtyData, GenesisIdty};
use gdev_runtime::{ use gdev_runtime::{
opaque::SessionKeys, AccountConfig, AccountId, AuthorityMembersConfig, BabeConfig, opaque::SessionKeys, pallet_universal_dividend, parameters, Runtime, WASM_BINARY,
BalancesConfig, CertConfig, GenesisConfig, IdentityConfig, ImOnlineId, MembershipConfig,
ParametersConfig, SessionConfig, SmithsCertConfig, SmithsMembershipConfig, SudoConfig,
SystemConfig, TechnicalCommitteeConfig, UniversalDividendConfig, WASM_BINARY,
}; };
use jsonrpsee::core::JsonValue;
use sc_network::config::MultiaddrWithPeerId;
use sc_service::ChainType; use sc_service::ChainType;
use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId; use sc_telemetry::TelemetryEndpoints;
use sp_consensus_babe::AuthorityId as BabeId; use serde::Deserialize;
use sp_core::{blake2_256, sr25519, Encode, H256}; use sp_core::{sr25519, Get};
use sp_finality_grandpa::AuthorityId as GrandpaId; use std::{env, fs};
use sp_membership::MembershipData;
use std::collections::BTreeMap;
pub type AuthorityKeys = (
AccountId,
BabeId,
GrandpaId,
ImOnlineId,
AuthorityDiscoveryId,
);
pub type ChainSpec = sc_service::GenericChainSpec<GenesisConfig>; pub type ChainSpec = sc_service::GenericChainSpec;
type GenesisParameters = gdev_runtime::GenesisParameters<u32, u32, u64>; type GenesisParameters = gdev_runtime::GenesisParameters<u32, u32, u64, u32>;
const TOKEN_DECIMALS: usize = 2; const TOKEN_DECIMALS: usize = 2;
const TOKEN_SYMBOL: &str = "ĞD"; const TOKEN_SYMBOL: &str = "ĞD";
static EXISTENTIAL_DEPOSIT: u64 = parameters::ExistentialDeposit::get();
// The URL for the telemetry server. // The URL for the telemetry server.
// const STAGING_TELEMETRY_URL: &str = "wss://telemetry.polkadot.io/submit/"; // const STAGING_TELEMETRY_URL: &str = "wss://telemetry.polkadot.io/submit/";
/// Generate an authority keys. struct GDevSKP;
pub fn get_authority_keys_from_seed(s: &str) -> AuthorityKeys { impl SessionKeysProvider<SessionKeys> for GDevSKP {
( fn session_keys(keys: &AuthorityKeys) -> SessionKeys {
get_account_id_from_seed::<sr25519::Public>(s), let cloned = keys.clone();
get_from_seed::<BabeId>(s), SessionKeys {
get_from_seed::<GrandpaId>(s), grandpa: cloned.1,
get_from_seed::<ImOnlineId>(s), babe: cloned.2,
get_from_seed::<AuthorityDiscoveryId>(s), im_online: cloned.3,
) authority_discovery: cloned.4,
}
}
} }
pub fn development_chain_spec() -> Result<ChainSpec, String> { fn get_parameters(parameters_from_file: &Option<GenesisParameters>) -> CommonParameters {
let wasm_binary = WASM_BINARY.ok_or_else(|| "Development wasm not available".to_string())?; let parameters_from_file = parameters_from_file
.clone()
.expect("parameters must be defined in file for GDev");
CommonParameters {
currency_name: TOKEN_SYMBOL.to_string(),
decimals: TOKEN_DECIMALS,
babe_epoch_duration: parameters_from_file.babe_epoch_duration,
babe_expected_block_time: parameters::ExpectedBlockTime::get(),
babe_max_authorities: parameters::MaxAuthorities::get(),
timestamp_minimum_period: parameters::MinimumPeriod::get(),
balances_existential_deposit: EXISTENTIAL_DEPOSIT,
authority_members_max_authorities: parameters::MaxAuthorities::get(),
grandpa_max_authorities: parameters::MaxAuthorities::get(),
universal_dividend_max_past_reevals:
<Runtime as pallet_universal_dividend::Config>::MaxPastReeval::get(),
universal_dividend_square_money_growth_rate: parameters::SquareMoneyGrowthRate::get(),
universal_dividend_ud_creation_period: parameters_from_file.ud_creation_period,
universal_dividend_ud_reeval_period: parameters_from_file.ud_reeval_period,
wot_first_issuable_on: parameters_from_file.wot_first_cert_issuable_on,
wot_min_cert_for_membership: parameters_from_file.wot_min_cert_for_membership,
wot_min_cert_for_create_idty_right: parameters_from_file.wot_min_cert_for_create_idty_right,
identity_confirm_period: parameters_from_file.idty_confirm_period,
identity_change_owner_key_period: parameters::ChangeOwnerKeyPeriod::get(),
identity_idty_creation_period: parameters_from_file.idty_creation_period,
identity_autorevocation_period: parameters::AutorevocationPeriod::get(),
identity_deletion_period: parameters::DeletionPeriod::get(),
membership_membership_period: parameters_from_file.membership_period,
membership_membership_renewal_period: parameters_from_file.membership_renewal_period,
cert_max_by_issuer: parameters_from_file.cert_max_by_issuer,
cert_min_received_cert_to_be_able_to_issue_cert: parameters_from_file
.cert_min_received_cert_to_issue_cert,
cert_validity_period: parameters_from_file.cert_validity_period,
distance_min_accessible_referees: parameters::MinAccessibleReferees::get(),
distance_max_depth: parameters::MaxRefereeDistance::get(),
smith_sub_wot_min_cert_for_membership: parameters_from_file
.smith_wot_min_cert_for_membership,
smith_cert_max_by_issuer: parameters_from_file.smith_cert_max_by_issuer,
smith_inactivity_max_duration: parameters_from_file.smith_inactivity_max_duration,
cert_cert_period: parameters_from_file.cert_period,
treasury_spend_period: <Runtime as pallet_treasury::Config>::SpendPeriod::get(),
}
}
if std::env::var("DUNITER_GENESIS_CONFIG").is_ok() { /// generate development chainspec with Alice validator
super::gen_genesis_data::generate_genesis_data( pub fn gdev_development_chain_spec(config_file_path: String) -> Result<ChainSpec, String> {
|genesis_data| { Ok(ChainSpec::builder(
ChainSpec::from_genesis( &get_wasm_binary().ok_or_else(|| "Development wasm not available".to_string())?,
// Name
"Development",
// ID
"gdev",
sc_service::ChainType::Development,
move || genesis_data_to_gdev_genesis_conf(genesis_data.clone(), wasm_binary),
// Bootnodes
vec![],
// Telemetry
None,
// Protocol ID
None,
//Fork ID
None, None,
// Properties
Some(
serde_json::json!({
"tokenDecimals": TOKEN_DECIMALS,
"tokenSymbol": TOKEN_SYMBOL,
})
.as_object()
.expect("must be a map")
.clone(),
),
// Extensions
None,
)
},
Some(get_authority_keys_from_seed("Alice").encode()),
) )
} else { .with_name("Development")
Ok(ChainSpec::from_genesis( .with_id("gdev")
// Name .with_chain_type(ChainType::Development)
"Development", .with_genesis_config_patch({
// ID let genesis_data = gen_genesis_data::generate_genesis_data::<_, _, SessionKeys, GDevSKP>(
"gdev", config_file_path.clone(),
ChainType::Development, get_parameters,
move || { Some("Alice".to_owned()),
gen_genesis_for_local_chain(
wasm_binary,
// Initial authorities len
1,
// Initial smiths members len
3,
// Inital identities len
4,
// Sudo account
get_account_id_from_seed::<sr25519::Public>("Alice"),
true,
) )
}, .expect("Genesis Data must be buildable");
// Bootnodes genesis_data_to_gdev_genesis_conf(genesis_data)
vec![], })
// Telemetry .with_properties(
None,
// Protocol ID
None,
//Fork ID
None,
// Properties
Some(
serde_json::json!({ serde_json::json!({
"tokenDecimals": TOKEN_DECIMALS, "tokenDecimals": TOKEN_DECIMALS,
"tokenSymbol": TOKEN_SYMBOL, "tokenSymbol": TOKEN_SYMBOL,
...@@ -135,94 +123,89 @@ pub fn development_chain_spec() -> Result<ChainSpec, String> { ...@@ -135,94 +123,89 @@ pub fn development_chain_spec() -> Result<ChainSpec, String> {
.as_object() .as_object()
.expect("must be a map") .expect("must be a map")
.clone(), .clone(),
), )
// Extensions .build())
None,
))
}
} }
pub fn gen_live_conf() -> Result<ChainSpec, String> { // === client specifications ===
let wasm_binary = WASM_BINARY.ok_or_else(|| "wasm not available".to_string())?;
super::gen_genesis_data::generate_genesis_data( /// emulate client specifications to get them from json
|genesis_data| { #[derive(Deserialize, Clone)]
ChainSpec::from_genesis( #[serde(rename_all = "camelCase")]
// Name #[serde(deny_unknown_fields)]
"Ğdev", pub struct ClientSpec {
// ID name: String,
"gdev", id: String,
sc_service::ChainType::Live, chain_type: ChainType,
move || genesis_data_to_gdev_genesis_conf(genesis_data.clone(), wasm_binary), boot_nodes: Vec<MultiaddrWithPeerId>,
// Bootnodes telemetry_endpoints: Option<TelemetryEndpoints>,
vec![], // protocol_id: Option<String>,
// Telemetry // #[serde(default = "Default::default", skip_serializing_if = "Option::is_none")]
Some( // fork_id: Option<String>,
sc_service::config::TelemetryEndpoints::new(vec![( properties: Option<serde_json::Map<std::string::String, JsonValue>>,
"wss://telemetry.polkadot.io/submit/".to_owned(), // #[serde(default)]
0, // code_substitutes: BTreeMap<String, Bytes>,
)]) }
.expect("invalid telemetry endpoints"),
), /// generate live network chainspecs
// Protocol ID pub fn gen_live_conf(
Some("gdev2"), client_spec: ClientSpec,
//Fork ID config_file_path: String,
None, ) -> Result<ChainSpec, String> {
// Properties Ok(ChainSpec::builder(
Some( &get_wasm_binary().ok_or_else(|| "Development wasm not available".to_string())?,
serde_json::json!({
"tokenDecimals": TOKEN_DECIMALS,
"tokenSymbol": TOKEN_SYMBOL,
})
.as_object()
.expect("must be a map")
.clone(),
),
// Extensions
None, None,
) )
}, .with_name(client_spec.name.as_str())
.with_id(client_spec.id.as_str())
.with_chain_type(client_spec.chain_type)
.with_genesis_config_patch({
let genesis_data = gen_genesis_data::generate_genesis_data::<_, _, SessionKeys, GDevSKP>(
config_file_path.clone(),
get_parameters,
None, None,
) )
.expect("Genesis Data must be buildable");
genesis_data_to_gdev_genesis_conf(genesis_data)
})
.with_telemetry_endpoints(client_spec.telemetry_endpoints.unwrap())
.with_properties(client_spec.properties.unwrap())
.with_boot_nodes(client_spec.boot_nodes)
.build())
} }
/// generate local network chainspects
pub fn local_testnet_config( pub fn local_testnet_config(
initial_authorities_len: usize, initial_authorities_len: usize,
initial_smiths_len: usize, initial_smiths_len: usize,
initial_identities_len: usize, initial_identities_len: usize,
) -> Result<ChainSpec, String> { ) -> Result<ChainSpec, String> {
let wasm_binary = WASM_BINARY.ok_or_else(|| "wasm not available".to_string())?; Ok(ChainSpec::builder(
&get_wasm_binary().ok_or_else(|| "Development wasm not available".to_string())?,
Ok(ChainSpec::from_genesis( None,
// Name )
"Ğdev Local Testnet", .with_name("Ğdev Local Testnet")
// ID .with_id("gdev_local")
"gdev_local", .with_chain_type(ChainType::Local)
ChainType::Local, .with_genesis_config_patch({
move || { let genesis_data =
gen_genesis_for_local_chain( gen_genesis_data::generate_genesis_data_for_local_chain::<_, _, SessionKeys, GDevSKP>(
wasm_binary,
// Initial authorities len // Initial authorities len
initial_authorities_len, initial_authorities_len,
// Initial smiths len, // Initial smiths len,
initial_smiths_len, initial_smiths_len,
// Initial identities len // Initial identities len
initial_identities_len, initial_identities_len,
EXISTENTIAL_DEPOSIT,
get_local_chain_parameters(),
// Sudo account // Sudo account
get_account_id_from_seed::<sr25519::Public>("Alice"), get_account_id_from_seed::<sr25519::Public>("Alice"),
true, get_parameters,
) )
}, .expect("Genesis Data must be buildable");
// Bootnodes genesis_data_to_gdev_genesis_conf(genesis_data)
vec![], })
// Telemetry .with_properties(
None,
// Protocol ID
None,
//Fork ID
None,
// Properties
Some(
serde_json::json!({ serde_json::json!({
"tokenDecimals": TOKEN_DECIMALS, "tokenDecimals": TOKEN_DECIMALS,
"tokenSymbol": TOKEN_SYMBOL, "tokenSymbol": TOKEN_SYMBOL,
...@@ -230,203 +213,17 @@ pub fn local_testnet_config( ...@@ -230,203 +213,17 @@ pub fn local_testnet_config(
.as_object() .as_object()
.expect("must be a map") .expect("must be a map")
.clone(), .clone(),
),
// Extensions
None,
))
}
fn gen_genesis_for_local_chain(
wasm_binary: &[u8],
initial_authorities_len: usize,
initial_smiths_len: usize,
initial_identities_len: usize,
root_key: AccountId,
_enable_println: bool,
) -> gdev_runtime::GenesisConfig {
assert!(initial_identities_len <= 6);
assert!(initial_smiths_len <= initial_identities_len);
assert!(initial_authorities_len <= initial_smiths_len);
let babe_epoch_duration = get_env_u32("DUNITER_BABE_EPOCH_DURATION", 30) as u64;
let cert_validity_period = get_env_u32("DUNITER_CERT_VALIDITY_PERIOD", 1_000);
let first_ud = 1_000;
let membership_period = get_env_u32("DUNITER_MEMBERSHIP_PERIOD", 1_000);
let smith_cert_validity_period = get_env_u32("DUNITER_SMITH_CERT_VALIDITY_PERIOD", 1_000);
let smith_membership_period = get_env_u32("DUNITER_SMITH_MEMBERSHIP_PERIOD", 1_000);
let ud_creation_period = get_env_u32("DUNITER_UD_CREATION_PERIOD", 10);
let ud_reeval_period = get_env_u32("DUNITER_UD_REEEVAL_PERIOD", 200);
let initial_smiths = (0..initial_smiths_len)
.map(|i| get_authority_keys_from_seed(NAMES[i]))
.collect::<Vec<AuthorityKeys>>();
let initial_identities = (0..initial_identities_len)
.map(|i| {
(
IdtyName::from(NAMES[i]),
get_account_id_from_seed::<sr25519::Public>(NAMES[i]),
)
})
.collect::<BTreeMap<IdtyName, AccountId>>();
gdev_runtime::GenesisConfig {
system: SystemConfig {
// Add Wasm runtime to storage.
code: wasm_binary.to_vec(),
},
account: AccountConfig {
accounts: initial_identities
.iter()
.enumerate()
.map(|(i, (_, owner_key))| {
(
owner_key.clone(),
GenesisAccountData {
random_id: H256(blake2_256(&(i as u32, owner_key).encode())),
balance: first_ud,
is_identity: true,
},
) )
}) .build())
.collect(),
},
parameters: ParametersConfig {
parameters: GenesisParameters {
babe_epoch_duration,
cert_period: 15,
cert_max_by_issuer: 10,
cert_min_received_cert_to_issue_cert: 2,
cert_validity_period,
idty_confirm_period: 40,
idty_creation_period: 50,
membership_period,
pending_membership_period: 500,
ud_creation_period,
ud_reeval_period,
smith_cert_period: 15,
smith_cert_max_by_issuer: 8,
smith_cert_min_received_cert_to_issue_cert: 2,
smith_cert_validity_period,
smith_membership_period,
smith_pending_membership_period: 500,
smiths_wot_first_cert_issuable_on: 20,
smiths_wot_min_cert_for_membership: 2,
wot_first_cert_issuable_on: 20,
wot_min_cert_for_create_idty_right: 2,
wot_min_cert_for_membership: 2,
},
},
authority_discovery: Default::default(),
authority_members: AuthorityMembersConfig {
initial_authorities: initial_smiths
.iter()
.enumerate()
.map(|(i, keys)| (i as u32 + 1, (keys.0.clone(), i < initial_authorities_len)))
.collect(),
},
balances: BalancesConfig {
balances: Default::default(),
},
babe: BabeConfig {
authorities: Vec::with_capacity(0),
epoch_config: Some(BABE_GENESIS_EPOCH_CONFIG),
},
grandpa: Default::default(),
im_online: Default::default(),
session: SessionConfig {
keys: initial_smiths
.iter()
.map(|x| {
(
x.0.clone(),
x.0.clone(),
session_keys(x.1.clone(), x.2.clone(), x.3.clone(), x.4.clone()),
)
})
.collect::<Vec<_>>(),
},
sudo: SudoConfig {
// Assign network admin rights.
key: Some(root_key),
},
technical_committee: TechnicalCommitteeConfig {
members: initial_smiths
.iter()
.map(|x| x.0.clone())
.collect::<Vec<_>>(),
..Default::default()
},
identity: IdentityConfig {
identities: initial_identities
.iter()
.enumerate()
.map(|(i, (name, owner_key))| GenesisIdty {
index: i as u32 + 1,
name: name.clone(),
value: IdtyValue {
data: IdtyData::new(),
next_creatable_identity_on: Default::default(),
old_owner_key: None,
owner_key: owner_key.clone(),
removable_on: 0,
status: IdtyStatus::Validated,
},
})
.collect(),
},
membership: MembershipConfig {
memberships: (1..=initial_identities.len())
.map(|i| (i as u32, MembershipData { expire_on: 0 }))
.collect(),
},
cert: CertConfig {
apply_cert_period_at_genesis: false,
certs_by_receiver: clique_wot(initial_identities.len()),
},
smiths_membership: SmithsMembershipConfig {
memberships: (1..=initial_smiths_len)
.map(|i| (i as u32, MembershipData { expire_on: 0 }))
.collect(),
},
smiths_cert: SmithsCertConfig {
apply_cert_period_at_genesis: false,
certs_by_receiver: clique_wot(initial_smiths_len),
},
universal_dividend: UniversalDividendConfig {
first_reeval: 100,
first_ud,
initial_monetary_mass: initial_identities_len as u64 * first_ud,
},
treasury: Default::default(),
}
}
fn get_env_u32(env_var_name: &'static str, default_value: u32) -> u32 {
std::env::var(env_var_name)
.map_or(Ok(default_value), |s| s.parse())
.unwrap_or_else(|_| panic!("{} must be a number", env_var_name))
}
fn session_keys(
babe: BabeId,
grandpa: GrandpaId,
im_online: ImOnlineId,
authority_discovery: AuthorityDiscoveryId,
) -> SessionKeys {
SessionKeys {
babe,
grandpa,
im_online,
authority_discovery,
}
} }
/// custom genesis
fn genesis_data_to_gdev_genesis_conf( fn genesis_data_to_gdev_genesis_conf(
genesis_data: super::gen_genesis_data::GenesisData<GenesisParameters, SessionKeys>, genesis_data: super::gen_genesis_data::GenesisData<GenesisParameters, SessionKeys>,
wasm_binary: &[u8], ) -> serde_json::Value {
) -> gdev_runtime::GenesisConfig {
let super::gen_genesis_data::GenesisData { let super::gen_genesis_data::GenesisData {
accounts, accounts,
treasury_balance,
certs_by_receiver, certs_by_receiver,
first_ud, first_ud,
first_ud_reeval, first_ud_reeval,
...@@ -435,77 +232,136 @@ fn genesis_data_to_gdev_genesis_conf( ...@@ -435,77 +232,136 @@ fn genesis_data_to_gdev_genesis_conf(
initial_monetary_mass, initial_monetary_mass,
memberships, memberships,
parameters, parameters,
common_parameters: _,
session_keys_map, session_keys_map,
smiths_certs_by_receiver, initial_smiths,
smiths_memberships,
sudo_key, sudo_key,
technical_committee_members, technical_committee_members,
ud,
} = genesis_data; } = genesis_data;
gdev_runtime::GenesisConfig { serde_json::json!({
system: SystemConfig { "account": {
// Add Wasm runtime to storage. "accounts": accounts,
code: wasm_binary.to_vec(), "treasuryBalance": treasury_balance,
}, },
account: AccountConfig { accounts }, "parameters": {
parameters: ParametersConfig { parameters }, "parameters": parameters.expect("mandatory for GDev"),
authority_discovery: Default::default(), },
authority_members: AuthorityMembersConfig { "authorityMembers": {
initial_authorities, "initialAuthorities": initial_authorities,
},
"balances": {
"totalIssuance": initial_monetary_mass,
}, },
balances: Default::default(), "babe": {
babe: BabeConfig { "epochConfig": Some(BABE_GENESIS_EPOCH_CONFIG),
authorities: Vec::with_capacity(0),
epoch_config: Some(common_runtime::constants::BABE_GENESIS_EPOCH_CONFIG),
}, },
grandpa: Default::default(), "session": {
im_online: Default::default(), "keys": session_keys_map
session: SessionConfig {
keys: session_keys_map
.into_iter() .into_iter()
.map(|(account_id, session_keys)| (account_id.clone(), account_id, session_keys)) .map(|(account_id, session_keys)| (account_id.clone(), account_id, session_keys))
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
}, },
sudo: SudoConfig { key: sudo_key }, "sudo": { "key": sudo_key },
technical_committee: TechnicalCommitteeConfig { "technicalCommittee": {
members: technical_committee_members, "members": technical_committee_members,
..Default::default()
}, },
identity: IdentityConfig { "quota": {
identities: identities "identities": identities.iter().map(|i| i.idty_index).collect::<Vec<_>>(),
},
"identity": {
"identities": identities
.into_iter() .into_iter()
.enumerate() .map(
.map(|(i, (name, pubkey))| common_runtime::GenesisIdty { |GenesisIdentity {
index: i as u32 + 1, idty_index,
name,
owner_key,
status,
// at this point next_scheduled takes status into account
// so is null for member account
next_scheduled,
}| GenesisIdty {
index: idty_index,
name: common_runtime::IdtyName::from(name.as_str()), name: common_runtime::IdtyName::from(name.as_str()),
value: common_runtime::IdtyValue { value: common_runtime::IdtyValue {
data: IdtyData::new(), data: IdtyData {
first_eligible_ud: match status {
// Only members are eligible to UD.
// The first claimable UD has the minimum index (1).
common_runtime::IdtyStatus::Member => gdev_runtime::pallet_universal_dividend::FirstEligibleUd::min(),
_ => gdev_runtime::pallet_universal_dividend::FirstEligibleUd(None),
}
},
next_creatable_identity_on: 0, next_creatable_identity_on: 0,
old_owner_key: None, old_owner_key: None,
owner_key: pubkey, owner_key,
removable_on: 0, next_scheduled,
status: IdtyStatus::Validated, status,
}, },
})
.collect(),
}, },
cert: CertConfig { )
apply_cert_period_at_genesis: true, .collect::<Vec<GenesisIdty<gdev_runtime::Runtime>>>(),
certs_by_receiver,
}, },
membership: MembershipConfig { memberships }, "certification": {
smiths_cert: SmithsCertConfig { "applyCertPeriodAtGenesis": false,
apply_cert_period_at_genesis: true, "certsByReceiver": certs_by_receiver,
certs_by_receiver: smiths_certs_by_receiver,
}, },
smiths_membership: SmithsMembershipConfig { "membership": { "memberships": memberships },
memberships: smiths_memberships, "smithMembers": { "initialSmiths": initial_smiths},
"universalDividend": {
"firstReeval": first_ud_reeval,
"firstUd": first_ud,
"initialMonetaryMass": initial_monetary_mass,
"ud": ud,
}, },
universal_dividend: UniversalDividendConfig { })
first_reeval: first_ud_reeval,
first_ud,
initial_monetary_mass,
},
treasury: Default::default(),
} }
fn get_local_chain_parameters() -> Option<GenesisParameters> {
let babe_epoch_duration = get_env("DUNITER_BABE_EPOCH_DURATION", 30) as u64;
let cert_validity_period = get_env("DUNITER_CERT_VALIDITY_PERIOD", 1_000);
let membership_period = get_env("DUNITER_MEMBERSHIP_PERIOD", 1_000);
let membership_renewal_period = get_env("DUNITER_MEMBERSHIP_RENEWAL_PERIOD", 1_000);
let ud_creation_period = get_env("DUNITER_UD_CREATION_PERIOD", 60_000);
let ud_reeval_period = get_env("DUNITER_UD_REEEVAL_PERIOD", 1_200_000);
Some(GenesisParameters {
babe_epoch_duration,
cert_period: 15,
cert_max_by_issuer: 10,
cert_min_received_cert_to_issue_cert: 2,
cert_validity_period,
idty_confirm_period: 40,
idty_creation_period: 50,
membership_period,
membership_renewal_period,
ud_creation_period,
ud_reeval_period,
smith_cert_max_by_issuer: 8,
smith_inactivity_max_duration: 48,
smith_wot_min_cert_for_membership: 2,
wot_first_cert_issuable_on: 20,
wot_min_cert_for_create_idty_right: 2,
wot_min_cert_for_membership: 2,
})
}
/// get environment variable
fn get_env<T: std::str::FromStr>(env_var_name: &'static str, default_value: T) -> T {
std::env::var(env_var_name)
.map_or(Ok(default_value), |s| s.parse())
.unwrap_or_else(|_| panic!("{} must be a {}", env_var_name, std::any::type_name::<T>()))
}
/// Get the WASM bytes either from filesytem (`WASM_FILE` env variable giving the path to the wasm blob)
/// or else get the one compiled from source code.
/// Goal: allow to provide the WASM built with srtool, which is reproductible.
fn get_wasm_binary() -> Option<Vec<u8>> {
let wasm_bytes_from_file = if let Ok(file_path) = env::var("WASM_FILE") {
Some(fs::read(file_path).unwrap_or_else(|e| panic!("Could not read wasm file: {}", e)))
} else {
None
};
wasm_bytes_from_file.or_else(|| WASM_BINARY.map(|bytes| bytes.to_vec()))
} }
...@@ -14,385 +14,2022 @@ ...@@ -14,385 +14,2022 @@
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU Affero General Public License
// along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>. // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
use common_runtime::*; #![allow(unused_imports)]
#![allow(dead_code)]
use crate::chain_spec::{get_account_id_from_seed, get_from_seed, AccountPublic};
use common_runtime::{
constants::{DAYS, MILLISECS_PER_BLOCK},
*,
};
use log::{error, warn};
use num_format::{Locale, ToFormattedString};
use pallet_im_online::sr25519::AuthorityId as ImOnlineId;
use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde::{de::DeserializeOwned, Deserialize, Serialize};
use sp_core::{blake2_256, Decode, Encode, H256}; use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId;
use std::collections::BTreeMap; use sp_consensus_babe::AuthorityId as BabeId;
use sp_consensus_grandpa::AuthorityId as GrandpaId;
use sp_core::{crypto::AccountId32, ed25519, sr25519, Decode, Encode};
use sp_runtime::{
traits::{IdentifyAccount, Verify},
MultiSignature, Perbill,
};
use std::{
collections::{BTreeMap, HashMap},
fmt::{Display, Formatter},
ops::{Add, Sub},
};
type MembershipData = sp_membership::MembershipData<u32>; static G1_DUNITER_V1_EXISTENTIAL_DEPOSIT: u64 = 100;
static G1_DUNITER_V1_DECIMALS: usize = 2;
static G1_DUNITER_V1_DT: u64 = 86400;
static G1_DUNITER_V1_SIGPERIOD: u32 = 432000;
static G1_DUNITER_V1_SIGSTOCK: u32 = 100;
static G1_DUNITER_V1_SIGVALIDITY: u32 = 63115200;
static G1_DUNITER_V1_SIGQTY: u32 = 5;
static G1_DUNITER_V1_MSVALIDITY: u32 = 31557600;
static G1_DUNITER_V1_STEPMAX: u32 = 5;
static G1_DUNITER_V1_DTREEVAL: u64 = 15778800;
// Warning: Duniter V1 "days" are expressed in seconds, while V2 are expressed in blocks
static DUNITER_V1_DAYS: u32 = 3600 * 24;
// Not used in V2S
// static G1_DUNITER_V1_SIGWINDOW: u32 = 5259600; // no more pool
// static G1_DUNITER_V1_IDTYWINDOW: u32 = 5259600; // no more pool
// static G1_DUNITER_V1_MSWINDOW: u32 = 5259600; // no more pool
// static G1_DUNITER_V1_PERCENTROT: f32 = 0.67; // no more PoW
// static G1_DUNITER_V1_MEDIANTIMEBLOCKS: u32 = 24; // no more PoW
// static G1_DUNITER_V1_AVGGENTIME: u32 = 300; // no more PoW
// static G1_DUNITER_V1_DTDIFFEVAL: u32 = 12; // no more PoW
// static G1_DUNITER_V1_UD0: u32 = 1000; // new value
// static G1_DUNITER_V1_MSPERIOD: u32 = 5259600; // no more used
// static G1_DUNITER_V1_UDTIME0: u32 = 1488970800; // duniter v1 specific
// static G1_DUNITER_V1_UDREEVALTIME0: u32 = 1490094000; // duniter v1 specific
const EXISTENTIAL_DEPOSIT: u64 = 200; type MembershipData = sp_membership::MembershipData<u32>;
#[derive(Clone)] #[derive(Clone)]
pub struct GenesisData<Parameters: DeserializeOwned, SessionKeys: Decode> { pub struct GenesisData<Parameters: DeserializeOwned, SessionKeys: Decode> {
pub accounts: BTreeMap<AccountId, GenesisAccountData<u64>>, pub accounts: BTreeMap<AccountId, GenesisAccountData<u64, u32>>,
pub treasury_balance: u64,
pub certs_by_receiver: BTreeMap<u32, BTreeMap<u32, Option<u32>>>, pub certs_by_receiver: BTreeMap<u32, BTreeMap<u32, Option<u32>>>,
pub first_ud: u64, pub first_ud: Option<u64>,
pub first_ud_reeval: u32, pub first_ud_reeval: Option<u64>,
pub identities: Vec<(String, AccountId)>, pub identities: Vec<GenesisIdentity>,
pub initial_authorities: BTreeMap<u32, (AccountId, bool)>, pub initial_authorities: BTreeMap<u32, (AccountId, bool)>,
pub initial_monetary_mass: u64, pub initial_monetary_mass: u64,
pub memberships: BTreeMap<u32, MembershipData>, pub memberships: BTreeMap<u32, MembershipData>,
pub parameters: Parameters, pub parameters: Option<Parameters>,
pub common_parameters: Option<CommonParameters>,
pub session_keys_map: BTreeMap<AccountId, SessionKeys>, pub session_keys_map: BTreeMap<AccountId, SessionKeys>,
pub smiths_certs_by_receiver: BTreeMap<u32, BTreeMap<u32, Option<u32>>>, pub initial_smiths: BTreeMap<u32, (bool, Vec<u32>)>,
pub smiths_memberships: BTreeMap<u32, MembershipData>,
pub sudo_key: Option<AccountId>, pub sudo_key: Option<AccountId>,
pub technical_committee_members: Vec<AccountId>, pub technical_committee_members: Vec<AccountId>,
pub ud: u64,
} }
#[derive(Default, Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
pub struct ParamsAppliedAtGenesis { struct BlockV1 {
pub genesis_certs_min_received: u32, number: u32,
pub genesis_memberships_expire_on: u32, #[serde(rename = "medianTime")]
pub genesis_smith_certs_min_received: u32, median_time: u64,
pub genesis_smith_memberships_expire_on: u32, }
#[derive(Clone)]
pub struct GenesisIdentity {
pub idty_index: u32,
pub name: String,
pub owner_key: AccountId,
pub status: IdtyStatus,
pub next_scheduled: u32,
} }
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
struct GenesisConfig<Parameters> { struct GenesisInput<Parameters> {
first_ud: u64, first_ud: Option<u64>,
first_ud_reeval: u32, first_ud_reeval: Option<u64>,
genesis_parameters: ParamsAppliedAtGenesis,
identities: BTreeMap<String, Idty>,
#[serde(default)] #[serde(default)]
parameters: Parameters, parameters: Option<Parameters>,
#[serde(rename = "smiths")] #[serde(rename = "smiths")]
smith_identities: BTreeMap<String, SmithData>, smith_identities: Option<BTreeMap<String, RawSmith>>,
clique_smiths: Option<Vec<CliqueSmith>>,
sudo_key: Option<AccountId>, sudo_key: Option<AccountId>,
treasury_funder_pubkey: Option<PubkeyV1>,
treasury_funder_address: Option<AccountId>,
technical_committee: Vec<String>, technical_committee: Vec<String>,
#[serde(default)] ud: u64,
}
#[derive(Deserialize, Serialize)]
pub struct GenesisIndexerExport {
first_ud: Option<u64>,
first_ud_reeval: Option<u64>,
genesis_parameters: CommonParameters,
identities: HashMap<String, IdentityV2>,
smiths: BTreeMap<String, SmithData>,
sudo_key: Option<AccountId>,
technical_committee: Vec<String>,
ud: u64,
wallets: BTreeMap<AccountId, u64>, wallets: BTreeMap<AccountId, u64>,
} }
#[derive(Clone, Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
struct Idty { struct TransactionV1 {
issuer: PubkeyV1,
amount: String,
written_time: Option<u32>,
comment: String,
}
#[derive(Deserialize, Serialize)]
struct TransactionV2 {
issuer: AccountId,
amount: String,
written_time: Option<u32>,
comment: String,
}
#[derive(Deserialize, Serialize)]
struct GenesisMigrationData {
initial_monetary_mass: u64,
current_block: BlockV1,
identities: BTreeMap<String, IdentityV1>,
#[serde(default)] #[serde(default)]
wallets: BTreeMap<PubkeyV1, u64>,
}
// Base58 encoded Ed25519 public key
#[derive(Clone, Deserialize, Serialize, Ord, PartialOrd, Eq, PartialEq)]
struct PubkeyV1(String);
// Timestamp
#[derive(Clone, Deserialize, Serialize)]
struct TimestampV1(u32);
impl Display for PubkeyV1 {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
/// identities
#[derive(Clone, Deserialize, Serialize)]
struct IdentityV1 {
/// indentity index matching the order of appearance in the Ǧ1v1 blockchain
index: u32,
/// Base58 public key in Ğ1v1
owner_pubkey: Option<PubkeyV1>,
/// Ğ1v2 address
owner_address: Option<AccountId>,
/// Optional Base58 public key in Ğ1v1
old_owner_key: Option<PubkeyV1>,
/// timestamp at which the membership is set to expire (0 for expired members)
membership_expire_on: TimestampV1,
/// timestamp at which the identity would be set as revoked (value in the past for already revoked identities)
membership_revokes_on: TimestampV1,
/// whether the identity is revoked (manually or automatically)
revoked: bool,
/// balance of the account of this identity
balance: u64, balance: u64,
/// certs received with their expiration timestamp
certs_received: HashMap<String, TimestampV1>,
}
/// identities
// note: having membership_expire_on and identity_revoke_on is tricky
// because the model of identity does not take into account the status
// see this forum topic for a suggestion
// https://forum.duniter.org/t/proposition-pour-supprimer-la-pallet-membership/11918
#[derive(Clone, Deserialize, Serialize)]
struct IdentityV2 {
/// indentity index matching the order of appearance in the Ǧ1v1 blockchain
index: u32,
/// ss58 address in gx network
owner_key: AccountId,
/// block at which the membership is set to expire (0 for expired members)
membership_expire_on: u32,
/// block at which the identity should be revoked (value in the past for already revoked identities)
identity_revoke_on: u32,
/// whether the identity is revoked (manually or automatically)
revoked: bool,
/// balance of the account of this identity
balance: u64,
/// certs received with their expiration block
certs_received: HashMap<String, u32>,
}
#[derive(Clone, Deserialize, Serialize)]
struct RawSmith {
name: String,
/// optional pre-set session keys (at least for the smith bootstraping the blockchain)
session_keys: Option<String>,
#[serde(default)] #[serde(default)]
certs: Vec<Cert>, certs_received: Vec<String>,
#[serde(rename = "expire_on")]
membership_expire_on: Option<u64>,
pubkey: AccountId,
} }
#[derive(Clone, Deserialize, Serialize)] #[derive(Clone, Deserialize, Serialize)]
struct SmithData { struct SmithData {
idty_index: u32,
name: String,
account: AccountId,
/// optional pre-set session keys (at least for the smith bootstraping the blockchain)
session_keys: Option<String>, session_keys: Option<String>,
#[serde(default)] #[serde(default)]
certs: Vec<Cert>, certs_received: Vec<String>,
} }
#[derive(Clone, Deserialize, Serialize)] #[derive(Clone, Deserialize, Serialize)]
#[serde(untagged)] struct CliqueSmith {
enum Cert { name: String,
Issuer(String), session_keys: Option<String>,
IssuerAndExpireOn(String, u32),
}
impl From<Cert> for (String, Option<u32>) {
fn from(cert: Cert) -> Self {
match cert {
Cert::Issuer(issuer) => (issuer, None),
Cert::IssuerAndExpireOn(issuer, expire_on) => (issuer, Some(expire_on)),
} }
struct SmithMembers<SK: Decode> {
initial_smiths_wot: BTreeMap<u32, (bool, Vec<u32>)>,
session_keys_map:
BTreeMap<<<MultiSignature as Verify>::Signer as IdentifyAccount>::AccountId, SK>,
} }
struct GenesisInfo<'a> {
genesis_timestamp: u64,
accounts: &'a BTreeMap<AccountId32, GenesisAccountData<u64, u32>>,
genesis_data_wallets_count: &'a usize,
inactive_identities: &'a HashMap<u32, (String, IdtyStatus)>,
identities: &'a Vec<GenesisIdentity>,
identity_index: &'a HashMap<u32, String>,
initial_smiths: &'a BTreeMap<u32, (bool, Vec<u32>)>,
counter_online_authorities: &'a u32,
counter_cert: &'a u32,
counter_smith_cert: &'a u32,
technical_committee_members: &'a Vec<AccountId32>,
common_parameters: &'a CommonParameters,
} }
pub fn generate_genesis_data<CS, P, SK, F>( /// generate genesis data from a json file
f: F, /// takes DUNITER_GENESIS_CONFIG env var if present or duniter-gen-conf.json by default
maybe_force_authority: Option<Vec<u8>>, // this function is targeting dev chainspecs, do not use in production network
) -> Result<CS, String> pub fn generate_genesis_data<P, SK, SessionKeys: Encode, SKP>(
config_file_path: String,
get_common_parameters: fn(&Option<P>) -> CommonParameters,
maybe_force_authority: Option<String>,
) -> Result<GenesisData<P, SK>, String>
where where
P: Default + DeserializeOwned, P: Default + DeserializeOwned,
SK: Decode, SK: Decode,
F: Fn(GenesisData<P, SK>) -> CS, SKP: SessionKeysProvider<SessionKeys>,
{ {
let genesis_timestamp: u64 = let genesis_timestamp: u64 = get_genesis_timestamp()?;
if let Ok(genesis_timestamp) = std::env::var("DUNITER_GENESIS_TIMESTAMP") {
genesis_timestamp
.parse()
.map_err(|_| "DUNITER_GENESIS_TIMESTAMP must be a number".to_owned())?
} else {
use std::time::SystemTime;
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.expect("SystemTime before UNIX EPOCH!")
.as_secs()
};
let json_file_path = std::env::var("DUNITER_GENESIS_CONFIG") // Per network input
.unwrap_or_else(|_| "duniter-gen-conf.json".to_owned()); let GenesisInput {
// We mmap the file into memory first, as this is *a lot* faster than using
// `serde_json::from_reader`. See https://github.com/serde-rs/json/issues/160
let file = std::fs::File::open(&json_file_path)
.map_err(|e| format!("Error opening gen conf file `{}`: {}", json_file_path, e))?;
// SAFETY: `mmap` is fundamentally unsafe since technically the file can change
// underneath us while it is mapped; in practice it's unlikely to be a problem
let bytes = unsafe {
memmap2::Mmap::map(&file)
.map_err(|e| format!("Error mmaping gen conf file `{}`: {}", json_file_path, e))?
};
let genesis_config = serde_json::from_slice(&bytes)
.map_err(|e| format!("Error parsing gen conf file: {}", e))?;
let GenesisConfig {
sudo_key, sudo_key,
treasury_funder_pubkey,
treasury_funder_address,
first_ud, first_ud,
first_ud_reeval, first_ud_reeval,
genesis_parameters:
ParamsAppliedAtGenesis {
genesis_certs_min_received,
genesis_memberships_expire_on,
genesis_smith_certs_min_received,
genesis_smith_memberships_expire_on,
},
parameters, parameters,
identities,
smith_identities, smith_identities,
clique_smiths,
technical_committee, technical_committee,
wallets, ud,
} = genesis_config; } = get_genesis_input::<P>(
std::env::var("DUNITER_GENESIS_CONFIG").unwrap_or_else(|_| config_file_path.to_owned()),
)?;
// Per network parameters
let common_parameters = get_common_parameters(&parameters);
// MONEY AND WOT // // Per network smiths (without link to an account yet — identified by their pseudonym)
let mut smiths = build_smiths_wot(&clique_smiths, smith_identities)?;
let mut accounts = BTreeMap::new(); // G1 data migration (common to all networks)
let mut identities_ = Vec::with_capacity(identities.len()); let mut genesis_data = get_genesis_migration_data()?;
let mut idty_index: u32 = 1; check_parameters_consistency(&genesis_data.wallets, &first_ud, &first_ud_reeval, &ud)?;
let mut idty_index_of = BTreeMap::new(); check_genesis_data_and_filter_expired_certs_since_export(
let mut initial_monetary_mass = 0; &mut genesis_data,
genesis_timestamp,
&common_parameters,
);
let mut identities_v2: HashMap<String, IdentityV2> =
genesis_data_to_identities_v2(genesis_data.identities, genesis_timestamp);
check_identities_v2(&identities_v2, &common_parameters);
// MONEY AND WOT //
// declare variables for building genesis
// -------------------------------------
// track if fatal error occured, but let processing continue
let mut fatal = false;
// initial Treasury balance
let mut treasury_balance = 0;
// track identity index
let mut identity_index = HashMap::new();
// track inactive identities
let mut inactive_identities = HashMap::<u32, (String, IdtyStatus)>::new();
// declare variables to fill in genesis
// -------------------------------------
// members of technical committee
let mut technical_committee_members: Vec<AccountId> = Vec::new();
// memberships
let mut memberships = BTreeMap::new(); let mut memberships = BTreeMap::new();
let mut technical_committee_members = Vec::with_capacity(technical_committee.len()); // certifications
let mut certs_by_receiver = BTreeMap::new();
// initial authorities
let mut initial_authorities = BTreeMap::new();
//let mut total_dust = 0; //let mut total_dust = 0;
// FORCED AUTHORITY //
// If this authority is defined (most likely Alice), then it must exist in both _identities_
// and _smiths_. We create all this, for *development purposes* (used with `gdev_dev` or `gtest_dev` chains).
if let Some(authority_name) = &maybe_force_authority {
make_authority_exist::<SessionKeys, SKP>(
&mut identities_v2,
&mut smiths,
&common_parameters,
authority_name,
);
}
// SIMPLE WALLETS // // SIMPLE WALLETS //
let genesis_data_wallets_count = genesis_data.wallets.len();
let (was_fatal, mut monetary_mass, mut accounts, invalid_wallets) =
v1_wallets_to_v2_accounts(genesis_data.wallets, &common_parameters);
if was_fatal {
fatal = true;
}
let mut wallet_index: u32 = 0; // Technical Comittee //
for (pubkey, balance) in wallets { // NOTE : when changing owner key, the technical committee is not changed
wallet_index += 1; for name in &technical_committee {
accounts.insert( if let Some(identity) = &identities_v2.get(name) {
pubkey.clone(), technical_committee_members.push(identity.owner_key.clone());
GenesisAccountData { } else {
random_id: H256(blake2_256(&(wallet_index, &pubkey).encode())), log::error!("Identity '{}' does not exist", name);
balance, fatal = true;
is_identity: false, }
}
// IDENTITIES //
let (was_fatal, identities) = feed_identities(
&mut accounts,
&mut identity_index,
&mut monetary_mass,
&mut inactive_identities,
&mut memberships,
&identities_v2,
&common_parameters,
)?;
if was_fatal {
fatal = true;
}
// CERTIFICATIONS //
// counter for certifications
let (was_fatal, counter_cert) = feed_certs_by_receiver(&mut certs_by_receiver, &identities_v2);
if was_fatal {
fatal = true;
}
// SMITHS SUB-WOT //
// Authorities
if let Some(name) = &maybe_force_authority {
check_authority_exists_in_both_wots(name, &identities_v2, &smiths);
}
let smiths = decorate_smiths_with_identity(smiths, &identity_index, &identities_v2);
// counter for online authorities at genesis
let (
was_fatal,
counter_online_authorities,
counter_smith_cert,
SmithMembers {
initial_smiths_wot,
session_keys_map,
}, },
) = create_smith_wot(
&mut initial_authorities,
&identities_v2,
&smiths,
&clique_smiths,
)?;
if was_fatal {
fatal = true;
}
// Verify certifications coherence (can be ignored for old users)
for (idty_index, receiver_certs) in &certs_by_receiver {
if receiver_certs.len() < common_parameters.wot_min_cert_for_membership as usize {
let name = identity_index.get(idty_index).unwrap();
let identity = identities_v2.get(&(*name).clone()).unwrap();
if identity.membership_expire_on != 0 {
log::error!(
"[{}] has received only {}/{} certifications",
name,
receiver_certs.len(),
common_parameters.wot_min_cert_for_membership
); );
fatal = true;
}
}
} }
// Technical Comittee // // Verify smith certifications coherence
for (idty_index, (_online, certs)) in &initial_smiths_wot {
if certs.len() < common_parameters.smith_sub_wot_min_cert_for_membership as usize {
log::error!(
"[{}] has received only {}/{} smith certifications",
identity_index.get(idty_index).unwrap(),
certs.len(),
common_parameters.smith_sub_wot_min_cert_for_membership
);
fatal = true;
}
}
for idty_name in technical_committee { // check number of online authorities
if let Some(identity) = identities.get(&idty_name) { if maybe_force_authority.is_none() && counter_online_authorities != 1 {
technical_committee_members.push(identity.pubkey.clone()); log::error!("one and only one smith must be online, not {counter_online_authorities}");
} else { }
return Err(format!("Identity '{}' not exist", idty_name));
// check monetary mass
if monetary_mass != genesis_data.initial_monetary_mass {
log::warn!(
"actual monetary_mass ({}) and initial_monetary_mass ({}) do not match",
monetary_mass.to_formatted_string(&Locale::en),
genesis_data
.initial_monetary_mass
.to_formatted_string(&Locale::en)
);
if monetary_mass > genesis_data.initial_monetary_mass {
log::error!("money has been created");
fatal = true;
} }
} }
// IDENTITIES // // treasury balance must come from existing money
let treasury_funder: AccountId = match (treasury_funder_address, treasury_funder_pubkey) {
(Some(address), None) => address,
(None, Some(pubkey)) => {
v1_pubkey_to_account_id(pubkey).expect("treasury founder must have a valid public key")
}
_ => panic!("One of treasury_funder_address or treasury_funder_pubkey must be set"),
};
if let Some(existing_account) = accounts.get_mut(&treasury_funder) {
existing_account.balance = existing_account
.balance
.checked_sub(common_parameters.balances_existential_deposit)
.expect("should have enough money to fund Treasury");
treasury_balance = common_parameters.balances_existential_deposit;
}
if treasury_balance < common_parameters.balances_existential_deposit {
log::error!(
"Treasury balance {} is inferior to existential deposit {}",
treasury_balance,
common_parameters.balances_existential_deposit
);
fatal = true;
}
smiths.iter().for_each(|smith| {
log::info!(
"[Smith] {} ({} - {})",
smith.idty_index,
smith.account,
smith.name.clone()
);
});
for (idty_name, identity) in &identities { initial_authorities
if !validate_idty_name(idty_name) { .iter()
return Err(format!("Identity name '{}' is invalid", &idty_name)); .for_each(|(index, (authority_account, online))| {
log::info!(
"[Authority] {} : {} ({} - {})",
index,
if *online { "online" } else { "offline" },
authority_account,
identity_index
.get(index)
.expect("authority should have an identity")
);
});
let genesis_info = GenesisInfo {
genesis_timestamp,
accounts: &accounts,
genesis_data_wallets_count: &genesis_data_wallets_count,
identities: &identities,
inactive_identities: &inactive_identities,
identity_index: &identity_index,
initial_smiths: &initial_smiths_wot,
counter_online_authorities: &counter_online_authorities,
counter_cert: &counter_cert,
counter_smith_cert: &counter_smith_cert,
technical_committee_members: &technical_committee_members,
common_parameters: &common_parameters,
};
dump_genesis_info(genesis_info);
if parameters.is_some() {
let g1_duniter_v1_c = 0.0488;
let g1_duniter_v1_xpercent: Perbill = Perbill::from_float(0.8);
let c = f32::sqrt(
common_parameters
.universal_dividend_square_money_growth_rate
.deconstruct() as f32
/ 1_000_000_000f32,
);
// static parameters (GTest or G1)
if common_parameters.decimals != G1_DUNITER_V1_DECIMALS {
warn!(
"parameter `decimals` value ({}) is different from Ğ1 value ({})",
common_parameters.decimals, G1_DUNITER_V1_DECIMALS
)
}
if common_parameters.balances_existential_deposit != G1_DUNITER_V1_EXISTENTIAL_DEPOSIT {
warn!(
"parameter `existential_deposit` value ({}) is different from Ğ1 value ({})",
common_parameters.balances_existential_deposit, G1_DUNITER_V1_EXISTENTIAL_DEPOSIT
)
}
if common_parameters.membership_membership_period / DAYS
!= G1_DUNITER_V1_MSVALIDITY / DUNITER_V1_DAYS
{
warn!(
"parameter `membership_period` ({} days) is different from Ğ1's ({} days)",
common_parameters.membership_membership_period as f32 / DAYS as f32,
G1_DUNITER_V1_MSVALIDITY as f32 / DUNITER_V1_DAYS as f32
)
}
if common_parameters.membership_membership_renewal_period / DAYS
!= G1_DUNITER_V1_MSVALIDITY / DUNITER_V1_DAYS
{
warn!(
"parameter `membership_renewal_period` ({} days) is different from Ğ1's ({} days)",
common_parameters.membership_membership_renewal_period as f32 / DAYS as f32,
G1_DUNITER_V1_MSVALIDITY as f32 / DUNITER_V1_DAYS as f32
)
}
if common_parameters.cert_cert_period / DAYS != G1_DUNITER_V1_SIGPERIOD / DUNITER_V1_DAYS {
warn!(
"parameter `cert_period` ({} days) is different from Ğ1's ({} days)",
common_parameters.cert_cert_period as f32 / DAYS as f32,
G1_DUNITER_V1_SIGPERIOD as f32 / DUNITER_V1_DAYS as f32
)
}
if common_parameters.cert_validity_period / DAYS
!= G1_DUNITER_V1_SIGVALIDITY / DUNITER_V1_DAYS
{
warn!(
"parameter `cert_validity_period` ({} days) is different from Ğ1's ({} days)",
common_parameters.cert_validity_period as f32 / DAYS as f32,
G1_DUNITER_V1_SIGVALIDITY as f32 / DUNITER_V1_DAYS as f32
)
}
if common_parameters.wot_min_cert_for_membership != G1_DUNITER_V1_SIGQTY {
warn!(
"parameter `min_cert` value ({}) is different from Ğ1 value ({})",
common_parameters.wot_min_cert_for_membership, G1_DUNITER_V1_SIGQTY
)
}
if common_parameters.cert_max_by_issuer != G1_DUNITER_V1_SIGSTOCK {
warn!(
"parameter `cert_max_by_issuer` value ({}) is different from Ğ1 value ({})",
common_parameters.cert_max_by_issuer, G1_DUNITER_V1_SIGSTOCK
)
}
if c != g1_duniter_v1_c {
warn!(
"parameter `c` value ({}) is different from Ğ1 value ({})",
c, g1_duniter_v1_c
)
}
if common_parameters.universal_dividend_ud_creation_period as f32 / DAYS as f32
!= G1_DUNITER_V1_DT as f32 / DUNITER_V1_DAYS as f32
{
warn!(
"parameter `ud_creation_period` value ({} days) is different from Ğ1 value ({} days)",
common_parameters.universal_dividend_ud_creation_period as f32 / DAYS as f32, G1_DUNITER_V1_DT as f32 / DUNITER_V1_DAYS as f32
)
}
if common_parameters.universal_dividend_ud_reeval_period as f32 / DAYS as f32
!= G1_DUNITER_V1_DTREEVAL as f32 / DUNITER_V1_DAYS as f32
{
warn!(
"parameter `ud_reeval_period` value ({} days) is different from Ğ1 value ({} days)",
common_parameters.universal_dividend_ud_reeval_period as f32 / DAYS as f32,
G1_DUNITER_V1_DTREEVAL as f32 / DUNITER_V1_DAYS as f32
)
}
if common_parameters.distance_min_accessible_referees != g1_duniter_v1_xpercent {
warn!(
"parameter `distance_min_accessible_referees` value ({}) is different from Ğ1 value ({})",
format!("{:?}", common_parameters.distance_min_accessible_referees), format!("{:?}", g1_duniter_v1_xpercent)
)
}
if common_parameters.distance_max_depth != G1_DUNITER_V1_STEPMAX {
warn!(
"parameter `max_depth` value ({}) is different from Ğ1 value ({})",
common_parameters.distance_max_depth, G1_DUNITER_V1_STEPMAX
)
}
let count_uds = common_parameters.universal_dividend_ud_reeval_period
/ common_parameters.universal_dividend_ud_creation_period;
if count_uds == 0 {
error!(
"the `ud_reeval_period / ud_creation_period` is zero ({} days/{} days)",
common_parameters.universal_dividend_ud_reeval_period / DAYS as u64,
common_parameters.universal_dividend_ud_creation_period / DAYS as u64
);
fatal = true;
}
} }
// Money // some more checks
let balance = if identity.balance >= EXISTENTIAL_DEPOSIT { assert_eq!(
identity.balance identities.len() - inactive_identities.len(),
memberships.len()
);
assert_eq!(initial_smiths_wot.len(), initial_authorities.len());
assert_eq!(initial_smiths_wot.len(), session_keys_map.len());
assert_eq!(identity_index.len(), identities.len());
assert_eq!(
accounts.len(),
identity_index.len() + genesis_data_wallets_count.sub(invalid_wallets)
);
smiths_and_technical_committee_checks(&inactive_identities, &technical_committee, &smiths);
// check the logs to see all the fatal error preventing from starting gtest currency
if fatal {
log::error!("some previously logged error prevent from building a sane genesis");
panic!();
}
// Indexer output
// handled by indexer directly from py-g1-migrator output
let genesis_data = GenesisData {
accounts,
treasury_balance,
certs_by_receiver,
first_ud,
first_ud_reeval,
identities,
initial_authorities,
initial_monetary_mass: genesis_data.initial_monetary_mass,
memberships,
parameters,
common_parameters: Some(common_parameters),
session_keys_map,
initial_smiths: initial_smiths_wot,
sudo_key,
technical_committee_members,
ud,
};
Ok(genesis_data)
}
fn dump_genesis_info(info: GenesisInfo) {
// give genesis info
log::info!(
"prepared genesis with:
- {} as genesis timestamp
- {} accounts ({} identities, {} simple wallets)
- {} total identities ({} members, {} inactives, {} revoked)
- {} smiths
- {} initial online authorities
- {} certifications
- {} smith certifications
- {} members in technical committee",
info.genesis_timestamp,
info.accounts.len(),
info.identities.len() - info.inactive_identities.len(),
info.genesis_data_wallets_count,
info.identity_index.len(),
info.identities.len() - info.inactive_identities.len(),
info.inactive_identities
.iter()
.filter(|(_, (_, status))| *status == IdtyStatus::NotMember)
.count(),
info.inactive_identities
.iter()
.filter(|(_, (_, status))| *status == IdtyStatus::Revoked)
.count(),
info.initial_smiths.len(),
info.counter_online_authorities,
info.counter_cert,
info.counter_smith_cert,
info.technical_committee_members.len(),
);
let p = info.common_parameters.clone();
let (babe_epoch_duration, babe_epoch_duration_unit) =
get_best_unit_and_diviser_for_blocks(p.babe_epoch_duration as u32);
let (babe_expected_block_time, babe_expected_block_time_unit) =
get_best_unit_and_diviser_for_ms(p.babe_expected_block_time as f32);
let (babe_max_authorities, babe_max_authorities_unit) =
get_best_unit_and_diviser_for_participants(p.babe_max_authorities);
let (timestamp_minimum_period, timestamp_minimum_period_unit) =
get_best_unit_and_diviser_for_ms(p.timestamp_minimum_period as f32);
let (balances_existential_deposit, balances_existential_deposit_unit) =
get_best_unit_and_diviser_for_currency_units(
p.balances_existential_deposit,
p.currency_name.clone(),
);
let (authority_members_max_authorities, authority_members_max_authorities_unit) =
get_best_unit_and_diviser_for_participants(p.authority_members_max_authorities);
let (grandpa_max_authorities, grandpa_max_authorities_unit) =
get_best_unit_and_diviser_for_participants(p.grandpa_max_authorities);
let (universal_dividend_max_past_reevals, universal_dividend_max_past_reevals_unit) =
get_best_unit_and_diviser_for_equinoxes(p.universal_dividend_max_past_reevals);
let (
universal_dividend_square_money_growth_rate,
universal_dividend_square_money_growth_rate_unit,
) = get_best_unit_and_diviser_for_perbill_square(p.universal_dividend_square_money_growth_rate);
let (universal_dividend_ud_creation_period, universal_dividend_ud_creation_period_unit) =
get_best_unit_and_diviser_for_ms(p.universal_dividend_ud_creation_period as f32);
let (universal_dividend_ud_reeval_period, universal_dividend_ud_reeval_period_unit) =
get_best_unit_and_diviser_for_ms(p.universal_dividend_ud_reeval_period as f32);
let (wot_first_issuable_on, wot_first_issuable_on_unit) =
get_best_unit_and_diviser_for_blocks(p.wot_first_issuable_on);
let (wot_min_cert_for_membership, wot_min_cert_for_membership_unit) =
get_best_unit_and_diviser_for_certs(p.wot_min_cert_for_membership);
let (wot_min_cert_for_create_idty_right, wot_min_cert_for_create_idty_right_unit) =
get_best_unit_and_diviser_for_certs(p.wot_min_cert_for_create_idty_right);
let (identity_confirm_period, identity_confirm_period_unit) =
get_best_unit_and_diviser_for_blocks(p.identity_confirm_period);
let (identity_change_owner_key_period, identity_change_owner_key_period_unit) =
get_best_unit_and_diviser_for_blocks(p.identity_change_owner_key_period);
let (identity_idty_creation_period, identity_idty_creation_period_unit) =
get_best_unit_and_diviser_for_blocks(p.identity_idty_creation_period);
let (membership_membership_period, membership_membership_period_unit) =
get_best_unit_and_diviser_for_blocks(p.membership_membership_period);
let (membership_membership_renewal_period, membership_membership_renewal_period_unit) =
get_best_unit_and_diviser_for_blocks(p.membership_membership_renewal_period);
let (cert_cert_period, cert_cert_period_unit) =
get_best_unit_and_diviser_for_blocks(p.cert_cert_period);
let (cert_max_by_issuer, cert_max_by_issuer_unit) =
get_best_unit_and_diviser_for_certs(p.cert_max_by_issuer);
let (
cert_min_received_cert_to_be_able_to_issue_cert,
cert_min_received_cert_to_be_able_to_issue_cert_unit,
) = get_best_unit_and_diviser_for_certs(p.cert_min_received_cert_to_be_able_to_issue_cert);
let (cert_validity_period, cert_validity_period_unit) =
get_best_unit_and_diviser_for_blocks(p.cert_validity_period);
let (distance_min_accessible_referees, distance_min_accessible_referees_unit) =
get_best_unit_and_diviser_for_perbill(p.distance_min_accessible_referees);
let (distance_max_depth, distance_max_depth_unit) =
get_best_unit_and_diviser_for_depth(p.distance_max_depth);
let (smith_members_min_cert_for_membership, smith_members_min_cert_for_membership_unit) =
get_best_unit_and_diviser_for_certs(p.smith_sub_wot_min_cert_for_membership);
let (smith_members_max_by_issuer, smith_members_max_by_issuer_unit) =
get_best_unit_and_diviser_for_certs(p.smith_cert_max_by_issuer);
let (smith_members_inactivity_max_duration, smith_members_inactivity_max_duration_unit) =
get_best_unit_and_diviser_for_sessions(
p.smith_inactivity_max_duration,
p.babe_epoch_duration,
);
let (treasury_spend_period, treasury_spend_period_unit) =
get_best_unit_and_diviser_for_blocks(p.treasury_spend_period);
// give genesis info
log::info!(
"currency parameters:
- babe.epoch_duration: {} {}
- babe.expected_block_time: {} {}
- babe.max_authorities: {} {}
- timestamp.minimum_period: {} {}
- balances.existential_deposit: {} {}
- authority_members.max_authorities: {} {}
- grandpa.max_authorities: {} {}
- universal_dividend.max_past_reevals: {} {}
- universal_dividend.square_money_growth_rate: {} {}/equinox
- universal_dividend.ud_creation_period: {} {}
- universal_dividend.ud_reeval_period: {} {}
- wot.first_issuable_on: {} {}
- wot.min_cert_for_membership: {} {}
- wot.min_cert_for_create_idty_right: {} {}
- identity.confirm_period: {} {}
- identity.change_owner_key_period: {} {}
- identity.idty_creation_period: {} {}
- membership.membership_period: {} {}
- membership.membership_renewal_period: {} {}
- cert.cert_period: {} {}
- cert.max_by_issuer: {} {}
- cert.min_received_cert_to_be_able_to_issue_cert: {} {}
- cert.validity_period: {} {}
- distance.min_accessible_referees: {} {}
- distance.max_depth: {} {},
- smith_members.min_cert_for_membership: {} {}
- smith_members.max_by_issuer: {} {}
- smith_members.smith_inactivity_max_duration: {} {}
- treasury.spend_period: {} {}
- currency decimals: {}",
babe_epoch_duration,
babe_epoch_duration_unit,
babe_expected_block_time,
babe_expected_block_time_unit,
babe_max_authorities,
babe_max_authorities_unit,
timestamp_minimum_period,
timestamp_minimum_period_unit,
balances_existential_deposit,
balances_existential_deposit_unit,
authority_members_max_authorities,
authority_members_max_authorities_unit,
grandpa_max_authorities,
grandpa_max_authorities_unit,
universal_dividend_max_past_reevals,
universal_dividend_max_past_reevals_unit,
universal_dividend_square_money_growth_rate,
universal_dividend_square_money_growth_rate_unit,
universal_dividend_ud_creation_period,
universal_dividend_ud_creation_period_unit,
universal_dividend_ud_reeval_period,
universal_dividend_ud_reeval_period_unit,
wot_first_issuable_on,
wot_first_issuable_on_unit,
wot_min_cert_for_membership,
wot_min_cert_for_membership_unit,
wot_min_cert_for_create_idty_right,
wot_min_cert_for_create_idty_right_unit,
identity_confirm_period,
identity_confirm_period_unit,
identity_change_owner_key_period,
identity_change_owner_key_period_unit,
identity_idty_creation_period,
identity_idty_creation_period_unit,
membership_membership_period,
membership_membership_period_unit,
membership_membership_renewal_period,
membership_membership_renewal_period_unit,
cert_cert_period,
cert_cert_period_unit,
cert_max_by_issuer,
cert_max_by_issuer_unit,
cert_min_received_cert_to_be_able_to_issue_cert,
cert_min_received_cert_to_be_able_to_issue_cert_unit,
cert_validity_period,
cert_validity_period_unit,
distance_min_accessible_referees,
distance_min_accessible_referees_unit,
distance_max_depth,
distance_max_depth_unit,
smith_members_min_cert_for_membership,
smith_members_min_cert_for_membership_unit,
smith_members_max_by_issuer,
smith_members_max_by_issuer_unit,
smith_members_inactivity_max_duration,
smith_members_inactivity_max_duration_unit,
treasury_spend_period,
treasury_spend_period_unit,
info.common_parameters.decimals,
);
}
fn get_best_unit_and_diviser_for_ms(duration_in_ms: f32) -> (f32, String) {
let diviser = get_best_diviser(duration_in_ms);
let qty = duration_in_ms / diviser;
let unit = diviser_to_unit(diviser, qty);
(qty, unit)
}
fn get_best_unit_and_diviser_for_blocks(duration_in_blocks: u32) -> (f32, String) {
let duration_in_ms = duration_in_blocks as f32 * (MILLISECS_PER_BLOCK as u32) as f32;
get_best_unit_and_diviser_for_ms(duration_in_ms)
}
fn get_best_unit_and_diviser_for_sessions(
duration_in_sessions: u32,
babe_epoch_duration_in_blocks: u64,
) -> (f32, String) {
let duration_in_ms = duration_in_sessions as f32
* babe_epoch_duration_in_blocks as f32
* (MILLISECS_PER_BLOCK as u32) as f32;
get_best_unit_and_diviser_for_ms(duration_in_ms)
}
fn get_best_unit_and_diviser_for_perbill_square(qty: Perbill) -> (f32, String) {
let qty = f32::sqrt(qty.deconstruct() as f32 / 1_000_000_000f32) * 100f32;
(qty, "%".to_string())
}
fn get_best_unit_and_diviser_for_perbill(qty: Perbill) -> (f32, String) {
let qty = qty.deconstruct() as f32 / 1_000_000_000f32 * 100f32;
(qty, "%".to_string())
}
fn get_best_unit_and_diviser_for_participants(qty: u32) -> (u32, String) {
(qty, "participants".to_string())
}
fn get_best_unit_and_diviser_for_equinoxes(qty: u32) -> (u32, String) {
(qty, "equinoxes".to_string())
}
fn get_best_unit_and_diviser_for_certs(qty: u32) -> (u32, String) {
(qty, "certs".to_string())
}
fn get_best_unit_and_diviser_for_currency_units(qty: u64, currency_name: String) -> (f64, String) {
let qty = qty as f64 / 100.0;
(qty, currency_name.clone())
}
fn get_best_unit_and_diviser_for_depth(qty: u32) -> (u32, String) {
(qty, "steps".to_string())
}
fn diviser_to_unit(value_in_ms: f32, qty: f32) -> String {
let unit = if value_in_ms >= 24.0 * 3600.0 * 1000.0 {
"day".to_string()
} else if value_in_ms >= 3600.0 * 1000.0 {
"hour".to_string()
} else if value_in_ms >= 60.0 * 1000.0 {
"minute".to_string()
} else { } else {
//total_dust += identity.balance; "second".to_string()
0
}; };
let plural = if qty > 1f32 { "s" } else { "" };
format!("{}{}", unit, plural)
}
fn get_best_diviser(ms_value: f32) -> f32 {
let one_second: f32 = 1000.0;
let one_minute: f32 = one_second * 60.0;
let one_hour: f32 = one_minute * 60.0;
let one_day: f32 = one_hour * 24.0;
if ms_value >= one_day {
one_day
} else if ms_value >= one_hour {
one_hour
} else if ms_value >= one_minute {
one_minute
} else {
one_second
}
}
fn smiths_and_technical_committee_checks(
inactive_identities: &HashMap<u32, (String, IdtyStatus)>,
technical_committee: &Vec<String>,
smiths: &Vec<SmithData>,
) {
// no inactive tech comm
for tech_com_member in technical_committee {
let inactive_commitee_member = inactive_identities
.values()
.any(|(name, _)| name == tech_com_member);
if inactive_commitee_member {
log::error!(
"{} is an inactive technical commitee member",
tech_com_member
);
assert!(!inactive_commitee_member);
}
}
// no inactive smith
for SmithData { name: smith, .. } in smiths {
let inactive_smiths: Vec<_> = inactive_identities
.values()
.filter(|(name, _)| name == smith)
.collect();
inactive_smiths
.iter()
.for_each(|(name, _)| log::warn!("Smith {} is inactive", name));
assert_eq!(inactive_smiths.len(), 0);
}
}
fn create_smith_wot<SK: Decode>(
initial_authorities: &mut BTreeMap<u32, (AccountId32, bool)>,
identities_v2: &HashMap<String, IdentityV2>,
smiths: &Vec<SmithData>,
clique_smiths: &Option<Vec<CliqueSmith>>,
) -> Result<(bool, u32, u32, SmithMembers<SK>), String> {
let mut fatal = false;
let mut counter_online_authorities = 0;
// counter for smith certifications
let mut counter_smith_cert = 0;
let mut smith_certs_by_receiver = BTreeMap::new();
// smith memberships
let mut session_keys_map = BTreeMap::new();
// Then create the smith WoT
for smith in smiths {
// check that smith exists
let identities_v2_clone = identities_v2.clone();
if let Some(identity) = identities_v2.get(&smith.name.clone()) {
counter_online_authorities = set_smith_session_keys_and_authority_status(
initial_authorities,
&mut session_keys_map,
&smith,
identity,
)?;
// smith certifications
counter_smith_cert += feed_smith_certs_by_receiver(
&mut smith_certs_by_receiver,
clique_smiths,
&smith,
identity,
&identities_v2_clone,
)?;
} else {
log::error!(
"Smith '{}' does not correspond to exising identity",
&smith.name
);
fatal = true;
}
}
Ok((
fatal,
counter_online_authorities,
counter_smith_cert,
SmithMembers {
initial_smiths_wot: smith_certs_by_receiver
.iter()
.map(|(k, v)| {
let is_online = initial_authorities
.iter()
.filter(|(k2, (_, online))| *k2 == k && *online)
.count()
> 0;
(*k, (is_online, v.clone()))
})
.collect(),
session_keys_map,
},
))
}
fn v1_wallets_to_v2_accounts(
wallets: BTreeMap<PubkeyV1, u64>,
common_parameters: &CommonParameters,
) -> (
bool,
u64,
BTreeMap<AccountId32, GenesisAccountData<u64, u32>>,
usize,
) {
// monetary mass for double check
let mut monetary_mass = 0u64;
// account inserted in genesis
let mut accounts: BTreeMap<AccountId, GenesisAccountData<u64, u32>> = BTreeMap::new();
let mut invalid_wallets = 0;
let mut fatal = false;
for (pubkey, balance) in wallets {
// check existential deposit
if balance < common_parameters.balances_existential_deposit {
log::error!(
"wallet {pubkey} has {balance} cǦT which is below {}",
common_parameters.balances_existential_deposit
);
fatal = true;
}
// double check the monetary mass
monetary_mass += balance;
// json prevents duplicate wallets
if let Ok(owner_key) = v1_pubkey_to_account_id(pubkey.clone()) {
accounts.insert( accounts.insert(
identity.pubkey.clone(), owner_key.clone(),
GenesisAccountData { GenesisAccountData {
random_id: H256(blake2_256(&(idty_index, &identity.pubkey).encode())),
balance, balance,
is_identity: true, idty_id: None,
}, },
); );
} else {
log::warn!("wallet {pubkey} has wrong format");
invalid_wallets = invalid_wallets.add(1);
}
}
(fatal, monetary_mass, accounts, invalid_wallets)
}
// We must count the money under the existential deposit because what we count is fn check_identities_v2(
// the monetary mass created (for the revaluation of the DU) identities_v2: &HashMap<String, IdentityV2>,
initial_monetary_mass += identity.balance; common_parameters: &CommonParameters,
) {
// // Identities whose membership was lost since export
// identities_v2.iter_mut()
// .filter(|(name, i)| (i.membership_expire_on as u64) < genesis_timestamp)
// .for_each(|(name, i)| {
// log::warn!("{} membership expired since export", name);
// i.membership_expire_on = 0;
// });
// Wot // // Identities that are no more members because of a lack of certs
identities_.push((idty_name.clone(), identity.pubkey.clone())); // identities_v2.iter_mut()
memberships.insert( // .filter(|(name, i)| i.membership_expire_on != 0 && (i.certs_received.len() as u32) < common_parameters.min_cert)
idty_index, // .for_each(|(name, i)| {
MembershipData { // log::warn!("{} lost membership because of lost certifications since export", name);
expire_on: identity // i.membership_expire_on = 0;
.membership_expire_on // });
.map_or(genesis_memberships_expire_on, |expire_on| {
to_bn(genesis_timestamp, expire_on)
}),
},
);
// Identity index // Check that members have enough certs
idty_index_of.insert(idty_name, idty_index); identities_v2
idty_index += 1; .iter()
.filter(|(_, i)| i.membership_expire_on != 0)
.for_each(|(name, i)| {
let nb_certs = i.certs_received.len() as u32;
if nb_certs < common_parameters.wot_min_cert_for_membership {
log::warn!("{} has only {} valid certifications", name, nb_certs);
}
});
} }
// CERTIFICATIONS // fn check_genesis_data_and_filter_expired_certs_since_export(
genesis_data: &mut GenesisMigrationData,
genesis_timestamp: u64,
common_parameters: &CommonParameters,
) {
// Remove expired certs since export
genesis_data
.identities
.iter_mut()
.for_each(|(receiver, i)| {
i.certs_received.retain(|issuer, v| {
let retain = (v.0 as u64) >= genesis_timestamp;
if !retain {
log::warn!("{} -> {} cert expired since export", issuer, receiver);
}
retain
});
});
let mut certs_by_receiver = BTreeMap::new(); genesis_data.identities.iter_mut().for_each(|(name, i)| {
for (idty_name, identity) in &identities { if (i.membership_expire_on.0 as u64) < genesis_timestamp {
let issuer_index = idty_index_of if (i.membership_expire_on.0 as u64) >= genesis_data.current_block.median_time {
.get(&idty_name) log::warn!("{} membership expired since export", name);
.ok_or(format!("Identity '{}' not exist", &idty_name))?;
let mut receiver_certs = BTreeMap::new();
for cert in &identity.certs {
let (issuer, maybe_expire_on) = cert.clone().into();
let issuer_index = idty_index_of
.get(&issuer)
.ok_or(format!("Identity '{}' not exist", issuer))?;
receiver_certs.insert(*issuer_index, maybe_expire_on);
}
certs_by_receiver.insert(*issuer_index, receiver_certs);
}
// Verify certifications coherence
for (idty_index, receiver_certs) in &certs_by_receiver {
if receiver_certs.len() < genesis_certs_min_received as usize {
return Err(format!(
"Identity n°{} has received only {}/{} certifications)",
idty_index,
receiver_certs.len(),
genesis_certs_min_received
));
} }
i.membership_expire_on = TimestampV1(0);
} }
});
// SMITHS SUB-WOT // genesis_data.identities.iter_mut().for_each(|(name, i)| {
if i.membership_expire_on.0 != 0
&& i.certs_received.len() < common_parameters.wot_min_cert_for_membership as usize
{
i.membership_expire_on = TimestampV1(0);
log::warn!(
"{} lost membership because of lost certifications since export",
name
);
}
});
let mut initial_authorities = BTreeMap::new(); genesis_data.identities.iter().for_each(|(name, i)| {
let mut online_authorities_counter = 0; if i.owner_pubkey.is_some() && i.owner_address.is_some() {
let mut session_keys_map = BTreeMap::new(); log::warn!(
let mut smiths_memberships = BTreeMap::new(); "{} both has a pubkey and an address defined - address will be used",
let mut smiths_certs_by_receiver = BTreeMap::new(); name
for (idty_name, smith_data) in smith_identities { );
let idty_index = idty_index_of }
.get(&idty_name) if i.owner_pubkey.is_none() && i.owner_address.is_none() {
.ok_or(format!("Identity '{}' not exist", &idty_name))?; log::error!("{} neither has a pubkey and an address defined", name);
let identity = identities }
.get(&idty_name) });
.ok_or(format!("Identity '{}' not exist", &idty_name))?; }
if identity.balance < EXISTENTIAL_DEPOSIT { fn genesis_data_to_identities_v2(
return Err(format!( genesis_identities: BTreeMap<String, IdentityV1>,
"Identity '{}' have balance '{}' < EXISTENTIAL_DEPOSIT", genesis_timestamp: u64,
idty_name, identity.balance, ) -> HashMap<String, IdentityV2> {
)); genesis_identities
.into_iter()
.map(|(name, i)| {
let legacy_account = i
.owner_pubkey
.map(|pubkey| {
v1_pubkey_to_account_id(pubkey)
.expect("a G1 identity necessarily has a valid pubkey")
})
.unwrap_or_else(|| {
i.owner_address.unwrap_or_else(|| {
panic!("neither pubkey nor address is defined for {}", name)
})
});
let owner_key = legacy_account.clone();
(
name,
IdentityV2 {
index: i.index,
owner_key,
membership_expire_on: timestamp_to_relative_blocs(
i.membership_expire_on,
genesis_timestamp,
),
identity_revoke_on: timestamp_to_relative_blocs(
i.membership_revokes_on,
genesis_timestamp,
),
revoked: i.revoked,
balance: i.balance,
certs_received: i
.certs_received
.into_iter()
.map(|(issuer, timestamp)| {
(
issuer,
timestamp_to_relative_blocs(timestamp, genesis_timestamp),
)
})
.collect(),
},
)
})
.collect()
}
fn make_authority_exist<SessionKeys: Encode, SKP: SessionKeysProvider<SessionKeys>>(
identities_v2: &mut HashMap<String, IdentityV2>,
smiths: &mut Vec<RawSmith>,
common_parameters: &CommonParameters,
authority_name: &String,
) {
// The identity might already exist, notably: G1 "Alice" already exists
if let Some(authority) = identities_v2.get_mut(authority_name) {
// Force authority to be active
authority.membership_expire_on = common_parameters.membership_membership_period;
} else {
// Not found: we must create it
identities_v2.insert(
authority_name.clone(),
IdentityV2 {
index: (identities_v2.len() as u32 + 1),
owner_key: get_account_id_from_seed::<sr25519::Public>(authority_name),
balance: common_parameters.balances_existential_deposit,
certs_received: HashMap::new(),
// note: in this context of generating genesis identities
membership_expire_on: common_parameters.membership_membership_period,
identity_revoke_on: common_parameters.membership_membership_period,
revoked: false,
},
);
};
// Forced authority gets its required certs from first "minCert" WoT identities (fake certs)
let mut new_certs: HashMap<String, u32> = HashMap::new();
let certs_of_authority = &identities_v2.get(authority_name).unwrap().certs_received;
identities_v2
.keys()
// Identities which are not the authority and have not already certified her
.filter(|issuer| {
issuer != &authority_name
&& !certs_of_authority
.iter()
.any(|(authority_issuer, _)| issuer == &authority_issuer)
})
.take(common_parameters.wot_min_cert_for_membership as usize)
.map(String::clone)
.for_each(|issuer| {
new_certs.insert(issuer, common_parameters.cert_cert_period);
});
let authority = identities_v2
.get_mut(authority_name)
.expect("authority must exist or be created");
new_certs.into_iter().for_each(|(issuer, c)| {
authority.certs_received.insert(issuer, c);
});
let sk: SessionKeys = SKP::session_keys(&get_authority_keys_from_seed(authority_name.as_str()));
let forced_authority_session_keys = format!("0x{}", hex::encode(sk.encode()));
// Add forced authority to smiths (whether explicit smith WoT or clique)
if let Some(smith) = smiths.iter_mut().find(|s| &s.name == authority_name) {
smith.session_keys = Some(forced_authority_session_keys);
} else {
smiths.push(RawSmith {
name: authority_name.clone(),
session_keys: Some(forced_authority_session_keys),
certs_received: vec![],
})
}
} }
// Initial authorities fn feed_identities(
if maybe_force_authority.is_some() { accounts: &mut BTreeMap<AccountId32, GenesisAccountData<u64, u32>>,
if smith_data.session_keys.is_some() { identity_index: &mut HashMap<u32, String>,
return Err("session_keys field forbidden".to_owned()); monetary_mass: &mut u64,
inactive_identities: &mut HashMap<u32, (String, IdtyStatus)>,
memberships: &mut BTreeMap<u32, MembershipData>,
identities_v2: &HashMap<String, IdentityV2>,
common_parameters: &CommonParameters,
) -> Result<(bool, Vec<GenesisIdentity>), String> {
let mut fatal = false;
let mut identities: Vec<GenesisIdentity> = Vec::new();
for (name, identity) in identities_v2 {
// identity name
if !validate_idty_name(name) {
return Err(format!("Identity name '{}' is invalid", &name));
}
// Money
// check that wallet with same owner_key does not exist
if accounts.get(&identity.owner_key).is_some() {
log::error!(
"{name} owner_key {} already exists as a simple wallet",
identity.owner_key
);
fatal = true;
} }
if *idty_index == 1 { // insert as an account
initial_authorities.insert(1, (identity.pubkey.clone(), true)); accounts.insert(
identity.owner_key.clone(),
GenesisAccountData {
balance: identity.balance,
idty_id: Some(identity.index),
},
);
// double check the monetary mass
*monetary_mass += identity.balance;
// insert identity
// check that index does not already exist
if let Some(other_name) = identity_index.get(&identity.index) {
log::error!(
"{other_name} already has identity index {} of {name}",
identity.index
);
fatal = true;
} }
identity_index.insert(identity.index, name.to_owned());
let expired = identity.membership_expire_on == 0;
// only add the identity if not expired
if expired {
inactive_identities.insert(
identity.index,
(
name.clone(),
if identity.revoked {
IdtyStatus::Revoked
} else { } else {
initial_authorities.insert( IdtyStatus::NotMember
*idty_index, },
(identity.pubkey.clone(), smith_data.session_keys.is_some()), ),
); );
};
let status = match (identity.revoked, expired) {
(true, _) => IdtyStatus::Revoked,
(false, true) => IdtyStatus::NotMember,
(false, false) => IdtyStatus::Member,
};
identities.push(GenesisIdentity {
// N.B.: every **non-expired** identity on Genesis is considered to have:
// - removable_on: 0,
// - next_creatable_identity_on: 0,
idty_index: identity.index,
name: name.to_owned().clone(),
owner_key: identity.owner_key.clone(),
// but expired identities will just have their pseudonym reserved in the storage
status,
next_scheduled: match status {
IdtyStatus::Unconfirmed | IdtyStatus::Unvalidated => {
// these intermediary formats are disallowed in the genesis
// since they correspond to offchain v1 data
panic!("Unconfirmed or Unvalidated identity in genesis")
}
// Member identities schedule is managed by membership pallet
IdtyStatus::Member => 0,
// The identity will be scheduled for revocation after the auto-revocation period.
IdtyStatus::NotMember => common_parameters.identity_autorevocation_period,
// The identity will be scheduled for removal at the revoke block plus the deletion period.
IdtyStatus::Revoked => {
identity.identity_revoke_on + common_parameters.identity_deletion_period
} }
},
});
// Session keys // insert the membership data (only if not expired)
let session_keys_bytes = if let Some(ref session_keys) = smith_data.session_keys { if !expired {
online_authorities_counter += 1; memberships.insert(
hex::decode(&session_keys[2..]) identity.index,
.map_err(|_| format!("invalid session keys for idty {}", &idty_name))? MembershipData {
} else if let (1, Some(ref session_keys_bytes)) = (*idty_index, &maybe_force_authority) { // here we are using the correct expire block
session_keys_bytes.clone() expire_on: identity.membership_expire_on,
},
);
}
}
// sort the identities by index for reproducibility (should have been a vec in json)
identities.sort_unstable_by(|a, b| a.idty_index.cmp(&b.idty_index));
Ok((fatal, identities))
}
fn set_smith_session_keys_and_authority_status<SK>(
initial_authorities: &mut BTreeMap<
u32,
(
<<MultiSignature as Verify>::Signer as IdentifyAccount>::AccountId,
bool,
),
>,
session_keys_map: &mut BTreeMap<
<<MultiSignature as Verify>::Signer as IdentifyAccount>::AccountId,
SK,
>,
smith: &&SmithData,
identity: &IdentityV2,
) -> Result<u32, String>
where
SK: Decode,
{
let mut counter_online_authorities = 0;
// Initial authorities and session keys
let session_keys_bytes = if let Some(declared_session_keys) = &smith.session_keys {
counter_online_authorities += 1;
// insert authority as online
initial_authorities.insert(identity.index, (identity.owner_key.clone(), true));
// decode session keys or force to given value
hex::decode(&declared_session_keys[2..])
.map_err(|_| format!("invalid session keys for idty {}", smith.name))?
} else { } else {
// Create fake session keys (must be unique and deterministic) // still authority but offline
let mut fake_session_keys_bytes = Vec::with_capacity(128); initial_authorities.insert(identity.index, (identity.owner_key.clone(), false));
// fake session keys
let mut fake_bytes = Vec::with_capacity(128);
for _ in 0..4 { for _ in 0..4 {
fake_session_keys_bytes.extend_from_slice(identity.pubkey.as_ref()) fake_bytes.extend_from_slice(identity.owner_key.as_ref())
} }
fake_session_keys_bytes fake_bytes
//vec![initial_authorities.len() as u8; std::mem::size_of::<SK>()]
}; };
// insert session keys to map
session_keys_map.insert( session_keys_map.insert(
identity.pubkey.clone(), identity.owner_key.clone(),
SK::decode(&mut &session_keys_bytes[..]) SK::decode(&mut &session_keys_bytes[..]).unwrap(),
.map_err(|_| format!("invalid session keys for idty {}", &idty_name))?,
); );
// Certifications Ok(counter_online_authorities)
let mut receiver_certs = BTreeMap::new();
for cert in &smith_data.certs {
let (issuer, maybe_expire_on) = cert.clone().into();
let issuer_index = idty_index_of
.get(&issuer)
.ok_or(format!("Identity '{}' not exist", issuer))?;
receiver_certs.insert(*issuer_index, maybe_expire_on);
} }
smiths_certs_by_receiver.insert(*idty_index, receiver_certs);
// Memberships fn feed_smith_certs_by_receiver(
smiths_memberships.insert( smith_certs_by_receiver: &mut BTreeMap<u32, Vec<u32>>,
*idty_index, clique_smiths: &Option<Vec<CliqueSmith>>,
MembershipData { smith: &&SmithData,
expire_on: genesis_smith_memberships_expire_on, identity: &IdentityV2,
}, identities_v2: &HashMap<String, IdentityV2>,
); ) -> Result<u32, String> {
let mut counter_smith_cert = 0;
let mut certs = Vec::<u32>::new();
if clique_smiths.is_some() {
// All initial smiths are considered to be certifying all each other
clique_smiths
.as_ref()
.unwrap()
.iter()
.filter(|other_smith| *other_smith.name.as_str() != *smith.name)
.for_each(|other_smith| {
let issuer_index = &identities_v2
.get(other_smith.name.as_str())
.unwrap_or_else(|| {
panic!("Identity '{}' does not exist", other_smith.name.as_str())
})
.index;
certs.push(*issuer_index);
counter_smith_cert += 1;
});
} else {
for issuer in &smith.certs_received {
let issuer_index = &identities_v2
.get(issuer)
.ok_or(format!("Identity '{}' does not exist", issuer))?
.index;
certs.push(*issuer_index);
counter_smith_cert += 1;
}
}
smith_certs_by_receiver.insert(identity.index, certs);
Ok(counter_smith_cert)
} }
// Verify smiths certifications coherence fn feed_certs_by_receiver(
if smiths_certs_by_receiver.len() < smiths_memberships.len() { certs_by_receiver: &mut BTreeMap<u32, BTreeMap<u32, Option<u32>>>,
return Err(format!( identities_v2: &HashMap<String, IdentityV2>,
"{} smith identities has not received any smiths certifications", ) -> (bool, u32) {
smiths_memberships.len() - smiths_certs_by_receiver.len() let mut fatal = false;
)); let mut counter_cert = 0;
for identity in identities_v2.values() {
let mut certs = BTreeMap::new();
for (issuer, expire_on) in &identity.certs_received {
if let Some(issuer) = &identities_v2.get(issuer) {
certs.insert(issuer.index, Some(*expire_on));
counter_cert += 1;
} else {
log::error!("Identity '{}' does not exist", issuer);
fatal = true;
};
} }
for (idty_index, receiver_certs) in &smiths_certs_by_receiver { certs_by_receiver.insert(identity.index, certs);
if receiver_certs.len() < genesis_smith_certs_min_received as usize { }
return Err(format!( (fatal, counter_cert)
"Identity n°{} has received only {}/{} smiths certifications)", }
idty_index,
receiver_certs.len(), fn check_authority_exists_in_both_wots(
genesis_smith_certs_min_received name: &String,
)); identities_v2: &HashMap<String, IdentityV2>,
smiths: &[RawSmith],
) {
identities_v2
.get(name)
.ok_or(format!("Identity '{}' not exist", name))
.expect("Initial authority must have an identity");
smiths
.iter()
.find(|smith| &smith.name == name)
.expect("Forced authority must be present in smiths");
}
fn build_smiths_wot(
clique_smiths: &Option<Vec<CliqueSmith>>,
smith_identities: Option<BTreeMap<String, RawSmith>>,
) -> Result<Vec<RawSmith>, String> {
if smith_identities.is_some() && clique_smiths.is_some() {
return Err(
"'smiths' and 'clique_smiths' cannot be both defined at the same time".to_string(),
);
} }
// Create a single source of smiths
let smiths = if let Some(clique) = &clique_smiths {
// From a clique
clique
.iter()
.map(|smith| RawSmith {
name: smith.name.clone(),
session_keys: smith.session_keys.clone(),
certs_received: vec![],
})
.collect::<Vec<RawSmith>>()
} else {
// From explicit smith WoT
smith_identities
.expect("existence has been tested earlier")
.into_values()
.collect::<Vec<RawSmith>>()
};
Ok(smiths)
} }
if maybe_force_authority.is_none() && online_authorities_counter == 0 { fn decorate_smiths_with_identity(
return Err("The session_keys field must be filled in for at least one smith.".to_owned()); smiths: Vec<RawSmith>,
identity_index: &HashMap<u32, String>,
identities_v2: &HashMap<String, IdentityV2>,
) -> Vec<SmithData> {
smiths
.into_iter()
.map(|smith| SmithData {
idty_index: identity_index
.iter()
.find(|(_, v)| ***v == smith.name)
.map(|(k, _)| *k)
.expect("smith must have an identity"),
account: identities_v2
.get(smith.name.as_str())
.map(|i| i.owner_key.clone())
.expect("identity must exist"),
name: smith.name,
session_keys: smith.session_keys,
certs_received: smith.certs_received,
})
.collect()
} }
pub fn generate_genesis_data_for_local_chain<P, SK, SessionKeys: Encode, SKP>(
initial_authorities_len: usize,
initial_smiths_len: usize,
initial_identities_len: usize,
treasury_balance: u64,
parameters: Option<P>,
root_key: AccountId,
get_common_parameters: fn(&Option<P>) -> CommonParameters,
) -> Result<GenesisData<P, SK>, String>
where
P: Default + DeserializeOwned,
SK: Decode,
SKP: SessionKeysProvider<SessionKeys>,
{
// For benchmarking, the total length of identities should be at least MinReceivedCertToBeAbleToIssueCert + 1
assert!(initial_identities_len <= 6);
assert!(initial_smiths_len <= initial_identities_len);
assert!(initial_authorities_len <= initial_smiths_len);
let ud = 1_000;
let idty_index_start: u32 = 1;
let common_parameters = get_common_parameters(&parameters);
let names: [&str; 6] = ["Alice", "Bob", "Charlie", "Dave", "Eve", "Ferdie"];
let initial_idty_balance = 1_000 * ud;
let initial_smiths = (0..initial_smiths_len)
.map(|i| get_authority_keys_from_seed(names[i]))
.collect::<Vec<AuthorityKeys>>();
let initial_identities = (0..initial_identities_len)
.map(|i| {
(
IdtyName::from(names[i]),
get_account_id_from_seed::<sr25519::Public>(names[i]),
)
})
.collect::<BTreeMap<IdtyName, AccountId>>();
let mut session_keys_map = BTreeMap::new();
initial_smiths.iter().for_each(|x| {
let session_keys_bytes = SKP::session_keys(x).encode();
let sk = SK::decode(&mut &session_keys_bytes[..])
.map_err(|_| format!("invalid session keys for idty {}", x.0.clone()))
.unwrap();
session_keys_map.insert(x.0.clone(), sk);
});
let identities: Vec<GenesisIdentity> = initial_identities
.iter()
.enumerate()
.map(|(i, (name, owner_key))| GenesisIdentity {
idty_index: i as u32 + idty_index_start,
name: String::from_utf8(name.0.clone()).unwrap(),
owner_key: owner_key.clone(),
status: IdtyStatus::Member,
next_scheduled: 0,
})
.collect();
let (certs_by_receiver, counter_cert) = clique_wot(initial_identities.len());
let accounts = initial_identities
.iter()
.enumerate()
.map(|(i, (_, owner_key))| {
(
owner_key.clone(),
GenesisAccountData {
// Should be sufficient for the overhead benchmark
balance: initial_idty_balance,
idty_id: Some(i as u32 + idty_index_start),
},
)
})
.collect();
let identity_index = identities
.iter()
.map(|i| (i.idty_index, i.name.clone()))
.collect();
let genesis_data_wallets_count = 0;
let inactive_identities: HashMap<u32, (String, IdtyStatus)> = HashMap::new();
let initial_smiths_wot: BTreeMap<u32, (bool, Vec<u32>)> = clique_smith_wot(initial_smiths_len);
let counter_smith_cert = initial_smiths_wot
.iter()
.map(|(_, (_, v))| v.len())
.sum::<usize>() as u32;
let initial_authorities: BTreeMap<
u32,
(
<<MultiSignature as Verify>::Signer as IdentifyAccount>::AccountId,
bool,
),
> = initial_smiths
.iter()
.enumerate()
.map(|(i, keys)| {
(
i as u32 + idty_index_start,
(keys.0.clone(), i < initial_authorities_len),
)
})
.collect();
let counter_online_authorities = initial_authorities
.iter()
.filter(|(_, authority)| authority.1)
.count() as u32;
let technical_committee_members = initial_smiths
.iter()
.map(|x| x.0.clone())
.collect::<Vec<_>>();
let genesis_timestamp: u64 = get_genesis_timestamp()?;
let genesis_info = GenesisInfo {
genesis_timestamp,
accounts: &accounts,
genesis_data_wallets_count: &genesis_data_wallets_count,
identities: &identities,
inactive_identities: &inactive_identities,
identity_index: &identity_index,
initial_smiths: &initial_smiths_wot,
counter_online_authorities: &counter_online_authorities,
counter_cert: &counter_cert,
counter_smith_cert: &counter_smith_cert,
technical_committee_members: &technical_committee_members,
common_parameters: &common_parameters,
};
dump_genesis_info(genesis_info);
let genesis_data = GenesisData { let genesis_data = GenesisData {
accounts, accounts,
// Treasury balance is created out of nothing for local blockchain
treasury_balance,
certs_by_receiver, certs_by_receiver,
first_ud, first_ud: None,
first_ud_reeval, first_ud_reeval: None,
identities: identities_, identities,
initial_authorities, initial_authorities,
initial_monetary_mass, initial_monetary_mass: initial_identities_len as u64 * initial_idty_balance,
memberships, // when generating data for local chain, we can set membersip expiration to membership period
memberships: (1..=initial_identities.len())
.map(|i| {
(
i as u32,
MembershipData {
expire_on: common_parameters.membership_membership_period,
},
)
})
.collect(),
parameters, parameters,
common_parameters: None,
session_keys_map, session_keys_map,
smiths_certs_by_receiver, initial_smiths: initial_smiths_wot,
smiths_memberships, sudo_key: Some(root_key),
sudo_key,
technical_committee_members, technical_committee_members,
ud,
};
Ok(genesis_data)
}
fn clique_wot(
initial_identities_len: usize,
) -> (
BTreeMap<IdtyIndex, BTreeMap<IdtyIndex, Option<common_runtime::BlockNumber>>>,
u32,
) {
let mut certs_by_issuer = BTreeMap::new();
let mut count: u32 = 0;
for i in 1..=initial_identities_len {
count += initial_identities_len as u32;
certs_by_issuer.insert(
i as IdtyIndex,
(1..=initial_identities_len)
.filter_map(|j| {
if i != j {
Some((j as IdtyIndex, None))
} else {
None
}
})
.collect(),
);
}
(certs_by_issuer, count)
}
fn clique_smith_wot(initial_identities_len: usize) -> BTreeMap<IdtyIndex, (bool, Vec<IdtyIndex>)> {
let mut certs_by_issuer = BTreeMap::new();
for i in 1..=initial_identities_len {
certs_by_issuer.insert(
i as IdtyIndex,
(
true,
(1..=initial_identities_len)
.filter_map(|j| if i != j { Some(j as IdtyIndex) } else { None })
.collect(),
),
);
}
certs_by_issuer
}
fn check_parameters_consistency(
wallets: &BTreeMap<PubkeyV1, u64>,
first_ud: &Option<u64>,
first_reeval: &Option<u64>,
ud: &u64,
) -> Result<(), String> {
// No empty wallet
if let Some((account, _)) = wallets.iter().find(|(_, amount)| **amount == 0) {
return Err(format!("Wallet {} is empty", account));
}
if let (Some(first_ud), Some(first_reeval)) = (first_ud, first_reeval) {
if first_ud > first_reeval {
return Err(format!(
"`first_ud` ({}) should be lower than `first_ud_reeval` ({})",
first_ud, first_reeval
));
}
}
if *ud == 0 {
return Err("`ud` is expected to be > 0".to_owned());
}
Ok(())
}
fn get_genesis_input<P: Default + DeserializeOwned>(
config_file_path: String,
) -> Result<GenesisInput<P>, String> {
// We mmap the file into memory first, as this is *a lot* faster than using
// `serde_json::from_reader`. See https://github.com/serde-rs/json/issues/160
let file = std::fs::File::open(&config_file_path)
.map_err(|e| format!("Error opening gen conf file `{}`: {}", config_file_path, e))?;
// SAFETY: `mmap` is fundamentally unsafe since technically the file can change
// underneath us while it is mapped; in practice it's unlikely to be a problem
let bytes = unsafe {
memmap2::Mmap::map(&file)
.map_err(|e| format!("Error mmaping gen conf file `{}`: {}", config_file_path, e))?
}; };
if config_file_path.ends_with(".json") {
serde_json::from_slice::<GenesisInput<P>>(&bytes)
.map_err(|e| format!("Error parsing JSON gen conf file: {}", e))
} else {
serde_yaml::from_slice::<GenesisInput<P>>(&bytes)
.map_err(|e| format!("Error parsing YAML gen conf file: {}", e))
}
}
Ok(f(genesis_data)) fn get_genesis_migration_data() -> Result<GenesisMigrationData, String> {
let json_file_path = std::env::var("DUNITER_GENESIS_DATA")
.unwrap_or_else(|_| "./resources/g1-data.json".to_owned());
let file = std::fs::File::open(&json_file_path).map_err(|e| {
format!(
"Error opening gen migration file `{}`: {}",
json_file_path, e
)
})?;
let bytes = unsafe {
memmap2::Mmap::map(&file).map_err(|e| {
format!(
"Error mmaping gen migration file `{}`: {}",
json_file_path, e
)
})?
};
serde_json::from_slice::<GenesisMigrationData>(&bytes)
.map_err(|e| format!("Error parsing gen migration file: {}", e))
} }
// Timestamp to block number fn get_genesis_timestamp() -> Result<u64, String> {
fn to_bn(genesis_timestamp: u64, timestamp: u64) -> u32 { if let Ok(genesis_timestamp) = std::env::var("DUNITER_GENESIS_TIMESTAMP") {
let duration_in_secs = timestamp.saturating_sub(genesis_timestamp); genesis_timestamp
(duration_in_secs / 6) as u32 .parse()
.map_err(|_| "DUNITER_GENESIS_TIMESTAMP must be a number".to_owned())
} else {
use std::time::SystemTime;
Ok(SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.expect("SystemTime before UNIX EPOCH!")
.as_secs())
}
} }
fn validate_idty_name(name: &str) -> bool { fn validate_idty_name(name: &str) -> bool {
name.len() <= 64 name.len() <= 64
} }
pub type AuthorityKeys = (
AccountId,
GrandpaId,
BabeId,
ImOnlineId,
AuthorityDiscoveryId,
);
/// Because SessionKeys struct is defined by each Runtime, it cannot be constructed here.
/// Its construction must be provided.
pub trait SessionKeysProvider<SessionKeys: Encode> {
fn session_keys(keys: &AuthorityKeys) -> SessionKeys;
}
#[derive(Default, Deserialize, Serialize, Clone)]
pub struct CommonParameters {
pub babe_epoch_duration: u64,
pub babe_expected_block_time: u64,
pub babe_max_authorities: u32,
pub timestamp_minimum_period: u64,
pub balances_existential_deposit: u64,
pub authority_members_max_authorities: u32,
pub grandpa_max_authorities: u32,
pub universal_dividend_max_past_reevals: u32,
pub universal_dividend_square_money_growth_rate: Perbill,
pub universal_dividend_ud_creation_period: u64,
pub universal_dividend_ud_reeval_period: u64,
pub wot_first_issuable_on: u32,
pub wot_min_cert_for_membership: u32,
pub wot_min_cert_for_create_idty_right: u32,
pub identity_confirm_period: u32,
pub identity_change_owner_key_period: u32,
pub identity_idty_creation_period: u32,
pub identity_autorevocation_period: u32,
pub identity_deletion_period: u32,
pub membership_membership_period: u32,
pub membership_membership_renewal_period: u32,
pub cert_cert_period: u32,
pub cert_max_by_issuer: u32,
pub cert_min_received_cert_to_be_able_to_issue_cert: u32,
pub cert_validity_period: u32,
pub distance_min_accessible_referees: Perbill,
pub distance_max_depth: u32,
pub smith_sub_wot_min_cert_for_membership: u32,
pub smith_cert_max_by_issuer: u32,
pub smith_inactivity_max_duration: u32,
pub treasury_spend_period: u32,
// TODO: replace u32 by BlockNumber when appropriate
pub currency_name: String,
pub decimals: usize,
}
/// Generate an authority keys.
fn get_authority_keys_from_seed(s: &str) -> AuthorityKeys {
(
get_account_id_from_seed::<sr25519::Public>(s),
get_from_seed::<GrandpaId>(s),
get_from_seed::<BabeId>(s),
get_from_seed::<ImOnlineId>(s),
get_from_seed::<AuthorityDiscoveryId>(s),
)
}
/// Converts a Duniter v1 public key (Ed25519) to an Account Id.
/// No need to convert to address.
fn v1_pubkey_to_account_id(pubkey: PubkeyV1) -> Result<AccountId, String> {
let bytes = bs58::decode(pubkey.0)
.into_vec()
.expect("Duniter v1 pubkey should be decodable");
if bytes.len() > 32 {
return Err("Pubkey is too long".to_string());
}
let prepend = vec![0u8; 32 - &bytes.len()];
let bytes: [u8; 32] = [prepend.as_slice(), bytes.as_slice()]
.concat()
.as_slice()
.try_into()
.expect("incorrect pubkey length");
Ok(AccountPublic::from(ed25519::Public::from_raw(bytes)).into_account())
}
fn timestamp_to_relative_blocs(timestamp: TimestampV1, start: u64) -> u32 {
let diff = (timestamp.0 as u64).saturating_sub(start);
seconds_to_blocs(diff as u32)
}
/// Converts a number of seconds to a number of 6-seconds blocs
/// use lower approximation
/// example : 2 seconds will be block 0
/// example : 7 seconds will be block 1
fn seconds_to_blocs(seconds: u32) -> u32 {
seconds / 6
}
#[cfg(test)]
mod tests {
use super::*;
use sp_core::{
crypto::{Ss58AddressFormat, Ss58Codec},
ByteArray,
};
use std::str::FromStr;
#[test]
fn test_timestamp_to_relative_blocs() {
assert_eq!(seconds_to_blocs(2), 0);
assert_eq!(seconds_to_blocs(6), 1);
assert_eq!(seconds_to_blocs(7), 1);
}
#[test]
fn test_v1_pubkey_to_v2_address_translation() {
assert_eq!(
v1_pubkey_to_account_id(PubkeyV1(
"2ny7YAdmzReQxAayyJZsyVYwYhVyax2thKcGknmQy5nQ".to_string()
))
.unwrap()
.to_ss58check_with_version(Ss58AddressFormat::custom(42)),
"5CfdJjEgh3jDkg3bzmZ1ED1xVhXAARtNmZJWbcXh53rU8z5a".to_owned()
);
}
#[test]
fn test_pubkey_with_33_bytes() {
assert_eq!(
v1_pubkey_to_account_id(PubkeyV1(
"d2meevcahfts2gqmvmrw5hzi25jddikk4nc4u1fkwrau".to_string()
)),
Err("Pubkey is too long".to_owned())
);
}
#[test]
fn test_address_to_pubkey_v1() {
let account = AccountId::from_str("5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY")
.expect("valid address");
let pubkey = bs58::encode(account.as_slice()).into_vec();
let pubkey = String::from_utf8(pubkey).expect("valid conversion");
assert_eq!(pubkey, "FHNpKmJrUtusuvKPGomAygQqeiks98bdV6yD61Stb6vg");
}
}
...@@ -15,132 +15,84 @@ ...@@ -15,132 +15,84 @@
// along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>. // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
use super::*; use super::*;
use common_runtime::constants::*; use crate::chain_spec::gen_genesis_data::{CommonParameters, GenesisIdentity, SessionKeysProvider};
use common_runtime::entities::IdtyData; use common_runtime::{constants::*, entities::IdtyData, GenesisIdty};
use common_runtime::*;
use gtest_runtime::{ use gtest_runtime::{
opaque::SessionKeys, AccountConfig, AccountId, AuthorityMembersConfig, BabeConfig, opaque::SessionKeys, pallet_universal_dividend, parameters, ImOnlineId, Runtime, WASM_BINARY,
BalancesConfig, CertConfig, GenesisConfig, IdentityConfig, IdtyValue, ImOnlineId,
MembershipConfig, SessionConfig, SmithsCertConfig, SmithsMembershipConfig, SudoConfig,
SystemConfig, TechnicalCommitteeConfig, UniversalDividendConfig, WASM_BINARY,
}; };
use jsonrpsee::core::JsonValue;
use sc_consensus_grandpa::AuthorityId as GrandpaId;
use sc_network::config::MultiaddrWithPeerId;
use sc_service::ChainType; use sc_service::ChainType;
use sc_telemetry::TelemetryEndpoints;
use serde::Deserialize;
use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId; use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId;
use sp_consensus_babe::AuthorityId as BabeId; use sp_consensus_babe::AuthorityId as BabeId;
use sp_core::{blake2_256, sr25519, Encode, H256}; use sp_core::{sr25519, Get};
use sp_finality_grandpa::AuthorityId as GrandpaId; use std::{env, fs};
use sp_membership::MembershipData;
use std::collections::BTreeMap;
pub type ChainSpec = sc_service::GenericChainSpec;
pub type AuthorityKeys = ( pub type AuthorityKeys = (
AccountId, AccountId,
BabeId,
GrandpaId, GrandpaId,
BabeId,
ImOnlineId, ImOnlineId,
AuthorityDiscoveryId, AuthorityDiscoveryId,
); );
pub type ChainSpec = sc_service::GenericChainSpec<GenesisConfig>;
const TOKEN_DECIMALS: usize = 2; const TOKEN_DECIMALS: usize = 2;
const TOKEN_SYMBOL: &str = "ĞT"; const TOKEN_SYMBOL: &str = "ĞT";
// The URL for the telemetry server. static EXISTENTIAL_DEPOSIT: u64 = parameters::ExistentialDeposit::get();
// const STAGING_TELEMETRY_URL: &str = "wss://telemetry.polkadot.io/submit/";
/// Generate an authority keys.
pub fn get_authority_keys_from_seed(s: &str) -> AuthorityKeys {
(
get_account_id_from_seed::<sr25519::Public>(s),
get_from_seed::<BabeId>(s),
get_from_seed::<GrandpaId>(s),
get_from_seed::<ImOnlineId>(s),
get_from_seed::<AuthorityDiscoveryId>(s),
)
}
pub fn development_chain_spec() -> Result<ChainSpec, String> { #[derive(Default, Clone, Deserialize)]
let wasm_binary = WASM_BINARY.ok_or_else(|| "wasm not available".to_string())?; // No parameters for GTest (unlike GDev)
struct GenesisParameters {}
Ok(ChainSpec::from_genesis( struct GTestSKP;
// Name impl SessionKeysProvider<SessionKeys> for GTestSKP {
"Ğtest Development", fn session_keys(keys: &AuthorityKeys) -> SessionKeys {
// ID let cloned = keys.clone();
"gtest_dev", SessionKeys {
ChainType::Development, grandpa: cloned.1,
move || { babe: cloned.2,
gen_genesis_for_local_chain( im_online: cloned.3,
wasm_binary, authority_discovery: cloned.4,
// Initial authorities len }
1, }
// Initial smiths members len
3,
// Inital identities len
4,
// Sudo account
get_account_id_from_seed::<sr25519::Public>("Alice"),
true,
)
},
// Bootnodes
vec![],
// Telemetry
None,
// Protocol ID
None,
//Fork ID
None,
// Properties
Some(
serde_json::json!({
"tokenDecimals": TOKEN_DECIMALS,
"tokenSymbol": TOKEN_SYMBOL,
})
.as_object()
.expect("must be a map")
.clone(),
),
// Extensions
None,
))
} }
/// generate local network chainspects
pub fn local_testnet_config( pub fn local_testnet_config(
initial_authorities_len: usize, initial_authorities_len: usize,
initial_smiths_len: usize, initial_smiths_len: usize,
initial_identities_len: usize, initial_identities_len: usize,
) -> Result<ChainSpec, String> { ) -> Result<ChainSpec, String> {
let wasm_binary = WASM_BINARY.ok_or_else(|| "wasm not available".to_string())?; Ok(ChainSpec::builder(
&get_wasm_binary().ok_or_else(|| "Development wasm not available".to_string())?,
Ok(ChainSpec::from_genesis( None,
// Name )
"Ğtest Local Testnet", .with_name("ĞTest Local Testnet")
// ID .with_id("gtest_local")
"gtest_local", .with_chain_type(ChainType::Local)
ChainType::Local, .with_genesis_config_patch({
move || { let genesis_data =
gen_genesis_for_local_chain( gen_genesis_data::generate_genesis_data_for_local_chain::<_, _, SessionKeys, GTestSKP>(
wasm_binary,
// Initial authorities len // Initial authorities len
initial_authorities_len, initial_authorities_len,
// Initial smiths len, // Initial smiths len,
initial_smiths_len, initial_smiths_len,
// Initial identities len // Initial identities len
initial_identities_len, initial_identities_len,
EXISTENTIAL_DEPOSIT,
None,
// Sudo account // Sudo account
get_account_id_from_seed::<sr25519::Public>("Alice"), get_account_id_from_seed::<sr25519::Public>("Alice"),
true, get_parameters,
) )
}, .expect("Genesis Data must be buildable");
// Bootnodes genesis_data_to_gtest_genesis_conf(genesis_data)
vec![], })
// Telemetry .with_properties(
None,
// Protocol ID
None,
//Fork ID
None,
// Properties
Some(
serde_json::json!({ serde_json::json!({
"tokenDecimals": TOKEN_DECIMALS, "tokenDecimals": TOKEN_DECIMALS,
"tokenSymbol": TOKEN_SYMBOL, "tokenSymbol": TOKEN_SYMBOL,
...@@ -148,168 +100,238 @@ pub fn local_testnet_config( ...@@ -148,168 +100,238 @@ pub fn local_testnet_config(
.as_object() .as_object()
.expect("must be a map") .expect("must be a map")
.clone(), .clone(),
), )
// Extensions .build())
None,
))
} }
fn gen_genesis_for_local_chain( fn get_parameters(_: &Option<GenesisParameters>) -> CommonParameters {
wasm_binary: &[u8], CommonParameters {
initial_authorities_len: usize, currency_name: TOKEN_SYMBOL.to_string(),
initial_smiths_len: usize, decimals: TOKEN_DECIMALS,
initial_identities_len: usize, babe_epoch_duration: parameters::EpochDuration::get(),
root_key: AccountId, babe_expected_block_time: parameters::ExpectedBlockTime::get(),
_enable_println: bool, babe_max_authorities: parameters::MaxAuthorities::get(),
) -> GenesisConfig { timestamp_minimum_period: parameters::MinimumPeriod::get(),
assert!(initial_identities_len <= 6); balances_existential_deposit: parameters::ExistentialDeposit::get(),
assert!(initial_smiths_len <= initial_identities_len); authority_members_max_authorities: parameters::MaxAuthorities::get(),
assert!(initial_authorities_len <= initial_smiths_len); grandpa_max_authorities: parameters::MaxAuthorities::get(),
universal_dividend_max_past_reevals:
<Runtime as pallet_universal_dividend::Config>::MaxPastReeval::get(),
universal_dividend_square_money_growth_rate: parameters::SquareMoneyGrowthRate::get(),
universal_dividend_ud_creation_period: parameters::UdCreationPeriod::get() as u64,
universal_dividend_ud_reeval_period: parameters::UdReevalPeriod::get() as u64,
wot_first_issuable_on: parameters::WotFirstCertIssuableOn::get(),
wot_min_cert_for_membership: parameters::WotMinCertForMembership::get(),
wot_min_cert_for_create_idty_right: parameters::WotMinCertForCreateIdtyRight::get(),
identity_confirm_period: parameters::ConfirmPeriod::get(),
identity_change_owner_key_period: parameters::ChangeOwnerKeyPeriod::get(),
identity_idty_creation_period: parameters::IdtyCreationPeriod::get(),
identity_autorevocation_period: parameters::AutorevocationPeriod::get(),
identity_deletion_period: parameters::DeletionPeriod::get(),
membership_membership_period: parameters::MembershipPeriod::get(),
membership_membership_renewal_period: parameters::MembershipRenewalPeriod::get(),
cert_max_by_issuer: parameters::MaxByIssuer::get(),
cert_min_received_cert_to_be_able_to_issue_cert:
parameters::MinReceivedCertToBeAbleToIssueCert::get(),
cert_validity_period: parameters::ValidityPeriod::get(),
distance_min_accessible_referees: parameters::MinAccessibleReferees::get(),
distance_max_depth: parameters::MaxRefereeDistance::get(),
smith_sub_wot_min_cert_for_membership: parameters::SmithWotMinCertForMembership::get(),
smith_inactivity_max_duration: parameters::SmithInactivityMaxDuration::get(),
smith_cert_max_by_issuer: parameters::SmithMaxByIssuer::get(),
cert_cert_period: parameters::CertPeriod::get(),
treasury_spend_period: <Runtime as pallet_treasury::Config>::SpendPeriod::get(),
}
}
let first_ud = 1_000; // === client specifications ===
let initial_smiths = (0..initial_smiths_len) /// emulate client specifications to get them from json
.map(|i| get_authority_keys_from_seed(NAMES[i])) #[derive(Deserialize, Clone)]
.collect::<Vec<AuthorityKeys>>(); #[serde(rename_all = "camelCase")]
let initial_identities = (0..initial_identities_len) #[serde(deny_unknown_fields)]
.map(|i| { pub struct ClientSpec {
( name: String,
IdtyName::from(NAMES[i]), id: String,
get_account_id_from_seed::<sr25519::Public>(NAMES[i]), chain_type: ChainType,
boot_nodes: Vec<MultiaddrWithPeerId>,
telemetry_endpoints: Option<TelemetryEndpoints>,
// protocol_id: Option<String>,
// #[serde(default = "Default::default", skip_serializing_if = "Option::is_none")]
// fork_id: Option<String>,
properties: Option<serde_json::Map<std::string::String, JsonValue>>,
// #[serde(default)]
// code_substitutes: BTreeMap<String, Bytes>,
}
// === development chainspecs ===
/// generate development chainspec with Alice validator
// there is some code duplication because we can not use ClientSpec
pub fn development_chainspecs(config_file_path: String) -> Result<ChainSpec, String> {
Ok(ChainSpec::builder(
&get_wasm_binary().ok_or_else(|| "Development wasm not available".to_string())?,
None,
)
.with_name("ĞTest Development")
.with_id("gtest_dev")
.with_chain_type(ChainType::Development)
.with_genesis_config_patch({
let genesis_data = gen_genesis_data::generate_genesis_data::<_, _, SessionKeys, GTestSKP>(
config_file_path.clone(),
get_parameters,
Some("Alice".to_owned()),
) )
.expect("Genesis Data must be buildable");
genesis_data_to_gtest_genesis_conf(genesis_data)
}) })
.collect::<BTreeMap<IdtyName, AccountId>>(); .with_properties(
serde_json::json!({
"tokenDecimals": TOKEN_DECIMALS,
"tokenSymbol": TOKEN_SYMBOL,
})
.as_object()
.expect("must be a map")
.clone(),
)
.build())
}
GenesisConfig { // === live chainspecs ===
system: SystemConfig {
// Add Wasm runtime to storage. /// live chainspecs
code: wasm_binary.to_vec(), // one smith must have session keys
}, pub fn live_chainspecs(
account: AccountConfig { client_spec: ClientSpec,
accounts: initial_identities config_file_path: String,
.iter() ) -> Result<ChainSpec, String> {
.enumerate() Ok(ChainSpec::builder(
.map(|(i, (_, owner_key))| { &get_wasm_binary().ok_or_else(|| "Development wasm not available".to_string())?,
( None,
owner_key.clone(), )
GenesisAccountData { .with_name(client_spec.name.as_str())
random_id: H256(blake2_256(&(i as u32, owner_key).encode())), .with_id(client_spec.id.as_str())
balance: first_ud, .with_chain_type(client_spec.chain_type)
is_identity: true, .with_genesis_config_patch({
}, let genesis_data = gen_genesis_data::generate_genesis_data::<_, _, SessionKeys, GTestSKP>(
config_file_path.clone(),
get_parameters,
None,
) )
.expect("Genesis Data must be buildable");
genesis_data_to_gtest_genesis_conf(genesis_data)
}) })
.collect(), .with_telemetry_endpoints(client_spec.telemetry_endpoints.unwrap())
.with_properties(client_spec.properties.unwrap())
.with_boot_nodes(client_spec.boot_nodes)
.build())
}
/// custom genesis
fn genesis_data_to_gtest_genesis_conf(
genesis_data: super::gen_genesis_data::GenesisData<GenesisParameters, SessionKeys>,
) -> serde_json::Value {
let super::gen_genesis_data::GenesisData {
accounts,
treasury_balance,
certs_by_receiver,
first_ud,
first_ud_reeval,
identities,
initial_authorities,
initial_monetary_mass,
memberships,
parameters: _,
common_parameters: _,
session_keys_map,
initial_smiths,
sudo_key,
technical_committee_members,
ud,
} = genesis_data;
serde_json::json!({
"account": {
"accounts": accounts,
"treasuryBalance": treasury_balance,
}, },
authority_discovery: Default::default(), "authorityMembers": {
authority_members: AuthorityMembersConfig { "initialAuthorities": initial_authorities,
initial_authorities: initial_smiths
.iter()
.enumerate()
.map(|(i, keys)| (i as u32 + 1, (keys.0.clone(), true)))
.collect(),
}, },
balances: BalancesConfig { "balances": {
balances: Vec::with_capacity(0), "totalIssuance": initial_monetary_mass,
}, },
babe: BabeConfig { "babe": {
authorities: Vec::with_capacity(0), "epochConfig": Some(BABE_GENESIS_EPOCH_CONFIG),
epoch_config: Some(BABE_GENESIS_EPOCH_CONFIG),
}, },
grandpa: Default::default(), "session": {
im_online: Default::default(), "keys": session_keys_map
session: SessionConfig { .into_iter()
keys: initial_smiths .map(|(account_id, session_keys)| (account_id.clone(), account_id, session_keys))
.iter()
.map(|x| {
(
x.0.clone(),
x.0.clone(),
session_keys(x.1.clone(), x.2.clone(), x.3.clone(), x.4.clone()),
)
})
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
}, },
sudo: SudoConfig { "sudo": { "key": sudo_key },
// Assign network admin rights. "technicalCommittee": {
key: Some(root_key), "members": technical_committee_members,
}, },
technical_committee: TechnicalCommitteeConfig { "quota": {
members: initial_smiths "identities": identities.iter().map(|i| i.idty_index).collect::<Vec<_>>(),
.iter()
.map(|x| x.0.clone())
.collect::<Vec<_>>(),
..Default::default()
}, },
identity: IdentityConfig { "identity": {
identities: initial_identities "identities": identities
.iter() .into_iter()
.enumerate() .map(
.map(|(i, (name, owner_key))| common_runtime::GenesisIdty { |GenesisIdentity {
index: i as u32 + 1, idty_index,
name: name.clone(), name,
value: IdtyValue { owner_key,
data: IdtyData::new(), status,
next_creatable_identity_on: Default::default(), next_scheduled,
old_owner_key: None, }| GenesisIdty {
owner_key: owner_key.clone(), index: idty_index,
removable_on: 0, name: common_runtime::IdtyName::from(name.as_str()),
status: IdtyStatus::Validated, value: common_runtime::IdtyValue {
data: IdtyData {
first_eligible_ud: match status {
// Only members are eligible to UD.
// The first claimable UD has the minimum index (1).
common_runtime::IdtyStatus::Member => gtest_runtime::pallet_universal_dividend::FirstEligibleUd::min(),
_ => gtest_runtime::pallet_universal_dividend::FirstEligibleUd(None),
}
}, },
}) next_creatable_identity_on: 0,
.collect(), old_owner_key: None,
owner_key,
next_scheduled,
status,
}, },
membership: MembershipConfig {
memberships: (1..=initial_identities.len())
.map(|i| {
(
i as u32,
MembershipData {
expire_on: gtest_runtime::MembershipPeriod::get(),
}, },
) )
}) .collect::<Vec<GenesisIdty<gtest_runtime::Runtime>>>(),
.collect(),
}, },
cert: CertConfig { "certification": {
apply_cert_period_at_genesis: false, "applyCertPeriodAtGenesis": false,
certs_by_receiver: clique_wot(initial_identities.len()), "certsByReceiver": certs_by_receiver,
}, },
smiths_membership: SmithsMembershipConfig { "membership": { "memberships": memberships },
memberships: (1..=initial_smiths_len) "smithMembers": { "initialSmiths": initial_smiths},
.map(|i| { "universalDividend": {
( "firstReeval": first_ud_reeval,
i as u32, "firstUd": first_ud,
MembershipData { "initialMonetaryMass": initial_monetary_mass,
expire_on: gtest_runtime::SmithMembershipPeriod::get(), "ud": ud,
}, },
)
}) })
.collect(),
},
smiths_cert: SmithsCertConfig {
apply_cert_period_at_genesis: false,
certs_by_receiver: clique_wot(initial_smiths_len),
},
universal_dividend: UniversalDividendConfig {
first_reeval: 100,
first_ud: 1_000,
initial_monetary_mass: 0,
},
treasury: Default::default(),
}
} }
fn session_keys( /// Get the WASM bytes either from filesytem (`WASM_FILE` env variable giving the path to the wasm blob)
babe: BabeId, /// or else get the one compiled from source code.
grandpa: GrandpaId, /// Goal: allow to provide the WASM built with srtool, which is reproductible.
im_online: ImOnlineId, fn get_wasm_binary() -> Option<Vec<u8>> {
authority_discovery: AuthorityDiscoveryId, let wasm_bytes_from_file = if let Ok(file_path) = env::var("WASM_FILE") {
) -> SessionKeys { Some(fs::read(file_path).unwrap_or_else(|e| panic!("Could not read wasm file: {}", e)))
SessionKeys { } else {
babe, None
grandpa, };
im_online, wasm_bytes_from_file.or_else(|| WASM_BINARY.map(|bytes| bytes.to_vec()))
authority_discovery,
}
} }
...@@ -19,9 +19,14 @@ pub struct Cli { ...@@ -19,9 +19,14 @@ pub struct Cli {
#[clap(subcommand)] #[clap(subcommand)]
pub subcommand: Option<Subcommand>, pub subcommand: Option<Subcommand>,
/// substrate base options
#[clap(flatten)] #[clap(flatten)]
pub run: sc_cli::RunCmd, pub run: sc_cli::RunCmd,
/// duniter specific options
#[clap(flatten)]
pub duniter_options: DuniterConfigExtension,
/// How blocks should be sealed /// How blocks should be sealed
/// ///
/// Options are "production", "instant", "manual", or timer interval in milliseconds /// Options are "production", "instant", "manual", or timer interval in milliseconds
...@@ -29,6 +34,33 @@ pub struct Cli { ...@@ -29,6 +34,33 @@ pub struct Cli {
pub sealing: crate::cli::Sealing, pub sealing: crate::cli::Sealing,
} }
/// add options specific to duniter client
#[derive(Debug, Default, Clone, clap::Parser)]
pub struct DuniterConfigExtension {
/// Public RPC endpoint to gossip on the network and make available in the apps.
#[arg(long)]
pub public_rpc: Option<String>,
/// Public Squid graphql endpoint to gossip on the network and make available in the apps.
#[arg(long)]
pub public_squid: Option<String>,
/// Public endpoints from a JSON file, using following format where `protocol` and `address` are
/// strings (value is free) :
///
/// ```json
/// {
/// "endpoints": [
/// { "protocol": "rpc", "address": "wss://gdev.example.com" },
/// { "protocol": "squid", "address": "gdev.example.com/graphql/v1" },
/// { "protocol": "other", "address": "gdev.example.com/other" }
/// ]
/// }
/// ```
#[arg(long, value_name = "JSON_FILE_PATH")]
pub public_endpoints: Option<String>,
}
#[derive(Debug, clap::Subcommand)] #[derive(Debug, clap::Subcommand)]
pub enum Subcommand { pub enum Subcommand {
/// Build a chain specification. /// Build a chain specification.
...@@ -37,6 +69,10 @@ pub enum Subcommand { ...@@ -37,6 +69,10 @@ pub enum Subcommand {
/// Validate blocks. /// Validate blocks.
CheckBlock(sc_cli::CheckBlockCmd), CheckBlock(sc_cli::CheckBlockCmd),
/// Run distance oracle.
#[cfg(feature = "distance-oracle")]
DistanceOracle(DistanceOracle),
/// Export blocks. /// Export blocks.
ExportBlocks(sc_cli::ExportBlocksCmd), ExportBlocks(sc_cli::ExportBlocksCmd),
...@@ -75,15 +111,7 @@ pub enum Subcommand { ...@@ -75,15 +111,7 @@ pub enum Subcommand {
/// Sub-commands concerned with benchmarking. /// Sub-commands concerned with benchmarking.
/// The pallet benchmarking moved to the `pallet` sub-command. /// The pallet benchmarking moved to the `pallet` sub-command.
#[clap(subcommand)] #[clap(subcommand)]
Benchmark(frame_benchmarking_cli::BenchmarkCmd), Benchmark(Box<frame_benchmarking_cli::BenchmarkCmd>),
/// Try some command against runtime state.
#[cfg(feature = "try-runtime")]
TryRuntime(try_runtime_cli::TryRuntimeCmd),
/// Try some command against runtime state. Note: `try-runtime` feature must be enabled.
#[cfg(not(feature = "try-runtime"))]
TryRuntime,
} }
/// Block authoring scheme to be used by the node /// Block authoring scheme to be used by the node
...@@ -132,3 +160,20 @@ pub struct Completion { ...@@ -132,3 +160,20 @@ pub struct Completion {
#[clap(short, long, value_enum)] #[clap(short, long, value_enum)]
pub generator: clap_complete::Shell, pub generator: clap_complete::Shell,
} }
#[cfg(feature = "distance-oracle")]
#[derive(Debug, clap::Parser)]
pub struct DistanceOracle {
/// Saving path.
#[clap(short = 'd', long, default_value = "/tmp/duniter/chains/gdev/distance")]
pub evaluation_result_dir: String,
/// Number of seconds between two evaluations (oneshot if absent).
#[clap(short = 'i', long)]
pub interval: Option<u64>,
/// Node used for fetching state.
#[clap(short = 'u', long, default_value = "ws://127.0.0.1:9944")]
pub rpc_url: String,
/// Sets the logging level (e.g., debug, error, info, trace, warn).
#[clap(short = 'l', long, default_value = "info")]
pub log: String,
}
...@@ -15,22 +15,23 @@ ...@@ -15,22 +15,23 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#![allow(unused_imports)]
pub mod key; pub mod key;
pub mod utils; pub mod utils;
use crate::cli::{Cli, Subcommand}; use crate::{
#[cfg(feature = "g1")] chain_spec,
use crate::service::G1Executor; cli::{Cli, DuniterConfigExtension, Subcommand},
#[cfg(feature = "gdev")] service,
use crate::service::GDevExecutor; service::{runtime_executor::Executor, RuntimeType},
#[cfg(feature = "gtest")] };
use crate::service::GTestExecutor;
use crate::service::{IdentifyRuntimeType, RuntimeType};
use crate::{chain_spec, service};
use clap::CommandFactory; use clap::CommandFactory;
#[cfg(feature = "runtime-benchmarks")] #[cfg(feature = "runtime-benchmarks")]
use frame_benchmarking_cli::{BenchmarkCmd, SUBSTRATE_REFERENCE_HARDWARE}; use frame_benchmarking_cli::{BenchmarkCmd, SUBSTRATE_REFERENCE_HARDWARE};
use sc_cli::{ChainSpec, RuntimeVersion, SubstrateCli}; use sc_cli::SubstrateCli;
#[cfg(feature = "runtime-benchmarks")]
use sc_executor::{sp_wasm_interface::ExtendedHostFunctions, NativeExecutionDispatch};
// TODO: create our own reference hardware // TODO: create our own reference hardware
/* /*
...@@ -42,22 +43,12 @@ lazy_static! { ...@@ -42,22 +43,12 @@ lazy_static! {
}; };
}*/ }*/
/// Unwraps a [`crate::client::Client`] into the concrete runtime client. /// Unwraps a [`crate::service::client::Client`] into the concrete runtime client.
#[cfg(feature = "runtime-benchmarks")] #[cfg(feature = "runtime-benchmarks")]
macro_rules! unwrap_client { macro_rules! unwrap_client {
( ($client:ident, $code:expr) => {
$client:ident,
$code:expr
) => {
match $client.as_ref() { match $client.as_ref() {
#[cfg(feature = "g1")] crate::service::client::Client::Client($client) => $code,
crate::service::client::Client::G1($client) => $code,
#[cfg(feature = "gtest")]
crate::service::client::Client::GTest($client) => $code,
#[cfg(feature = "gdev")]
crate::service::client::Client::GDev($client) => $code,
#[allow(unreachable_patterns)]
_ => panic!("unknown runtime"),
} }
}; };
} }
...@@ -89,43 +80,89 @@ impl SubstrateCli for Cli { ...@@ -89,43 +80,89 @@ impl SubstrateCli for Cli {
fn load_spec(&self, id: &str) -> Result<Box<dyn sc_service::ChainSpec>, String> { fn load_spec(&self, id: &str) -> Result<Box<dyn sc_service::ChainSpec>, String> {
Ok(match id { Ok(match id {
// Development chainspec with generated genesis and Alice as a validator
// For benchmarking, the total length of identities should be at least MinReceivedCertToBeAbleToIssueCert + 1
#[cfg(feature = "gdev")] #[cfg(feature = "gdev")]
"dev" => Box::new(chain_spec::gdev::development_chain_spec()?), "dev" => Box::new(chain_spec::gdev::local_testnet_config(1, 5, 6)?),
#[cfg(feature = "gdev")]
"local" | "gdev_local" => Box::new(chain_spec::gdev::local_testnet_config(1, 3, 4)?), // Local testnet with G1 data, Gdev configuration (parameters & Smiths), and Alice as a validator.
#[cfg(feature = "gdev")] // Optionally, load configuration from DUNITER_GENESIS_CONFIG file to override default Gdev configuration.
"local2" => Box::new(chain_spec::gdev::local_testnet_config(2, 3, 4)?),
#[cfg(feature = "gdev")]
"local3" => Box::new(chain_spec::gdev::local_testnet_config(3, 3, 4)?),
#[cfg(feature = "gdev")] #[cfg(feature = "gdev")]
"local4" => Box::new(chain_spec::gdev::local_testnet_config(4, 4, 5)?), "gdev_dev" => Box::new(chain_spec::gdev::gdev_development_chain_spec(
"resources/gdev.yaml".to_string(),
)?),
// Chainspecs for live network with G1 data, Gdev configuration (parameters & Smiths).
// A Smith with declared session keys is required.
// Optionally load configuration from DUNITER_GENESIS_CONFIG file to override default Gdev configuration.
#[cfg(feature = "gdev")] #[cfg(feature = "gdev")]
"gdev-gl" | "gdev_gl" => Box::new(chain_spec::gdev::gen_live_conf()?), "gdev_live" => {
const CLIENT_SPEC: &str = "./node/specs/gdev_client-specs.yaml";
let client_spec: chain_spec::gdev::ClientSpec = serde_yaml::from_slice(
&std::fs::read(
std::env::var("DUNITER_CLIENT_SPEC")
.unwrap_or_else(|_| CLIENT_SPEC.to_string()),
)
.map_err(|e| format!("failed to read {CLIENT_SPEC} {e}"))?[..],
)
.map_err(|e| format!("failed to parse {e}"))?;
Box::new(chain_spec::gdev::gen_live_conf(
client_spec,
"resources/gdev.yaml".to_string(),
)?)
}
// Hardcoded raw chainspecs with previously generated values, resulting in a needlessly heavy binary due to hexadecimal-text-encoded values.
#[cfg(feature = "gdev")] #[cfg(feature = "gdev")]
"gdev" => Box::new(chain_spec::gdev::ChainSpec::from_json_bytes( "gdev" => Box::new(chain_spec::gdev::ChainSpec::from_json_bytes(
&include_bytes!("../specs/gdev-raw.json")[..], &include_bytes!("../specs/gdev-raw.json")[..],
)?), )?),
// For benchmarking, the total length of identities should be at least MinReceivedCertToBeAbleToIssueCert + 1
#[cfg(feature = "gtest")] #[cfg(feature = "gtest")]
"gtest_dev" => Box::new(chain_spec::gtest::development_chain_spec()?), "dev" => Box::new(chain_spec::gtest::local_testnet_config(1, 5, 6)?),
#[cfg(feature = "gtest")]
"gtest_local" => Box::new(chain_spec::gtest::local_testnet_config(2, 3, 4)?), // Generate development chainspecs with Alice as a validator.
#[cfg(feature = "gtest")] // Provide the DUNITER_GTEST_GENESIS environment variable to build genesis from JSON; otherwise, a local testnet with generated genesis will be used.
"gtest_local3" => Box::new(chain_spec::gtest::local_testnet_config(3, 3, 4)?),
#[cfg(feature = "gtest")] #[cfg(feature = "gtest")]
"gtest_local4" => Box::new(chain_spec::gtest::local_testnet_config(4, 4, 5)?), "gtest_dev" => Box::new(chain_spec::gtest::development_chainspecs(
"resources/gtest.yaml".to_string(),
)?),
// Chainspecs for the live network.
// Required files in the ./node/specs folder or override with environment variables:
// - gtest.json / DUNITER_GTEST_GENESIS
// - gtest_client-specs.json / DUNITER_GTEST_CLIENT_SPEC
#[cfg(feature = "gtest")] #[cfg(feature = "gtest")]
"gtest" => { "gtest_live" => {
unimplemented!() const JSON_CLIENT_SPEC: &str = "./node/specs/gtest_client-specs.yaml";
//Box::new(chain_spec::gtest::ChainSpec::from_json_file(file_path)?) let client_spec: chain_spec::gtest::ClientSpec = serde_yaml::from_slice(
&std::fs::read(
std::env::var("DUNITER_CLIENT_SPEC")
.unwrap_or_else(|_| JSON_CLIENT_SPEC.to_string()),
)
.map_err(|e| format!("failed to read {JSON_CLIENT_SPEC} {e}"))?[..],
)
.map_err(|e| format!("failed to parse {e}"))?;
Box::new(chain_spec::gtest::live_chainspecs(
client_spec,
"resources/gtest.yaml".to_string(),
)?)
} }
// Return hardcoded live chainspecs, only with the embed feature enabled.
// Embed client spec and genesis to avoid embedding hexadecimal runtime
// and having hexadecimal runtime in the git history.
// This will only build on a branch that has a file named ./node/specs/gtest-raw.json.
#[cfg(all(feature = "gtest", feature = "embed"))]
"gtest" => Box::new(chain_spec::gtest::ChainSpec::from_json_bytes(
&include_bytes!("../specs/gtest-raw.json")[..],
)?),
// For benchmarking, the total length of identities should be at least MinReceivedCertToBeAbleToIssueCert + 1
#[cfg(feature = "g1")] #[cfg(feature = "g1")]
"g1" => { "dev" => Box::new(chain_spec::g1::local_testnet_config(1, 5, 6)?),
unimplemented!()
//Box::new(chain_spec::g1::ChainSpec::from_json_file(file_path)?)
}
// Specs provided as json specify which runtime to use in their file name. For example,
// `g1-custom.json` uses the g1 runtime.
// `gdev-workshop.json` uses the gdev runtime.
path => { path => {
let path = std::path::PathBuf::from(path); let path = std::path::PathBuf::from(path);
...@@ -137,8 +174,6 @@ impl SubstrateCli for Cli { ...@@ -137,8 +174,6 @@ impl SubstrateCli for Cli {
let runtime_type = if starts_with("g1") { let runtime_type = if starts_with("g1") {
RuntimeType::G1 RuntimeType::G1
} else if starts_with("gdem") {
RuntimeType::GTest
} else if starts_with("dev") || starts_with("gdev") { } else if starts_with("dev") || starts_with("gdev") {
RuntimeType::GDev RuntimeType::GDev
} else if starts_with("gt") { } else if starts_with("gt") {
...@@ -163,18 +198,6 @@ impl SubstrateCli for Cli { ...@@ -163,18 +198,6 @@ impl SubstrateCli for Cli {
} }
}) })
} }
fn native_runtime_version(spec: &Box<dyn ChainSpec>) -> &'static RuntimeVersion {
match spec.runtime_type() {
#[cfg(feature = "g1")]
RuntimeType::G1 => &g1_runtime::VERSION,
#[cfg(feature = "gtest")]
RuntimeType::GTest => &gtest_runtime::VERSION,
#[cfg(feature = "gdev")]
RuntimeType::GDev => &gdev_runtime::VERSION,
_ => panic!("unknown runtime"),
}
}
} }
/// Parse and run command line arguments /// Parse and run command line arguments
...@@ -191,25 +214,25 @@ pub fn run() -> sc_cli::Result<()> { ...@@ -191,25 +214,25 @@ pub fn run() -> sc_cli::Result<()> {
} }
Some(Subcommand::CheckBlock(cmd)) => { Some(Subcommand::CheckBlock(cmd)) => {
let runner = cli.create_runner(cmd)?; let runner = cli.create_runner(cmd)?;
runner.async_run(|mut config| { runner.async_run(|config| {
let (client, _, import_queue, task_manager) = let (client, _, import_queue, task_manager) =
service::new_chain_ops(&mut config, cli.sealing.is_manual_consensus())?; service::new_chain_ops(&config, cli.sealing.is_manual_consensus())?;
Ok((cmd.run(client, import_queue), task_manager)) Ok((cmd.run(client, import_queue), task_manager))
}) })
} }
Some(Subcommand::ExportBlocks(cmd)) => { Some(Subcommand::ExportBlocks(cmd)) => {
let runner = cli.create_runner(cmd)?; let runner = cli.create_runner(cmd)?;
runner.async_run(|mut config| { runner.async_run(|config| {
let (client, _, _, task_manager) = let (client, _, _, task_manager) =
service::new_chain_ops(&mut config, cli.sealing.is_manual_consensus())?; service::new_chain_ops(&config, cli.sealing.is_manual_consensus())?;
Ok((cmd.run(client, config.database), task_manager)) Ok((cmd.run(client, config.database), task_manager))
}) })
} }
Some(Subcommand::ExportState(cmd)) => { Some(Subcommand::ExportState(cmd)) => {
let runner = cli.create_runner(cmd)?; let runner = cli.create_runner(cmd)?;
runner.async_run(|mut config| { runner.async_run(|config| {
let (client, _, _, task_manager) = let (client, _, _, task_manager) =
service::new_chain_ops(&mut config, cli.sealing.is_manual_consensus())?; service::new_chain_ops(&config, cli.sealing.is_manual_consensus())?;
Ok((cmd.run(client, config.chain_spec), task_manager)) Ok((cmd.run(client, config.chain_spec), task_manager))
}) })
} }
...@@ -224,7 +247,7 @@ pub fn run() -> sc_cli::Result<()> { ...@@ -224,7 +247,7 @@ pub fn run() -> sc_cli::Result<()> {
} }
let (client, _, import_queue, task_manager) = let (client, _, import_queue, task_manager) =
service::new_chain_ops(&mut config, cli.sealing.is_manual_consensus())?; service::new_chain_ops(&config, cli.sealing.is_manual_consensus())?;
Ok((cmd.run(client, import_queue), task_manager)) Ok((cmd.run(client, import_queue), task_manager))
}) })
} }
...@@ -234,9 +257,9 @@ pub fn run() -> sc_cli::Result<()> { ...@@ -234,9 +257,9 @@ pub fn run() -> sc_cli::Result<()> {
} }
Some(Subcommand::Revert(cmd)) => { Some(Subcommand::Revert(cmd)) => {
let runner = cli.create_runner(cmd)?; let runner = cli.create_runner(cmd)?;
runner.async_run(|mut config| { runner.async_run(|config| {
let (client, backend, _, task_manager) = let (client, backend, _, task_manager) =
service::new_chain_ops(&mut config, cli.sealing.is_manual_consensus())?; service::new_chain_ops(&config, cli.sealing.is_manual_consensus())?;
let aux_revert = Box::new(|client, backend, blocks| { let aux_revert = Box::new(|client, backend, blocks| {
service::revert_backend(client, backend, blocks) service::revert_backend(client, backend, blocks)
}); });
...@@ -257,26 +280,50 @@ pub fn run() -> sc_cli::Result<()> { ...@@ -257,26 +280,50 @@ pub fn run() -> sc_cli::Result<()> {
); );
Ok(()) Ok(())
} }
#[cfg(feature = "distance-oracle")]
Some(Subcommand::DistanceOracle(cmd)) => sc_cli::build_runtime()?.block_on(async move {
let mut builder = sc_cli::LoggerBuilder::new("");
builder.with_profiling(sc_cli::TracingReceiver::Log.into(), cmd.log.clone());
builder.init()?;
let client = distance_oracle::api::client(&cmd.rpc_url).await;
let settings = distance_oracle::Settings {
evaluation_result_dir: cmd.evaluation_result_dir.clone().into(),
rpc_url: cmd.rpc_url.clone(),
};
if let Some(duration) = cmd.interval {
let mut interval = tokio::time::interval(std::time::Duration::from_secs(duration));
interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
loop {
distance_oracle::run(&client, &settings).await;
interval.tick().await;
}
} else {
distance_oracle::run(&client, &settings).await;
}
Ok(())
}),
#[cfg(feature = "runtime-benchmarks")] #[cfg(feature = "runtime-benchmarks")]
Some(Subcommand::Benchmark(cmd)) => { Some(Subcommand::Benchmark(cmd)) => {
let runner = cli.create_runner(cmd)?; let runner = cli.create_runner(&**cmd)?;
let chain_spec = &runner.config().chain_spec; let _chain_spec = &runner.config().chain_spec;
match cmd { match &**cmd {
BenchmarkCmd::Storage(cmd) => runner.sync_run(|mut config| { BenchmarkCmd::Storage(cmd) => runner.sync_run(|config| {
let (client, backend, _, _) = service::new_chain_ops(&mut config, false)?; let (client, backend, _, _) = service::new_chain_ops(&config, false)?;
let db = backend.expose_db(); let db = backend.expose_db();
let storage = backend.expose_storage(); let storage = backend.expose_storage();
unwrap_client!(client, cmd.run(config, client.clone(), db, storage)) unwrap_client!(client, cmd.run(config, client.clone(), db, storage))
}), }),
BenchmarkCmd::Block(cmd) => runner.sync_run(|mut config| { BenchmarkCmd::Block(cmd) => runner.sync_run(|config| {
let (client, _, _, _) = service::new_chain_ops(&mut config, false)?; let (client, _, _, _) = service::new_chain_ops(&config, false)?;
unwrap_client!(client, cmd.run(client.clone())) unwrap_client!(client, cmd.run(client.clone()))
}), }),
BenchmarkCmd::Overhead(cmd) => runner.sync_run(|mut config| { BenchmarkCmd::Overhead(cmd) => runner.sync_run(|config| {
let (client, _, _, _) = service::new_chain_ops(&mut config, false)?; let (client, _, _, _) = service::new_chain_ops(&config, false)?;
let wrapped = client.clone(); let wrapped = client.clone();
let inherent_data = crate::service::client::benchmark_inherent_data() let inherent_data = crate::service::client::benchmark_inherent_data()
...@@ -285,31 +332,25 @@ pub fn run() -> sc_cli::Result<()> { ...@@ -285,31 +332,25 @@ pub fn run() -> sc_cli::Result<()> {
unwrap_client!( unwrap_client!(
client, client,
cmd.run( cmd.run(
config, config.chain_spec.name().into(),
client.clone(), client.clone(),
inherent_data, inherent_data,
Vec::new(), Vec::new(),
wrapped.as_ref() wrapped.as_ref(),
false,
) )
) )
}), }),
BenchmarkCmd::Pallet(cmd) => { BenchmarkCmd::Pallet(cmd) => {
if cfg!(feature = "runtime-benchmarks") { if cfg!(feature = "runtime-benchmarks") {
match chain_spec.runtime_type() { runner.sync_run(|config| {
#[cfg(feature = "g1")] cmd.run_with_spec::<sp_runtime::traits::HashingFor<
RuntimeType::G1 => runner.sync_run(|config| { service::runtime_executor::runtime::Block,
cmd.run::<g1_runtime::Block, G1Executor>(config) >, ExtendedHostFunctions<
}), sp_io::SubstrateHostFunctions,
#[cfg(feature = "gtest")] <Executor as NativeExecutionDispatch>::ExtendHostFunctions,
RuntimeType::GTest => runner.sync_run(|config| { >>(Some(config.chain_spec))
cmd.run::<gtest_runtime::Block, GTestExecutor>(config) })
}),
#[cfg(feature = "gdev")]
RuntimeType::GDev => runner.sync_run(|config| {
cmd.run::<gdev_runtime::Block, GDevExecutor>(config)
}),
_ => Err(sc_cli::Error::Application("unknown runtime type".into())),
}
} else { } else {
Err("Benchmarking wasn't enabled when building the node. \ Err("Benchmarking wasn't enabled when building the node. \
You can enable it with `--features runtime-benchmarks`." You can enable it with `--features runtime-benchmarks`."
...@@ -331,49 +372,9 @@ pub fn run() -> sc_cli::Result<()> { ...@@ -331,49 +372,9 @@ pub fn run() -> sc_cli::Result<()> {
You can enable it with `--features runtime-benchmarks`." You can enable it with `--features runtime-benchmarks`."
.into()) .into())
} }
#[cfg(feature = "try-runtime")]
Some(Subcommand::TryRuntime(cmd)) => {
let runner = cli.create_runner(cmd)?;
let chain_spec = &runner.config().chain_spec;
use sc_service::TaskManager;
let registry = &runner
.config()
.prometheus_config
.as_ref()
.map(|cfg| &cfg.registry);
let task_manager = TaskManager::new(runner.config().tokio_handle.clone(), *registry)
.map_err(|e| {
sc_cli::Error::Application(format!("Fail to create TaskManager: {}", e).into())
})?;
// Ensure dev spec
if !chain_spec.id().ends_with("dev") {
return Err(sc_cli::Error::Application(
"try-runtime only support dev specs".into(),
));
}
match chain_spec.runtime_type() {
#[cfg(feature = "gdev")]
RuntimeType::GDev => {
//sp_core::crypto::set_default_ss58_version(Ss58AddressFormatRegistry::GDev);
runner.async_run(|config| {
Ok((
cmd.run::<gdev_runtime::Block, GDevExecutor>(config),
task_manager,
))
})
}
_ => Err(sc_cli::Error::Application("unknown runtime type".into())),
}
}
#[cfg(not(feature = "try-runtime"))]
Some(Subcommand::TryRuntime) => Err("TryRuntime wasn't enabled when building the node. \
You can enable it with `--features try-runtime`."
.into()),
None => { None => {
let runner = cli.create_runner(&cli.run)?; let runner = cli.create_runner(&cli.run)?;
let duniter_options: DuniterConfigExtension = cli.duniter_options;
runner.run_node_until_exit(|mut config| async move { runner.run_node_until_exit(|mut config| async move {
// Force offchain worker and offchain indexing if we have the role Authority // Force offchain worker and offchain indexing if we have the role Authority
if config.role.is_authority() { if config.role.is_authority() {
...@@ -381,28 +382,14 @@ pub fn run() -> sc_cli::Result<()> { ...@@ -381,28 +382,14 @@ pub fn run() -> sc_cli::Result<()> {
config.offchain_worker.indexing_enabled = true; config.offchain_worker.indexing_enabled = true;
} }
match config.chain_spec.runtime_type() { {
#[cfg(feature = "g1")] service::new_full::<
RuntimeType::G1 => { service::runtime_executor::runtime::RuntimeApi,
service::new_full::<g1_runtime::RuntimeApi, G1Executor>(config, cli.sealing) Executor,
.map_err(sc_cli::Error::Service) sc_network::Litep2pNetworkBackend,
} >(config, cli.sealing, duniter_options)
#[cfg(feature = "gtest")]
RuntimeType::GTest => service::new_full::<
gtest_runtime::RuntimeApi,
GTestExecutor,
>(config, cli.sealing)
.map_err(sc_cli::Error::Service),
#[cfg(feature = "gdev")]
RuntimeType::GDev => {
service::new_full::<gdev_runtime::RuntimeApi, GDevExecutor>(
config,
cli.sealing,
)
.map_err(sc_cli::Error::Service) .map_err(sc_cli::Error::Service)
} }
_ => Err(sc_cli::Error::Application("unknown runtime".into())),
}
}) })
} }
} }
......
...@@ -20,7 +20,7 @@ use sc_cli::{ ...@@ -20,7 +20,7 @@ use sc_cli::{
use sc_keystore::LocalKeystore; use sc_keystore::LocalKeystore;
use sc_service::config::{BasePath, KeystoreConfig}; use sc_service::config::{BasePath, KeystoreConfig};
use sp_core::crypto::{AccountId32, KeyTypeId, SecretString}; use sp_core::crypto::{AccountId32, KeyTypeId, SecretString};
use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; use sp_keystore::{Keystore, KeystorePtr};
use std::sync::Arc; use std::sync::Arc;
#[derive(Debug, clap::Subcommand)] #[derive(Debug, clap::Subcommand)]
...@@ -82,29 +82,32 @@ impl GenSessionKeysCmd { ...@@ -82,29 +82,32 @@ impl GenSessionKeysCmd {
let mut public_keys_bytes = Vec::with_capacity(128); let mut public_keys_bytes = Vec::with_capacity(128);
for (key_type_id, crypto_scheme) in KEY_TYPES { for (key_type_id, crypto_scheme) in KEY_TYPES {
let (keystore, public) = match self.keystore_params.keystore_config(&config_dir)? { let (keystore, public) = match self.keystore_params.keystore_config(&config_dir)? {
(_, KeystoreConfig::Path { path, password }) => { KeystoreConfig::Path { path, password } => {
let public = let public =
with_crypto_scheme!(crypto_scheme, to_vec(&suri, password.clone()))?; with_crypto_scheme!(crypto_scheme, to_vec(&suri, password.clone()))?;
let keystore: SyncCryptoStorePtr = let keystore: KeystorePtr = Arc::new(LocalKeystore::open(path, password)?);
Arc::new(LocalKeystore::open(path, password)?);
(keystore, public) (keystore, public)
} }
_ => unreachable!("keystore_config always returns path and password; qed"), _ => unreachable!("keystore_config always returns path and password; qed"),
}; };
SyncCryptoStore::insert_unknown(&*keystore, key_type_id, &suri, &public[..]) Keystore::insert(&*keystore, key_type_id, &suri, &public[..])
.map_err(|_| Error::KeyStoreOperation)?; .map_err(|_| Error::KeystoreOperation)?;
public_keys_bytes.extend_from_slice(&public[..]); public_keys_bytes.extend_from_slice(&public[..]);
} }
let mut buffer = [0; 32]; let mut buffer = [0; 32];
// grandpa
buffer.copy_from_slice(&public_keys_bytes[..32]); buffer.copy_from_slice(&public_keys_bytes[..32]);
println!("grandpa: {}", AccountId32::new(buffer)); println!("grandpa: {}", AccountId32::new(buffer));
// babe
buffer.copy_from_slice(&public_keys_bytes[32..64]); buffer.copy_from_slice(&public_keys_bytes[32..64]);
println!("babe: {}", AccountId32::new(buffer)); println!("babe: {}", AccountId32::new(buffer));
// im_online
buffer.copy_from_slice(&public_keys_bytes[64..96]); buffer.copy_from_slice(&public_keys_bytes[64..96]);
println!("im_online: {}", AccountId32::new(buffer)); println!("im_online: {}", AccountId32::new(buffer));
// authority discovery
buffer.copy_from_slice(&public_keys_bytes[96..]); buffer.copy_from_slice(&public_keys_bytes[96..]);
println!("authority_discovery: {}", AccountId32::new(buffer)); println!("authority_discovery: {}", AccountId32::new(buffer));
......
use crate::endpoint_gossip::{
types::validation_result::DuniterStreamValidationResult, DuniterEndpoints, Peer, Peering,
PROPAGATE_TIMEOUT,
};
use codec::{Decode, Encode};
use futures::{future, stream, FutureExt, Stream, StreamExt};
use log::debug;
use sc_network::{
service::traits::{NotificationEvent, ValidationResult},
utils::interval,
NetworkEventStream, NetworkPeers, NetworkStateInfo, NotificationService, ObservedRole, PeerId,
};
use sc_utils::mpsc::{TracingUnboundedReceiver, TracingUnboundedSender};
use sp_api::__private::BlockT;
use std::{collections::HashMap, future::Future, marker::PhantomData, pin::Pin};
pub fn build<
B: BlockT + 'static,
N: NetworkPeers + NetworkEventStream + NetworkStateInfo + Clone,
>(
notification_service: Box<dyn NotificationService>,
network: N,
rpc_sink: TracingUnboundedSender<DuniterPeeringEvent>,
command_rx: Option<TracingUnboundedReceiver<DuniterPeeringCommand>>,
endpoints: DuniterEndpoints,
) -> GossipsHandler<B, N> {
let local_peer_id = network.local_peer_id();
GossipsHandler {
b: PhantomData::<B>,
notification_service,
propagate_timeout: (Box::pin(interval(PROPAGATE_TIMEOUT))
as Pin<Box<dyn Stream<Item = ()> + Send>>)
.fuse(),
network,
peers: HashMap::new(),
command_rx: CommandHandler(command_rx),
self_peering: Peering { endpoints },
events_reporter: DuniterEventsReporter {
sink: rpc_sink,
local_peer_id,
},
}
}
// Structure to avoid borrowing issues with the command receiver.
struct CommandHandler(Option<TracingUnboundedReceiver<DuniterPeeringCommand>>);
impl CommandHandler {
/// Wait for the next command to be received.
pub fn get_next_command(
&mut self,
) -> Pin<Box<dyn Future<Output = Option<DuniterPeeringCommand>> + Send + '_>> {
match &mut self.0 {
Some(tx) => Box::pin(tx.next()),
// Cannot receive any command
None => Box::pin(future::pending()),
}
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub enum DuniterPeeringEvent {
StreamOpened(PeerId, ObservedRole),
StreamValidation(PeerId, DuniterStreamValidationResult),
StreamClosed(PeerId),
/// Received gossip from a peer, `bool` indicates whether the gossip was successfully decoded.
GossipReceived(PeerId, bool),
GoodPeering(PeerId, Peering),
AlreadyReceivedPeering(PeerId),
SelfPeeringPropagationSuccess(PeerId, Peering),
SelfPeeringPropagationFailed(PeerId, Peering, String),
}
pub enum DuniterPeeringCommand {
/// Send a peering to a peer.
#[allow(dead_code)] // only used in tests for now, maybe in the future by RPC
SendPeering(PeerId, Peering),
}
struct DuniterEventsReporter {
sink: TracingUnboundedSender<DuniterPeeringEvent>,
local_peer_id: PeerId,
}
impl DuniterEventsReporter {
/// Report an event for monitoring purposes (logs + unit tests).
fn report_event(&self, event: DuniterPeeringEvent) {
self.sink.unbounded_send(event.clone())
.unwrap_or_else(|e| {
log::error!(target: "duniter-libp2p", "[{}] Failed to send notification: {}", self.local_peer_id, e);
})
}
}
/// Handler for gossips. Call [`GossipsHandler::run`] to start the processing.
pub struct GossipsHandler<
B: BlockT + 'static,
N: NetworkPeers + NetworkEventStream + NetworkStateInfo,
> {
b: PhantomData<B>,
/// Interval at which we try to propagate our peering
propagate_timeout: stream::Fuse<Pin<Box<dyn Stream<Item = ()> + Send>>>,
/// Network service to use to send messages and manage peers.
network: N,
/// All connected peers and their known peering.
peers: HashMap<PeerId, Peer>,
/// The interal peering of the node.
self_peering: Peering,
/// Internal sink to report events.
events_reporter: DuniterEventsReporter,
/// Receiver for external commands (tests/RPC methods).
command_rx: CommandHandler,
/// Handle that is used to communicate with `sc_network::Notifications`.
notification_service: Box<dyn NotificationService>,
}
impl<B, N> GossipsHandler<B, N>
where
B: BlockT + 'static,
N: NetworkPeers + NetworkEventStream + NetworkStateInfo,
{
/// Turns the [`TransactionsHandler`] into a future that should run forever and not be
/// interrupted.
pub async fn run(mut self) {
// Share self peering do listeners of current handler
self.events_reporter
.report_event(DuniterPeeringEvent::GoodPeering(
self.network.local_peer_id(),
self.self_peering.clone(),
));
// Then start the network loop
loop {
futures::select! {
_ = self.propagate_timeout.next() => {
for (peer, peer_data) in self.peers.iter_mut() {
if !peer_data.sent_peering {
debug!(target: "duniter-libp2p", "[{}] sending self peering to {}", self.network.local_peer_id(), peer);
match self.notification_service.send_async_notification(peer, self.self_peering.encode()).await {
Ok(_) => {
peer_data.sent_peering = true;
debug!(target: "duniter-libp2p", "[{}] self peering sent to {}", self.network.local_peer_id(), peer);
self.events_reporter.report_event(DuniterPeeringEvent::SelfPeeringPropagationSuccess(*peer, self.self_peering.clone()));
}
Err(e) => {
debug!(target: "duniter-libp2p", "[{}] failed to send self peering to {}: {}", self.network.local_peer_id(), peer, e);
self.events_reporter.report_event(DuniterPeeringEvent::SelfPeeringPropagationFailed(*peer, self.self_peering.clone(), e.to_string()));
}
}
}
}
},
event = self.notification_service.next_event().fuse() => {
if let Some(event) = event {
self.handle_notification_event(event)
} else {
// `Notifications` has seemingly closed. Closing as well.
return
}
},
command = self.command_rx.get_next_command().fuse() => {
if let Some(command) = command {
self.handle_command(command).await
}
},
}
}
}
fn handle_notification_event(&mut self, event: NotificationEvent) {
match event {
NotificationEvent::ValidateInboundSubstream {
peer,
handshake,
result_tx,
..
} => {
debug!(target: "duniter-libp2p", "[{}] validating stream from {}", self.network.local_peer_id(), peer);
// only accept peers whose role can be determined
let result = self
.network
.peer_role(peer, handshake)
.map_or(ValidationResult::Reject, |_| ValidationResult::Accept);
let duniter_validation = DuniterStreamValidationResult::from(result);
debug!(target: "duniter-libp2p", "[{}] stream validation result for {}: {:?}", self.network.local_peer_id(), peer, duniter_validation);
self.events_reporter
.report_event(DuniterPeeringEvent::StreamValidation(
peer,
duniter_validation.clone(),
));
let _ = result_tx.send(duniter_validation.into());
}
NotificationEvent::NotificationStreamOpened {
peer, handshake, ..
} => {
let Some(role) = self.network.peer_role(peer, handshake) else {
debug!(target: "duniter-libp2p", "[{}] role for {peer} couldn't be determined", self.network.local_peer_id());
return;
};
let _was_in = self.peers.insert(
peer,
Peer {
sent_peering: false,
known_peering: None,
},
);
debug_assert!(_was_in.is_none());
debug!(target: "duniter-libp2p", "[{}] stream opened with {peer}", self.network.local_peer_id());
self.events_reporter
.report_event(DuniterPeeringEvent::StreamOpened(peer, role));
}
NotificationEvent::NotificationStreamClosed { peer } => {
let _peer = self.peers.remove(&peer);
debug_assert!(_peer.is_some());
debug!(target: "duniter-libp2p", "[{}] stream closed with {peer}", self.network.local_peer_id());
self.events_reporter
.report_event(DuniterPeeringEvent::StreamClosed(peer));
}
NotificationEvent::NotificationReceived { peer, notification } => {
debug!(target: "duniter-libp2p", "[{}] received gossip from {}", self.network.local_peer_id(), peer);
if let Ok(peering) = <Peering as Decode>::decode(&mut notification.as_ref()) {
self.events_reporter
.report_event(DuniterPeeringEvent::GossipReceived(peer, true));
debug!(target: "duniter-libp2p", "[{}] received gossip from {}: {:?}", self.network.local_peer_id(), peer, peering);
self.on_peering(peer, peering);
} else {
self.events_reporter
.report_event(DuniterPeeringEvent::GossipReceived(peer, false));
debug!(target: "duniter-libp2p", "[{}] received gossip from {} but couldn't decode it", self.network.local_peer_id(), peer);
self.network.report_peer(peer, rep::BAD_PEERING);
}
}
}
}
/// Called when peer sends us new peerings
fn on_peering(&mut self, who: PeerId, peering: Peering) {
if let Some(ref mut peer) = self.peers.get_mut(&who) {
if peer.known_peering.is_some() {
// Peering has already been received for this peer. Only one is allowed per connection.
self.network.report_peer(who, rep::BAD_PEERING);
self.events_reporter
.report_event(DuniterPeeringEvent::AlreadyReceivedPeering(who));
} else {
peer.known_peering = Some(peering.clone());
self.events_reporter
.report_event(DuniterPeeringEvent::GoodPeering(who, peering.clone()));
self.network.report_peer(who, rep::GOOD_PEERING);
}
}
}
async fn handle_command(&mut self, cmd: DuniterPeeringCommand) {
match cmd {
DuniterPeeringCommand::SendPeering(peer, peering) => {
debug!(target: "duniter-libp2p", "[{}]Sending COMMANDED self peering to {}", self.network.local_peer_id(), peer);
match self
.notification_service
.send_async_notification(&peer, peering.encode())
.await
{
Ok(_) => {
self.events_reporter.report_event(
DuniterPeeringEvent::SelfPeeringPropagationSuccess(peer, peering),
);
}
Err(e) => {
self.events_reporter.report_event(
DuniterPeeringEvent::SelfPeeringPropagationFailed(
peer,
peering,
e.to_string(),
),
);
}
}
}
};
}
}
mod rep {
use sc_network::ReputationChange as Rep;
/// Reputation change when a peer sends us an peering that we didn't know about.
pub const GOOD_PEERING: Rep = Rep::new(1 << 7, "Good peering");
/// Reputation change when a peer sends us a bad peering.
pub const BAD_PEERING: Rep = Rep::new(-(1 << 12), "Bad peering");
}
pub(crate) mod handler;
pub(crate) mod rpc;
#[cfg(test)]
mod tests;
mod types;
use crate::endpoint_gossip::duniter_peering_protocol_name::NAME;
use codec::{Decode, Encode};
use frame_benchmarking::__private::traits::ConstU32;
use sc_network::{
config::{PeerStoreProvider, SetConfig},
types::ProtocolName,
NetworkBackend, NotificationMetrics, NotificationService, MAX_RESPONSE_SIZE,
};
use serde::{Deserialize, Serialize};
use sp_api::__private::BlockT;
use sp_core::bounded_vec::BoundedVec;
use std::{sync::Arc, time};
pub mod well_known_endpoint_types {
pub const RPC: &str = "rpc";
pub const SQUID: &str = "squid";
}
pub struct DuniterPeeringParams {
/// Handle that is used to communicate with `sc_network::Notifications`.
pub notification_service: Box<dyn NotificationService>,
}
/// Maximum allowed size for a transactions notification.
pub(crate) const MAX_GOSSIP_SIZE: u64 = MAX_RESPONSE_SIZE;
/// Interval at which we propagate gossips;
pub(crate) const PROPAGATE_TIMEOUT: time::Duration = time::Duration::from_secs(1);
pub mod duniter_peering_protocol_name {
pub(crate) const NAME: &str = "duniter-peerings/1";
}
impl DuniterPeeringParams {
/// Create a new instance.
pub fn new<
Hash: AsRef<[u8]>,
Block: BlockT,
Net: NetworkBackend<Block, <Block as BlockT>::Hash>,
>(
genesis_hash: Hash,
fork_id: Option<&str>,
metrics: NotificationMetrics,
peer_store_handle: Arc<dyn PeerStoreProvider>,
) -> (Self, Net::NotificationProtocolConfig) {
let genesis_hash = genesis_hash.as_ref();
let protocol_name: ProtocolName = if let Some(fork_id) = fork_id {
format!(
"/{}/{}/{}",
array_bytes::bytes2hex("", genesis_hash),
fork_id,
NAME,
)
} else {
format!("/{}/{}", array_bytes::bytes2hex("", genesis_hash), NAME)
}
.into();
let (config, notification_service) = Net::notification_config(
protocol_name.clone(),
vec![format!("/{}/{}", array_bytes::bytes2hex("", genesis_hash), NAME).into()],
MAX_GOSSIP_SIZE,
None,
// Default config, allowing some non-reserved nodes to connect
SetConfig::default(),
metrics,
peer_store_handle,
);
(
Self {
notification_service,
},
config,
)
}
}
/// Peer information
#[derive(Debug)]
struct Peer {
/// Holds a set of transactions known to this peer.
known_peering: Option<Peering>,
sent_peering: bool,
}
#[derive(Encode, Decode, Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct DuniterEndpoint {
/// The name of the endpoint (e.g. "rpc" or "squid") are well-known names
pub protocol: String,
/// The endpoint itself (e.g. "squid.example.com/v1/graphql")
pub address: String,
}
pub type DuniterEndpoints = BoundedVec<DuniterEndpoint, ConstU32<10>>;
#[derive(Encode, Decode, Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct Peering {
pub endpoints: DuniterEndpoints,
}
//! # Duniter Peering RPC API
//!
//! Exposes the `duniter_peerings` RPC method.
use crate::endpoint_gossip::rpc::{data::DuniterPeeringsData, state::DuniterPeeringsState};
use jsonrpsee::{core::async_trait, proc_macros::rpc, Extensions};
use sc_consensus_babe_rpc::Error;
/// The exposed RPC methods
#[rpc(client, server)]
pub trait DuniterPeeringRpcApi {
/// Returns the known peerings list received by network gossips
#[method(name = "duniter_peerings", with_extensions)]
async fn duniter_peerings(&self) -> Result<Option<DuniterPeeringsData>, Error>;
}
/// API implementation
pub struct DuniterPeeringRpcApiImpl {
shared_peer_state: DuniterPeeringsState,
}
impl DuniterPeeringRpcApiImpl {
/// Creates a new instance of the Duniter Peering Rpc handler.
pub fn new(shared_peer_state: DuniterPeeringsState) -> Self {
Self { shared_peer_state }
}
}
#[async_trait]
impl DuniterPeeringRpcApiServer for DuniterPeeringRpcApiImpl {
async fn duniter_peerings(
&self,
_ext: &Extensions,
) -> Result<Option<DuniterPeeringsData>, Error> {
let option = self.shared_peer_state.peer_state();
Ok(option)
}
}
use crate::endpoint_gossip::rpc::state::PeeringWithId;
use jsonrpsee::core::Serialize;
use serde::Deserialize;
#[derive(PartialEq, Eq, Clone, Serialize, Deserialize)]
#[cfg_attr(test, derive(Debug))]
pub struct DuniterPeeringsData {
pub peerings: Vec<PeeringWithId>,
}
//! # RPC for peering
//!
//! This module gathers all known peering documents for connected peers in memory and provides
//! an RPC interface to query them.
//!
//! ## RPC methods
//!
//! Currently, only one RPC method is available to query the currently known peerings.
//! In the future, the RPC interface could add methods to dynamically change the current node's peering
//! without restarting the node.
//!
//! ### `duniter_peerings`
//!
//! Returns the known peerings list received by network gossips.
//!
//! ```json
//! {
//! "jsonrpc": "2.0",
//! "id": 0,
//! "result": {
//! "peers": [
//! {
//! "endpoints": [
//! "/rpc/wss://gdev.example.com",
//! "/squid/https://squid.gdev.gyroi.de/v1/graphql"
//! ]
//! },
//! {
//! "endpoints": [
//! "/rpc/ws://gdev.example.com:9944"
//! ]
//! }
//! ]
//! }
//! }
//! ```
//!
pub mod api;
pub mod data;
pub mod state;
#[cfg(test)]
mod tests;
use crate::endpoint_gossip::{
handler::DuniterPeeringEvent, rpc::data::DuniterPeeringsData, DuniterEndpoints,
};
use codec::{Decode, Encode};
use futures::StreamExt;
use jsonrpsee::core::Serialize;
use parking_lot::RwLock;
use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedSender};
use serde::Deserialize;
use std::sync::Arc;
/// A struct to hold a peer endpoints along with its id for RPC exposure.
#[derive(Encode, Decode, Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct PeeringWithId {
pub peer_id: String,
pub endpoints: DuniterEndpoints,
}
#[derive(Clone)]
pub struct DuniterPeeringsState {
inner: Arc<RwLock<Option<Box<DuniterPeeringsData>>>>,
}
/// Dummy CRUD operations for the state to be exposed, plus a listening sink to be notified of
/// network events and automatically insert/remove peers from the state.
impl DuniterPeeringsState {
pub fn empty() -> Self {
Self {
inner: Arc::new(RwLock::new(Some(Box::new(DuniterPeeringsData {
peerings: Vec::new(),
})))),
}
}
pub fn insert(&self, peering: PeeringWithId) -> &Self {
if let Some(vs) = self.inner.write().as_mut() {
vs.peerings.push(peering);
}
self
}
pub fn remove(&self, peer_id: String) -> &Self {
if let Some(vs) = self.inner.write().as_mut() {
vs.peerings.retain(|p| p.peer_id != peer_id);
}
self
}
pub fn peer_state(&self) -> Option<DuniterPeeringsData> {
self.inner.read().as_ref().map(|vs| vs.as_ref().clone())
}
/// Creates a channel for binding to the network events.
pub fn listen(&self) -> TracingUnboundedSender<DuniterPeeringEvent> {
let (sink, stream) = tracing_unbounded("mpsc_duniter_peering_rpc_stream", 1_000);
let state = self.clone();
tokio::spawn(async move {
stream
.for_each(|event| async {
match event {
DuniterPeeringEvent::GoodPeering(who, peering) => {
state.insert(PeeringWithId {
peer_id: who.to_base58(),
endpoints: peering.endpoints,
});
}
DuniterPeeringEvent::StreamClosed(who) => {
state.remove(who.to_base58());
}
_ => {}
}
})
.await
});
sink
}
}