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
  • fix_picked_up_file_in_runtime_release
  • 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
  • network/gtest-1000
  • 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
  • runtime/gtest-1000
  • tests/distance-with-oracle
  • tuxmain/anonymous-tx
  • tuxmain/benchmark-distance
  • tuxmain/fix-change-owner-key
  • update-docker-compose-rpc-squid-names
  • upgradable-multisig
  • 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
  • gtest-1000
  • gtest-1000-0.11.0
  • gtest-1000-0.11.1
  • 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
88 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 1624 additions and 397 deletions
[package]
authors = ['Axiom-Team Developers <https://axiom-team.fr>']
description = 'duniter end2end tests.'
edition = "2021"
homepage = 'https://substrate.dev'
license = 'AGPL-3.0'
name = 'duniter-end2end-tests'
repository = 'https://git.duniter.org/nodes/rust/duniter-v2s'
version = '3.0.0'
[dev-dependencies]
anyhow = "1.0"
async-trait = "0.1"
clap = { version = "3.0", features = ["derive"] }
ctrlc = "3.2.2"
cucumber = "0.11"
env_logger = "0.9.0"
notify = "4.0"
parity-scale-codec = "3.1.5"
portpicker = "0.1.1"
serde_json = "1.0.64"
sp-keyring = { git = "https://github.com/duniter/substrate", branch = "duniter-substrate-v0.9.23" }
subxt = { git = 'https://github.com/duniter/subxt.git', branch = 'duniter-substrate-v0.9.23' }
tokio = { version = "1.15.0", features = ["macros"] }
authors.workspace = true
description = "duniter end2end tests"
edition.workspace = true
homepage.workspace = true
license.workspace = true
name = "duniter-end2end-tests"
repository.workspace = true
version.workspace = true
[[test]]
name = "cucumber_tests"
harness = false # allows Cucumber to print output instead of libtest
[features]
default = ["std"]
std = [
"anyhow/std",
"codec/std",
"distance-oracle/std",
"hex/std",
"serde_json/std",
"sp-core/std",
"sp-runtime/std",
]
standalone = ["distance-oracle/standalone"]
try-runtime = ["distance-oracle/try-runtime", "sp-runtime/try-runtime"]
runtime-benchmarks = []
[dev-dependencies]
anyhow = { workspace = true }
clap = { workspace = true, features = ["derive", "cargo"] }
codec = { workspace = true }
ctrlc = { workspace = true }
cucumber = { workspace = true, features = ["macros"] }
distance-oracle = { workspace = true, features = ["gdev"] }
env_logger = { workspace = true }
hex = { workspace = true }
notify = { workspace = true }
notify-debouncer-mini = { workspace = true }
portpicker = { workspace = true }
serde_json = { workspace = true }
sp-core = { workspace = true }
sp-keyring = { workspace = true }
sp-runtime = { workspace = true }
subxt = { workspace = true, features = [
"native",
"jsonrpsee",
] }
tokio = { workspace = true, features = ["macros", "time", "rt-multi-thread"] }
# Duniter-v2s end2end tests
## Cucumber functionnal tests
## Cucumber functional tests
We use [cucumber] to be able to describe test scenarios in human language.
......@@ -81,14 +81,22 @@ List of possible actions:
### Test users
6 test users are provided:
8 test users are provided derived from the same [dev mnemonic](https://docs.substrate.io/v3/getting-started/glossary/#dev-phrase)
- alice
- bob
- charlie
- dave
- eve
- ferdie
```
bottom drive obey lake curtain smoke basket hold race lonely fit walk
```
with the derivation path `//Alice`, `//Bob`...
- alice `5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY`
- bob `5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty`
- charlie `5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y`
- dave `5DAAnrj7VHTznn2AWBemMuyBwZWs6FNFjdyVXUeYum3PTXFy`
- eve `5HGjWAeFDfFCWPsjFQdVV2Msvz2XtMktvgocEZcCj68kUMaw`
- ferdie `5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL`
- one `5Fxune7f71ZbpP2FoY3mhYcmM596Erhv1gRue4nsPwkxMR4n`
- two `5CUjxa4wVKMj3FqKdqAUf7zcEMr4MYAjXeWmUf44B41neLmJ`
### Currency amounts
......@@ -117,7 +125,7 @@ For some scenarios, you may need to perform an action (When) that fails voluntar
### Run cucumber functional tests
The cucumber tests use the last debug binary in your `target` folder. Make sure this binary corresponds to the executable you want to test by running `cargo build` before.
The cucumber tests use the last debug binary in your `target` folder. Make sure this binary corresponds to the executable you want to test by running `cargo build --features constant-fees` before.
To run the cucumber tests, you will need to have the rust toolchain installed locally.
......@@ -126,7 +134,7 @@ To run all the scenarios (there are many) use the command: `cargo cucumber`
You can filter the `.feature` files to run with the option `i`, for instance:
```
cargo cucumber -i monetary*
cargo cucumber -i 'monetary*'
```
will only run `.feature` files that start with `"monetary"`.
......@@ -160,6 +168,19 @@ subxt metadata -f bytes > resources/metadata.scale
If you don't have subxt, install it: `cargo install subxt-cli`
### Debug
Cucumber uses carriage returns to pretty-print.
Hence you may need to append an additional newline to your debug printings.
This commandline can be used to enable more debugging:
```bash
RUST_LOG=debug cargo cucumber -i distance_fail.feature -vvv --show-output
```
You can use the `log::debug` macro if the line `env_logger::init()` is uncommented in `cucumber_tests.rs`.
[BDD]: https://en.wikipedia.org/wiki/Behavior-driven_development
[cucumber]: https://cucumber.io/
[Cucumber Rust Book]: https://cucumber-rs.github.io/cucumber/current/writing/index.html
......
Feature: Balance transfer
Feature: Account creation
Scenario: Create a new account with enough funds
When alice sends 5 ĞD to dave
Then dave should have 5 ĞD
When 1 block later
"""
The blockchain should automatically withdraw account creation tax (3 ĞD)
The blockchain did not automatically withdraw account creation tax (3 ĞD) because this feature has been removed
"""
Then dave should have 2 ĞD
Then dave should have 5 ĞD
Scenario: Create a new account without enough funds then retry with enough funds
When alice sends 2 ĞD to eve
Then eve should have 2 ĞD
When 1 block later
"""
The blockchain should automatically destroy Eve account
because Eve does not have enough funds to pay the new account tax
The blockchain did not automatically destroy Eve account for Eve not having enough funds to pay the new account tax
Because this feature has been removed
"""
Then eve should have 0 ĞD
Then eve should have 2 ĞD
When alice send 5 ĞD to eve
Then eve should have 5 ĞD
Then eve should have 7 ĞD
When 1 block later
"""
The blockchain should automatically withdraw account creation tax (3 ĞD)
The blockchain did not automatically withdraw account creation tax (3 ĞD) because this feature has been removed
"""
Then eve should have 2 ĞD
Then eve should have 7 ĞD
@ignoreErrors
Scenario: Create a new account without any funds
Then eve should have 0 ĞD
# Alice is treasury funder for 1 ĞD
Then alice should have 9 ĞD
When eve send 0 ĞD to alice
Then alice should have 10 ĞD
Then alice should have 9 ĞD
When alice send 5 ĞD to eve
Then eve should have 5 ĞD
When 1 block later
"""
The blockchain should automatically withdraw account creation tax (3 ĞD)
The blockchain did not automatically withdraw account creation tax (3 ĞD) because this feature has been removed
"""
Then eve should have 2 ĞD
Then eve should have 5 ĞD
@genesis.bad_distance
Feature: Distance fail
#
# WoT:
#
# H<->G<->E<->D<->C-->B
# ^ ^ ^ ^
# \ / \ /
# v v v v
# I A
#
# Every member is referee. Referee count = 8; 80% = 6.4
# Certs from Alice and Bob do not ensure the fulfilling of the distance rule
# because the newcomer would reach only 6 members up to G.
Scenario: an unvalidated member fails the distance rule
Then treasury should contain 1 ĞD
When alice sends 7 ĞD to ferdie
Then alice should have 0 ĞD reserved
Then alice should have 199 cĞD
When bob sends 750 cĞD to ferdie
When 15 block later
When alice creates identity for ferdie
Then ferdie identity should be unconfirmed
Then ferdie should be certified by alice
When ferdie confirms his identity with pseudo "ferdie"
Then ferdie identity should be unvalidated
When 3 block later
When bob certifies ferdie
Then ferdie should be certified by bob
Then ferdie should have 0 ĞD reserved
# 700 + 750 - 2(one transaction fee, not a member yet)
Then ferdie should have 1448 cĞD
When ferdie requests distance evaluation
Then ferdie should have 10 ĞD reserved
# 1448 - 1000 - 2(one transaction fee, not a member yet)
Then ferdie should have 446 cĞD
When 7 blocks later
Then treasury should contain 105 cĞD
When alice runs distance oracle
When 7 blocks later
Then ferdie should be certified by alice
Then ferdie should be certified by bob
# The distance rule is failed
Then ferdie identity should be unvalidated
# Ferdie got his reserve slashed
Then ferdie should have 0 ĞD reserved
Then ferdie should have 446 cĞD
# Slashed amount is transfered to treasury
Then treasury should contain 1105 cĞD
Feature: Identity creation
Scenario: alice invites a new member to join the web of trust
# 6 ĞD covers:
# - existential deposit (1 ĞD)
# - transaction fees (below 1 ĞD)
When alice sends 7 ĞD to dave
# Alice is treasury funder for 1 ĞD => 10-1-7 = 2 (minus TODO fees)
Then alice should have 0 ĞD reserved
Then alice should have 199 cĞD
When bob sends 750 cĞD to dave
When charlie sends 6 ĞD to eve
# alice last certification is counted from block zero
# then next cert can be done after cert_period, which is 15
When 15 block later
When alice creates identity for dave
Then dave identity should be unconfirmed
Then dave should be certified by alice
When dave confirms his identity with pseudo "dave"
Then dave identity should be unvalidated
When 3 block later
When bob certifies dave
When charlie certifies dave
Then dave should be certified by bob
Then dave should be certified by charlie
Then dave should have 0 ĞD reserved
# 700 + 750 - 2(one transaction fee, not a member yet)
Then dave should have 1448 cĞD
When dave requests distance evaluation
Then dave should have 10 ĞD reserved
# 1448 - 1000 - 2(one transaction fee, not a member yet)
Then dave should have 446 cĞD
When 7 blocks later
When alice runs distance oracle
When 7 blocks later
Then dave identity should be member
Then dave should have 0 ĞD reserved
Then dave should have 1446 cĞD
Feature: Balance transfer
Feature: Monetary mass
Scenario: After 10 blocks, the monetary mass should be 60 ĞD
Then Monetary mass should be 30.00 ĞD
Then Current UD amount should be 10.00 ĞD
When 10 blocks later
When 15 blocks later
Then Monetary mass should be 60.00 ĞD
When 10 blocks later
Then Monetary mass should be 90.00 ĞD
......
Feature: Oneshot account
Scenario: Simple oneshot consumption
When charlie sends 7 ĞD to dave
# Cover the oneshot calls fees
When alice sends 7 ĞD to oneshot dave
# Alice is treasury funder for 1 ĞD, and member so fees are refunded
Then alice should have 2 ĞD
Then dave should have oneshot 7 ĞD
When oneshot dave consumes into account bob
Then dave should have oneshot 0 ĞD
Then bob should have 1698 cĞD
Then bob should have oneshot 0 ĞD
Scenario: Double oneshot consumption
When charlie sends 7 ĞD to dave
Then charlie should have 299 cĞD
# Cover the oneshot calls fees
When alice sends 7 ĞD to oneshot dave
# Alice is treasury funder for 1 ĞD, and member so fees are refunded
Then alice should have 2 ĞD
Then dave should have oneshot 7 ĞD
When oneshot dave consumes 4 ĞD into account bob and the rest into oneshot charlie
Then dave should have oneshot 0 ĞD
Then bob should have 14 ĞD
Then bob should have oneshot 0 ĞD
Then charlie should have 299 cĞD
Then charlie should have oneshot 298 cĞD
......@@ -2,13 +2,16 @@
Feature: Balance transfer all
Scenario: If bob sends all his ĞDs to Dave
When bob sends all her ĞDs to dave
When bob sends all his ĞDs to dave
"""
Bob is a member, as such he is not allowed to empty his account completely,
if he tries to do so, the existence deposit (2 ĞD) must remain.
if he tries to do so, the existence deposit (1 ĞD) must remain.
Bob is a member, transaction fees are refunded for him
101 = existential deposit (100) + fees refunded using quota (001)
"""
Then bob should have 2 ĞD
Then bob should have 101 cĞD
"""
10 ĞD (initial Bob balance) - 2 ĞD (Existential deposit) - 0.02 ĞD (transaction fees)
10 ĞD (initial Bob balance) - 1 ĞD (Existential deposit) - 0.02 ĞD (transaction fees)
"""
Then dave should have 798 cĞD
Then dave should have 898 cĞD
# TODO check that the missing cent went to treasury
@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_reeval": 100,
"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": ["Bob", "Charlie"],
"pubkey": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"
"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": ["Alice", "Charlie"],
"pubkey": "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty"
"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": ["Alice", "Bob"],
"pubkey": "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y"
"certs_received": {
"Alice": 2700000000,
"Bob": 2700000000
},
"owner_address": "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y",
"membership_expire_on": 2700000000,
"membership_revokes_on": 2700000001,
"revoked": false,
"next_cert_issuable_on": 0
}
},
"parameters": {
......@@ -27,32 +59,38 @@
"idty_confirm_period": 40,
"idty_creation_period": 50,
"membership_period": 1000,
"pending_membership_period": 500,
"ud_creation_period": 10,
"ud_reeval_period": 100,
"smith_cert_period": 15,
"membership_renewal_period": 500,
"ud_creation_period": 60000,
"ud_reeval_period": 600000,
"smith_cert_max_by_issuer": 8,
"smith_cert_min_received_cert_to_issue_cert": 2,
"smith_cert_validity_period": 1000,
"smith_membership_period": 1000,
"smith_pending_membership_period": 500,
"smiths_wot_first_cert_issuable_on": 20,
"smiths_wot_min_cert_for_membership": 2,
"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
},
"smiths": {
"Alice": {
"certs": ["Bob", "Charlie"]
"clique_smiths": [
{
"name": "Alice"
},
"Bob": {
"certs": ["Alice", "Charlie"]
{
"name": "Bob"
},
"Charlie": {
"certs": ["Alice", "Bob"]
{
"name": "Charlie"
}
},
],
"sudo_key": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
"technical_committee": ["Alice", "Bob", "Charlie"]
"technical_committee": [
"Alice",
"Bob",
"Charlie"
],
"treasury_funder_pubkey": "FHNpKmJrUtusuvKPGomAygQqeiks98bdV6yD61Stb6vg",
"ud": 1000,
"initial_monetary_mass": 3000,
"current_block": {
"number": 0,
"medianTime": 1700000000
}
}
{
"first_ud": 1000,
"first_ud_reeval": 100,
"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": ["Bob", "Charlie"],
"pubkey": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"
"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": ["Alice", "Charlie"],
"pubkey": "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty"
"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": ["Alice", "Bob"],
"pubkey": "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y"
"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": ["Alice", "Bob"],
"pubkey": "5DAAnrj7VHTznn2AWBemMuyBwZWs6FNFjdyVXUeYum3PTXFy"
"certs_received": {
"Alice": 2700000000
},
"owner_address": "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL",
"membership_expire_on": 2700000000,
"membership_revokes_on": 2700000001,
"revoked": false,
"next_cert_issuable_on": 0
}
},
"parameters": {
......@@ -32,32 +95,38 @@
"idty_confirm_period": 40,
"idty_creation_period": 50,
"membership_period": 1000,
"pending_membership_period": 500,
"ud_creation_period": 10,
"ud_reeval_period": 100,
"smith_cert_period": 15,
"membership_renewal_period": 500,
"ud_creation_period": 60000,
"ud_reeval_period": 600000,
"smith_cert_max_by_issuer": 8,
"smith_cert_min_received_cert_to_issue_cert": 2,
"smith_cert_validity_period": 1000,
"smith_membership_period": 1000,
"smith_pending_membership_period": 500,
"smiths_wot_first_cert_issuable_on": 20,
"smiths_wot_min_cert_for_membership": 2,
"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
},
"smiths": {
"Alice": {
"certs": ["Bob", "Charlie"]
"clique_smiths": [
{
"name": "Alice"
},
"Bob": {
"certs": ["Alice", "Charlie"]
{
"name": "Bob"
},
"Charlie": {
"certs": ["Alice", "Bob"]
{
"name": "Charlie"
}
},
],
"sudo_key": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
"technical_committee": ["Alice", "Bob", "Charlie"]
"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
//
// 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
// 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
// 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/>.
// along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
// 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
// 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
// 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/>.
// along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
use super::node_runtime::runtime_types::gdev_runtime;
use super::node_runtime::runtime_types::pallet_balances;
use super::*;
use sp_keyring::AccountKeyring;
use subxt::{sp_runtime::MultiAddress, PairSigner};
use super::{gdev, gdev::runtime_types::pallet_balances, *};
use crate::common::pair_signer::PairSigner;
use sp_keyring::sr25519::Keyring;
use subxt::utils::MultiAddress;
pub async fn set_balance(
api: &Api,
client: &Client,
who: AccountKeyring,
amount: u64,
) -> Result<()> {
pub async fn set_balance(client: &FullClient, who: Keyring, amount: u64) -> Result<()> {
let _events = create_block_with_extrinsic(
client,
api.tx()
&client.rpc,
client
.client
.tx()
.create_signed(
&gdev::tx()
.sudo()
.sudo(gdev_runtime::Call::Balances(
pallet_balances::pallet::Call::set_balance {
who: MultiAddress::Id(who.to_account_id()),
.sudo(gdev::runtime_types::gdev_runtime::RuntimeCall::Balances(
pallet_balances::pallet::Call::force_set_balance {
who: MultiAddress::Id(who.to_raw_public().into()),
new_free: amount,
new_reserved: 0,
},
))?
.create_signed(
)),
&PairSigner::new(SUDO_ACCOUNT.pair()),
BaseExtrinsicParamsBuilder::new(),
SubstrateExtrinsicParamsBuilder::new().build(),
)
.await?,
)
......@@ -48,22 +44,20 @@ pub async fn set_balance(
Ok(())
}
pub async fn transfer(
api: &Api,
client: &Client,
from: AccountKeyring,
amount: u64,
to: AccountKeyring,
) -> Result<()> {
pub async fn transfer(client: &FullClient, from: Keyring, amount: u64, to: Keyring) -> Result<()> {
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(
client,
api.tx()
.balances()
.transfer(to.clone().into(), amount)?
.create_signed(&from, BaseExtrinsicParamsBuilder::new())
&client.rpc,
client
.client
.tx()
.create_signed(
&gdev::tx().universal_dividend().transfer_ud(to, amount),
&from,
SubstrateExtrinsicParamsBuilder::new().build(),
)
.await?,
)
.await?;
......@@ -71,21 +65,20 @@ pub async fn transfer(
Ok(())
}
pub async fn transfer_all(
api: &Api,
client: &Client,
from: AccountKeyring,
to: AccountKeyring,
) -> Result<()> {
pub async fn transfer_all(client: &FullClient, from: Keyring, to: Keyring) -> Result<()> {
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(
client,
api.tx()
.balances()
.transfer_all(to.clone().into(), false)?
.create_signed(&from, BaseExtrinsicParamsBuilder::new())
&client.rpc,
client
.client
.tx()
.create_signed(
&gdev::tx().balances().transfer_all(to, false),
&from,
SubstrateExtrinsicParamsBuilder::new().build(),
)
.await?,
)
.await?;
......@@ -94,21 +87,25 @@ pub async fn transfer_all(
}
pub async fn transfer_ud(
api: &Api,
client: &Client,
from: AccountKeyring,
client: &FullClient,
from: Keyring,
amount: u64,
to: AccountKeyring,
to: Keyring,
) -> Result<()> {
let from = PairSigner::new(from.pair());
let to = to.to_account_id();
let _events = create_block_with_extrinsic(
client,
api.tx()
&client.rpc,
client
.client
.tx()
.create_signed(
&gdev::tx()
.universal_dividend()
.transfer_ud(to.clone().into(), amount)?
.create_signed(&from, BaseExtrinsicParamsBuilder::new())
.transfer_ud(MultiAddress::Id(to.to_raw_public().into()), amount),
&from,
SubstrateExtrinsicParamsBuilder::new().build(),
)
.await?,
)
.await?;
......
// 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
// 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
// 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/>.
// along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
use super::node_runtime::runtime_types::gdev_runtime;
use super::node_runtime::runtime_types::pallet_certification;
use super::*;
use sp_keyring::AccountKeyring;
use subxt::{sp_runtime::MultiAddress, PairSigner};
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(
api: &Api,
client: &Client,
from: AccountKeyring,
to: AccountKeyring,
) -> Result<()> {
pub async fn certify(client: &FullClient, from: Keyring, to: Keyring) -> Result<()> {
let signer = PairSigner::new(from.pair());
let from = from.to_account_id();
let to = to.to_account_id();
let from: subxt::utils::AccountId32 = from.to_raw_public().into();
let to: subxt::utils::AccountId32 = to.to_raw_public().into();
let issuer_index = api
let _issuer_index = client
.client
.storage()
.identity()
.identity_index_of(&from, None)
.at_latest()
.await
.unwrap()
.fetch(&gdev::storage().identity().identity_index_of(from.clone()))
.await?
.unwrap();
let receiver_index = api
.unwrap_or_else(|| panic!("{} issuer must exist", from));
let receiver_index = client
.client
.storage()
.identity()
.identity_index_of(&to, None)
.at_latest()
.await
.unwrap()
.fetch(&gdev::storage().identity().identity_index_of(to))
.await?
.unwrap();
.unwrap_or_else(|| panic!("{} issuer must exist", from));
let _events = create_block_with_extrinsic(
client,
api.tx()
.cert()
.add_cert(issuer_index, receiver_index)?
.create_signed(&signer, BaseExtrinsicParamsBuilder::new())
&client.rpc,
client
.client
.tx()
.create_signed(
&gdev::tx().certification().add_cert(receiver_index),
&signer,
SubstrateExtrinsicParamsBuilder::new().build(),
)
.await?,
)
.await?;
......
// 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
//
// 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
// 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
// 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/>.
// along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
#![allow(clippy::enum_variant_names, dead_code, unused_imports)]
pub mod balances;
pub mod cert;
pub mod distance;
pub mod identity;
pub mod oneshot;
#[subxt::subxt(runtime_metadata_path = "../resources/metadata.scale")]
pub mod node_runtime {}
#[subxt::subxt(
runtime_metadata_path = "../resources/metadata.scale",
derive_for_all_types = "Eq, PartialEq"
)]
pub mod gdev {}
use anyhow::anyhow;
use parity_scale_codec::Encode;
use codec::Encode;
use notify_debouncer_mini::new_debouncer;
use serde_json::Value;
use sp_keyring::AccountKeyring;
use std::io::prelude::*;
use std::path::PathBuf;
use std::process::Command;
use std::str::FromStr;
use subxt::rpc::{rpc_params, ClientT, SubscriptionClientT};
use subxt::{extrinsic::BaseExtrinsicParamsBuilder, ClientBuilder, DefaultConfig};
pub type Api = node_runtime::RuntimeApi<DefaultConfig, BaseExtrinsicParams<DefaultConfig>>;
type BaseExtrinsicParams<T> = subxt::extrinsic::BaseExtrinsicParams<T, Tip>;
pub type Client = subxt::Client<DefaultConfig>;
pub type Event = node_runtime::Event;
use sp_keyring::sr25519::Keyring;
use std::{
io::prelude::*,
path::{Path, PathBuf},
process::Command,
str::FromStr,
time::{Duration, Instant},
};
use subxt::{
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 SignedSubmittableExtrinsic<'client> = subxt::SignedSubmittableExtrinsic<
'client,
DefaultConfig,
BaseExtrinsicParams<DefaultConfig>,
node_runtime::DispatchError,
Event,
>;
pub type TransactionProgress<'client> =
subxt::TransactionProgress<'client, DefaultConfig, node_runtime::DispatchError, Event>;
pub type SubmittableExtrinsic = subxt::tx::SubmittableTransaction<GdevConfig, Client>;
pub type TxProgress = subxt::tx::TxProgress<GdevConfig, Client>;
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 {
......@@ -54,6 +71,11 @@ pub struct Tip {
tip: u64,
}
pub struct FullClient {
pub rpc: RpcClient,
pub client: Client,
}
impl Tip {
pub fn new(amount: u64) -> Self {
Tip { tip: amount }
......@@ -66,7 +88,7 @@ impl From<u64> for Tip {
}
}
pub const SUDO_ACCOUNT: AccountKeyring = AccountKeyring::Alice;
pub const SUDO_ACCOUNT: Keyring = Keyring::Alice;
pub struct Process(std::process::Child);
impl Process {
......@@ -75,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_LOCAL_PATH: &str = "../target/debug/duniter";
struct FullNode {
process: Process,
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);
let duniter_binary_path = std::env::var("DUNITER_BINARY_PATH").unwrap_or_else(|_| {
if std::path::Path::new(DUNITER_DOCKER_PATH).exists() {
......@@ -94,58 +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 {
process,
p2p_port: _,
ws_port,
rpc_port,
} = 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,
maybe_genesis_conf_file,
);
let client = ClientBuilder::new()
.set_url(format!("ws://127.0.0.1:{}", ws_port))
.build()
opt_process = Some(process);
the_rpc_port = rpc_port;
}
let rpc = RpcClient::from_url(format!("ws://127.0.0.1:{}", the_rpc_port))
.await
.expect("fail to connect to node");
let api = client.clone().to_runtime_api::<Api>();
.expect("Failed to create the rpc backend");
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
let _: Value = client
.rpc()
.client
.request("engine_createBlock", rpc_params![true, false, Value::Null])
.request("engine_createBlock", rpc_params![true, true, Value::Null])
.await?;
Ok(())
}
pub async fn create_block_with_extrinsic(
client: &Client,
extrinsic: SignedSubmittableExtrinsic<'_>,
) -> Result<subxt::TransactionEvents<DefaultConfig, Event>> {
/*// Get a hash of the extrinsic (we'll need this later).
use subxt::sp_runtime::traits::Hash as _;
let ext_hash = <DefaultConfig as subxt::Config>::Hashing::hash_of(&encoded_extrinsic);
// Submit and watch for transaction progress.
let sub = client.rpc().submit_extrinsic(encoded_extrinsic).await?;
let watcher = TransactionProgress::new(sub, client, ext_hash);*/
client: &RpcClient,
extrinsic: SubmittableExtrinsic,
) -> Result<subxt::blocks::ExtrinsicEvents<GdevConfig>> {
//println!("extrinsic encoded: {}", hex::encode(extrinsic.encoded()));
let watcher = extrinsic.submit_and_watch().await?;
// Create a non-empty block
let _: Value = client
.rpc()
.client
.request("engine_createBlock", rpc_params![false, false, Value::Null])
.request("engine_createBlock", rpc_params![false, true, Value::Null])
.await?;
// Get extrinsic events
watcher
.wait_for_in_block()
.wait_for_finalized()
.await?
.fetch_events()
.await
......@@ -160,16 +204,16 @@ fn spawn_full_node(
// Ports
let p2p_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
let mut envs = Vec::new();
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
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");
// Command
......@@ -179,13 +223,10 @@ fn spawn_full_node(
[
"--no-telemetry",
"--no-prometheus",
"--tmp",
"--port",
&p2p_port.to_string(),
"--rpc-port",
&rpc_port.to_string(),
"--ws-port",
&ws_port.to_string(),
]
.iter()
.chain(args),
......@@ -199,9 +240,9 @@ fn spawn_full_node(
let 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 {
4
10
};
wait_until_log_line(
......@@ -213,23 +254,43 @@ fn spawn_full_node(
FullNode {
process,
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 mut watcher = notify::watcher(tx, std::time::Duration::from_millis(100)).unwrap();
use notify::Watcher as _;
watcher
.watch(&log_file_path, notify::RecursiveMode::NonRecursive)
let mut debouncer = new_debouncer(std::time::Duration::from_millis(100), tx).unwrap();
debouncer
.watcher()
.watch(
Path::new(log_file_path),
notify::RecursiveMode::NonRecursive,
)
.unwrap();
let mut pos = 0;
loop {
match rx.recv_timeout(timeout) {
Ok(notify::DebouncedEvent::Write(_)) => {
let mut file = std::fs::File::open(&log_file_path).unwrap();
Ok(_) => {
let mut file = std::fs::File::open(log_file_path).unwrap();
file.seek(std::io::SeekFrom::Start(pos)).unwrap();
pos = file.metadata().unwrap().len();
let reader = std::io::BufReader::new(file);
......@@ -240,7 +301,6 @@ fn wait_until_log_line(expected_log_line: &str, log_file_path: &str, timeout: st
}
}
}
Ok(_) => {}
Err(err) => {
eprintln!("Error: {:?}", err);
std::process::exit(1);
......@@ -248,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
//
// 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
// 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
// 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/>.
// along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
mod common;
use async_trait::async_trait;
use common::*;
use cucumber::{given, then, when, World, WorldInit};
use sp_keyring::AccountKeyring;
use std::convert::Infallible;
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::{
use cucumber::{given, then, when, StatsWriter, World};
use sp_keyring::sr25519::Keyring;
use std::{
path::PathBuf,
str::FromStr,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
};
use subxt::backend::rpc::RpcClient;
// ===== world =====
#[derive(WorldInit)]
#[derive(cucumber::World, Default)]
pub struct DuniterWorld {
ignore_errors: bool,
inner: Option<DuniterWorldInner>,
......@@ -38,38 +39,89 @@ pub struct DuniterWorld {
impl DuniterWorld {
// Write methods
async fn init(&mut self, maybe_genesis_conf_file: Option<PathBuf>) {
async fn init(&mut self, maybe_genesis_conf_file: Option<PathBuf>, no_spawn: bool) {
if let Some(ref mut inner) = self.inner {
inner.kill();
}
self.inner = 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 set_ignore_errors(&mut self, ignore_errors: bool) {
self.ignore_errors = ignore_errors;
}
// Read methods
fn api(&self) -> &Api {
fn rpc_client(&self) -> &RpcClient {
if let Some(ref inner) = self.inner {
&inner.api
&inner.client.rpc
} else {
panic!("uninit")
}
}
fn client(&self) -> &Client {
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
} else {
panic!("uninit")
}
}
// Read methods
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)
}
}
impl std::fmt::Debug for DuniterWorld {
......@@ -78,36 +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(Self {
ignore_errors: false,
inner: None,
})
}
}
struct DuniterWorldInner {
api: Api,
client: Client,
process: Process,
client: FullClient,
process: Option<Process>,
ws_port: u16,
}
impl DuniterWorldInner {
async fn new(maybe_genesis_conf_file: Option<PathBuf>) -> Self {
let (api, client, process) = spawn_node(maybe_genesis_conf_file).await;
async fn new(maybe_genesis_conf_file: Option<PathBuf>, no_spawn: bool) -> Self {
let (client, process, ws_port) = spawn_node(maybe_genesis_conf_file, no_spawn).await;
DuniterWorldInner {
api,
client,
process,
ws_port,
}
}
fn kill(&mut self) {
self.process.kill();
if let Some(p) = &mut self.process {
p.kill();
}
}
}
......@@ -123,39 +165,41 @@ fn parse_amount(amount: u64, unit: &str) -> (u64, bool) {
// ===== 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<()> {
// 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);
if is_ud {
let current_ud_amount = world
.api()
.storage()
.universal_dividend()
.current_ud(None)
.await?;
.read(&gdev::storage().universal_dividend().current_ud())
.await
.await?
.unwrap_or_default();
amount = (amount * current_ud_amount) / 1_000;
}
// 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(())
}
// ===== when =====
#[allow(clippy::needless_pass_by_ref_mut)]
#[when(regex = r"(\d+) blocks? later")]
async fn n_blocks_later(world: &mut DuniterWorld, n: usize) -> Result<()> {
for _ in 0..n {
common::create_empty_block(world.client()).await?;
common::create_empty_block(world.rpc_client()).await?;
}
Ok(())
}
#[when(regex = r"([a-zA-Z]+) sends? (\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(
world: &mut DuniterWorld,
from: String,
......@@ -164,14 +208,14 @@ async fn transfer(
to: String,
) -> Result<()> {
// Parse inputs
let from = AccountKeyring::from_str(&from).expect("unknown from");
let to = AccountKeyring::from_str(&to).expect("unknown to");
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);
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.api(), world.client(), from, amount, to).await
common::balances::transfer(world.full_client(), from, amount, to).await
};
if world.ignore_errors() {
......@@ -181,44 +225,221 @@ async fn transfer(
}
}
#[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<()> {
// Parse inputs
let from = AccountKeyring::from_str(&from).expect("unknown from");
let to = AccountKeyring::from_str(&to).expect("unknown to");
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::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 = AccountKeyring::from_str(&from).expect("unknown from");
let to = AccountKeyring::from_str(&to).expect("unknown to");
let from = Keyring::from_str(&from).expect("unknown from");
let to = Keyring::from_str(&to).expect("unknown to");
common::cert::certify(world.full_client(), from, to).await
}
#[allow(clippy::needless_pass_by_ref_mut)]
#[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
let from = Keyring::from_str(&from).expect("unknown from");
let to = Keyring::from_str(&to).expect("unknown to");
common::cert::certify(world.api(), world.client(), from, to).await
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 ====
#[then(regex = r"([a-zA-Z]+) should have (\d+) (ĞD|cĞD)")]
#[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 = AccountKeyring::from_str(&who)
let who: subxt::utils::AccountId32 = Keyring::from_str(&who)
.expect("unknown to")
.to_account_id();
.to_raw_public()
.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);
} 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(())
}
#[allow(clippy::needless_pass_by_ref_mut)]
#[then(regex = r"Current UD amount should be (\d+).(\d+)")]
async fn current_ud_amount_should_be(
world: &mut DuniterWorld,
......@@ -227,28 +448,26 @@ async fn current_ud_amount_should_be(
) -> Result<()> {
let expected = (amount * 100) + cents;
let actual = world
.api()
.storage()
.universal_dividend()
.current_ud(None)
.read_or_default(&gdev::storage().universal_dividend().current_ud())
.await
.await?;
assert_eq!(actual, expected);
Ok(())
}
#[allow(clippy::needless_pass_by_ref_mut)]
#[then(regex = r"Monetary mass should be (\d+).(\d+)")]
async fn monetary_mass_should_be(world: &mut DuniterWorld, amount: u64, cents: u64) -> Result<()> {
let expected = (amount * 100) + cents;
let actual = world
.api()
.storage()
.universal_dividend()
.monetary_mass(None)
.read_or_default(&gdev::storage().universal_dividend().monetary_mass())
.await
.await?;
assert_eq!(actual, expected);
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,
......@@ -256,39 +475,49 @@ async fn should_be_certified_by(
issuer: String,
) -> Result<()> {
// Parse inputs
let receiver_account = AccountKeyring::from_str(&receiver)
let receiver_account: subxt::utils::AccountId32 = Keyring::from_str(&receiver)
.expect("unknown to")
.to_account_id();
let issuer_account = AccountKeyring::from_str(&issuer)
.to_raw_public()
.into();
let issuer_account: subxt::utils::AccountId32 = Keyring::from_str(&issuer)
.expect("unknown to")
.to_account_id();
.to_raw_public()
.into();
// get corresponding identities index
let issuer_index = world
.api()
.storage()
.read(
&gdev::storage()
.identity()
.identity_index_of(&issuer_account, None)
.identity_index_of(&issuer_account),
)
.await
.await?
.unwrap();
let receiver_index = world
.api()
.storage()
.read(
&gdev::storage()
.identity()
.identity_index_of(&receiver_account, None)
.identity_index_of(&receiver_account),
)
.await
.await?
.unwrap();
let issuers = world
.api()
.storage()
.cert()
.certs_by_receiver(&receiver_index, None)
.read_or_default(
&gdev::storage()
.certification()
.certs_by_receiver(receiver_index),
)
.await
.await?;
match issuers.binary_search_by(|(issuer_, _)| issuer_index.cmp(issuer_)) {
// 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 {} to {}: {:?}",
"no certification found from {} ({issuer_index}) to {} ({receiver_index}): {:?}",
issuer,
receiver,
issuers
......@@ -297,13 +526,76 @@ async fn should_be_certified_by(
}
}
#[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)]
struct CustomOpts {
/// Keep running
#[clap(short, long)]
#[arg(short, long)]
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";
......@@ -323,6 +615,7 @@ async fn main() {
let opts = cucumber::cli::Opts::<_, _, _, CustomOpts>::parsed();
let keep_running = opts.custom.keep_running;
let no_spawn = opts.custom.no_spawn;
// Handle crtl+C
let running = Arc::new(AtomicBool::new(true));
......@@ -332,32 +625,45 @@ async fn main() {
})
.expect("Error setting Ctrl-C handler");
DuniterWorld::cucumber()
//.fail_on_skipped()
let summarize = DuniterWorld::cucumber()
.fail_on_skipped()
.max_concurrent_scenarios(4)
.before(|feature, _rule, scenario, world| {
.before(move |feature, _rule, scenario, world| {
let mut genesis_conf_file_path = PathBuf::new();
genesis_conf_file_path.push("cucumber-genesis");
genesis_conf_file_path.push(&format!(
genesis_conf_file_path.push(format!(
"{}.json",
genesis_conf_name(&feature.tags, &scenario.tags)
));
world.set_ignore_errors(ignore_errors(&scenario.tags));
Box::pin(world.init(Some(genesis_conf_file_path)))
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 {
while running.load(Ordering::SeqCst) {}
}
// Early kill (not waiting destructor) to save CPU/memory
if let Some(world) = maybe_world {
world.kill();
}
Box::pin(std::future::ready(()))
})
.with_cli(opts)
.run_and_exit(features_path)
.run(features_path)
.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 {
......