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
  • distance
  • elois-ci-binary-release
  • elois-compose-metrics
  • elois-duniter-storage
  • elois-fix-85
  • elois-opti-cert
  • elois-remove-renewable-period
  • elois-rework-certs
  • elois-smoldot
  • elois-substrate-v0.9.23
  • elois-technical-commitee
  • hugo-cucumber-identity
  • master
  • no-bootnodes
  • poc-oneshot-accounts
  • release/runtime-100
  • release/runtime-200
  • ts-types
  • ud-time-64
  • runtime-100
  • runtime-101
  • runtime-102
  • runtime-103
  • runtime-104
  • runtime-105
  • runtime-200
  • runtime-201
  • v0.1.0
28 results
Show changes
Showing
with 2415 additions and 280 deletions
@genesis.wot
Feature: Universal Dividend
Scenario: Eligibility at genesis
When 2 blocks later
# Members
Then alice should be eligible to UD
Then bob should be eligible to UD
Then charlie should be eligible to UD
# Not members
Then eve should not be eligible to UD
Then ferdie should not be eligible to UD
{
"first_ud": null,
"first_ud_reeval": null,
"genesis_parameters": {
"genesis_certs_expire_on": 1000,
"genesis_certs_min_received": 2,
"genesis_memberships_expire_on": 1000,
"genesis_smith_certs_expire_on": 1000,
"genesis_smith_certs_min_received": 2,
"genesis_smith_memberships_expire_on": 100000
},
"identities": {
"Alice": {
"index": 1,
"balance": 1000,
"certs_received": {
"Bob": 2700000000,
"Charlie": 2700000000
},
"owner_address": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
"membership_expire_on": 2700000000,
"membership_revokes_on": 2700000001,
"revoked": false,
"next_cert_issuable_on": 0
},
"Bob": {
"index": 2,
"balance": 1000,
"certs_received": {
"Alice": 2700000000,
"Charlie": 2700000000
},
"owner_address": "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty",
"membership_expire_on": 2700000000,
"membership_revokes_on": 2700000001,
"revoked": false,
"next_cert_issuable_on": 0
},
"Charlie": {
"index": 3,
"balance": 1000,
"certs_received": {
"Alice": 2700000000,
"Dave": 2700000000
},
"owner_address": "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y",
"membership_expire_on": 2700000000,
"membership_revokes_on": 2700000001,
"revoked": false,
"next_cert_issuable_on": 0
},
"Dave": {
"index": 4,
"balance": 1000,
"certs_received": {
"Charlie": 2700000000,
"Eve": 2700000000
},
"owner_address": "5EWtNo12S57725GF641a6avLkHaXMWnJ5RPoo721GR92gSEt",
"membership_expire_on": 2700000000,
"membership_revokes_on": 2700000001,
"revoked": false,
"next_cert_issuable_on": 0
},
"Eve": {
"index": 5,
"balance": 1000,
"certs_received": {
"Dave": 2700000000,
"Gertrude": 2700000000
},
"owner_address": "5EjiXi8p1sCEUevSCQkHom6yUFQY9N5U5xpEofE2N4ZhJ9vs",
"membership_expire_on": 2700000000,
"membership_revokes_on": 2700000001,
"revoked": false,
"next_cert_issuable_on": 0
},
"Gertrude": {
"index": 6,
"balance": 1000,
"certs_received": {
"Eve": 2700000000,
"Henry": 2700000000,
"Irene": 2700000000
},
"owner_address": "5Hgx76GUW5ETtxTX53BJqzeRtxTASC4AgCPwGDuUjvSuKbC6",
"membership_expire_on": 2700000000,
"membership_revokes_on": 2700000001,
"revoked": false,
"next_cert_issuable_on": 0
},
"Henry": {
"index": 7,
"balance": 1000,
"certs_received": {
"Irene": 2700000000,
"Gertrude": 2700000000
},
"owner_address": "5DJZ3Ns49G7B8tr2av53WZorSsMLzvfhPPVeS2RPispMK3Y9",
"membership_expire_on": 2700000000,
"membership_revokes_on": 2700000001,
"revoked": false,
"next_cert_issuable_on": 0
},
"Irene": {
"index": 8,
"balance": 1000,
"certs_received": {
"Henry": 2700000000,
"Gertrude": 2700000000
},
"owner_address": "5EqdTcg58VmBfd7DGPj1GJeHahqUEh3fjWH2fS4KRQmhpAjN",
"membership_expire_on": 2700000000,
"membership_revokes_on": 2700000001,
"revoked": false,
"next_cert_issuable_on": 0
}
},
"parameters": {
"babe_epoch_duration": 30,
"cert_period": 15,
"cert_max_by_issuer": 10,
"cert_min_received_cert_to_issue_cert": 2,
"cert_validity_period": 1000,
"idty_confirm_period": 40,
"idty_creation_period": 50,
"membership_period": 1000,
"membership_renewal_period": 500,
"ud_creation_period": 60000,
"ud_reeval_period": 600000,
"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
},
"clique_smiths": [
{
"name": "Alice"
},
{
"name": "Bob"
},
{
"name": "Charlie"
}
],
"sudo_key": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
"technical_committee": [
"Alice",
"Bob",
"Charlie"
],
"treasury_funder_pubkey": "FHNpKmJrUtusuvKPGomAygQqeiks98bdV6yD61Stb6vg",
"ud": 1000,
"initial_monetary_mass": 8000,
"current_block": {
"number": 0,
"medianTime": 1700000000
}
}
{ {
"first_ud": 1000, "first_ud": null,
"first_ud_reeval": 100, "first_ud_reeval": null,
"genesis_parameters": {
"genesis_certs_expire_on": 1000,
"genesis_certs_min_received": 2,
"genesis_memberships_expire_on": 1000,
"genesis_smith_certs_expire_on": 1000,
"genesis_smith_certs_min_received": 2,
"genesis_smith_memberships_expire_on": 100000
},
"identities": { "identities": {
"Alice": { "Alice": {
"index": 1,
"balance": 1000, "balance": 1000,
"certs": ["Bob", "Charlie"], "certs_received": {
"pubkey": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" "Bob": 2700000000,
"Charlie": 2700000000
},
"owner_address": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
"membership_expire_on": 2700000000,
"membership_revokes_on": 2700000001,
"revoked": false,
"next_cert_issuable_on": 0
}, },
"Bob": { "Bob": {
"index": 2,
"balance": 1000, "balance": 1000,
"certs": ["Alice", "Charlie"], "certs_received": {
"pubkey": "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty" "Alice": 2700000000,
"Charlie": 2700000000
},
"owner_address": "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty",
"membership_expire_on": 2700000000,
"membership_revokes_on": 2700000001,
"revoked": false,
"next_cert_issuable_on": 0
}, },
"Charlie": { "Charlie": {
"index": 3,
"balance": 1000, "balance": 1000,
"certs": ["Alice", "Bob"], "certs_received": {
"pubkey": "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y" "Alice": 2700000000,
"Bob": 2700000000
},
"owner_address": "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y",
"membership_expire_on": 2700000000,
"membership_revokes_on": 2700000001,
"revoked": false,
"next_cert_issuable_on": 0
} }
}, },
"parameters": { "parameters": {
...@@ -23,39 +55,42 @@ ...@@ -23,39 +55,42 @@
"cert_period": 15, "cert_period": 15,
"cert_max_by_issuer": 10, "cert_max_by_issuer": 10,
"cert_min_received_cert_to_issue_cert": 2, "cert_min_received_cert_to_issue_cert": 2,
"cert_renewable_period": 50,
"cert_validity_period": 1000, "cert_validity_period": 1000,
"idty_confirm_period": 40, "idty_confirm_period": 40,
"idty_creation_period": 50, "idty_creation_period": 50,
"membership_period": 1000, "membership_period": 1000,
"membership_renewable_period": 50, "membership_renewal_period": 500,
"pending_membership_period": 500, "ud_creation_period": 60000,
"ud_creation_period": 10, "ud_reeval_period": 600000,
"ud_reeval_period": 100,
"smith_cert_period": 15,
"smith_cert_max_by_issuer": 8, "smith_cert_max_by_issuer": 8,
"smith_cert_min_received_cert_to_issue_cert": 2, "smith_inactivity_max_duration": 48,
"smith_cert_renewable_period": 50, "smith_wot_min_cert_for_membership": 2,
"smith_cert_validity_period": 1000,
"smith_membership_period": 1000,
"smith_membership_renewable_period": 20,
"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_first_cert_issuable_on": 20,
"wot_min_cert_for_create_idty_right": 2, "wot_min_cert_for_create_idty_right": 2,
"wot_min_cert_for_membership": 2 "wot_min_cert_for_membership": 2
}, },
"smiths": { "clique_smiths": [
"Alice": { {
"certs": ["Bob", "Charlie"] "name": "Alice"
}, },
"Bob": { {
"certs": ["Alice", "Charlie"] "name": "Bob"
}, },
"Charlie": { {
"certs": ["Alice", "Bob"] "name": "Charlie"
}
],
"sudo_key": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
"technical_committee": [
"Alice",
"Bob",
"Charlie"
],
"treasury_funder_pubkey": "FHNpKmJrUtusuvKPGomAygQqeiks98bdV6yD61Stb6vg",
"ud": 1000,
"initial_monetary_mass": 3000,
"current_block": {
"number": 0,
"medianTime": 1700000000
} }
},
"sudo_key": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"
} }
{
"first_ud": null,
"first_ud_reeval": null,
"genesis_parameters": {
"genesis_certs_min_received": 2,
"genesis_memberships_expire_on": 100000,
"genesis_smith_certs_min_received": 2,
"genesis_smith_memberships_expire_on": 100000
},
"identities": {
"Alice": {
"index": 1,
"balance": 1000,
"certs_received": {
"Bob": 2700000000,
"Charlie": 2700000000
},
"owner_address": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
"membership_expire_on": 2700000000,
"membership_revokes_on": 2700000001,
"revoked": false,
"next_cert_issuable_on": 0
},
"Bob": {
"index": 2,
"balance": 1000,
"certs_received": {
"Alice": 2700000000,
"Charlie": 2700000000
},
"owner_address": "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty",
"membership_expire_on": 2700000000,
"membership_revokes_on": 2700000001,
"revoked": false,
"next_cert_issuable_on": 0
},
"Charlie": {
"index": 3,
"balance": 1000,
"certs_received": {
"Alice": 2700000000,
"Bob": 2700000000
},
"owner_address": "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y",
"membership_expire_on": 2700000000,
"membership_revokes_on": 2700000001,
"revoked": false,
"next_cert_issuable_on": 0
},
"Dave": {
"index": 4,
"balance": 1000,
"certs_received": {
"Alice": 2700000000,
"Bob": 2700000000
},
"owner_address": "5DAAnrj7VHTznn2AWBemMuyBwZWs6FNFjdyVXUeYum3PTXFy",
"membership_expire_on": 2700000000,
"membership_revokes_on": 2700000001,
"revoked": false,
"next_cert_issuable_on": 0
},
"Eve": {
"index": 5,
"balance": 1000,
"certs_received": {
"Alice": 2700000000,
"Bob": 2700000000
},
"owner_address": "5HGjWAeFDfFCWPsjFQdVV2Msvz2XtMktvgocEZcCj68kUMaw",
"membership_expire_on": 2700000000,
"membership_revokes_on": 2700000001,
"revoked": true,
"next_cert_issuable_on": 0
},
"Ferdie": {
"index": 6,
"balance": 1000,
"certs_received": {
"Alice": 2700000000
},
"owner_address": "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL",
"membership_expire_on": 2700000000,
"membership_revokes_on": 2700000001,
"revoked": false,
"next_cert_issuable_on": 0
}
},
"parameters": {
"babe_epoch_duration": 30,
"cert_period": 2,
"cert_max_by_issuer": 10,
"cert_min_received_cert_to_issue_cert": 2,
"cert_validity_period": 1000,
"idty_confirm_period": 40,
"idty_creation_period": 50,
"membership_period": 1000,
"membership_renewal_period": 500,
"ud_creation_period": 60000,
"ud_reeval_period": 600000,
"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
},
"clique_smiths": [
{
"name": "Alice"
},
{
"name": "Bob"
},
{
"name": "Charlie"
}
],
"sudo_key": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
"technical_committee": [
"Alice",
"Bob",
"Charlie"
],
"treasury_funder_pubkey": "FHNpKmJrUtusuvKPGomAygQqeiks98bdV6yD61Stb6vg",
"ud": 1000,
"initial_monetary_mass": 6000,
"current_block": {
"number": 0,
"medianTime": 1700000000
}
}
\ No newline at end of file
// Copyright 2021 Axiom-Team // Copyright 2021 Axiom-Team
// //
// This file is part of Substrate-Libre-Currency. // This file is part of Duniter-v2S.
// //
// Substrate-Libre-Currency is free software: you can redistribute it and/or modify // Duniter-v2S is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License. // the Free Software Foundation, version 3 of the License.
// //
// Substrate-Libre-Currency is distributed in the hope that it will be useful, // Duniter-v2S is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU Affero General Public License for more details.
// //
// 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 Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>. // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
// Copyright 2021 Axiom-Team // Copyright 2021 Axiom-Team
// //
// This file is part of Substrate-Libre-Currency. // This file is part of Duniter-v2S.
// //
// Substrate-Libre-Currency is free software: you can redistribute it and/or modify // Duniter-v2S is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License. // the Free Software Foundation, version 3 of the License.
// //
// Substrate-Libre-Currency is distributed in the hope that it will be useful, // Duniter-v2S is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU Affero General Public License for more details.
// //
// 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 Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>. // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
use super::node_runtime::runtime_types::gdev_runtime; use super::{gdev, gdev::runtime_types::pallet_balances, *};
use super::node_runtime::runtime_types::pallet_balances; use crate::common::pair_signer::PairSigner;
use super::*; use sp_keyring::sr25519::Keyring;
use sp_keyring::AccountKeyring; use subxt::utils::MultiAddress;
use subxt::{sp_runtime::MultiAddress, PairSigner};
pub async fn set_balance( pub async fn set_balance(client: &FullClient, who: Keyring, amount: u64) -> Result<()> {
api: &Api,
client: &Client,
who: AccountKeyring,
amount: u64,
) -> Result<()> {
let _events = create_block_with_extrinsic( let _events = create_block_with_extrinsic(
client, &client.rpc,
api.tx() client
.client
.tx()
.create_signed(
&gdev::tx()
.sudo() .sudo()
.sudo(gdev_runtime::Call::Balances( .sudo(gdev::runtime_types::gdev_runtime::RuntimeCall::Balances(
pallet_balances::pallet::Call::set_balance { pallet_balances::pallet::Call::force_set_balance {
who: MultiAddress::Id(who.to_account_id()), who: MultiAddress::Id(who.to_raw_public().into()),
new_free: amount, new_free: amount,
new_reserved: 0,
}, },
)) )),
.create_signed(&PairSigner::new(SUDO_ACCOUNT.pair()), ()) &PairSigner::new(SUDO_ACCOUNT.pair()),
SubstrateExtrinsicParamsBuilder::new().build(),
)
.await?, .await?,
) )
.await?; .await?;
...@@ -45,22 +44,20 @@ pub async fn set_balance( ...@@ -45,22 +44,20 @@ pub async fn set_balance(
Ok(()) Ok(())
} }
pub async fn transfer( pub async fn transfer(client: &FullClient, from: Keyring, amount: u64, to: Keyring) -> Result<()> {
api: &Api,
client: &Client,
from: AccountKeyring,
amount: u64,
to: AccountKeyring,
) -> Result<()> {
let from = PairSigner::new(from.pair()); let from = PairSigner::new(from.pair());
let to = to.to_account_id(); let to = MultiAddress::Id(to.to_raw_public().into());
let _events = create_block_with_extrinsic( let _events = create_block_with_extrinsic(
client, &client.rpc,
api.tx() client
.balances() .client
.transfer(to.clone().into(), amount) .tx()
.create_signed(&from, ()) .create_signed(
&gdev::tx().universal_dividend().transfer_ud(to, amount),
&from,
SubstrateExtrinsicParamsBuilder::new().build(),
)
.await?, .await?,
) )
.await?; .await?;
...@@ -68,21 +65,20 @@ pub async fn transfer( ...@@ -68,21 +65,20 @@ pub async fn transfer(
Ok(()) Ok(())
} }
pub async fn transfer_all( pub async fn transfer_all(client: &FullClient, from: Keyring, to: Keyring) -> Result<()> {
api: &Api,
client: &Client,
from: AccountKeyring,
to: AccountKeyring,
) -> Result<()> {
let from = PairSigner::new(from.pair()); let from = PairSigner::new(from.pair());
let to = to.to_account_id(); let to = MultiAddress::Id(to.to_raw_public().into());
let _events = create_block_with_extrinsic( let _events = create_block_with_extrinsic(
client, &client.rpc,
api.tx() client
.balances() .client
.transfer_all(to.clone().into(), false) .tx()
.create_signed(&from, ()) .create_signed(
&gdev::tx().balances().transfer_all(to, false),
&from,
SubstrateExtrinsicParamsBuilder::new().build(),
)
.await?, .await?,
) )
.await?; .await?;
...@@ -91,21 +87,25 @@ pub async fn transfer_all( ...@@ -91,21 +87,25 @@ pub async fn transfer_all(
} }
pub async fn transfer_ud( pub async fn transfer_ud(
api: &Api, client: &FullClient,
client: &Client, from: Keyring,
from: AccountKeyring,
amount: u64, amount: u64,
to: AccountKeyring, to: Keyring,
) -> Result<()> { ) -> Result<()> {
let from = PairSigner::new(from.pair()); let from = PairSigner::new(from.pair());
let to = to.to_account_id();
let _events = create_block_with_extrinsic( let _events = create_block_with_extrinsic(
client, &client.rpc,
api.tx() client
.client
.tx()
.create_signed(
&gdev::tx()
.universal_dividend() .universal_dividend()
.transfer_ud(to.clone().into(), amount) .transfer_ud(MultiAddress::Id(to.to_raw_public().into()), amount),
.create_signed(&from, ()) &from,
SubstrateExtrinsicParamsBuilder::new().build(),
)
.await?, .await?,
) )
.await?; .await?;
......
// Copyright 2021 Axiom-Team
//
// This file is part of Duniter-v2S.
//
// Duniter-v2S is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Duniter-v2S is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// 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/>.
use super::{gdev, gdev::runtime_types::pallet_certification, *};
use crate::common::pair_signer::PairSigner;
use sp_keyring::sr25519::Keyring;
use subxt::utils::MultiAddress;
pub async fn certify(client: &FullClient, from: Keyring, to: Keyring) -> Result<()> {
let signer = PairSigner::new(from.pair());
let from: subxt::utils::AccountId32 = from.to_raw_public().into();
let to: subxt::utils::AccountId32 = to.to_raw_public().into();
let _issuer_index = client
.client
.storage()
.at_latest()
.await
.unwrap()
.fetch(&gdev::storage().identity().identity_index_of(from.clone()))
.await?
.unwrap_or_else(|| panic!("{} issuer must exist", from));
let receiver_index = client
.client
.storage()
.at_latest()
.await
.unwrap()
.fetch(&gdev::storage().identity().identity_index_of(to))
.await?
.unwrap_or_else(|| panic!("{} issuer must exist", from));
let _events = create_block_with_extrinsic(
&client.rpc,
client
.client
.tx()
.create_signed(
&gdev::tx().certification().add_cert(receiver_index),
&signer,
SubstrateExtrinsicParamsBuilder::new().build(),
)
.await?,
)
.await?;
Ok(())
}
// Copyright 2023 Axiom-Team
//
// This file is part of Duniter-v2S.
//
// Duniter-v2S is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Duniter-v2S is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// 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/>.
use super::{gdev, gdev::runtime_types::pallet_identity, *};
use crate::{common::pair_signer::PairSigner, DuniterWorld};
use sp_keyring::sr25519::Keyring;
use subxt::{backend::rpc::RpcClient, tx::Signer, utils::AccountId32};
pub async fn request_evaluation(client: &FullClient, origin: Keyring) -> Result<()> {
let origin = PairSigner::new(origin.pair());
let _events = create_block_with_extrinsic(
&client.rpc,
client
.client
.tx()
.create_signed(
&gdev::tx().distance().request_distance_evaluation(),
&origin,
SubstrateExtrinsicParamsBuilder::new().build(),
)
.await?,
)
.await?;
Ok(())
}
pub async fn run_oracle(client: &FullClient, origin: Keyring, rpc_url: String) -> Result<()> {
let origin = PairSigner::new(origin.pair());
let account_id: &AccountId32 = origin.account_id();
if let Some((distances, _current_session, _evaluation_result_path)) =
distance_oracle::compute_distance_evaluation(
&distance_oracle::api::client(rpc_url.clone()).await,
&distance_oracle::Settings {
evaluation_result_dir: PathBuf::default(),
rpc_url,
},
)
.await
{
// Distance evaluation period is 7 blocks
for _ in 0..7 {
super::create_empty_block(&client.rpc).await?;
}
let _events = create_block_with_extrinsic(
&client.rpc,
client.client
.tx()
.create_signed(
&gdev::tx().sudo().sudo(gdev::runtime_types::gdev_runtime::RuntimeCall::Distance(
gdev::runtime_types::pallet_distance::pallet::Call::force_update_evaluation {
evaluator: account_id.clone(),
computation_result:
gdev::runtime_types::sp_distance::ComputationResult {
distances: distances.into_iter().map(|res| unsafe{std::mem::transmute(res)}).collect(),
},
},
)
),
&origin,
SubstrateExtrinsicParamsBuilder::new().build(),
)
.await?,
)
.await?;
/*for event in events.iter() {
let event = event.unwrap();
println!(
"Event: {}::{} -> {:?}\n\n",
event.pallet_name(),
event.variant_name(),
event.field_values()
);
}*/
}
Ok(())
}
// Copyright 2021 Axiom-Team
//
// This file is part of Substrate-Libre-Currency.
//
// Substrate-Libre-Currency is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Substrate-Libre-Currency is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>.
use super::{gdev, gdev::runtime_types::pallet_identity, *};
use crate::{
common::pair_signer::PairSigner, gdev::runtime_types::pallet_identity::types::IdtyName,
DuniterWorld,
};
use sp_keyring::sr25519::Keyring;
use subxt::config::substrate::MultiAddress;
type BlockNumber = u32;
type AccountId = subxt::utils::AccountId32;
type IdtyData = gdev::runtime_types::common_runtime::entities::IdtyData;
type IdtyValue =
gdev::runtime_types::pallet_identity::types::IdtyValue<BlockNumber, AccountId, IdtyData>;
// submit extrinsics
pub async fn create_identity(client: &FullClient, from: Keyring, to: Keyring) -> Result<()> {
let from = PairSigner::new(from.pair());
let to = to.to_raw_public();
let _events = create_block_with_extrinsic(
&client.rpc,
client
.client
.tx()
.create_signed(
&gdev::tx().identity().create_identity(to.into()),
&from,
SubstrateExtrinsicParamsBuilder::new().build(),
)
.await?,
)
.await?;
Ok(())
}
pub async fn confirm_identity(client: &FullClient, from: Keyring, pseudo: String) -> Result<()> {
let from = PairSigner::new(from.pair());
let pseudo: IdtyName = IdtyName(pseudo.as_bytes().to_vec());
let _events = create_block_with_extrinsic(
&client.rpc,
client
.client
.tx()
.create_signed(
&gdev::tx().identity().confirm_identity(pseudo),
&from,
SubstrateExtrinsicParamsBuilder::new().build(),
)
.await?,
)
.await?;
Ok(())
}
// get identity index from account keyring name
pub async fn get_identity_index(world: &DuniterWorld, account: String) -> Result<u32> {
let account: AccountId = Keyring::from_str(&account)
.expect("unknown account")
.to_raw_public()
.into();
let identity_index = world
.read(&gdev::storage().identity().identity_index_of(&account))
.await
.await?
.ok_or_else(|| anyhow::anyhow!("identity {} has no associated index", account))
.unwrap();
Ok(identity_index)
}
// get identity value from account keyring name
pub async fn get_identity_value(world: &DuniterWorld, account: String) -> Result<IdtyValue> {
let identity_index = get_identity_index(world, account).await.unwrap();
let identity_value = world
.read(&gdev::storage().identity().identities(identity_index))
.await
.await?
.ok_or_else(|| {
anyhow::anyhow!(
"indentity index {} does not have associated value",
identity_index
)
})?;
Ok(identity_value)
}
// Copyright 2021 Axiom-Team // Copyright 2021 Axiom-Team
// //
// This file is part of Substrate-Libre-Currency. // This file is part of Duniter-v2S.
// //
// Substrate-Libre-Currency is free software: you can redistribute it and/or modify // Duniter-v2S is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License. // the Free Software Foundation, version 3 of the License.
// //
// Substrate-Libre-Currency is distributed in the hope that it will be useful, // Duniter-v2S is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU Affero General Public License for more details.
// //
// 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 Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>. // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
#![allow(clippy::enum_variant_names, dead_code, unused_imports)] #![allow(clippy::enum_variant_names, dead_code, unused_imports)]
pub mod balances; pub mod balances;
pub mod cert;
pub mod distance;
pub mod identity;
pub mod oneshot;
#[subxt::subxt(runtime_metadata_path = "../resources/metadata.scale")] #[subxt::subxt(
pub mod node_runtime {} runtime_metadata_path = "../resources/metadata.scale",
derive_for_all_types = "Eq, PartialEq"
)]
pub mod gdev {}
use anyhow::anyhow;
use codec::Encode;
use notify_debouncer_mini::new_debouncer;
use serde_json::Value; use serde_json::Value;
use sp_keyring::AccountKeyring; use sp_keyring::sr25519::Keyring;
use std::io::prelude::*; use std::{
use std::path::PathBuf; io::prelude::*,
use std::process::Command; path::{Path, PathBuf},
use std::str::FromStr; process::Command,
use subxt::rpc::{rpc_params, ClientT, SubscriptionClientT}; str::FromStr,
use subxt::{ClientBuilder, DefaultConfig, DefaultExtra}; time::{Duration, Instant},
};
pub type Api = node_runtime::RuntimeApi<DefaultConfig, DefaultExtra<DefaultConfig>>; use subxt::{
pub type Client = subxt::Client<DefaultConfig>; backend::rpc::RpcClient,
config::{substrate::SubstrateExtrinsicParamsBuilder, SubstrateExtrinsicParams},
ext::subxt_rpcs::client::{rpc_params, RpcParams},
};
pub type Client = subxt::OnlineClient<GdevConfig>;
pub type Event = gdev::Event;
pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>; pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
pub type TransactionProgress<'client> = pub type SubmittableExtrinsic = subxt::tx::SubmittableTransaction<GdevConfig, Client>;
subxt::TransactionProgress<'client, DefaultConfig, node_runtime::DispatchError>; pub type TxProgress = subxt::tx::TxProgress<GdevConfig, Client>;
pub const SUDO_ACCOUNT: AccountKeyring = AccountKeyring::Alice; pub enum GdevConfig {}
impl subxt::config::Config for GdevConfig {
type AccountId = subxt::utils::AccountId32;
type Address = subxt::utils::MultiAddress<Self::AccountId, u32>;
type AssetId = ();
type ExtrinsicParams = SubstrateExtrinsicParams<Self>;
type Hash = subxt::utils::H256;
type Hasher = subxt::config::substrate::BlakeTwo256;
type Header =
subxt::config::substrate::SubstrateHeader<u32, subxt::config::substrate::BlakeTwo256>;
type Signature = subxt::utils::MultiSignature;
}
#[derive(Copy, Clone, Debug, Default, Encode)]
pub struct Tip {
#[codec(compact)]
tip: u64,
}
pub struct FullClient {
pub rpc: RpcClient,
pub client: Client,
}
impl Tip {
pub fn new(amount: u64) -> Self {
Tip { tip: amount }
}
}
impl From<u64> for Tip {
fn from(n: u64) -> Self {
Self::new(n)
}
}
pub const SUDO_ACCOUNT: Keyring = Keyring::Alice;
pub struct Process(std::process::Child); pub struct Process(std::process::Child);
impl Process { impl Process {
...@@ -45,16 +97,27 @@ impl Process { ...@@ -45,16 +97,27 @@ impl Process {
} }
} }
// Do not let the process keep running after the tests ended
impl Drop for Process {
fn drop(&mut self) {
self.kill()
}
}
pub const DISTANCE_ORACLE_LOCAL_PATH: &str = "../target/debug/distance-oracle";
const DUNITER_DOCKER_PATH: &str = "/usr/local/bin/duniter"; const DUNITER_DOCKER_PATH: &str = "/usr/local/bin/duniter";
const DUNITER_LOCAL_PATH: &str = "../target/debug/duniter"; const DUNITER_LOCAL_PATH: &str = "../target/debug/duniter";
struct FullNode { struct FullNode {
process: Process, process: Process,
p2p_port: u16, p2p_port: u16,
ws_port: u16, rpc_port: u16,
} }
pub async fn spawn_node(maybe_genesis_conf_file: Option<PathBuf>) -> (Api, Client, Process) { pub async fn spawn_node(
maybe_genesis_conf_file: Option<PathBuf>,
no_spawn: bool,
) -> (FullClient, Option<Process>, u16) {
println!("maybe_genesis_conf_file={:?}", maybe_genesis_conf_file); println!("maybe_genesis_conf_file={:?}", maybe_genesis_conf_file);
let duniter_binary_path = std::env::var("DUNITER_BINARY_PATH").unwrap_or_else(|_| { let duniter_binary_path = std::env::var("DUNITER_BINARY_PATH").unwrap_or_else(|_| {
if std::path::Path::new(DUNITER_DOCKER_PATH).exists() { if std::path::Path::new(DUNITER_DOCKER_PATH).exists() {
...@@ -64,57 +127,69 @@ pub async fn spawn_node(maybe_genesis_conf_file: Option<PathBuf>) -> (Api, Clien ...@@ -64,57 +127,69 @@ pub async fn spawn_node(maybe_genesis_conf_file: Option<PathBuf>) -> (Api, Clien
} }
}); });
let mut the_rpc_port = 9944;
let mut opt_process = None;
// Eventually spawn a node (we most likely will - unless --no-spawn option is used)
if !no_spawn {
let FullNode { let FullNode {
process, process,
p2p_port: _, p2p_port: _,
ws_port, rpc_port,
} = spawn_full_node( } = spawn_full_node(
&["--dev", "--execution=Native", "--sealing=manual"], &[
"--chain=gdev_dev",
"--execution=Native",
"--sealing=manual",
// Necessary options which were previously set by --dev option:
"--force-authoring",
"--rpc-cors=all",
"--alice",
"--tmp",
"--unsafe-force-node-key-generation",
// Fix: End2End test may fail due to network discovery. This option disables automatic peer discovery.π
"--reserved-only",
// prevent local network discovery (even it does not connect due to above flag)
"--no-mdns",
],
&duniter_binary_path, &duniter_binary_path,
maybe_genesis_conf_file, maybe_genesis_conf_file,
); );
let client = ClientBuilder::new() opt_process = Some(process);
.set_url(format!("ws://127.0.0.1:{}", ws_port)) the_rpc_port = rpc_port;
.build() }
let rpc = RpcClient::from_url(format!("ws://127.0.0.1:{}", the_rpc_port))
.await .await
.expect("fail to connect to node"); .expect("Failed to create the rpc backend");
let api = client.clone().to_runtime_api::<Api>(); let client = Client::from_rpc_client(rpc.clone()).await.unwrap();
(api, client, process) (FullClient { rpc, client }, opt_process, the_rpc_port)
} }
pub async fn create_empty_block(client: &Client) -> Result<()> { pub async fn create_empty_block(client: &RpcClient) -> Result<()> {
// Create an empty block // Create an empty block
let _: Value = client let _: Value = client
.rpc() .request("engine_createBlock", rpc_params![true, true, Value::Null])
.client
.request("engine_createBlock", rpc_params![true, false, Value::Null])
.await?; .await?;
Ok(()) Ok(())
} }
pub async fn create_block_with_extrinsic( pub async fn create_block_with_extrinsic(
client: &Client, client: &RpcClient,
extrinsic: subxt::UncheckedExtrinsic<DefaultConfig, DefaultExtra<DefaultConfig>>, extrinsic: SubmittableExtrinsic,
) -> Result<subxt::TransactionEvents<DefaultConfig>> { ) -> Result<subxt::blocks::ExtrinsicEvents<GdevConfig>> {
// Get a hash of the extrinsic (we'll need this later). //println!("extrinsic encoded: {}", hex::encode(extrinsic.encoded()));
use subxt::sp_runtime::traits::Hash as _;
let ext_hash = <DefaultConfig as subxt::Config>::Hashing::hash_of(&extrinsic); let watcher = extrinsic.submit_and_watch().await?;
// Submit and watch for transaction progress.
let sub = client.rpc().watch_extrinsic(extrinsic).await?;
let watcher = TransactionProgress::new(sub, client, ext_hash);
// Create a non-empty block // Create a non-empty block
let _: Value = client let _: Value = client
.rpc() .request("engine_createBlock", rpc_params![false, true, Value::Null])
.client
.request("engine_createBlock", rpc_params![false, false, Value::Null])
.await?; .await?;
// Get extrinsic events // Get extrinsic events
watcher watcher
.wait_for_in_block() .wait_for_finalized()
.await? .await?
.fetch_events() .fetch_events()
.await .await
...@@ -129,16 +204,16 @@ fn spawn_full_node( ...@@ -129,16 +204,16 @@ fn spawn_full_node(
// Ports // Ports
let p2p_port = portpicker::pick_unused_port().expect("No ports free"); let p2p_port = portpicker::pick_unused_port().expect("No ports free");
let rpc_port = portpicker::pick_unused_port().expect("No ports free"); let rpc_port = portpicker::pick_unused_port().expect("No ports free");
let ws_port = portpicker::pick_unused_port().expect("No ports free");
// Env vars // Env vars
let mut envs = Vec::new(); let mut envs = Vec::new();
if let Some(genesis_conf_file) = maybe_genesis_conf_file { if let Some(genesis_conf_file) = maybe_genesis_conf_file {
envs.push(("DUNITER_GENESIS_CONFIG", genesis_conf_file)); envs.push(("DUNITER_GENESIS_CONFIG", genesis_conf_file.clone()));
envs.push(("DUNITER_GENESIS_DATA", genesis_conf_file));
} }
// Logs // Logs
let log_file_path = format!("duniter-v2s-{}.log", ws_port); let log_file_path = format!("duniter-v2s-{}.log", rpc_port);
let log_file = std::fs::File::create(&log_file_path).expect("fail to create log file"); let log_file = std::fs::File::create(&log_file_path).expect("fail to create log file");
// Command // Command
...@@ -148,13 +223,10 @@ fn spawn_full_node( ...@@ -148,13 +223,10 @@ fn spawn_full_node(
[ [
"--no-telemetry", "--no-telemetry",
"--no-prometheus", "--no-prometheus",
"--tmp",
"--port", "--port",
&p2p_port.to_string(), &p2p_port.to_string(),
"--rpc-port", "--rpc-port",
&rpc_port.to_string(), &rpc_port.to_string(),
"--ws-port",
&ws_port.to_string(),
] ]
.iter() .iter()
.chain(args), .chain(args),
...@@ -168,9 +240,9 @@ fn spawn_full_node( ...@@ -168,9 +240,9 @@ fn spawn_full_node(
let timeout = let timeout =
if let Ok(duration_string) = std::env::var("DUNITER_END2END_TESTS_SPAWN_NODE_TIMEOUT") { if let Ok(duration_string) = std::env::var("DUNITER_END2END_TESTS_SPAWN_NODE_TIMEOUT") {
duration_string.parse().unwrap_or(4) duration_string.parse().unwrap_or(10)
} else { } else {
4 10
}; };
wait_until_log_line( wait_until_log_line(
...@@ -182,23 +254,43 @@ fn spawn_full_node( ...@@ -182,23 +254,43 @@ fn spawn_full_node(
FullNode { FullNode {
process, process,
p2p_port, p2p_port,
ws_port, rpc_port,
} }
} }
fn wait_until_log_line(expected_log_line: &str, log_file_path: &str, timeout: std::time::Duration) { fn wait_until_log_line(expected_log_line: &str, log_file_path: &str, timeout: Duration) {
if cfg!(target_os = "macos") {
// MacOs seems to not be able to use inotify (buggy)
// So we use a specific implementation for `wait_until_log_line()` here
let start = Instant::now();
loop {
let now = Instant::now();
if now.duration_since(start) > timeout {
eprintln!("Timeout starting node");
std::process::exit(1);
}
if has_log_line(log_file_path, expected_log_line) {
// Ready
return;
}
std::thread::sleep(Duration::from_millis(100));
}
} else {
let (tx, rx) = std::sync::mpsc::channel(); let (tx, rx) = std::sync::mpsc::channel();
let mut watcher = notify::watcher(tx, std::time::Duration::from_millis(100)).unwrap(); let mut debouncer = new_debouncer(std::time::Duration::from_millis(100), tx).unwrap();
use notify::Watcher as _; debouncer
watcher .watcher()
.watch(&log_file_path, notify::RecursiveMode::NonRecursive) .watch(
Path::new(log_file_path),
notify::RecursiveMode::NonRecursive,
)
.unwrap(); .unwrap();
let mut pos = 0; let mut pos = 0;
loop { loop {
match rx.recv_timeout(timeout) { match rx.recv_timeout(timeout) {
Ok(notify::DebouncedEvent::Write(_)) => { Ok(_) => {
let mut file = std::fs::File::open(&log_file_path).unwrap(); let mut file = std::fs::File::open(log_file_path).unwrap();
file.seek(std::io::SeekFrom::Start(pos)).unwrap(); file.seek(std::io::SeekFrom::Start(pos)).unwrap();
pos = file.metadata().unwrap().len(); pos = file.metadata().unwrap().len();
let reader = std::io::BufReader::new(file); let reader = std::io::BufReader::new(file);
...@@ -209,7 +301,6 @@ fn wait_until_log_line(expected_log_line: &str, log_file_path: &str, timeout: st ...@@ -209,7 +301,6 @@ fn wait_until_log_line(expected_log_line: &str, log_file_path: &str, timeout: st
} }
} }
} }
Ok(_) => {}
Err(err) => { Err(err) => {
eprintln!("Error: {:?}", err); eprintln!("Error: {:?}", err);
std::process::exit(1); std::process::exit(1);
...@@ -217,3 +308,85 @@ fn wait_until_log_line(expected_log_line: &str, log_file_path: &str, timeout: st ...@@ -217,3 +308,85 @@ fn wait_until_log_line(expected_log_line: &str, log_file_path: &str, timeout: st
} }
} }
} }
}
fn has_log_line(log_file_path: &str, expected_log_line: &str) -> bool {
let mut file = std::fs::File::open(log_file_path).unwrap();
file.seek(std::io::SeekFrom::Start(0)).unwrap();
let reader = std::io::BufReader::new(file);
for line in reader.lines() {
if line.expect("fail to read line").contains(expected_log_line) {
return true;
}
}
false
}
pub fn spawn_distance_oracle(distance_oracle_binary_path: &str, duniter_rpc_port: u16) {
Command::new(distance_oracle_binary_path)
.args(
[
"-u",
&format!("ws://127.0.0.1:{duniter_rpc_port}"),
"-d",
"/tmp/duniter-cucumber/chains/gdev/distance",
]
.iter(),
)
.spawn()
.expect("failed to spawn distance oracle")
.wait()
.unwrap();
}
/// A concrete PairSigner implementation which relies on `sr25519::Pair` for signing
/// and where GdevConfig is the runtime configuration.
mod pair_signer {
use super::*;
use sp_core::{sr25519, Pair as _};
use sp_runtime::{
traits::{IdentifyAccount, Verify},
MultiSignature as SpMultiSignature,
};
use subxt::{
config::substrate::{AccountId32, MultiSignature},
tx::Signer,
Config,
};
#[derive(Clone)]
pub struct PairSigner {
account_id: <GdevConfig as Config>::AccountId,
signer: sr25519::Pair,
}
impl PairSigner {
pub fn new(signer: sr25519::Pair) -> Self {
let account_id =
<SpMultiSignature as Verify>::Signer::from(signer.public()).into_account();
Self {
account_id: AccountId32(account_id.into()),
signer,
}
}
pub fn signer(&self) -> &sr25519::Pair {
&self.signer
}
pub fn account_id(&self) -> &AccountId32 {
&self.account_id
}
}
impl Signer<GdevConfig> for PairSigner {
fn account_id(&self) -> <GdevConfig as Config>::AccountId {
self.account_id.clone()
}
fn sign(&self, signer_payload: &[u8]) -> <GdevConfig as Config>::Signature {
let signature = self.signer.sign(signer_payload);
MultiSignature::Sr25519(signature.0)
}
}
}
// Copyright 2021 Axiom-Team
//
// This file is part of Duniter-v2S.
//
// Duniter-v2S is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Duniter-v2S is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// 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/>.
use super::{
gdev,
gdev::runtime_types::{pallet_balances, pallet_oneshot_account},
*,
};
use crate::common::pair_signer::PairSigner;
use sp_keyring::sr25519::Keyring;
use subxt::utils::{AccountId32, MultiAddress};
pub enum Account {
Normal(Keyring),
Oneshot(Keyring),
}
impl Account {
fn to_account_id(
&self,
) -> pallet_oneshot_account::types::Account<MultiAddress<AccountId32, ()>> {
match self {
Account::Normal(account) => pallet_oneshot_account::types::Account::Normal(
MultiAddress::Id(account.to_raw_public().into()),
),
Account::Oneshot(account) => pallet_oneshot_account::types::Account::Oneshot(
MultiAddress::Id(account.to_raw_public().into()),
),
}
}
}
pub async fn create_oneshot_account(
client: &FullClient,
from: Keyring,
amount: u64,
to: Keyring,
) -> Result<()> {
let from = PairSigner::new(from.pair());
let to = MultiAddress::Id(to.to_raw_public().into());
let _events = create_block_with_extrinsic(
&client.rpc,
client
.client
.tx()
.create_signed(
&gdev::tx()
.oneshot_account()
.create_oneshot_account(to, amount),
&from,
SubstrateExtrinsicParamsBuilder::new().build(),
)
.await?,
)
.await?;
Ok(())
}
pub async fn consume_oneshot_account(
client: &FullClient,
from: Keyring,
to: Account,
) -> Result<()> {
let from = PairSigner::new(from.pair());
let to = to.to_account_id();
let _events = create_block_with_extrinsic(
&client.rpc,
client
.client
.tx()
.create_signed(
&gdev::tx().oneshot_account().consume_oneshot_account(0, to),
&from,
SubstrateExtrinsicParamsBuilder::new().build(),
)
.await?,
)
.await?;
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub async fn consume_oneshot_account_with_remaining(
client: &FullClient,
from: Keyring,
amount: u64,
to: Account,
remaining_to: Account,
) -> Result<()> {
let from = PairSigner::new(from.pair());
let to = to.to_account_id();
let remaining_to = remaining_to.to_account_id();
let _events = create_block_with_extrinsic(
&client.rpc,
client
.client
.tx()
.create_signed(
&gdev::tx()
.oneshot_account()
.consume_oneshot_account_with_remaining(0, to, remaining_to, amount),
&from,
SubstrateExtrinsicParamsBuilder::new().build(),
)
.await?,
)
.await?;
Ok(())
}
// Copyright 2021 Axiom-Team // Copyright 2021 Axiom-Team
// //
// This file is part of Substrate-Libre-Currency. // This file is part of Duniter-v2S.
// //
// Substrate-Libre-Currency is free software: you can redistribute it and/or modify // Duniter-v2S is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License. // the Free Software Foundation, version 3 of the License.
// //
// Substrate-Libre-Currency is distributed in the hope that it will be useful, // Duniter-v2S is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU Affero General Public License for more details.
// //
// 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 Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>. // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
mod common; mod common;
use async_trait::async_trait;
use common::*; use common::*;
use cucumber::{given, then, when, World, WorldInit}; use cucumber::{given, then, when, StatsWriter, World};
use sp_keyring::AccountKeyring; use sp_keyring::sr25519::Keyring;
use std::convert::Infallible; use std::{
use std::path::PathBuf; path::PathBuf,
use std::str::FromStr; str::FromStr,
use std::sync::{ sync::{
atomic::{AtomicBool, Ordering}, atomic::{AtomicBool, Ordering},
Arc, Arc,
},
}; };
use subxt::backend::rpc::RpcClient;
#[derive(WorldInit)] // ===== world =====
pub struct DuniterWorld(Option<DuniterWorldInner>);
#[derive(cucumber::World, Default)]
pub struct DuniterWorld {
ignore_errors: bool,
inner: Option<DuniterWorldInner>,
}
impl DuniterWorld { impl DuniterWorld {
async fn init(&mut self, maybe_genesis_conf_file: Option<PathBuf>) { // Write methods
if let Some(ref mut inner) = self.0 { async fn init(&mut self, maybe_genesis_conf_file: Option<PathBuf>, no_spawn: bool) {
if let Some(ref mut inner) = self.inner {
inner.kill(); inner.kill();
} }
self.0 = Some(DuniterWorldInner::new(maybe_genesis_conf_file).await); self.inner = Some(DuniterWorldInner::new(maybe_genesis_conf_file, no_spawn).await);
}
fn kill(&mut self) {
if let Some(ref mut inner) = self.inner {
inner.kill();
} }
fn api(&self) -> &Api { }
if let Some(ref inner) = self.0 {
&inner.api fn set_ignore_errors(&mut self, ignore_errors: bool) {
self.ignore_errors = ignore_errors;
}
// Read methods
fn rpc_client(&self) -> &RpcClient {
if let Some(ref inner) = self.inner {
&inner.client.rpc
} else { } else {
panic!("uninit") panic!("uninit")
} }
} }
fn client(&self) -> &Client { fn client(&self) -> &Client {
if let Some(ref inner) = self.0 { if let Some(ref inner) = self.inner {
&inner.client.client
} else {
panic!("uninit")
}
}
fn full_client(&self) -> &FullClient {
if let Some(ref inner) = self.inner {
&inner.client &inner.client
} else { } else {
panic!("uninit") panic!("uninit")
} }
} }
fn kill(&mut self) {
if let Some(ref mut inner) = self.0 { // Read methods
inner.kill(); fn ignore_errors(&self) -> bool {
self.ignore_errors
} }
// Read storage entry on last block
async fn read<'a, Address>(
&self,
address: &'a Address,
) -> impl std::future::Future<
Output = std::result::Result<Option<Address::Target>, subxt::error::Error>,
> + 'a
where
Address: subxt::storage::Address<IsFetchable = subxt::custom_values::Yes> + 'a,
{
self.client()
.storage()
.at_latest()
.await
.unwrap()
.fetch(address)
}
// Read storage entry with default value (on last block)
async fn read_or_default<'a, Address>(
&self,
address: &'a Address,
) -> impl std::future::Future<Output = std::result::Result<Address::Target, subxt::error::Error>> + 'a
where
Address: subxt::storage::Address<
IsFetchable = subxt::custom_values::Yes,
IsDefaultable = subxt::custom_values::Yes,
> + 'a,
{
self.client()
.storage()
.at_latest()
.await
.unwrap()
.fetch_or_default(address)
} }
} }
...@@ -65,33 +130,26 @@ impl std::fmt::Debug for DuniterWorld { ...@@ -65,33 +130,26 @@ impl std::fmt::Debug for DuniterWorld {
} }
} }
#[async_trait(?Send)]
impl World for DuniterWorld {
// We do require some error type.
type Error = Infallible;
async fn new() -> std::result::Result<Self, Infallible> {
Ok(DuniterWorld(None))
}
}
struct DuniterWorldInner { struct DuniterWorldInner {
api: Api, client: FullClient,
client: Client, process: Option<Process>,
process: Process, ws_port: u16,
} }
impl DuniterWorldInner { impl DuniterWorldInner {
async fn new(maybe_genesis_conf_file: Option<PathBuf>) -> Self { async fn new(maybe_genesis_conf_file: Option<PathBuf>, no_spawn: bool) -> Self {
let (api, client, process) = spawn_node(maybe_genesis_conf_file).await; let (client, process, ws_port) = spawn_node(maybe_genesis_conf_file, no_spawn).await;
DuniterWorldInner { DuniterWorldInner {
api,
client, client,
process, process,
ws_port,
} }
} }
fn kill(&mut self) { fn kill(&mut self) {
self.process.kill(); if let Some(p) = &mut self.process {
p.kill();
}
} }
} }
...@@ -105,37 +163,43 @@ fn parse_amount(amount: u64, unit: &str) -> (u64, bool) { ...@@ -105,37 +163,43 @@ fn parse_amount(amount: u64, unit: &str) -> (u64, bool) {
} }
} }
#[given(regex = r"([a-zA-Z]+) have (\d+) (ĞD|cĞD|UD|mUD)")] // ===== given =====
#[allow(clippy::needless_pass_by_ref_mut)]
#[given(regex = r"([a-zA-Z]+) ha(?:ve|s) (\d+) (ĞD|cĞD|UD|mUD)")]
async fn who_have(world: &mut DuniterWorld, who: String, amount: u64, unit: String) -> Result<()> { async fn who_have(world: &mut DuniterWorld, who: String, amount: u64, unit: String) -> Result<()> {
// Parse inputs // Parse inputs
let who = AccountKeyring::from_str(&who).expect("unknown to"); let who = Keyring::from_str(&who).expect("unknown to");
let (mut amount, is_ud) = parse_amount(amount, &unit); let (mut amount, is_ud) = parse_amount(amount, &unit);
if is_ud { if is_ud {
let current_ud_amount = world let current_ud_amount = world
.api() .read(&gdev::storage().universal_dividend().current_ud())
.storage() .await
.universal_dividend() .await?
.current_ud(None) .unwrap_or_default();
.await?;
amount = (amount * current_ud_amount) / 1_000; amount = (amount * current_ud_amount) / 1_000;
} }
// Create {amount} ĞD for {who} // Create {amount} ĞD for {who}
common::balances::set_balance(world.api(), world.client(), who, amount).await?; common::balances::set_balance(world.full_client(), who, amount).await?;
Ok(()) Ok(())
} }
// ===== when =====
#[allow(clippy::needless_pass_by_ref_mut)]
#[when(regex = r"(\d+) blocks? later")] #[when(regex = r"(\d+) blocks? later")]
async fn n_blocks_later(world: &mut DuniterWorld, n: usize) -> Result<()> { async fn n_blocks_later(world: &mut DuniterWorld, n: usize) -> Result<()> {
for _ in 0..n { for _ in 0..n {
common::create_empty_block(world.client()).await?; common::create_empty_block(world.rpc_client()).await?;
} }
Ok(()) Ok(())
} }
#[when(regex = r"([a-zA-Z]+) send (\d+) (ĞD|cĞD|UD|mUD) to ([a-zA-Z]+)")] #[allow(clippy::needless_pass_by_ref_mut)]
#[when(regex = r"([a-zA-Z]+) sends? (\d+) (ĞD|cĞD|UD|mUD) to ([a-zA-Z]+)$")]
async fn transfer( async fn transfer(
world: &mut DuniterWorld, world: &mut DuniterWorld,
from: String, from: String,
...@@ -144,39 +208,238 @@ async fn transfer( ...@@ -144,39 +208,238 @@ async fn transfer(
to: String, to: String,
) -> Result<()> { ) -> Result<()> {
// Parse inputs // Parse inputs
let from = AccountKeyring::from_str(&from).expect("unknown from"); let from = Keyring::from_str(&from).expect("unknown from");
let to = AccountKeyring::from_str(&to).expect("unknown to"); let to = Keyring::from_str(&to).expect("unknown to");
let (amount, is_ud) = parse_amount(amount, &unit); let (amount, is_ud) = parse_amount(amount, &unit);
if is_ud { let res = if is_ud {
common::balances::transfer_ud(world.api(), world.client(), from, amount, to).await common::balances::transfer_ud(world.full_client(), from, amount, to).await
} else {
common::balances::transfer(world.full_client(), from, amount, to).await
};
if world.ignore_errors() {
Ok(())
} else { } else {
common::balances::transfer(world.api(), world.client(), from, amount, to).await res
} }
} }
#[when(regex = r"([a-zA-Z]+) sends all (?:his|her) (?:ĞDs?|DUs?) to ([a-zA-Z]+)")] #[allow(clippy::needless_pass_by_ref_mut)]
#[when(regex = r"([a-zA-Z]+) sends? (\d+) (ĞD|cĞD) to oneshot ([a-zA-Z]+)")]
async fn create_oneshot_account(
world: &mut DuniterWorld,
from: String,
amount: u64,
unit: String,
to: String,
) -> Result<()> {
// Parse inputs
let from = Keyring::from_str(&from).expect("unknown from");
let to = Keyring::from_str(&to).expect("unknown to");
let (amount, is_ud) = parse_amount(amount, &unit);
assert!(!is_ud);
common::oneshot::create_oneshot_account(world.full_client(), from, amount, to).await
}
#[allow(clippy::needless_pass_by_ref_mut)]
#[when(regex = r"oneshot ([a-zA-Z]+) consumes? into (oneshot|account) ([a-zA-Z]+)")]
async fn consume_oneshot_account(
world: &mut DuniterWorld,
from: String,
is_dest_oneshot: String,
to: String,
) -> Result<()> {
// Parse inputs
let from = Keyring::from_str(&from).expect("unknown from");
let to = Keyring::from_str(&to).expect("unknown to");
let to = match is_dest_oneshot.as_str() {
"oneshot" => common::oneshot::Account::Oneshot(to),
"account" => common::oneshot::Account::Normal(to),
_ => unreachable!(),
};
common::oneshot::consume_oneshot_account(world.full_client(), from, to).await
}
#[when(
regex = r"oneshot ([a-zA-Z]+) consumes? (\d+) (ĞD|cĞD) into (oneshot|account) ([a-zA-Z]+) and the rest into (oneshot|account) ([a-zA-Z]+)"
)]
#[allow(clippy::too_many_arguments)]
#[allow(clippy::needless_pass_by_ref_mut)]
async fn consume_oneshot_account_with_remaining(
world: &mut DuniterWorld,
from: String,
amount: u64,
unit: String,
is_dest_oneshot: String,
to: String,
is_remaining_to_oneshot: String,
remaining_to: String,
) -> Result<()> {
// Parse inputs
let from = Keyring::from_str(&from).expect("unknown from");
let to = Keyring::from_str(&to).expect("unknown to");
let remaining_to = Keyring::from_str(&remaining_to).expect("unknown remaining_to");
let to = match is_dest_oneshot.as_str() {
"oneshot" => common::oneshot::Account::Oneshot(to),
"account" => common::oneshot::Account::Normal(to),
_ => unreachable!(),
};
let remaining_to = match is_remaining_to_oneshot.as_str() {
"oneshot" => common::oneshot::Account::Oneshot(remaining_to),
"account" => common::oneshot::Account::Normal(remaining_to),
_ => unreachable!(),
};
let (amount, is_ud) = parse_amount(amount, &unit);
assert!(!is_ud);
common::oneshot::consume_oneshot_account_with_remaining(
world.full_client(),
from,
amount,
to,
remaining_to,
)
.await
}
#[allow(clippy::needless_pass_by_ref_mut)]
#[when(regex = r"([a-zA-Z]+) sends? all (?:his|her) (?:ĞDs?|DUs?|UDs?) to ([a-zA-Z]+)")]
async fn send_all_to(world: &mut DuniterWorld, from: String, to: String) -> Result<()> { async fn send_all_to(world: &mut DuniterWorld, from: String, to: String) -> Result<()> {
// Parse inputs // Parse inputs
let from = AccountKeyring::from_str(&from).expect("unknown from"); let from = Keyring::from_str(&from).expect("unknown from");
let to = AccountKeyring::from_str(&to).expect("unknown to"); let to = Keyring::from_str(&to).expect("unknown to");
common::balances::transfer_all(world.full_client(), from, to).await
}
#[allow(clippy::needless_pass_by_ref_mut)]
#[when(regex = r"([a-zA-Z]+) certifies ([a-zA-Z]+)")]
async fn certifies(world: &mut DuniterWorld, from: String, to: String) -> Result<()> {
// Parse inputs
let from = Keyring::from_str(&from).expect("unknown from");
let to = Keyring::from_str(&to).expect("unknown to");
common::balances::transfer_all(world.api(), world.client(), from, to).await common::cert::certify(world.full_client(), from, to).await
} }
#[then(regex = r"([a-zA-Z]+) should have (\d+) ĞD")] #[allow(clippy::needless_pass_by_ref_mut)]
async fn should_have(world: &mut DuniterWorld, who: String, amount: u64) -> Result<()> { #[when(regex = r"([a-zA-Z]+) creates identity for ([a-zA-Z]+)")]
async fn creates_identity(world: &mut DuniterWorld, from: String, to: String) -> Result<()> {
// Parse inputs // Parse inputs
let who = AccountKeyring::from_str(&who) let from = Keyring::from_str(&from).expect("unknown from");
let to = Keyring::from_str(&to).expect("unknown to");
common::identity::create_identity(world.full_client(), from, to).await
}
#[allow(clippy::needless_pass_by_ref_mut)]
#[when(regex = r#"([a-zA-Z]+) confirms (?:his|her) identity with pseudo "([a-zA-Z]+)""#)]
async fn confirm_identity(world: &mut DuniterWorld, from: String, pseudo: String) -> Result<()> {
let from = Keyring::from_str(&from).expect("unknown from");
common::identity::confirm_identity(world.full_client(), from, pseudo).await
}
#[allow(clippy::needless_pass_by_ref_mut)]
#[when(regex = r#"([a-zA-Z]+) requests distance evaluation"#)]
async fn request_distance_evaluation(world: &mut DuniterWorld, who: String) -> Result<()> {
let who = Keyring::from_str(&who).expect("unknown origin");
common::distance::request_evaluation(world.full_client(), who).await
}
#[allow(clippy::needless_pass_by_ref_mut)]
#[when(regex = r#"([a-zA-Z]+) runs distance oracle"#)]
async fn run_distance_oracle(world: &mut DuniterWorld, who: String) -> Result<()> {
let who = Keyring::from_str(&who).expect("unknown origin");
common::distance::run_oracle(
world.full_client(),
who,
format!("ws://127.0.0.1:{}", world.inner.as_ref().unwrap().ws_port),
)
.await
}
// ===== then ====
#[allow(clippy::needless_pass_by_ref_mut)]
#[then(regex = r"treasury should contain (\d+) (ĞD|cĞD)")]
async fn treasury_should_contain(
world: &mut DuniterWorld,
amount: u64,
unit: String,
) -> Result<()> {
let who =
subxt::utils::AccountId32::from_str("5EYCAe5ijiYfyeZ2JJCGq56LmPyNRAKzpG4QkoQkkQNB5e6Z")
.expect("invalid treasury account id");
let (amount, _is_ud) = parse_amount(amount, &unit);
let who_account = world
.read_or_default(&gdev::storage().system().account(&who))
.await
.await?;
assert_eq!(who_account.data.free, amount);
Ok(())
}
#[allow(clippy::needless_pass_by_ref_mut)]
#[then(regex = r"([a-zA-Z]+) should have (\d+) (ĞD|cĞD)( reserved)?")]
async fn should_have(
world: &mut DuniterWorld,
who: String,
amount: u64,
unit: String,
reserved: String,
) -> Result<()> {
// Parse inputs
let who: subxt::utils::AccountId32 = Keyring::from_str(&who)
.expect("unknown to") .expect("unknown to")
.to_account_id(); .to_raw_public()
let amount = amount * 100; .into();
let (amount, _is_ud) = parse_amount(amount, &unit);
let who_account = world.api().storage().system().account(who, None).await?; let who_account = world
.read_or_default(&gdev::storage().system().account(&who))
.await
.await?;
if reserved.is_empty() {
assert_eq!(who_account.data.free, amount); assert_eq!(who_account.data.free, amount);
} else {
assert_eq!(who_account.data.reserved, amount);
}
Ok(())
}
#[allow(clippy::needless_pass_by_ref_mut)]
#[then(regex = r"([a-zA-Z]+) should have oneshot (\d+) (ĞD|cĞD)")]
async fn should_have_oneshot(
world: &mut DuniterWorld,
who: String,
amount: u64,
unit: String,
) -> Result<()> {
// Parse inputs
let who: subxt::utils::AccountId32 = Keyring::from_str(&who)
.expect("unknown to")
.to_raw_public()
.into();
let (amount, _is_ud) = parse_amount(amount, &unit);
let oneshot_amount = world
.read(&gdev::storage().oneshot_account().oneshot_accounts(&who))
.await
.await?;
assert_eq!(oneshot_amount.unwrap_or(0), amount);
Ok(()) Ok(())
} }
#[allow(clippy::needless_pass_by_ref_mut)]
#[then(regex = r"Current UD amount should be (\d+).(\d+)")] #[then(regex = r"Current UD amount should be (\d+).(\d+)")]
async fn current_ud_amount_should_be( async fn current_ud_amount_should_be(
world: &mut DuniterWorld, world: &mut DuniterWorld,
...@@ -185,33 +448,154 @@ async fn current_ud_amount_should_be( ...@@ -185,33 +448,154 @@ async fn current_ud_amount_should_be(
) -> Result<()> { ) -> Result<()> {
let expected = (amount * 100) + cents; let expected = (amount * 100) + cents;
let actual = world let actual = world
.api() .read_or_default(&gdev::storage().universal_dividend().current_ud())
.storage() .await
.universal_dividend()
.current_ud(None)
.await?; .await?;
assert_eq!(actual, expected); assert_eq!(actual, expected);
Ok(()) Ok(())
} }
#[allow(clippy::needless_pass_by_ref_mut)]
#[then(regex = r"Monetary mass should be (\d+).(\d+)")] #[then(regex = r"Monetary mass should be (\d+).(\d+)")]
async fn monetary_mass_should_be(world: &mut DuniterWorld, amount: u64, cents: u64) -> Result<()> { async fn monetary_mass_should_be(world: &mut DuniterWorld, amount: u64, cents: u64) -> Result<()> {
let expected = (amount * 100) + cents; let expected = (amount * 100) + cents;
let actual = world let actual = world
.api() .read_or_default(&gdev::storage().universal_dividend().monetary_mass())
.storage() .await
.universal_dividend()
.monetary_mass(None)
.await?; .await?;
assert_eq!(actual, expected); assert_eq!(actual, expected);
Ok(()) Ok(())
} }
#[allow(clippy::needless_pass_by_ref_mut)]
#[then(regex = r"([a-zA-Z]+) should be certified by ([a-zA-Z]+)")]
async fn should_be_certified_by(
world: &mut DuniterWorld,
receiver: String,
issuer: String,
) -> Result<()> {
// Parse inputs
let receiver_account: subxt::utils::AccountId32 = Keyring::from_str(&receiver)
.expect("unknown to")
.to_raw_public()
.into();
let issuer_account: subxt::utils::AccountId32 = Keyring::from_str(&issuer)
.expect("unknown to")
.to_raw_public()
.into();
// get corresponding identities index
let issuer_index = world
.read(
&gdev::storage()
.identity()
.identity_index_of(&issuer_account),
)
.await
.await?
.unwrap();
let receiver_index = world
.read(
&gdev::storage()
.identity()
.identity_index_of(&receiver_account),
)
.await
.await?
.unwrap();
let issuers = world
.read_or_default(
&gdev::storage()
.certification()
.certs_by_receiver(receiver_index),
)
.await
.await?;
// look for certification by issuer/receiver pair
match issuers.binary_search_by(|(issuer_, _)| issuer_.cmp(&issuer_index)) {
Ok(_) => Ok(()),
Err(_) => Err(anyhow::anyhow!(
"no certification found from {} ({issuer_index}) to {} ({receiver_index}): {:?}",
issuer,
receiver,
issuers
)
.into()),
}
}
#[then(regex = r"([a-zA-Z]+) should (not )?be eligible to UD")]
async fn should_be_eligible_to_ud(
world: &mut DuniterWorld,
identity: String,
not: String,
) -> Result<()> {
let eligible = not.is_empty();
assert_eq!(
identity::get_identity_value(world, identity)
.await
.expect("Identity not found")
.data
.first_eligible_ud
!= 0,
eligible
);
Ok(())
}
use gdev::runtime_types::pallet_identity::types::IdtyStatus;
// status from string
impl FromStr for IdtyStatus {
type Err = String;
fn from_str(input: &str) -> std::result::Result<IdtyStatus, String> {
match input {
"unconfirmed" => Ok(IdtyStatus::Unconfirmed),
"unvalidated" => Ok(IdtyStatus::Unvalidated),
"member" => Ok(IdtyStatus::Member),
"notmember" => Ok(IdtyStatus::NotMember),
"revoked" => Ok(IdtyStatus::Revoked),
_ => Err(format!("'{input}' does not match a status")),
}
}
}
#[allow(clippy::needless_pass_by_ref_mut)]
#[then(regex = r"([a-zA-Z]+) identity should be ([a-zA-Z ]+)")]
async fn identity_status_should_be(
world: &mut DuniterWorld,
name: String,
status: String,
) -> Result<()> {
let identity_value = common::identity::get_identity_value(world, name).await?;
let expected_status = IdtyStatus::from_str(&status)?;
assert_eq!(identity_value.status, expected_status);
Ok(())
}
// ============================================================
#[derive(clap::Args)] #[derive(clap::Args)]
struct CustomOpts { struct CustomOpts {
/// Keep running /// Keep running
#[clap(short, long)] #[arg(short, long)]
keep_running: bool, keep_running: bool,
/// Do not spawn a node, reuse expected node on port 9944
#[arg(long)]
no_spawn: bool,
/// For compliance with Jetbrains IDE which pushes extra args.
/// https://youtrack.jetbrains.com/issue/CPP-33071/cargo-test-adds-extra-options-which-conflict-with-Cucumber
#[arg(short, long)]
format: Option<String>,
#[arg(short, long = "show-output")]
show_output: bool,
#[arg(short = 'Z', long)]
z: Option<String>,
} }
const DOCKER_FEATURES_PATH: &str = "/var/lib/duniter/cucumber-features"; const DOCKER_FEATURES_PATH: &str = "/var/lib/duniter/cucumber-features";
...@@ -231,6 +615,7 @@ async fn main() { ...@@ -231,6 +615,7 @@ async fn main() {
let opts = cucumber::cli::Opts::<_, _, _, CustomOpts>::parsed(); let opts = cucumber::cli::Opts::<_, _, _, CustomOpts>::parsed();
let keep_running = opts.custom.keep_running; let keep_running = opts.custom.keep_running;
let no_spawn = opts.custom.no_spawn;
// Handle crtl+C // Handle crtl+C
let running = Arc::new(AtomicBool::new(true)); let running = Arc::new(AtomicBool::new(true));
...@@ -240,43 +625,66 @@ async fn main() { ...@@ -240,43 +625,66 @@ async fn main() {
}) })
.expect("Error setting Ctrl-C handler"); .expect("Error setting Ctrl-C handler");
DuniterWorld::cucumber() let summarize = DuniterWorld::cucumber()
//.fail_on_skipped() .fail_on_skipped()
.max_concurrent_scenarios(4) .max_concurrent_scenarios(4)
.before(|feature, _rule, scenario, world| { .before(move |feature, _rule, scenario, world| {
let mut genesis_conf_file_path = PathBuf::new(); let mut genesis_conf_file_path = PathBuf::new();
genesis_conf_file_path.push("cucumber-genesis"); genesis_conf_file_path.push("cucumber-genesis");
genesis_conf_file_path.push(&format!( genesis_conf_file_path.push(format!(
"{}.json", "{}.json",
genesis_conf_name(&feature.tags, &scenario.tags) genesis_conf_name(&feature.tags, &scenario.tags)
)); ));
Box::pin(world.init(Some(genesis_conf_file_path))) world.set_ignore_errors(ignore_errors(&scenario.tags));
Box::pin(world.init(Some(genesis_conf_file_path), no_spawn))
}) })
.after(move |_feature, _rule, _scenario, maybe_world| { .after(move |_feature, _rule, _scenario, _ev, maybe_world| {
if keep_running { if keep_running {
while running.load(Ordering::SeqCst) {} while running.load(Ordering::SeqCst) {}
} }
// Early kill (not waiting destructor) to save CPU/memory
if let Some(world) = maybe_world { if let Some(world) = maybe_world {
world.kill(); world.kill();
} }
Box::pin(std::future::ready(())) Box::pin(std::future::ready(()))
}) })
.with_cli(opts) .with_cli(opts)
.run_and_exit(features_path) .run(features_path)
.await; .await;
if summarize.failed_steps() > 0 {
panic!("Could not run tests correctly (failed steps)");
}
if summarize.hook_errors() > 0 {
panic!("Could not run tests correctly (hook errors)");
}
if summarize.parsing_errors() > 0 {
panic!("Could not run tests correctly (parsing errors)");
}
if summarize.execution_has_failed() {
panic!("Could not run tests correctly (execution has failed)");
}
} }
fn genesis_conf_name(feature_tags: &[String], scenario_tags: &[String]) -> String { fn genesis_conf_name(feature_tags: &[String], scenario_tags: &[String]) -> String {
for tag in scenario_tags { for tag in scenario_tags {
if let Some(("genesis", conf_name)) = tag.split_once(".") { if let Some(("genesis", conf_name)) = tag.split_once('.') {
return conf_name.to_owned(); return conf_name.to_owned();
} }
} }
for tag in feature_tags { for tag in feature_tags {
if let Some(("genesis", conf_name)) = tag.split_once(".") { if let Some(("genesis", conf_name)) = tag.split_once('.') {
return conf_name.to_owned(); return conf_name.to_owned();
} }
} }
"default".to_owned() "default".to_owned()
} }
fn ignore_errors(scenario_tags: &[String]) -> bool {
for tag in scenario_tags {
if tag == "ignoreErrors" {
return true;
}
}
false
}
// Copyright 2021-2022 Axiom-Team
//
// This file is part of Duniter-v2S.
//
// Duniter-v2S is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Duniter-v2S is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// 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/>.
[package]
authors.workspace = true
description = "duniter live tests"
edition.workspace = true
homepage.workspace = true
license.workspace = true
name = "duniter-live-tests"
repository.workspace = true
version.workspace = true
[dev-dependencies]
anyhow = { workspace = true }
codec = { workspace = true }
countmap = { workspace = true }
sp-core = { workspace = true }
sp-runtime = { workspace = true }
subxt = { workspace = true, features = [
"native",
"jsonrpsee",
] }
tokio = { workspace = true, features = ["macros", "time", "rt-multi-thread"] }
[features]
runtime-benchmarks = []
std = []
try-runtime = []
# Duniter live tests
Kind of tests that run against a live chain!
## Sanity tests
Test suite that verifies the consistency of the onchain storage.
### Run sanity tests
1. Checkout the git tag of the runtime that you want to check
2. run the tests again the default network of the specified runtime type: `cargo sanity-RUNTIME_TYPE`
`RUNTIME_TYPE` should be replaced by `gdev`, `gtest` or `g1`.
#### Custom RPC endpoint
You can choose to use another RPC endpoint by setting the environment variable `WS_RPC_ENDPOINT`.
This is also the only way to test against a different network that the default one which is `ws://localhost:9944`.
#### run against a specific block
You can choose to use run the sanity tests against a specific block by setting the environment
variable `AT_BLOCK_NUMBER`.
**Be careful: this would require to use an archive node.**
### Contribute to sanity tests
The code is in the file `live-tests/tests/sanity_RUNTIME_TYPE.rs`
There is 3 different parts:
1. Runtime types definitions
2. Collect storage data
3. Verify consistency of collected data
// Copyright 2021 Axiom-Team // Copyright 2021 Axiom-Team
// //
// This file is part of Substrate-Libre-Currency. // This file is part of Duniter-v2S.
// //
// Substrate-Libre-Currency is free software: you can redistribute it and/or modify // Duniter-v2S is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License. // the Free Software Foundation, version 3 of the License.
// //
// Substrate-Libre-Currency is distributed in the hope that it will be useful, // Duniter-v2S is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU Affero General Public License for more details.
// //
// 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 Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>. // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
pub mod pallet_babe;
pub mod pallet_grandpa;
// Copyright 2021-2022 Axiom-Team
//
// This file is part of Duniter-v2S.
//
// Duniter-v2S is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Duniter-v2S is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// 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/>.
#[subxt::subxt(runtime_metadata_path = "../resources/metadata.scale")]
pub mod gdev {}
use countmap::CountMap;
use sp_core::{blake2_128, crypto::AccountId32, ByteArray, H256};
use std::collections::{HashMap, HashSet};
use subxt::{backend::rpc::RpcClient, config::SubstrateConfig as GdevConfig};
const DEFAULT_ENDPOINT: &str = "ws://localhost:9944";
const EXISTENTIAL_DEPOSIT: u64 = 100;
//use hex_literal::hex;
//const TREASURY_ACCOUNT_ID: [u8; 32] =
// hex!("6d6f646c70792f74727372790000000000000000000000000000000000000000");
type Client = subxt::OnlineClient<GdevConfig>;
// define gdev basic types
type Balance = u64;
type BlockNumber = u32;
type Index = u32;
// Define gdev types
type AccountInfo = gdev::runtime_types::frame_system::AccountInfo<
Index,
gdev::runtime_types::pallet_duniter_account::types::AccountData<Balance, IdtyIndex>,
>;
type IdtyData = gdev::runtime_types::common_runtime::entities::IdtyData;
type IdtyIndex = u32;
type IdtyValue =
gdev::runtime_types::pallet_identity::types::IdtyValue<BlockNumber, AccountId32, IdtyData>;
type MembershipData = gdev::runtime_types::sp_membership::MembershipData<BlockNumber>;
use gdev::runtime_types::pallet_identity::types::{IdtyName, IdtyStatus};
struct Storage {
accounts: HashMap<AccountId32, AccountInfo>,
identities: HashMap<IdtyIndex, IdtyValue>,
identity_index_of: HashMap<[u8; 16], IdtyIndex>,
memberships: HashMap<IdtyIndex, MembershipData>,
identities_names: HashMap<IdtyIndex, IdtyName>,
}
#[tokio::test(flavor = "current_thread")]
async fn main() -> anyhow::Result<()> {
let ws_rpc_endpoint =
std::env::var("WS_RPC_ENDPOINT").unwrap_or_else(|_| DEFAULT_ENDPOINT.to_owned());
let rpc = RpcClient::from_url(ws_rpc_endpoint)
.await
.expect("Failed to create the rpc backend");
let client = Client::from_rpc_client(rpc.clone()).await.unwrap();
let maybe_block_hash = if let Ok(block_number) = std::env::var("AT_BLOCK_NUMBER") {
let block_number: BlockNumber = block_number.parse()?;
println!("Run sanity tests against ĞDev at block #{}.", block_number);
// FIXME
// client.at(block_number).await?
None
} else {
println!("Run sanity tests against ĞDev at last best block");
None
};
sanity_tests_at(client, maybe_block_hash).await
}
async fn sanity_tests_at(client: Client, _maybe_block_hash: Option<H256>) -> anyhow::Result<()> {
// ===== Collect storage ===== //
// Collect accounts
let mut accounts: HashMap<AccountId32, AccountInfo> = HashMap::new();
let mut account_iter = client
.storage()
.at_latest()
.await
.unwrap()
.iter(gdev::storage().system().account_iter())
.await?;
while let Some(Ok(key)) = account_iter.next().await {
let mut account_id_bytes = [0u8; 32];
account_id_bytes.copy_from_slice(&key.key_bytes[48..]);
accounts.insert(AccountId32::new(account_id_bytes), key.value);
}
println!("accounts.len(): {}.", accounts.len());
// Collect identities
let mut identities: HashMap<IdtyIndex, IdtyValue> = HashMap::new();
let mut idty_iter = client
.storage()
.at_latest()
.await
.unwrap()
.iter(gdev::storage().identity().identities_iter())
.await?;
while let Some(Ok(key)) = idty_iter.next().await {
let mut idty_index_bytes = [0u8; 4];
idty_index_bytes.copy_from_slice(&key.key_bytes[40..]);
let idty_val = IdtyValue {
data: key.value.data,
next_creatable_identity_on: key.value.next_creatable_identity_on,
old_owner_key: None, // Not used in the live test, skip the conversion
owner_key: AccountId32::from(key.value.owner_key.0),
next_scheduled: key.value.next_scheduled,
status: key.value.status,
};
identities.insert(IdtyIndex::from_le_bytes(idty_index_bytes), idty_val);
}
println!("identities.len(): {}.", identities.len());
// Collect identity_index_of
let mut identity_index_of: HashMap<[u8; 16], IdtyIndex> = HashMap::new();
let mut idty_index_of_iter = client
.storage()
.at_latest()
.await
.unwrap()
.iter(gdev::storage().identity().identity_index_of_iter())
.await?;
while let Some(Ok(key)) = idty_index_of_iter.next().await {
let mut blake2_128_bytes = [0u8; 16];
blake2_128_bytes.copy_from_slice(&key.key_bytes[32..48]);
identity_index_of.insert(blake2_128_bytes, key.value);
}
println!("identity_index_of.len(): {}.", identity_index_of.len());
// Collect identity_names
let mut identities_names: HashMap<IdtyIndex, IdtyName> = HashMap::new();
let mut idty_name_iter = client
.storage()
.at_latest()
.await
.unwrap()
.iter(gdev::storage().identity().identities_names_iter())
.await?;
while let Some(Ok(key)) = idty_name_iter.next().await {
let name = IdtyName(key.key_bytes);
identities_names.insert(key.value, name);
}
println!("identities_names.len(): {}.", identities_names.len());
// Collect memberships
let mut memberships: HashMap<IdtyIndex, MembershipData> = HashMap::new();
let mut membership_iter = client
.storage()
.at_latest()
.await
.unwrap()
.iter(gdev::storage().membership().membership_iter())
.await?;
while let Some(Ok(key)) = membership_iter.next().await {
let mut idty_index_bytes = [0u8; 4];
idty_index_bytes.copy_from_slice(&key.key_bytes[40..]);
let membership_val = MembershipData {
expire_on: key.value.expire_on,
};
memberships.insert(IdtyIndex::from_le_bytes(idty_index_bytes), membership_val);
}
println!("memberships.len(): {}.", memberships.len());
let storage = Storage {
accounts,
identities,
identity_index_of,
memberships,
identities_names,
};
// ===== Verify storage ===== //
verifier::Verifier::new().verify_storage(&storage).await
}
mod verifier {
use super::*;
pub(super) struct Verifier {
errors: Vec<String>,
}
impl Verifier {
pub(super) fn new() -> Self {
Self { errors: Vec::new() }
}
/// method to run all storage tests
pub(super) async fn verify_storage(&mut self, storage: &Storage) -> anyhow::Result<()> {
self.verify_accounts(&storage.accounts).await;
self.verify_identities(&storage.accounts, &storage.identities)
.await;
self.verify_identity_index_of(&storage.identities, &storage.identity_index_of)
.await;
self.verify_identity_coherence(&storage.identities, &storage.identity_index_of)
.await;
self.verify_status_coherence(
&storage.identities,
&storage.memberships,
&storage.identities_names,
)
.await;
if self.errors.is_empty() {
Ok(())
} else {
for error in &self.errors {
println!("{}", error);
}
Err(anyhow::anyhow!(
"Storage corrupted: {} errors.",
self.errors.len()
))
}
}
/// assert method to collect errors
fn assert(&mut self, assertion: bool, error: String) {
if !assertion {
self.errors.push(error);
}
}
/// like assert but just push error
fn error(&mut self, error: String) {
self.errors.push(error);
}
/// check accounts sufficients and consumers (specific to duniter-account pallet)
async fn verify_accounts(&mut self, accounts: &HashMap<AccountId32, AccountInfo>) {
for (account_id, account_info) in accounts {
if account_info.sufficients == 0 {
// Rule 1: If the account is not sufficient, it should have at least one provider
self.assert(
account_info.providers > 0,
format!("Account {} has no providers nor sufficients.", account_id),
);
// Rule 2: If the account is not sufficient, it should comply to the existential deposit
self.assert(
(account_info.data.free + account_info.data.reserved)
>= EXISTENTIAL_DEPOSIT,
format!(
"Account {} not respect existential deposit rule.",
account_id
),
);
}
// Rule 3: If the account have consumers, it should have at least one provider
if account_info.consumers > 0 {
// Rule 1: If the account is not sufficient [...]
self.assert(
account_info.providers > 0,
format!("Account {} has no providers nor sufficients.", account_id),
);
}
}
}
/// check list of identities (account existence, sufficient)
async fn verify_identities(
&mut self,
accounts: &HashMap<AccountId32, AccountInfo>,
identities: &HashMap<IdtyIndex, IdtyValue>,
) {
// counts occurence of owner key
let mut countmap = CountMap::<AccountId32, u8>::new();
// list owner key with multiple occurences
let mut duplicates = HashSet::new();
for (idty_index, idty_value) in identities {
countmap.insert_or_increment(idty_value.owner_key.clone());
if let Some(count) = countmap.get_count(&idty_value.owner_key) {
if count > 1 {
self.error(format!(
"address {} is the owner_key of {count} identities",
idty_value.owner_key
));
if count == 2 {
duplicates.insert(idty_value.owner_key.clone());
}
}
}
// Rule 1: each identity should have an account
let maybe_account = accounts.get(&idty_value.owner_key);
self.assert(
maybe_account.is_some(),
format!("Identity {} has no account.", idty_index),
);
if let Some(account) = maybe_account {
// Rule 2: each identity account should be sufficient
self.assert(
account.sufficients > 0,
format!(
"Identity {} is corrupted: idty_account.sufficients == 0",
idty_index
),
);
}
}
for (idty_index, idty_value) in identities {
if duplicates.contains(&idty_value.owner_key) {
self.error(format!(
"duplicate key {} at position {idty_index}",
idty_value.owner_key
));
}
}
}
/// check the identity hashmap (length, identity existence, hash matches owner key)
async fn verify_identity_index_of(
&mut self,
identities: &HashMap<IdtyIndex, IdtyValue>,
identity_index_of: &HashMap<[u8; 16], IdtyIndex>,
) {
// Rule1: identity_index_of should have the same lenght as identities
self.assert(
identities.len() == identity_index_of.len(),
format!(
"identities.len({}) != identity_index_of.len({}).",
identities.len(),
identity_index_of.len()
),
);
for (blake2_128_owner_key, idty_index) in identity_index_of {
let maybe_idty_value = identities.get(idty_index);
// Rule2: Each identity_index_of should point to an existing identity
self.assert(
maybe_idty_value.is_some(),
format!(
"Identity {} not exist, but still referenced in IdentityIndexOf.",
idty_index
),
);
if let Some(idty_value) = maybe_idty_value {
// Rule3: identity_index_of key should correspond to the blake2_12- hash of
// identity owner key
self.assert(
blake2_128_owner_key == &blake2_128(idty_value.owner_key.as_slice()),
format!(
"Identity {} is referenced in IdentityIndexOf with an invalid key hash.",
idty_index
),
);
}
}
}
/// check identities status and membership coherence
async fn verify_status_coherence(
&mut self,
identities: &HashMap<IdtyIndex, IdtyValue>,
memberships: &HashMap<IdtyIndex, MembershipData>,
names: &HashMap<IdtyIndex, IdtyName>,
) {
for (idty_index, idty_value) in identities {
// Rule 1: each Status::Member
// should have a membership and a name
// membership should be set to expire
// identity should have no scheduled action
if let IdtyStatus::Member = idty_value.status {
self.assert(
memberships.get(idty_index).is_some(),
format!("identity number {idty_index} should have a valid membership"),
);
self.assert(
names.get(idty_index).is_some(),
format!("identity number {idty_index} should have a name"),
);
self.assert(
memberships.get(idty_index).unwrap().expire_on != 0,
format!(
"Member identity number {idty_index} should have a non-null expire_on value"
),
);
self.assert(
identities.get(idty_index).unwrap().next_scheduled == 0,
format!(
"Member identity number {idty_index} should have a null next_scheduled value"
),
);
}
// Rule 2: each Status::NotMember
// should have a name but no membership
// should have a scheduled action (auto-revocation)
if let IdtyStatus::NotMember = idty_value.status {
self.assert(
memberships.get(idty_index).is_none(),
format!("identity number {idty_index} should not have a valid membership"),
);
self.assert(
names.get(idty_index).is_some(),
format!("identity number {idty_index} should have a name"),
);
self.assert(
identities.get(idty_index).unwrap().next_scheduled != 0,
format!("NotMember identity number {idty_index} should have a non-null next_scheduled value"),
);
}
// Rule 3: each Status::Revoked
// should should have a name
// no membership
// should be scheduled for removal
if let IdtyStatus::Revoked = idty_value.status {
self.assert(
memberships.get(idty_index).is_none(),
format!("identity number {idty_index} should not have a valid membership"),
);
self.assert(
names.get(idty_index).is_some(),
format!("identity number {idty_index} should have a name"),
);
self.assert(
identities.get(idty_index).unwrap().next_scheduled != 0,
format!("Revoked identity number {idty_index} should have a non-null next_scheduled value"),
);
}
// Rule 4: each Status::Unvalidaded
// should have a name but no membership.
// should be scheduled for removal
if let IdtyStatus::Unvalidated = idty_value.status {
self.assert(
memberships.get(idty_index).is_none(),
format!("identity number {idty_index} should not have a valid membership"),
);
self.assert(
names.get(idty_index).is_some(),
format!("identity number {idty_index} should have a name"),
);
self.assert(
identities.get(idty_index).unwrap().next_scheduled != 0,
format!("Unvalidated identity number {idty_index} should have a non-null next_scheduled value"),
);
}
// Rule 5: each Status::Unconfirmed
// should not have a name neither a membership.
// should be scheduled for removal soon
if let IdtyStatus::Unconfirmed = idty_value.status {
self.assert(
memberships.get(idty_index).is_none(),
format!("identity number {idty_index} should not have a valid membership"),
);
self.assert(
names.get(idty_index).is_none(),
format!("identity number {idty_index} should not have a name"),
);
self.assert(
identities.get(idty_index).unwrap().next_scheduled != 0,
format!("Unconfirmed identity number {idty_index} should have a non-null next_scheduled value"),
);
}
}
}
/// check coherence between identity list and identity index hashmap
async fn verify_identity_coherence(
&mut self,
identities: &HashMap<IdtyIndex, IdtyValue>,
identity_index_of: &HashMap<[u8; 16], IdtyIndex>,
) {
// each identity should be correcly referenced in the hashmap
for (idty_index, idty_value) in identities {
// hash owner key to get key
let blake2_128_owner_key = &blake2_128(idty_value.owner_key.as_slice());
// get identity index from hashmap
if let Some(index_of) = identity_index_of.get(blake2_128_owner_key) {
self.assert(idty_index == index_of,
format!("identity number {idty_index} with owner key {0} is mapped to identity index {index_of}", idty_value.owner_key));
} else {
self.error(format!(
"identity with owner key {} is not present in hashmap",
idty_value.owner_key
));
}
}
}
}
}
[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).
// Copyright 2021 Axiom-Team // Copyright 2021 Axiom-Team
// //
// This file is part of Substrate-Libre-Currency. // This file is part of Duniter-v2S.
// //
// Substrate-Libre-Currency is free software: you can redistribute it and/or modify // Duniter-v2S is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License. // the Free Software Foundation, version 3 of the License.
// //
// Substrate-Libre-Currency is distributed in the hope that it will be useful, // Duniter-v2S is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU Affero General Public License for more details.
// //
// 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 Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>. // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
fn main() { fn main() {
//cli::main(); //cli::main();
......