Compare commits

...

314 Commits

Author SHA1 Message Date
cc45fb5f9c
pin compiler version to fixed 1.93.1
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-09-13 11:14:21 +03:00
6e1fd726f3
merge ghost-exodus into main
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-09-12 20:23:24 +03:00
150e6438f8
new genesis file added
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-09-11 18:32:11 +03:00
c9288e7e1e
more final additions to genesis config
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-09-11 17:04:31 +03:00
3ed3c659c6
insert testnet preclaims into governance pallet
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-09-11 16:10:49 +03:00
1e1d2bdc81
update Cargo.lock after dependency version bumps
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-09-11 01:05:11 +03:00
2180d1c85b
extend on_initialize time ratio for safer block production
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-09-11 01:04:11 +03:00
3d2d905629
move complaints matrix to flat Vec, eliminate dynamic allocations
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-09-11 01:00:26 +03:00
8d097990a7
benchmark pallets based on new logic
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-09-10 23:26:20 +03:00
fe6582042a
extend genesis file with initial validator keys
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-09-10 22:25:21 +03:00
59b5356613
make decimals part of network data
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-09-10 21:40:06 +03:00
84195a64ef
migrate UTXO bridge proof to Legacy P2PKH with OP_RETURN account extraction
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-09-10 21:21:22 +03:00
fd8e52b520
adopt BIP-340 for all Secp256k1 networks in EXODUS
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-09-09 03:41:28 +03:00
a6e87c20d6
corrected text for genesis generation message
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-09-09 03:31:24 +03:00
43ec3492ef
merkle tree prefixes added
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-09-08 14:20:56 +03:00
3cf96b1820
make BFT function name more self-descriptive and align implementation with paper
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-09-08 14:17:01 +03:00
66bf42cbd3
remove default network gatekeepers because we don't any yet
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-09-06 22:14:42 +03:00
8a321ab5e1
apply last changes for the runtime
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-09-06 16:28:33 +03:00
6b353d1169
adjust reference machine read/write; fix script for genesis; remove legacy stuff
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-09-06 16:26:52 +03:00
90371a1412
finalizing the runtime
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-09-06 02:58:22 +03:00
1b0c4bbb31
remove legacy code
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-09-06 02:56:48 +03:00
589e88f6eb
update default weight for pallets
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-09-06 02:52:29 +03:00
3e6192cee7
benchmarking for evm bridge out added
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-09-06 01:35:46 +03:00
f2544e3876
make governance work over exodus
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-09-05 23:57:28 +03:00
2387a2fe32
make exodus to change gatekeeped amount after bridge out
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-09-05 23:56:24 +03:00
5243bd7c3d
apply benchmarked weights
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-09-05 23:45:44 +03:00
8ee32ff609
make benchmarking dependant on the actual data
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-09-05 23:42:28 +03:00
06af604cc8
apply benchmarked weight as default
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-09-05 23:41:03 +03:00
c4f22df03d
remove warnings from auto-generated code
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-09-05 23:38:22 +03:00
554bc4499f
fix governance benchmarking; make id dependant on the proof length
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-09-05 23:36:27 +03:00
c7637d8ce6
additional trait for the evm governance
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-09-05 23:34:33 +03:00
3b8030527b
make benchmarking flag available based on the feature flag
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-09-05 23:29:03 +03:00
4d836ee8ac
remove unused pallet
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-09-05 23:27:58 +03:00
90dfc76581
weights benchmarked based on reference machine
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-09-05 12:39:37 +03:00
613b09ac6a
fix weights calculations
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-09-02 03:13:11 +03:00
98de9373dd
make runtime more legit for real use case; make genesis file cleaner
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-09-02 03:11:15 +03:00
882ec7ec0d
remove unnecessary files and sligtly tweak the ratios
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-09-02 03:09:09 +03:00
afea8046f9
put benchmarking under the feature flag at once
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-09-02 03:06:41 +03:00
80f58e6b11
finally make feature to represent runtime
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-09-02 03:04:38 +03:00
20c8b9f1b2
fix the benchmarking script
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-09-02 03:03:39 +03:00
e694489f75
slightly optimize bridge in/out logic; make variable names more self descriptive; fix benchmarking for governance
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-09-02 02:59:26 +03:00
e104a49e53
fix runtime for the benchmarking
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-08-31 15:06:47 +03:00
99ac2f1f08
make fee calculations correct based on nanoseconds
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-08-31 15:05:56 +03:00
ffa65dc0a7
bump runtime-common version
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-08-31 15:04:27 +03:00
bfcd7af559
we need to use 1/3 if the block time because each validator will re-execute all extrinsics
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-08-31 15:03:33 +03:00
85f1dc9646
remove unnecessary code blocks
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-08-31 15:02:05 +03:00
e7a191d5e7
remove annoying warning
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-08-31 14:59:56 +03:00
c173734ea8
align governance preclaims with python script
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-08-31 14:58:52 +03:00
7b050f1363
fix tests for exodus pallet
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-08-31 14:57:06 +03:00
d83ee59508
let the EXODUS begin...
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-08-29 14:48:50 +03:00
0e228d890d
rustfmt changes
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-06-18 14:41:58 +03:00
5e17c12677
use new logic from slow-clap for casper runtime
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-06-18 14:40:52 +03:00
c112ad22c2
apply eviction logic during the offence preparation
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-06-18 14:38:34 +03:00
0dd27d6429
eviction trait added
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-06-18 14:36:51 +03:00
8252107e96
make multiplication to round up during slash fraction calculation
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-06-10 11:14:06 +03:00
6419d4ba16
add hoodi network for the rpc-tester
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-06-10 11:11:46 +03:00
4c4374c812
downgrade state version
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-03-04 17:11:17 +03:00
e7b8cd46e1
add transaction hash to the unique identifier of the bridging tx
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-03-03 22:18:32 +03:00
713cbdfdc1
latest benhcmarking setup for casper runtime
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-03-01 13:26:26 +03:00
5dcc23c996
update pallets weights based on the real benchmarkings
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-03-01 12:39:44 +03:00
562efe5744
apply final changes to casper runtime
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-02-26 21:38:34 +03:00
b0a69493cd
fix benchmarking for slow clap
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-02-26 21:36:14 +03:00
bd8d7145af
more optimized version for try_offend_validators
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-02-26 14:44:36 +03:00
ba7d2a8222
additional function for exposure trait
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-02-26 14:42:39 +03:00
cc300fefb0
apply changes to casper runtime; migrations are included
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-02-24 22:06:54 +03:00
c8f8ea7130
rustfmt of the chain specs
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-02-24 22:03:45 +03:00
93387218ec
make initialise authorities infallable and fix benchmarking
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-02-24 22:02:26 +03:00
6a5029017c
use ExternalBlockNumber type everywhere
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-02-23 19:13:14 +03:00
4c79048b49
migration to new block commitment data type; tests included
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-02-23 18:56:05 +03:00
24b08a87b1
make max commit deviation to be 1/8 of EpochDuration
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-02-23 16:55:33 +03:00
06618069e2
avoid commitments check on the beginning of the epoch
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-02-23 15:17:13 +03:00
028afc089f
use internal block number as last_update for block commitments
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-02-22 21:56:55 +03:00
f082dfc4e0
ability to iterate over network ids only and get networks lenght without storage read
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-02-22 21:54:51 +03:00
c963f7816b
slightly optimise try_slow_clap
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-02-22 13:25:10 +03:00
63c03ad6ce
extend trait with network existence check
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-02-22 13:14:58 +03:00
4e1a3de6de
prepare benchmarks and update default weights
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-02-22 11:49:39 +03:00
5dd0c73f7a
move block commitment check to the on_initialize hook
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-02-21 17:57:49 +03:00
03262539aa
adapt trait; add extra test; fix benchmarking
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-02-21 17:55:26 +03:00
f524b01b03
make function name more obvious
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-02-21 17:52:42 +03:00
f99eee2e1a
remove nullification functionality from trait
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-02-21 14:17:35 +03:00
d3cc3ac1b3
remove unnecessary storage entry in pallet
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-02-21 14:15:51 +03:00
7a7712df0b
replace custom muldiv with multiply_by_rational_with_rounding from substrate
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-02-21 12:54:29 +03:00
cce2910cf8
make networks pallet to be indexed storage map; migrations included
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-02-20 17:15:53 +03:00
5b5e53e6fd
update traits for the network handler
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-02-20 16:09:54 +03:00
2313bc97ec
user agent added to mimic the curl
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-02-19 19:23:50 +03:00
6263c620ab
fix starter and patch scripts
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-02-07 15:35:23 +03:00
4348580127
patches for the latest rustc compiler
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-02-05 18:19:54 +03:00
e12b85d5da
small script to test default rpc endpoints
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-02-04 20:28:46 +03:00
0a762388d7
make script work with main branch
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-02-03 22:30:34 +03:00
cdc14c5ed1
finally fix script for boostrappers
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-02-03 18:53:13 +03:00
fc3a6f0cd0
temporarily remove git manipulations during hard reset
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-02-03 18:14:20 +03:00
c90ef5f7e3
set git branch for bootstrapers
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-02-03 15:20:21 +03:00
a8651dfe84
prepare node to latest version of chain
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-02-03 15:17:46 +03:00
ddfbaae2f5
additional information during hard reset
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-02-02 19:18:33 +03:00
172edd46de
additional safety for multi request logic
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-02-02 18:10:52 +03:00
32483cdd40
make logs more self-descriptive
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-02-02 15:26:38 +03:00
eb9fc16b43
tests added for new offchain worker functionality
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-02-01 15:08:03 +03:00
b282ebad62
rustfmt the whole project
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-01-31 18:14:26 +03:00
73503fd13a
remove local testing scripts
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-01-31 18:12:05 +03:00
90c82f73cb
multiple requests from all endpoints
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-01-31 18:10:40 +03:00
3c7e51c0f8
new version of casper runtime
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-12-09 17:46:48 +03:00
ddd3a42564
new slashing function for block commitment offence
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-12-09 17:23:08 +03:00
dc785e30d9
more readable logs and use validators as offence reporters
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-12-09 16:24:41 +03:00
3b80a7a94a
change slashing logic during block commitments
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-12-09 14:56:24 +03:00
270f3a6191
apply changes from slow clap for the casper runtime
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-12-04 18:42:31 +03:00
a6ce55c1e8
rearrange offchain worker execution sequence
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-12-04 18:21:58 +03:00
0323c25d08
avoid excessive block commitments
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-12-04 17:51:29 +03:00
f9cd455968
make pull during hard reset
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-11-28 23:41:18 +03:00
06423f9ae5
prepare for the main branch usage
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-11-28 22:38:38 +03:00
5d92560513
make starter script usable for network bootstrap
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-11-28 17:38:11 +03:00
207bbec652
update default chain spec for casper network
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-11-28 17:08:03 +03:00
901c828d3f
legacy endowments and gatekeeped amount for networks
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-11-28 17:01:42 +03:00
876cea96ec
update weights for the latest runtime
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-11-28 17:00:45 +03:00
5b26d4ce28
make gatekeeped amount part of the genesis
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-11-28 16:56:46 +03:00
147542f10c
correct median; on genesis validators list is empty
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-11-28 00:02:26 +03:00
d45cc9bd13
add latest from_block to logs
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-11-27 19:22:48 +03:00
7c57b9ea3f
update chain specs for local testing
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-11-27 19:08:43 +03:00
638d31ba28
update casper runtime based on ghost pallets
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-11-27 19:07:59 +03:00
c9b4325c04
rustfmt slow-clap pallet
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-11-27 19:06:18 +03:00
911db3b974
apply changes from ghost-traits for ExposureListener
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-11-27 19:04:24 +03:00
fbc58e350b
conversion function to AccountId added
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-11-27 19:03:13 +03:00
b692959369
rustfmt ghost slow clap pallet
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-11-27 17:16:07 +03:00
16b933f3ba
rustfmt ghost traits pallet
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-11-27 17:15:30 +03:00
ad3de30be3
rustfmt ghost networks pallet
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-11-27 17:15:03 +03:00
e3c8e51749
update casper runtime in accordance with new functionality. not final
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-11-27 17:12:22 +03:00
d3c5380ba7
updated version of slow clap
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-11-27 17:10:57 +03:00
c3ef933c20
extend networks pallet with avg_block_speed
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-11-27 17:10:23 +03:00
954ae66294
bump traits package version
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-11-27 17:09:10 +03:00
a504d8eb87
trait to get exposure from external pallets such as staking
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-11-27 17:08:16 +03:00
eebcc18018
simple bitmap implementation
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-11-27 17:06:50 +03:00
3be36b6db2
set ghost emoji as prefix for logging
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-11-21 13:49:40 +03:00
6a2b5a34d2
applause based on the external expousre
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-11-21 13:46:22 +03:00
0bb46482b2
offchain worker restructure and block commitments added
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-11-20 03:06:48 +03:00
57c809c618
Merge branch 'pallet-slow-clap'
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-11-18 16:03:47 +03:00
d76646c191
remove unused structs to avoid warning during compilation
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-11-18 16:01:29 +03:00
94d28f254f
update offchain worker logic
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-11-18 16:00:17 +03:00
6fa5e5ed97
update casper runtime based on new version of slow clap
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-11-12 20:00:57 +03:00
58c5f1f33d
apply latest version of slow clap
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-11-12 19:06:42 +03:00
55a77cd3d4
introduce black swan event e.g. disable everybody
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-11-12 19:03:58 +03:00
2cf4637d0c
update casper runtime version
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-11-10 21:23:08 +03:00
8123295f91
Merge branch 'pallet-slow-clap'
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-11-10 20:55:37 +03:00
092679eb0c
disable only claps during offchain worker but not the block gathering
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-11-10 20:54:37 +03:00
75268b4c0a
casper runtime upgrade
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-11-09 16:09:56 +03:00
275567ef79
Merge branch 'pallet-slow-clap'
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-11-09 15:31:43 +03:00
64de0027bf
align self-applause logic and disable offchain worker if authority index is disabled
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-11-09 15:29:50 +03:00
a3ed395689
take disabled from the next session and rustfmt pallet code
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-11-08 14:02:04 +03:00
7a710ec9cb
check if validator disabled during the validate_unsigned and small typo fixes
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-11-08 13:48:14 +03:00
cc141105bb
propagate disabled authorities to upcoming sessions in era
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-11-08 13:38:15 +03:00
fc2e4e6bff
bump slow clap version inside lock file
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-11-06 17:06:11 +03:00
0e750c0261
update lock file
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-11-06 16:53:19 +03:00
cc566ac549
update weights for slow clap in casper runtime
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-11-06 15:01:01 +03:00
18241ea504
update weights for the slow clap
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-11-06 14:59:27 +03:00
af14543cc1
change casper runtime versioning
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-11-06 14:33:25 +03:00
6acec08fa5
increase storage version for the pallet
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-11-06 14:32:04 +03:00
d6ecdcd468
correct benchmarking of self applause and remove of unnecessary storage during tests
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-11-05 22:13:28 +03:00
decf6665df
make slow-clap tests work again based on new functionality, new tests added too
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-11-05 20:55:04 +03:00
ebae9fadbe
fixes for self_applause function
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-11-05 20:52:00 +03:00
c872eca8ac
back look during the slashing
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-11-05 15:55:37 +03:00
eb181c7f44
fixes for the inconsistency of the slow claps
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-11-04 17:43:36 +03:00
fae0fa4d7b
fixes in the readme guide
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-09-01 20:30:49 +03:00
71eb90a5fb
increase stable compiler version to match the ghost-eye dependencies
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-09-01 19:23:20 +03:00
5307afe352
bump locked version
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-08-27 15:33:26 +03:00
7edc8935b6
optimize offenders gathering with a single pass over the data
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-08-27 15:31:32 +03:00
46aa18aafe
implement cross session claim via self_applause
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-08-27 14:41:46 +03:00
25fcfed586
bump slow clap version
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-08-15 23:07:13 +03:00
f245879925
force parameters for RewardCurve to be a default balance type, which is u64
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-08-14 16:55:46 +03:00
101e7103f1
update specification version of CASPER runtime
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-08-14 16:20:38 +03:00
d9fa416d93
bump locked versions
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-08-10 22:09:14 +03:00
60b887f812
make MulDiv to be used with any 32 bit unsigned
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-08-10 22:07:53 +03:00
f7f25bd087
nullify bridged imbalance on each era reward payout
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-08-10 16:51:04 +03:00
e2c75ca558
implement muldiv without overflow and underflow
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-08-10 16:41:14 +03:00
9240f424e1
prepare starter script for the production
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-08-03 18:43:49 +03:00
72d6be6e29
change finality delay for sepolia in genesis specification
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-07-31 21:32:49 +03:00
da271a6f22
cargo clean only if the compiler version is downgraded
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-07-31 16:35:07 +03:00
b9b7d84466
update ghost-node and specification to the latest version
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-07-31 16:32:44 +03:00
8ff588cce9
remove spoiled rpc endpoint from the genesis file
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-07-31 16:18:47 +03:00
298a332681
make staking rewards dependant on bridged amount
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-07-31 16:17:10 +03:00
1c4c517728
avoiding back in time travel
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-07-31 15:30:28 +03:00
b969081cbf
make sure that disabled validator will not be checked with is_good_actor
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-07-31 14:21:55 +03:00
9bdb7b5d5c
add an early check for the disabled validator
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-07-31 14:02:14 +03:00
c4b16805f7
fix calculation of median, include special case when there're no claps
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-07-31 13:56:05 +03:00
f7b1b75d5a
dedup casper spec file
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-07-30 17:53:39 +03:00
6918e8057a
finalize starter script for the hard reset
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-07-29 19:41:14 +03:00
df85600159
generate chain spec for the latest ghost-node
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-07-29 16:03:34 +03:00
141f05ddab
finalize hard reset for starter script
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-07-29 16:00:58 +03:00
6ae3fd6291
rustfmt ghost cli and fix typos
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-07-29 15:03:44 +03:00
767161ac9c
rustfmt ghost client cli and fix typos
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-07-29 15:02:07 +03:00
a74e42369b
rustfmt ghost metrics and fix typos
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-07-29 15:00:54 +03:00
7216f1d82b
rustfmt ghost slow clap and fix typos
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-07-29 14:58:23 +03:00
792e70c988
rustfmt ghost sudo and fix typos
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-07-29 14:57:58 +03:00
eb3b4f2651
rustfmt ghost rpc and fix typos
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-07-29 14:54:56 +03:00
24a6f803c5
rustfmt common runtime and fix typos
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-07-29 14:53:50 +03:00
8d69e5c87e
rustfmt casper runtime and fix typos
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-07-29 14:51:15 +03:00
48ff511685
rustfmt service and fix typos
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-07-29 14:49:16 +03:00
8f20b7ef5c
rustfmt tests and fix typos
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-07-29 14:45:41 +03:00
ce26787a11
rustfmt staking-miner and fix typos
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-07-29 14:38:49 +03:00
e21ac88235
rustfmt ghostkey and fix typos
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-07-29 14:27:53 +03:00
c933ed3809
rustfmt generate-bags and fix typos
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-07-29 14:27:08 +03:00
2ad43a56ba
rustfmt chain-spec-builder and fix typos
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-07-29 14:26:12 +03:00
6d06fcf9a0
rustfmt bags-utils and fix typos
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-07-29 14:24:30 +03:00
c2f9958c1a
prepare chain spec for the genesis
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-07-29 14:09:32 +03:00
2bb66c4e19
update ghost-networks weights for the casper runtime
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-07-29 13:30:02 +03:00
63979e34a7
update weights for the ghost-networks based on the latest update
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-07-29 13:28:46 +03:00
28e8389bfc
fix struct field name in chain-spec
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-07-29 12:44:01 +03:00
1d826fbf7e
rotate endpoints on each offchain worker exection
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-07-29 12:42:36 +03:00
0375bd1434
preparation for the benchmarking of upgraded ghost-networks pallet
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-07-28 23:53:11 +03:00
b4ef445281
make ability to have multiple default endpoints for the network
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-07-28 23:34:42 +03:00
61056ed162
recomendations for rustc compilation and cargo usage for automation script
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-07-28 16:28:23 +03:00
b922bf6c20
update weights for casper runtime
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-07-28 16:20:06 +03:00
fe46566d7e
insert measured weights for ghost-sudo
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-07-28 16:18:24 +03:00
3380d16dc9
extend genesis config with ghost sudo
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-07-28 13:15:10 +03:00
384380ad55
ghost sudo added as a dependency to casper runtime
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-07-28 13:13:59 +03:00
4f23c39994
sudo pallet ported as local pallet
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-07-28 13:12:08 +03:00
67fd48d500
hard reset flag added to starter script
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-07-28 13:10:07 +03:00
d575895841
make 1 second as a rate limit for default networks rpc endpoints
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-06-25 19:31:43 +03:00
47f7ae3847
separate evm structs, deserialisation functions and main logic
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-06-25 19:28:59 +03:00
591cce1fb1
rustfmt the ghost-slow-clap pallet
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-06-25 18:20:10 +03:00
f7ba592d50
rustfmt the ghost-networks pallet
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-06-25 18:17:55 +03:00
40a8152f69
rustfmt the ghost-claims pallet
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-06-25 18:17:14 +03:00
116ca39dc4
rustfmt the ghost-traits pallet
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-06-25 18:16:06 +03:00
8464da831f
rate limit for the rpc endpoint based on network_id, storage guard lock for the current network_id and other minor improvements
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-06-25 18:09:29 +03:00
0c3636fe79
bump all pallet versions
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-06-19 22:38:24 +03:00
d87184bbc2
update weights for the ghost networks pallet
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-06-19 22:37:03 +03:00
43f72ec842
update weights and add rate_limit_delay for slow claps
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-06-19 22:35:51 +03:00
ac6d70ae91
update chain specification based on new rate_limit_delay field of NetworkData
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-06-19 21:40:05 +03:00
49c5f3a9e9
update weights for the casper runtime, benchmarked with stopped ghost-node service
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-06-19 21:39:17 +03:00
060f61105c
additional network field, that represents default rate limit measured in blocks
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-06-19 21:32:40 +03:00
b53a58f431
update chain specification because of finality delay non-option type
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-06-19 15:02:07 +03:00
818f41d05e
update slow claps because of finality delay non-option type
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-06-19 15:00:30 +03:00
7bb18939bd
no need for finality delay to be an Option<u64>
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-06-19 14:37:06 +03:00
d8e934a98e
ability to avoid too big deviations between from_block and to_block
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-06-19 14:31:33 +03:00
d5643472ee
update chain specification based on NetworkData new fields
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-06-18 18:56:08 +03:00
a503295939
update weights for the ghost-networks on the casper runtime
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-06-18 18:54:44 +03:00
671196ebac
remove legacy release_delay and add block_distance for maximum block range for the eth_getLogs
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-06-18 18:53:19 +03:00
39a6192d28
avoid re-using of to_block during rotation of block_range
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-06-18 17:20:25 +03:00
6100e79ebf
make clap regardless of fail during applause
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-06-18 17:03:09 +03:00
99c43a0c24
runtime update, with history depth for the slow clap
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-06-18 14:17:02 +03:00
417de5a7b2
clear storage based on provided history depth
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-06-18 13:47:13 +03:00
b5845924dc
fixes for the default weights on slow-clap package
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-06-17 20:56:32 +03:00
7107283121
update casper runtime with pre-calculated weights
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-06-17 20:46:39 +03:00
f28e57cf8e
add files with weights for the slow-clap, benchmarked according to the casper reference machine
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-06-17 20:44:44 +03:00
3040dfb775
make benchmark work, issue with authorities fixed
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-06-17 20:34:42 +03:00
1196cc9faa
changes to runtime based on the new version of ghost-slow claps
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-06-17 13:54:32 +03:00
66fa8409fa
additional tests for the pallet
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-06-17 13:50:38 +03:00
5beb22f116
avoid errors from minting sub-existential balance
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-06-13 16:42:14 +03:00
7be24ed139
use latest version of ghost-networks
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-06-13 16:22:21 +03:00
b5fc86aa9e
separate bridged imbalance and gatekeeper amount. requested by @st1nky for ghost-slow-clap
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-06-13 16:16:01 +03:00
e73f3855fd
authorities stored based on the session hash map
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-06-11 22:56:48 +03:00
a00eec9bb9
tests for clap and applause events added
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-06-04 16:33:03 +03:00
2c2df5a607
ability to do self applause if claps received
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-06-04 15:44:53 +03:00
186fb58367
merge new functionality which will be used for benchmarking purposes
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-06-04 15:40:13 +03:00
1e4abbfe69
function to trigger nullification period, tests for the new functionality and expansion of NetworkData trait
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-06-04 15:35:48 +03:00
573e57dfb4
new tests based on new ghost-network functinality
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-06-03 20:41:51 +03:00
46d4716f67
merge extended ghost-networks with ability to nullify accumulated commission
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-06-03 19:48:23 +03:00
9cb7f3c782
replacing the average claps in session with median to determine a harmful authority
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-06-03 19:38:07 +03:00
c55d9a05d9
auto nullification during finalization and appropriate tests
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-06-03 19:06:50 +03:00
5847097e94
extend network inspect handler trait with additional function
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-06-03 18:58:21 +03:00
04a63e234d
tests updated and small tweaks of the lib
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-06-02 19:20:14 +03:00
2b738c009b
merge latest ghost-network version
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-06-02 19:17:16 +03:00
f3d8ee3ab2
make bridge imbalance fields accesible aka public
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-06-02 19:11:11 +03:00
2da07d7b24
chmod 644 Cargo.toml for ghost-slow-claps
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-05-31 14:55:30 +03:00
9ba25d6b3d
friendly merge for ghost-claims weights
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-05-31 14:51:53 +03:00
3234341fed
use benchmarked weights as a default weights for ghost-claims pallet
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-05-31 14:48:08 +03:00
fa2cb811a8
update for slow-claps: remove companions, updgrade block range storage, new way of commission accumulation
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-05-31 14:30:15 +03:00
561d4430b4
another indention fix in main Cargo.toml
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-05-31 14:21:21 +03:00
b64700fb42
fix indentation in TOML files
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-05-31 13:12:48 +03:00
b3f85f426c
extend pallet ghost-networks and create BridgedInflationCurve
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-05-30 19:03:50 +03:00
ddec108ced
chmod 644 files in runtime
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-05-29 13:09:54 +03:00
cd8ad50ac3
chmod 644 utils
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-05-29 13:04:55 +03:00
e09ce03836
chmod 644 tests
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-05-29 13:01:30 +03:00
9f6f4953f7
chmod 644 service
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-05-29 12:59:25 +03:00
40aba0e3af
chmod 644 files in rpc
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-05-29 12:58:09 +03:00
596d9a6ca7
chmod 644 files in core-primitives
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-05-29 12:57:26 +03:00
39279697f2
chmod 644 files in cli
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-05-29 12:56:11 +03:00
d3a18e298a
chmod 644 files in project root
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-05-29 12:54:26 +03:00
aede9e6e2e
change access permissions of files in pallets folder
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-05-29 12:52:37 +03:00
9ce57763cb
extened the template.service for the node and add Requires= rule
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-04-29 22:02:33 +03:00
eab90d4173
adding first patch for the unit file
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-04-29 21:45:01 +03:00
bdc094663d
fix for the writing to permissioned file
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-03-11 15:57:11 +03:00
a22b6f92d2
pull from @doctor_whoami approved
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-02-25 18:57:20 +03:00
b51c0dffc4
Update README (update testnet version, link to the bootnode list and sha256sum of genesis, small text update)
Signed-off-by: Doctor K <doctor_whoami@ghostchain.io>
2025-02-17 10:43:11 +03:00
eae752a7bf
issue with --validate flag fixed
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-02-06 20:55:55 +03:00
346d629f95
validator flag added to the starter.sh script
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-02-06 16:21:04 +03:00
8a0cbb94b4
update chain specs
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-01-30 22:24:30 +03:00
a2afa3fbbc
bump version
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-01-30 22:11:21 +03:00
885c519519
bridge slashing issue fix
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-01-30 22:09:32 +03:00
0d796420f2
fix incorrect definition of good actor
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-01-30 15:12:55 +03:00
2298454519
update sha256sum for the new genesis config file
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-01-21 15:21:27 +03:00
fcc2e170f8
node version update
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-01-21 15:16:04 +03:00
1fccc49139
v0.0.3 specification added
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-01-21 15:14:43 +03:00
560ca369cd
new genesis config. whales endowments, new genesis validators and extra params for staking
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-01-21 13:55:28 +03:00
2b53777b41
alignment for preparation script
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-01-21 13:47:18 +03:00
bfb023ad4c
additional bootstrap validators based on the Proposal #7
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-01-21 13:38:34 +03:00
905ad14b73
additional bootstrap validator
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-01-21 13:33:14 +03:00
11c435893d
downgrade spec version because of ghost-eye compatibility
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-01-21 13:29:41 +03:00
054bb23910
awakening1
Signed-off-by: ogenkidu <ogenkidu@ghostchain.io>
2025-01-20 12:23:58 -06:00
320ef53b50
Osiris3
Signed-off-by: Barry062 <barry062@ghostchain.io>
2025-01-20 19:15:24 +01:00
1b95024dca
Osiris3
Signed-off-by: Barry062 <barry062@ghostchain.io>
2025-01-20 18:19:21 +01:00
b54c2aee58
awakening1
Signed-off-by: ogenkidu <ogenkidu@ghostchain.io>
2025-01-20 10:57:24 -06:00
a04f19558c
update specification version
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-01-20 18:38:04 +03:00
4d7330fd4d
remove unneded braces
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-01-20 18:37:16 +03:00
53d4d9e142
update version for runtime
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-01-20 18:35:19 +03:00
f1eca789a4
make slashing instant and decrease bonding duration to one era, needed for TUI development only
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-01-20 18:03:03 +03:00
f4c8144c71
curve parameters changed, make everything 69 because its fun
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-01-20 18:00:55 +03:00
12e6f98dec
Osiris3
Signed-off-by: Barry062 <barry062@ghostchain.io>
2025-01-19 20:38:42 +01:00
4c49cf6ad2
Osiris3
Signed-off-by: Barry062 <barry062@ghostchain.io>
2025-01-19 20:34:04 +01:00
5738d30fbb
ssh and ARM-based instructions added
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-01-13 16:47:32 +03:00
a9e949d95f
Update README (add command for installing wasm on an ARM-based CPU, add SSH service startup command)
Signed-off-by: Doctor K <doctor_whoami@ghostchain.io>
2025-01-11 22:19:09 +03:00
3fad815039
Merge branch 'PR_48'
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
Merging readme updates, many thanks to Doctor.K
2024-12-23 16:32:51 +03:00
a7cf93b730
Add bootnodes and Update images folder (ghostchain/ghost-node#47 (comment))
Signed-off-by: Doctor K <doctor_whoami@ghostchain.io>
2024-12-23 16:02:46 +03:00
6aae4ed622
telemetry added as a default to --set-arguments
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2024-12-16 16:16:54 +03:00
e1287e7bab
Merge branch 'PR_46'
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2024-12-05 13:27:44 +03:00
941da651a8
Add README
Resolves #46

Signed-off-by: Doctor K <doctor_whoami@ghostchain.io>
2024-12-04 17:19:10 +03:00
cd2d52df1c
temporary finalization test with limited group of validators
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2024-11-18 22:52:30 +03:00
290 changed files with 53020 additions and 12882 deletions

1252
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -17,7 +17,7 @@ homepage.workspace = true
[workspace.package] [workspace.package]
license = "GPL-3.0-only" license = "GPL-3.0-only"
authors = ["571nky", "57r37ch", "f4750"] authors = ["571nky", "57r37ch", "f4750"]
version = "0.7.180" version = "0.9.3"
edition = "2021" edition = "2021"
homepage = "https://ghostchain.io" homepage = "https://ghostchain.io"
repository = "https://git.ghostchain.io/ghostchain/ghost-node" repository = "https://git.ghostchain.io/ghostchain/ghost-node"
@ -67,17 +67,21 @@ serial_test = { version = "2.0.0" }
color-eyre = { version = "0.6.1" } color-eyre = { version = "0.6.1" }
bs58 = { version = "0.5.0" } bs58 = { version = "0.5.0" }
prometheus-parse = { version = "0.2.2" } prometheus-parse = { version = "0.2.2" }
k256 = { version = "0.13.0", default-features = false }
rustc-hex = { version = "2.1.0", default-features = false } rustc-hex = { version = "2.1.0", default-features = false }
log = { version = "0.4", default-features = false } log = { version = "0.4", default-features = false }
num-traits = { version = "0.2.17", default-features = false }
libsecp256k1 = { version = "0.7", default-features = false } libsecp256k1 = { version = "0.7", default-features = false }
bip39 = { package = "parity-bip39", version = "2.0.1" } bip39 = { package = "parity-bip39", version = "2.0.1" }
sha3 = { version = "0.10", default-features = false } sha3 = { version = "0.10", default-features = false }
sha2 = { version = "0.10.7", default-features = false }
serde = { version = "1.0.197", default-features = false } serde = { version = "1.0.197", default-features = false }
serde_derive = { version = "1.0.117", default-features = false } serde_derive = { version = "1.0.117", default-features = false }
serde_json = { version = "1.0.114", default-features = false } serde_json = { version = "1.0.114", default-features = false }
blake2-rfc = { version = "0.2.18", default-features = false } blake2-rfc = { version = "0.2.18", default-features = false }
impl-serde = { version = "0.4.0", default-features = false } impl-serde = { version = "0.4.0", default-features = false }
hex = { version = "0.4.3", default-features = false } hex = { version = "0.4.3", default-features = false }
strum = { version = "0.28.0", default-features = false }
metered = { package = "prioritized-metered-channel", version = "0.6.1", default-features = false } metered = { package = "prioritized-metered-channel", version = "0.6.1", default-features = false }
scale-value = { version = "0.16.0" } scale-value = { version = "0.16.0" }
@ -85,6 +89,13 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features =
scale-info = { version = "2.5.0", default-features = false } scale-info = { version = "2.5.0", default-features = false }
subxt = { version = "0.37.0", default-features = false } subxt = { version = "0.37.0", default-features = false }
frost-secp256k1-tr = { version = "2.2.0", default-features = false, features = ["serialization"] }
frost-ed25519 = { version = "2.2.0", default-features = false, features = ["serialization"] }
chacha20poly1305 = { version = "0.10.1", default-features = false, features = ["alloc"] }
frost-core = { version = "2.2.0", default-features = false }
rand_chacha = { version = "0.3.0", default-features = false }
hkdf = { version = "0.12.4", default-features = false }
# Frames # Frames
frame-support = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false } frame-support = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
frame-system = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false } frame-system = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
@ -140,6 +151,7 @@ pallet-authority-discovery = { git = "https://github.com/paritytech/polkadot-sdk
pallet-authorship = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false } pallet-authorship = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
pallet-babe = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false } pallet-babe = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
pallet-bags-list = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false } pallet-bags-list = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
pallet-bags-list-remote-tests = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
pallet-balances = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false } pallet-balances = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
pallet-bounties = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false } pallet-bounties = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
pallet-child-bounties = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false } pallet-child-bounties = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
@ -154,8 +166,6 @@ pallet-grandpa = { git = "https://github.com/paritytech/polkadot-sdk.git", tag =
pallet-identity = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false } pallet-identity = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
pallet-indices = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false } pallet-indices = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
pallet-multisig = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false } pallet-multisig = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
pallet-nomination-pools = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
pallet-nomination-pools-runtime-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
pallet-offences = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false } pallet-offences = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
pallet-preimage = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false } pallet-preimage = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
pallet-proxy = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false } pallet-proxy = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
@ -183,8 +193,6 @@ generate-bags = { git = "https://github.com/paritytech/polkadot-sdk.git", tag =
pallet-election-provider-support-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false } pallet-election-provider-support-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
pallet-offences-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false } pallet-offences-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
pallet-session-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false } pallet-session-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
pallet-nomination-pools-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
pallet-bags-list-remote-tests = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false }
# Substrate Client dependencies # Substrate Client dependencies
sc-cli = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0" } sc-cli = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0" }
@ -226,14 +234,22 @@ ghost-metrics = { path = "metrics", default-features = false }
ghost-rpc = { path = "rpc", default-features = false } ghost-rpc = { path = "rpc", default-features = false }
service = { package = "ghost-service", path = "service", default-features = false } service = { package = "ghost-service", path = "service", default-features = false }
ghost-traits = { path = "pallets/traits", default-features = false } ghost-traits = { path = "pallets/traits", default-features = false }
ghost-helpers = { path = "pallets/helpers", default-features = false }
ghost-networks = { path = "pallets/networks", default-features = false } ghost-networks = { path = "pallets/networks", default-features = false }
ghost-claims = { path = "pallets/claims", default-features = false } ghost-governance = { path = "pallets/governance", default-features = false }
ghost-slow-clap = { path = "pallets/slow-clap", default-features = false } ghost-weaver = { path = "pallets/weaver", default-features = false }
ghost-exodus = { path = "pallets/exodus", default-features = false }
ghost-sudo = { path = "pallets/sudo", default-features = false }
ghost-nomination-pools = { path = "pallets/pools", default-features = false }
ghost-nomination-pools-runtime-api = { path = "pallets/pools/runtime-api", default-features = false }
ghost-nomination-pools-benchmarking = { path = "pallets/pools/benchmarking", default-features = false }
ghost-nomination-pools-fuzzer = { path = "pallets/pools/benchmarking", default-features = false }
ghost-runtime-constants = { package = "ghost-runtime-constants", path = "runtime/ghost/constants", default-features = false } ghost-runtime-constants = { package = "ghost-runtime-constants", path = "runtime/ghost/constants", default-features = false }
casper-runtime = { path = "runtime/casper", default-features = false } casper-runtime = { path = "runtime/casper", default-features = false }
casper-runtime-constants = { package = "casper-runtime-constants", path = "runtime/casper/constants", default-features = false } casper-runtime-constants = { package = "casper-runtime-constants", path = "runtime/casper/constants", default-features = false }
primitives = { package = "ghost-core-primitives", path = "core-primitives", default-features = false } primitives = { package = "ghost-core-primitives", path = "core-primitives", default-features = false }
runtime-common = { package = "ghost-runtime-common", path = "runtime/common", default-features = false } runtime-common = { package = "runtime-common", path = "runtime/common", default-features = false }
[dependencies] [dependencies]
color-eyre = { workspace = true } color-eyre = { workspace = true }
@ -258,24 +274,29 @@ substrate-build-script-utils = { workspace = true }
resolver = "2" resolver = "2"
members = [ members = [
"core-primitives", "core-primitives",
"cli", "cli",
"rpc", "rpc",
"service", "service",
"metrics", "metrics",
"client/cli", "client/cli",
"primitives/machine", "primitives/machine",
"runtime/common", "runtime/common",
"runtime/casper", "runtime/casper",
"runtime/casper/constants", "runtime/casper/constants",
"pallets/networks", "pallets/networks",
"pallets/claims", "pallets/governance",
"pallets/slow-clap", "pallets/pools",
"utils/bags-list", "pallets/sudo",
"utils/chain-spec-builder", "pallets/exodus",
"utils/generate-bags", "pallets/exodus/runtime-api",
"pallets/helpers",
"pallets/weaver",
"utils/bags-list",
"utils/chain-spec-builder",
"utils/generate-bags",
"utils/ghostkey", "utils/ghostkey",
"utils/staking-miner", "utils/staking-miner",
] ]
[badges] [badges]
@ -323,9 +344,12 @@ crypto-mac = { opt-level = 3 }
curve25519-dalek = { opt-level = 3 } curve25519-dalek = { opt-level = 3 }
ed25519-dalek = { opt-level = 3 } ed25519-dalek = { opt-level = 3 }
flate2 = { opt-level = 3 } flate2 = { opt-level = 3 }
frost-core = { opt-level = 3 }
frost-secp256k1-tr = { opt-level = 3 }
futures-channel = { opt-level = 3 } futures-channel = { opt-level = 3 }
hash-db = { opt-level = 3 } hash-db = { opt-level = 3 }
hashbrown = { opt-level = 3 } hashbrown = { opt-level = 3 }
hkdf = { opt-level = 3 }
hmac = { opt-level = 3 } hmac = { opt-level = 3 }
httparse = { opt-level = 3 } httparse = { opt-level = 3 }
integer-sqrt = { opt-level = 3 } integer-sqrt = { opt-level = 3 }
@ -341,6 +365,7 @@ parking_lot = { opt-level = 3 }
parking_lot_core = { opt-level = 3 } parking_lot_core = { opt-level = 3 }
percent-encoding = { opt-level = 3 } percent-encoding = { opt-level = 3 }
primitive-types = { opt-level = 3 } primitive-types = { opt-level = 3 }
rand_chacha = { opt-level = 3 }
ring = { opt-level = 3 } ring = { opt-level = 3 }
rustls = { opt-level = 3 } rustls = { opt-level = 3 }
sha2 = { opt-level = 3 } sha2 = { opt-level = 3 }

0
LICENSE.md Executable file → Normal file
View File

462
README.md Executable file → Normal file
View File

@ -1 +1,461 @@
TODO # Notes
Arrows will mark the places where your input is needed
# Update system
Update Your System: Before installing any new software,
it's a good practice to update your system's package list and
upgrade the existing packages to their latest versions.
```bash
sudo apt update
sudo apt upgrade
```
# Firewall
Install Ubuntu firewall
```bash
sudo apt install ufw
```
If you want to connect to your node-machine, allow connection to the SSH port
```bash
sudo ufw allow ssh
```
![images/img.png](images/img.png)
Enable firewall, confirm the operation
```bash
sudo ufw enable
```
![images/img_1.png](images/img_1.png)
Check firewall status. You should see `Status: active`
```bash
sudo ufw numbered
```
![images/img_2.png](images/img_2.png)
# SSH
## Install SSH
Once you have installed the proper Operating System on you machine
we recommend that you enable SSH. Most people have a Daily Driver machine.
GHOST requires a separate machine. Adding an extra machine to your life could be cumbersome.
Chances are you had to use an external monitor to install Ubuntu on GHOST Node,
the same screen regularly used by the Daily Driver machine.
SSH enables you to manage your GHOST Nodes remotely from the comfort of your Daily Driver.
Install OpenSSH Server: With your system updated, the next step is to install
the OpenSSH server package. This package contains the necessary software to run
an SSH server.
```bash
sudo apt install openssh-server
```
Start SSH Service using _systemctl_.
```bash
sudo systemctl start ssh
```
Enable SSH Service to Start on Boot: To ensure that the SSH service automatically
starts after a reboot, you need to enable it using _systemctl_.
```bash
sudo systemctl enable ssh
```
To check the status of the SSH service, run the following command:
```bash
sudo systemctl status ssh
```
You should be seeing something like this:
![images/img_3.png](images/img_3.png)
## Connect by SSH
With SSH enabled and the firewall configured, you can now connect
to your node machine from another computer using SSH.
You have to know your username and your local network ip
```bash
ssh [username]@[your_server_ip_OR_hostname]
```
# Install GHOST Application
## Allow Ports
Allow `port 30333` on GHOST Node:
```bash
sudo ufw allow 30333
```
![images/img_5.png](images/img_5.png)
To ensure that the ports are allowed, run the following command:
```bash
sudo ufw numbered
```
![images/img_6.png](images/img_6.png)
## Install Substrate Libraries
Click [here](https://docs.substrate.io/install/linux/) for a detailed Rust guide if you want to dive deep into the documentation. Otherwise let's proceed.
### Install Dependencies
Check the documentation for your operating system for information about
the packages that are installed and how to download and install
any additional packages you might need. For example,
if you use Debian/Ubuntu, you can use the Advanced Packaging Tool (apt)
to install packages:
```bash
sudo apt install -y build-essential clang curl git make libssl-dev protobuf-compiler llvm libudev-dev
```
### Rust Install
Download the rustup installation program and use it to install Rust by running the following command:
```bash
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
```
Press Enter for using default options:
![images/img_7.png](images/img_7.png)
Update your current shell to include Cargo by running the following command:
```bash
source $HOME/.cargo/env
```
Verify your installation by running the following command:
```bash
rustc --version
```
You should see something like this:
![images/img_8.png](images/img_8.png)
Configure the Rust toolchain to default to the latest stable version by running the following commands:
```bash
rustup default stable
```
```bash
rustup update
```
```bash
rustup target add wasm32-unknown-unknown --toolchain stable
```
Verify the configuration of your development environment by running
the following command:
```bash
rustup show
```
You should see something like this:
![images/img_9.png](images/img_9.png)
![images/img_10.png](images/img_10.png)
Now run:
```bash
rustup component add rust-src --toolchain stable
```
## Install GHOST
Check if Git is already installed:
```bash
git --version
```
![images/img_11.png](images/img_11.png)
Make a GHOST Directory and go to it:
```bash
mkdir ~/ghost && cd ~/ghost
```
Clone GHOST Node Git:
```bash
git clone https://git.ghostchain.io/ghostchain/ghost-node.git
```
Go to ghost-node directory:
```bash
cd ghost-node
```
Compile the node template by running the following command:
```bash
./scripts/starter.sh --release
```
The build can take up to 20 minutes depending on the specifications of your machine.
![images/img_12.png](images/img_12.png)
# Boot Node Qualification
## Boot Node Qualification Test
Becoming a Boot Node on GHOST Chain is an absolute MUST if we want the network
to be sufficiently decentralized. However, due to certain ISP limitations
some countries make it being a Boot Node easier than others.
Hence, the GHOST Dev team came up with a Boot Node Qualification Test.
First we should install TRACEROUTE:
```bash
sudo apt install traceroute
```
Then we must determine the public IP for your GHOST Node Machine.
```bash
curl -4 icanhazip.com
```
![images/img_13.png](images/img_13.png)
Then type the following command replacing `<YOUR_IP_FROM_PREVIOUS_STEP>` with an actual IP address from previous step:
```bash
traceroute <YOUR_IP_FROM_PREVIOUS_STEP>
```
If your GHOST Node is **not behind a NAT** your terminal window should look something like this:
![images/img_14.png](images/img_14.png)
If your GHOST Node is **behind NAT** your terminal window should look something like this:
![images/img_15.png](images/img_15.png)
If your GHOST Node is NOT behind the NAT then your node can be a Boot Node.
If your GHOST Node is behind the NAT then your node CANNOT be a Boot Node
and you will have to use some kind of proxy to rectify the situation.
Afterwards, post the screenshot of your terminal window in the Whales group.
Feel free to blur out your IP.
## Setting Proper Firewall Rules
You must enable `port 30333` on your router
Based on prior instructions you may have configured your firewall in way that is
no longer needed. For the purpose of GHOST Chain you should only
open `port 30333` and close other ports.
### Checking Firewall
Check the ports that are opened on your firewall:
```bash
sudo ufw numbered
```
![images/img_16.png](images/img_16.png)
If `port 9945` is opened then close it:
```bash
sudo ufw deny 9945
```
### Configuring Router
Enable port forwarding for port 30333 on your router. Be mindful that different networks and routers have different ways of setting this up. It is best to search for portforwarding instruction for the specific router model.
![images/img_17.png](images/img_17.png)
### Checking Ports
Simulate a broadcasting node by running a dummy GHOST Node launch command:
```bash
./target/release/ghost --port=30333 --rpc-port=9945 --chain=dev --node-key=0000000000000000000000000000000000000000000000000000000000000001 --base-path=/tmp/alice --alice --validator --unsafe-rpc-external --no-telemetry --state-pruning=archive
```
To check ports go to [Port Checker Website](https://dnschecker.org/port-scanner.php) and check following ports:
```
30333, 9945
```
Only `port 30333` should be opened.
![images/img_18.png](images/img_18.png)
Press _CTRL+C_ to stop the node.
# Launching GHOST TestNet
Switch to main GIT branch:
```bash
git switch main
```
Make sure `ghost-node` is up to date:
```bash
git pull origin main
```
## Generating keys
Create ghost configuration directory:
```bash
sudo mkdir /etc/ghost
```
Give the user permission to the directory:
```bash
sudo chown $(whoami) /etc/ghost
```
To generate the node key use the following command:
```bash
./target/release/ghost key generate-node-key --bin --file=/etc/ghost/node-key
```
Feel free to backup the file on a separate storage device.
## Build and start the ghost-node
Recompile `ghost-node` using `starter.sh` and `--release` flag.
Make `ghost-node` service being able to be started by default user
using `--make-global`:
```bash
./scripts/starter.sh --release --make-global
```
We need to recompile so type y and press Enter to proceed:
![images/img_20.png](images/img_20.png)
Recompiling will take some time!
The script needs higher permissions to write the ghost-node startup file, so it may ask for your user's password
![images/img_21.png](images/img_21.png)
![images/img_28.png](images/img_28.png)
Check the hash of the build:
```bash
sha256sum /etc/ghost/casper.json
```
You should see:
```
4645c7d39a64424d823f659d408070f8c90426117e7af147b0360697b2db7f4f /etc/ghost/casper.json
```
Create running `ghost-node` service that starts on system boot using `--unit-file` flag.
And we must set up the `ghost-node` launch command by setting arguments using
`--set-arguments` flag:
```bash
./scripts/starter.sh --unit-file --set-arguments
```
Only change the defaults if you are advanced otherwise press _Enter_ for
the following prompts.
If this is your first node simply press _Enter_ to proceed with the default.
If this is your second node you should type a different port here,
for example `30334`, and then you should open this port on your firewall
and create a dedicated port forwarding rule on your router as specified
in the Testing Connectivity Part:
```
specify p2p protocol TCP port (default: 30333): 30334
```
To choose default options press Enter here:
![images/img_30.png](images/img_30.png)
If you want to validate from this node press Enter:
```
disable validator mode? [y/N]: y
```
![images/img_31.png](images/img_31.png)
Press _Enter_ for reject enabling Prometheus:
![images/img_32.png](images/img_32.png)
For the following prompt:
```
list of bootnodes if any:
```
Paste one of available **Boot Node** addresses.
Official **Boot Node** addresses:
```
/dns/bootnode69.chain.ghostchain.io/tcp/30334/p2p/12D3KooWF9SWxz9dmy6vfndQhoxqCa7PESaoFWEiF8Jkqh4xKDRf
```
```
/dns/bootnode70.chain.ghostchain.io/tcp/30333/p2p/12D3KooWP3h5dSdqcpvsCr7fp1jyfqDj291QDZ68a4gY6VQ8T1nW
```
A full list of available **Boot Nodes** can be found on [the guide page](https://blog.ghostchain.io/launching-ghost-testnet-3-1/#Boot_Node).
![images/img_33.png](images/img_33.png)
Press _Enter_:
![images/img_34.png](images/img_34.png)
Changing `unit-file` name is optional, otherwise press _Enter_:
![images/img_35.png](images/img_35.png)
Type `y` and press _Enter_ for create dedicated user for running `ghost-node`:
![images/img_36.png](images/img_36.png)
**DO NOT start and DO NOT enable** `ghost-node.service` (press _Enter_):
![images/img_37.png](images/img_37.png)
Full Node:
![images/img_38.png](images/img_38.png)
Validator Node:
![images/img_39.png](images/img_39.png)
Start `ghost-node`:
```bash
sudo systemctl start ghost-node
```
Wait ~60 seconds!
Check node is started:
```bash
sudo systemctl status ghost-node
```
![images/img_40.png](images/img_40.png)
For exit press _CTRL+C_
Restart `ghost-node`:
```bash
sudo systemctl restart ghost-node
```
Enable `ghost-node`:
```bash
sudo systemctl enable ghost-node
```
![images/img_41.png](images/img_41.png)
To see the logs produced by your `ghost-node`:
```bash
journalctl -f -u ghost-node
```
To exit press _CTRL+C_.
Congratulations! You have an operational GHOST Node.

14
cli/Cargo.toml Executable file → Normal file
View File

@ -52,14 +52,14 @@ substrate-build-script-utils = { workspace = true }
default = ["cli", "db", "full-node"] default = ["cli", "db", "full-node"]
db = ["service/db"] db = ["service/db"]
cli = [ cli = [
"clap", "clap",
"frame-benchmarking-cli", "frame-benchmarking-cli",
"sc-cli", "sc-cli",
"sc-service", "sc-service",
"sc-tracing", "sc-tracing",
"service", "service",
"ghost-client-cli", "ghost-client-cli",
"ghost-machine-primitives", "ghost-machine-primitives",
] ]
runtime-benchmarks = [ runtime-benchmarks = [
"frame-benchmarking-cli?/runtime-benchmarks", "frame-benchmarking-cli?/runtime-benchmarks",

2
cli/build.rs Executable file → Normal file
View File

@ -1,4 +1,4 @@
fn main () { fn main() {
if let Ok(profile) = std::env::var("PROFILE") { if let Ok(profile) = std::env::var("PROFILE") {
println!("cargo:rustc-cfg=build_type=\"{}\"", profile); println!("cargo:rustc-cfg=build_type=\"{}\"", profile);
} }

1
cli/src/cli.rs Executable file → Normal file
View File

@ -55,6 +55,7 @@ pub enum Subcommand {
/// Revert the chain to a previous state. /// Revert the chain to a previous state.
Revert(sc_cli::RevertCmd), Revert(sc_cli::RevertCmd),
#[cfg(feature = "runtime-benchmarks")]
/// The custom benchmark subcommmand benchmarking runtime pallets. /// The custom benchmark subcommmand benchmarking runtime pallets.
#[clap(subcommand)] #[clap(subcommand)]
Benchmark(frame_benchmarking_cli::BenchmarkCmd), Benchmark(frame_benchmarking_cli::BenchmarkCmd),

208
cli/src/command.rs Executable file → Normal file
View File

@ -1,14 +1,10 @@
use crate::cli::{Cli, Subcommand}; use crate::cli::{Cli, Subcommand};
use frame_benchmarking_cli::{BenchmarkCmd, ExtrinsicFactory};
use futures::future::TryFutureExt; use futures::future::TryFutureExt;
use sc_cli::SubstrateCli; use sc_cli::SubstrateCli;
use service::{ use service::{self, IdentifyVariant};
self, IdentifyVariant,
benchmarking::{ #[cfg(feature = "runtime-benchmarks")]
benchmark_inherent_data, RemarkBuilder, TransferKeepAliveBuilder use frame_benchmarking_cli::{BenchmarkCmd, ExtrinsicFactory};
},
};
use keyring::Sr25519Keyring;
pub use crate::{error::Error, service::BlockId}; pub use crate::{error::Error, service::BlockId};
#[cfg(feature = "pyroscope")] #[cfg(feature = "pyroscope")]
@ -68,28 +64,35 @@ impl SubstrateCli for Cli {
#[cfg(feature = "casper-native")] #[cfg(feature = "casper-native")]
"casper" => Box::new(service::chain_spec::casper_config()?), "casper" => Box::new(service::chain_spec::casper_config()?),
#[cfg(feature = "casper-native")] #[cfg(feature = "casper-native")]
"casper-dev" | "dev" | "development" => Box::new(service::chain_spec::casper_development_config()?), "casper-dev" | "casper-development" => {
Box::new(service::chain_spec::casper_development_config()?)
}
#[cfg(feature = "casper-native")] #[cfg(feature = "casper-native")]
"casper-local" | "local" => Box::new(service::chain_spec::casper_local_testnet_config()?), "casper-local" => {
Box::new(service::chain_spec::casper_local_testnet_config()?)
}
#[cfg(feature = "casper-native")] #[cfg(feature = "casper-native")]
"casper-staging" | "staging" => Box::new(service::chain_spec::casper_staging_testnet_config()?), "casper-staging" | "staging" => {
Box::new(service::chain_spec::casper_staging_testnet_config()?)
}
#[cfg(not(feature = "casper-native"))] #[cfg(not(feature = "casper-native"))]
name if name.starts_with("casper-") && !name.ends_with(".json") => name if name.starts_with("casper-") && !name.ends_with(".json") => Err(format!(
Err(format!("`{}` only supported with `casper-native` feature enabled.", name))?, "`{}` only supported with `casper-native` feature enabled.",
name
))?,
#[cfg(feature = "casper-native")] #[cfg(feature = "casper-native")]
path => { path => {
let path = std::path::PathBuf::from(path); let path = std::path::PathBuf::from(path);
let chain_spec = Box::new( let chain_spec = Box::new(service::GenericChainSpec::from_json_file(path.clone())?)
service::GenericChainSpec::from_json_file(path.clone())? as Box<dyn service::ChainSpec>;
) as Box<dyn service::ChainSpec>;
if chain_spec.is_casper() { if chain_spec.is_casper() {
Box::new(service::CasperChainSpec::from_json_file(path)?) Box::new(service::CasperChainSpec::from_json_file(path)?)
} else { } else {
chain_spec chain_spec
} }
}, }
}) })
} }
} }
@ -109,10 +112,7 @@ fn set_ss58_version(spec: &Box<dyn service::ChainSpec>) {
sp_core::crypto::set_default_ss58_version(ss58_version); sp_core::crypto::set_default_ss58_version(ss58_version);
} }
fn run_node_inner<F>( fn run_node_inner<F>(cli: Cli, logger_hook: F) -> Result<()>
cli: Cli,
logger_hook: F,
) -> Result<()>
where where
F: FnOnce(&mut sc_cli::LoggerBuilder, &sc_service::Configuration), F: FnOnce(&mut sc_cli::LoggerBuilder, &sc_service::Configuration),
{ {
@ -123,12 +123,16 @@ where
set_ss58_version(chain_spec); set_ss58_version(chain_spec);
runner.run_node_until_exit(move |config| async move { runner.run_node_until_exit(move |mut config| async move {
config.offchain_worker.enabled = true;
config.offchain_worker.indexing_enabled = true;
let hwbench = (!cli.run.no_hardware_benchmarks) let hwbench = (!cli.run.no_hardware_benchmarks)
.then_some(config.database.path().map(|database_path| { .then_some(config.database.path().map(|database_path| {
let _ = std::fs::create_dir_all(&database_path); let _ = std::fs::create_dir_all(&database_path);
sc_sysinfo::gather_hwbench(Some(database_path)) sc_sysinfo::gather_hwbench(Some(database_path))
})).flatten(); }))
.flatten();
let database_source = config.database.clone(); let database_source = config.database.clone();
let task_manager = service::build_full( let task_manager = service::build_full(
@ -138,7 +142,8 @@ where
telemetry_worker_handle: None, telemetry_worker_handle: None,
hwbench, hwbench,
}, },
).map(|full| full.task_manager)?; )
.map(|full| full.task_manager)?;
if let Some(path) = database_source.path() { if let Some(path) = database_source.path() {
sc_storage_monitor::StorageMonitorService::try_spawn( sc_storage_monitor::StorageMonitorService::try_spawn(
@ -167,7 +172,9 @@ pub fn run() -> Result<()> {
let agent = pyro::Pyroscope::builder( let agent = pyro::Pyroscope::builder(
"http://".to_owned() + address.to_string().as_str(), "http://".to_owned() + address.to_string().as_str(),
"ghost".to_owned(), "ghost".to_owned(),
).backend(pprof_backend(PprofConfig::new().sample_rate(113))).build()?; )
.backend(pprof_backend(PprofConfig::new().sample_rate(113)))
.build()?;
Some(agent.start()?) Some(agent.start()?)
} else { } else {
@ -176,7 +183,7 @@ pub fn run() -> Result<()> {
#[cfg(not(feature = "pyroscope"))] #[cfg(not(feature = "pyroscope"))]
if cli.run.pyroscope_server.is_some() { if cli.run.pyroscope_server.is_some() {
return Err(Error::PyroscopeNotCompiledIn) return Err(Error::PyroscopeNotCompiledIn);
} }
match &cli.subcommand { match &cli.subcommand {
@ -184,7 +191,7 @@ pub fn run() -> Result<()> {
Some(Subcommand::BuildSpec(cmd)) => { Some(Subcommand::BuildSpec(cmd)) => {
let runner = cli.create_runner(cmd).map_err(Error::SubstrateCli)?; let runner = cli.create_runner(cmd).map_err(Error::SubstrateCli)?;
Ok(runner.sync_run(|config| cmd.run(config.chain_spec, config.network))?) Ok(runner.sync_run(|config| cmd.run(config.chain_spec, config.network))?)
}, }
Some(Subcommand::CheckBlock(cmd)) => { Some(Subcommand::CheckBlock(cmd)) => {
let runner = cli.create_runner(cmd).map_err(Error::SubstrateCli)?; let runner = cli.create_runner(cmd).map_err(Error::SubstrateCli)?;
let chain_spec = &runner.config().chain_spec; let chain_spec = &runner.config().chain_spec;
@ -192,11 +199,13 @@ pub fn run() -> Result<()> {
set_ss58_version(chain_spec); set_ss58_version(chain_spec);
runner.async_run(|mut config| { runner.async_run(|mut config| {
let (client, _, import_queue, task_manager) = let (client, _, import_queue, task_manager) = service::new_chain_ops(&mut config)?;
service::new_chain_ops(&mut config)?; Ok((
Ok((cmd.run(client, import_queue).map_err(Error::SubstrateCli), task_manager)) cmd.run(client, import_queue).map_err(Error::SubstrateCli),
task_manager,
))
}) })
}, }
Some(Subcommand::ExportBlocks(cmd)) => { Some(Subcommand::ExportBlocks(cmd)) => {
let runner = cli.create_runner(cmd).map_err(Error::SubstrateCli)?; let runner = cli.create_runner(cmd).map_err(Error::SubstrateCli)?;
let chain_spec = &runner.config().chain_spec; let chain_spec = &runner.config().chain_spec;
@ -204,11 +213,14 @@ pub fn run() -> Result<()> {
set_ss58_version(chain_spec); set_ss58_version(chain_spec);
Ok(runner.async_run(|mut config| { Ok(runner.async_run(|mut config| {
let ( client, _, _, task_manager ) = let (client, _, _, task_manager) = service::new_chain_ops(&mut config)?;
service::new_chain_ops(&mut config)?; Ok((
Ok((cmd.run(client, config.database).map_err(Error::SubstrateCli), task_manager)) cmd.run(client, config.database)
.map_err(Error::SubstrateCli),
task_manager,
))
})?) })?)
}, }
Some(Subcommand::ExportState(cmd)) => { Some(Subcommand::ExportState(cmd)) => {
let runner = cli.create_runner(cmd).map_err(Error::SubstrateCli)?; let runner = cli.create_runner(cmd).map_err(Error::SubstrateCli)?;
let chain_spec = &runner.config().chain_spec; let chain_spec = &runner.config().chain_spec;
@ -216,11 +228,14 @@ pub fn run() -> Result<()> {
set_ss58_version(chain_spec); set_ss58_version(chain_spec);
Ok(runner.async_run(|mut config| { Ok(runner.async_run(|mut config| {
let ( client, _, _, task_manager ) = let (client, _, _, task_manager) = service::new_chain_ops(&mut config)?;
service::new_chain_ops(&mut config)?; Ok((
Ok((cmd.run(client, config.chain_spec).map_err(Error::SubstrateCli), task_manager)) cmd.run(client, config.chain_spec)
.map_err(Error::SubstrateCli),
task_manager,
))
})?) })?)
}, }
Some(Subcommand::ImportBlocks(cmd)) => { Some(Subcommand::ImportBlocks(cmd)) => {
let runner = cli.create_runner(cmd).map_err(Error::SubstrateCli)?; let runner = cli.create_runner(cmd).map_err(Error::SubstrateCli)?;
let chain_spec = &runner.config().chain_spec; let chain_spec = &runner.config().chain_spec;
@ -228,15 +243,17 @@ pub fn run() -> Result<()> {
set_ss58_version(chain_spec); set_ss58_version(chain_spec);
Ok(runner.async_run(|mut config| { Ok(runner.async_run(|mut config| {
let ( client, _, import_queue, task_manager ) = let (client, _, import_queue, task_manager) = service::new_chain_ops(&mut config)?;
service::new_chain_ops(&mut config)?; Ok((
Ok((cmd.run(client, import_queue).map_err(Error::SubstrateCli), task_manager)) cmd.run(client, import_queue).map_err(Error::SubstrateCli),
task_manager,
))
})?) })?)
}, }
Some(Subcommand::PurgeChain(cmd)) => { Some(Subcommand::PurgeChain(cmd)) => {
let runner = cli.create_runner(cmd).map_err(Error::SubstrateCli)?; let runner = cli.create_runner(cmd).map_err(Error::SubstrateCli)?;
Ok(runner.sync_run(|config| cmd.run(config.database))?) Ok(runner.sync_run(|config| cmd.run(config.database))?)
}, }
Some(Subcommand::Revert(cmd)) => { Some(Subcommand::Revert(cmd)) => {
let runner = cli.create_runner(cmd).map_err(Error::SubstrateCli)?; let runner = cli.create_runner(cmd).map_err(Error::SubstrateCli)?;
let chain_spec = &runner.config().chain_spec; let chain_spec = &runner.config().chain_spec;
@ -244,68 +261,58 @@ pub fn run() -> Result<()> {
set_ss58_version(chain_spec); set_ss58_version(chain_spec);
Ok(runner.async_run(|mut config| { Ok(runner.async_run(|mut config| {
let (client, backend, _, task_manager) = let (client, backend, _, task_manager) = service::new_chain_ops(&mut config)?;
service::new_chain_ops(&mut config)?;
let aux_revert = Box::new(|client, backend, blocks| { let aux_revert = Box::new(|client, backend, blocks| {
service::revert_backend(client, backend, blocks).map_err(|err| { service::revert_backend(client, backend, blocks).map_err(|err| match err {
match err { service::Error::Blockchain(err) => err.into(),
service::Error::Blockchain(err) => err.into(), err => sc_cli::Error::Application(err.into()),
err => sc_cli::Error::Application(err.into()),
}
}) })
}); });
Ok(( Ok((
cmd.run(client, backend, Some(aux_revert)).map_err(Error::SubstrateCli), cmd.run(client, backend, Some(aux_revert))
task_manager .map_err(Error::SubstrateCli),
task_manager,
)) ))
})?) })?)
}, },
#[cfg(feature = "runtime-benchmarks")]
Some(Subcommand::Benchmark(cmd)) => { Some(Subcommand::Benchmark(cmd)) => {
let runner = cli.create_runner(cmd).map_err(Error::SubstrateCli)?; let runner = cli.create_runner(cmd).map_err(Error::SubstrateCli)?;
let chain_spec = &runner.config().chain_spec; let chain_spec = &runner.config().chain_spec;
match cmd { match cmd {
#[cfg(not(feature = "runtime-benchmarks"))]
BenchmarkCmd::Storage(_) =>
return Err(sc_cli::Error::Input(
"Compile with `--feature=runtime-benchmarks \
to enable storage benchmarks.".into()
).into()),
#[cfg(feature = "runtime-benchmarks")]
BenchmarkCmd::Storage(cmd) => runner.sync_run(|mut config| { BenchmarkCmd::Storage(cmd) => runner.sync_run(|mut config| {
let (client, backend, _, _,) = let (client, backend, _, _) = service::new_chain_ops(&mut config)?;
service::new_chain_ops(&mut config)?;
let db = backend.expose_db(); let db = backend.expose_db();
let storage = backend.expose_storage(); let storage = backend.expose_storage();
cmd.run(config, client.clone(), db, storage).map_err(Error::SubstrateCli) cmd.run(config, client.clone(), db, storage)
.map_err(Error::SubstrateCli)
}), }),
BenchmarkCmd::Block(cmd) => runner.sync_run(|mut config| { BenchmarkCmd::Block(cmd) => runner.sync_run(|mut config| {
let (client, _, _, _,) = let (client, _, _, _) = service::new_chain_ops(&mut config)?;
service::new_chain_ops(&mut config)?;
cmd.run(client.clone()).map_err(Error::SubstrateCli) cmd.run(client.clone()).map_err(Error::SubstrateCli)
}), }),
// These commands are very similar and can be handled in nearly the same way // These commands are very similar and can be handled in nearly the same way
BenchmarkCmd::Extrinsic(_) | BenchmarkCmd::Overhead(_) => BenchmarkCmd::Extrinsic(_) | BenchmarkCmd::Overhead(_) => {
use service::benchmarking::{benchmark_inherent_data, RemarkBuilder, TransferKeepAliveBuilder};
runner.sync_run(|mut config| { runner.sync_run(|mut config| {
let (client, _, _, _) = let (client, _, _, _) = service::new_chain_ops(&mut config)?;
service::new_chain_ops(&mut config)?;
let inherent_data = benchmark_inherent_data() let inherent_data = benchmark_inherent_data()
.map_err(|e| format!("generating inherent data: {:?}", e))?; .map_err(|e| format!("generating inherent data: {:?}", e))?;
let remark_builder = RemarkBuilder::new( let remark_builder =
client.clone(), RemarkBuilder::new(client.clone(), config.chain_spec.identify_chain());
config.chain_spec.identify_chain(),
);
match cmd { match cmd {
BenchmarkCmd::Extrinsic(cmd) => { BenchmarkCmd::Extrinsic(cmd) => {
let tka_builder = TransferKeepAliveBuilder::new( let tka_builder = TransferKeepAliveBuilder::new(
client.clone(), client.clone(),
Sr25519Keyring::Alice.to_account_id(), keyring::Sr25519Keyring::Alice.to_account_id(),
config.chain_spec.identify_chain(), config.chain_spec.identify_chain(),
); );
@ -316,48 +323,47 @@ pub fn run() -> Result<()> {
cmd.run(client.clone(), inherent_data, Vec::new(), &ext_factory) cmd.run(client.clone(), inherent_data, Vec::new(), &ext_factory)
.map_err(Error::SubstrateCli) .map_err(Error::SubstrateCli)
}, }
BenchmarkCmd::Overhead(cmd) => cmd.run( BenchmarkCmd::Overhead(cmd) => cmd
config, .run(
client.clone(), config,
inherent_data, client.clone(),
Vec::new(), inherent_data,
&remark_builder, Vec::new(),
).map_err(Error::SubstrateCli), &remark_builder,
)
.map_err(Error::SubstrateCli),
_ => unreachable!("Ensured by the outside match; qed"), _ => unreachable!("Ensured by the outside match; qed"),
} }
}), })
}
BenchmarkCmd::Pallet(cmd) => { BenchmarkCmd::Pallet(cmd) => {
set_ss58_version(chain_spec); set_ss58_version(chain_spec);
if cfg!(feature = "runtime-benchmarks") { runner.sync_run(|config| {
runner.sync_run(|config| { cmd.run_with_spec::<sp_runtime::traits::HashingFor<service::Block>, ()>(
cmd.run_with_spec::<sp_runtime::traits::HashingFor<service::Block>, ()>( Some(config.chain_spec),
Some(config.chain_spec), ).map_err(|e| Error::SubstrateCli(e))
).map_err(|e| Error::SubstrateCli(e)) })
}) }
} else {
Err(sc_cli::Error::Input(
"Benchmarking wasn't enabled when building the node. \
You can enable it with `--features=runtime-benchmarks`.".into()
).into())
}
},
BenchmarkCmd::Machine(cmd) => runner.sync_run(|config| { BenchmarkCmd::Machine(cmd) => runner.sync_run(|config| {
cmd.run(&config, ghost_machine_primitives::GHOST_NODE_REFERENCE_HARDWARE.clone()) cmd.run(
.map_err(Error::SubstrateCli) &config,
ghost_machine_primitives::GHOST_NODE_REFERENCE_HARDWARE.clone(),
)
.map_err(Error::SubstrateCli)
}), }),
// Note: this allows to implement additional new benchmark // Note: this allows to implement additional new benchmark
// commands. // commands.
#[allow(unreachable_patterns)] #[allow(unreachable_patterns)]
_ => Err(Error::CommandNotImplemented), _ => Err(Error::CommandNotImplemented),
} }
}, }
Some(Subcommand::Key(cmd)) => Ok(cmd.run(&cli)?), Some(Subcommand::Key(cmd)) => Ok(cmd.run(&cli)?),
Some(Subcommand::ChainInfo(cmd)) => { Some(Subcommand::ChainInfo(cmd)) => {
let runner = cli.create_runner(cmd)?; let runner = cli.create_runner(cmd)?;
Ok(runner.sync_run(|config| cmd.run::<service::Block>(&config))?) Ok(runner.sync_run(|config| cmd.run::<service::Block>(&config))?)
}, }
}?; }?;
#[cfg(feature = "pyroscope")] #[cfg(feature = "pyroscope")]

0
cli/src/error.rs Executable file → Normal file
View File

4
cli/src/lib.rs Executable file → Normal file
View File

@ -6,9 +6,7 @@ mod command;
mod error; mod error;
#[cfg(feature = "service")] #[cfg(feature = "service")]
pub use service::{ pub use service::{self, Block, CoreApi, IdentifyVariant, ProvideRuntimeApi, TFullClient};
self, Block, CoreApi, IdentifyVariant, ProvideRuntimeApi, TFullClient,
};
#[cfg(feature = "cli")] #[cfg(feature = "cli")]
pub use cli::*; pub use cli::*;

View File

@ -1,6 +1,6 @@
[package] [package]
name = "ghost-client-cli" name = "ghost-client-cli"
version = "0.1.3" version = "0.1.4"
description = "Ghost CLI interface" description = "Ghost CLI interface"
license.workspace = true license.workspace = true
authors.workspace = true authors.workspace = true

View File

@ -3,13 +3,10 @@
use bip39::Mnemonic; use bip39::Mnemonic;
use clap::Parser; use clap::Parser;
use itertools::Itertools; use itertools::Itertools;
use sc_cli::{ use sc_cli::{with_crypto_scheme, CryptoSchemeFlag, Error, KeystoreParams, OutputTypeFlag};
with_crypto_scheme, KeystoreParams, OutputTypeFlag,
CryptoSchemeFlag, Error,
};
use crate::params::NetworkSchemeFlag;
use crate::commands::utils::print_from_uri; use crate::commands::utils::print_from_uri;
use crate::params::NetworkSchemeFlag;
/// The `generate` command /// The `generate` command
#[derive(Debug, Clone, Parser)] #[derive(Debug, Clone, Parser)]
@ -17,58 +14,58 @@ use crate::commands::utils::print_from_uri;
pub struct GenerateCmd { pub struct GenerateCmd {
/// The number of words in the phrase to generate. One of 12 (default), /// The number of words in the phrase to generate. One of 12 (default),
/// 15, 18, 21 and 24. /// 15, 18, 21 and 24.
#[arg(short = 'w', long, value_name = "WORDS")] #[arg(short = 'w', long, value_name = "WORDS")]
words: Option<usize>, words: Option<usize>,
#[allow(missing_docs)] #[allow(missing_docs)]
#[clap(flatten)] #[clap(flatten)]
pub keystore_params: KeystoreParams, pub keystore_params: KeystoreParams,
#[allow(missing_docs)] #[allow(missing_docs)]
#[clap(flatten)] #[clap(flatten)]
pub network_scheme: NetworkSchemeFlag, pub network_scheme: NetworkSchemeFlag,
#[allow(missing_docs)] #[allow(missing_docs)]
#[clap(flatten)] #[clap(flatten)]
pub output_scheme: OutputTypeFlag, pub output_scheme: OutputTypeFlag,
#[allow(missing_docs)] #[allow(missing_docs)]
#[clap(flatten)] #[clap(flatten)]
pub crypto_scheme: CryptoSchemeFlag, pub crypto_scheme: CryptoSchemeFlag,
} }
impl GenerateCmd { impl GenerateCmd {
/// Run the command /// Run the command
pub fn run(&self) -> Result<(), Error> { pub fn run(&self) -> Result<(), Error> {
let words = match self.words { let words = match self.words {
Some(words_count) if [12, 15, 18, 21, 24].contains(&words_count) => Ok(words_count), Some(words_count) if [12, 15, 18, 21, 24].contains(&words_count) => Ok(words_count),
Some(_) => Err(Error::Input( Some(_) => Err(Error::Input(
"Invalid number of words given for phrase: must be 12/15/18/21/24".into(), "Invalid number of words given for phrase: must be 12/15/18/21/24".into(),
)), )),
None => Ok(12), None => Ok(12),
}?; }?;
let mnemonic = Mnemonic::generate(words) let mnemonic = Mnemonic::generate(words)
.map_err(|e| Error::Input(format!("Mnemonic generation failed: {e}").into()))?; .map_err(|e| Error::Input(format!("Mnemonic generation failed: {e}").into()))?;
let password = self.keystore_params.read_password()?; let password = self.keystore_params.read_password()?;
let output = self.output_scheme.output_type; let output = self.output_scheme.output_type;
let phrase = mnemonic.words().join(" "); let phrase = mnemonic.words().join(" ");
with_crypto_scheme!( with_crypto_scheme!(
self.crypto_scheme.scheme, self.crypto_scheme.scheme,
print_from_uri(&phrase, password, self.network_scheme.network, output) print_from_uri(&phrase, password, self.network_scheme.network, output)
); );
Ok(()) Ok(())
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
#[test] #[test]
fn generate() { fn generate() {
let generate = GenerateCmd::parse_from(&["generate", "--password", "12345"]); let generate = GenerateCmd::parse_from(&["generate", "--password", "12345"]);
assert!(generate.run().is_ok()) assert!(generate.run().is_ok())
} }
} }

View File

@ -1,102 +1,101 @@
//! Implementation of the `inspect` subcommand //! Implementation of the `inspect` subcommand
use crate::commands::utils::{print_from_public, print_from_uri};
use crate::params::NetworkSchemeFlag;
use clap::Parser; use clap::Parser;
use sc_cli::{
utils::read_uri, with_crypto_scheme, CryptoSchemeFlag, Error, KeystoreParams, OutputTypeFlag,
};
use sp_core::crypto::{ExposeSecret, SecretString, SecretUri, Ss58Codec}; use sp_core::crypto::{ExposeSecret, SecretString, SecretUri, Ss58Codec};
use std::str::FromStr; use std::str::FromStr;
use sc_cli::{
with_crypto_scheme, KeystoreParams, OutputTypeFlag, CryptoSchemeFlag, Error,
utils::read_uri,
};
use crate::params::NetworkSchemeFlag;
use crate::commands::utils::{print_from_public, print_from_uri};
/// The `inspect` command /// The `inspect` command
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
#[command( #[command(
name = "inspect", name = "inspect",
about = "Gets a public key and a SS58 address from the provided Secret URI" about = "Gets a public key and a SS58 address from the provided Secret URI"
)] )]
pub struct InspectKeyCmd { pub struct InspectKeyCmd {
/// A Key URI to be inspected. May be a secret seed, secret URI /// A Key URI to be inspected. May be a secret seed, secret URI
/// (with derivation paths and password), SS58, public URI or a hex encoded public key. /// (with derivation paths and password), SS58, public URI or a hex encoded public key.
/// ///
/// If it is a hex encoded public key, `--public` needs to be given as argument. /// If it is a hex encoded public key, `--public` needs to be given as argument.
/// ///
/// If the given value is a file, the file content will be used /// If the given value is a file, the file content will be used
/// as URI. /// as URI.
/// ///
/// If omitted, you will be prompted for the URI. /// If omitted, you will be prompted for the URI.
uri: Option<String>, uri: Option<String>,
/// Is the given `uri` a hex encoded public key? /// Is the given `uri` a hex encoded public key?
#[arg(long)] #[arg(long)]
public: bool, public: bool,
#[allow(missing_docs)] #[allow(missing_docs)]
#[clap(flatten)] #[clap(flatten)]
pub keystore_params: KeystoreParams, pub keystore_params: KeystoreParams,
#[allow(missing_docs)] #[allow(missing_docs)]
#[clap(flatten)] #[clap(flatten)]
pub network_scheme: NetworkSchemeFlag, pub network_scheme: NetworkSchemeFlag,
#[allow(missing_docs)] #[allow(missing_docs)]
#[clap(flatten)] #[clap(flatten)]
pub output_scheme: OutputTypeFlag, pub output_scheme: OutputTypeFlag,
#[allow(missing_docs)] #[allow(missing_docs)]
#[clap(flatten)] #[clap(flatten)]
pub crypto_scheme: CryptoSchemeFlag, pub crypto_scheme: CryptoSchemeFlag,
/// Expect that `--uri` has the given public key/account-id. /// Expect that `--uri` has the given public key/account-id.
/// ///
/// If `--uri` has any derivations, the public key is checked against the base `uri`, i.e. the /// If `--uri` has any derivations, the public key is checked against the base `uri`, i.e. the
/// `uri` without any derivation applied. However, if `uri` has a password or there is one /// `uri` without any derivation applied. However, if `uri` has a password or there is one
/// given by `--password`, it will be used to decrypt `uri` before comparing the public /// given by `--password`, it will be used to decrypt `uri` before comparing the public
/// key/account-id. /// key/account-id.
/// ///
/// If there is no derivation in `--uri`, the public key will be checked against the public key /// If there is no derivation in `--uri`, the public key will be checked against the public key
/// of `--uri` directly. /// of `--uri` directly.
#[arg(long, conflicts_with = "public")] #[arg(long, conflicts_with = "public")]
pub expect_public: Option<String>, pub expect_public: Option<String>,
} }
impl InspectKeyCmd { impl InspectKeyCmd {
/// Run the command /// Run the command
pub fn run(&self) -> Result<(), Error> { pub fn run(&self) -> Result<(), Error> {
let uri = read_uri(self.uri.as_ref())?; let uri = read_uri(self.uri.as_ref())?;
let password = self.keystore_params.read_password()?; let password = self.keystore_params.read_password()?;
if self.public { if self.public {
with_crypto_scheme!( with_crypto_scheme!(
self.crypto_scheme.scheme, self.crypto_scheme.scheme,
print_from_public( print_from_public(
&uri, &uri,
self.network_scheme.network, self.network_scheme.network,
self.output_scheme.output_type, self.output_scheme.output_type,
) )
)?; )?;
} else { } else {
if let Some(ref expect_public) = self.expect_public { if let Some(ref expect_public) = self.expect_public {
with_crypto_scheme!( with_crypto_scheme!(
self.crypto_scheme.scheme, self.crypto_scheme.scheme,
expect_public_from_phrase(expect_public, &uri, password.as_ref()) expect_public_from_phrase(expect_public, &uri, password.as_ref())
)?; )?;
} }
with_crypto_scheme!( with_crypto_scheme!(
self.crypto_scheme.scheme, self.crypto_scheme.scheme,
print_from_uri( print_from_uri(
&uri, &uri,
password, password,
self.network_scheme.network, self.network_scheme.network,
self.output_scheme.output_type, self.output_scheme.output_type,
) )
); );
} }
Ok(()) Ok(())
} }
} }
/// Checks that `expect_public` is the public key of `suri`. /// Checks that `expect_public` is the public key of `suri`.
@ -106,148 +105,156 @@ impl InspectKeyCmd {
/// ///
/// Returns an error if the public key does not match. /// Returns an error if the public key does not match.
fn expect_public_from_phrase<Pair: sp_core::Pair>( fn expect_public_from_phrase<Pair: sp_core::Pair>(
expect_public: &str, expect_public: &str,
suri: &str, suri: &str,
password: Option<&SecretString>, password: Option<&SecretString>,
) -> Result<(), Error> { ) -> Result<(), Error> {
let secret_uri = SecretUri::from_str(suri).map_err(|e| format!("{:?}", e))?; let secret_uri = SecretUri::from_str(suri).map_err(|e| format!("{:?}", e))?;
let expected_public = if let Some(public) = expect_public.strip_prefix("0x") { let expected_public = if let Some(public) = expect_public.strip_prefix("0x") {
let hex_public = array_bytes::hex2bytes(public) let hex_public = array_bytes::hex2bytes(public)
.map_err(|_| format!("Invalid expected public key hex: `{}`", expect_public))?; .map_err(|_| format!("Invalid expected public key hex: `{}`", expect_public))?;
Pair::Public::try_from(&hex_public) Pair::Public::try_from(&hex_public)
.map_err(|_| format!("Invalid expected public key: `{}`", expect_public))? .map_err(|_| format!("Invalid expected public key: `{}`", expect_public))?
} else { } else {
Pair::Public::from_string_with_version(expect_public) Pair::Public::from_string_with_version(expect_public)
.map_err(|_| format!("Invalid expected account id: `{}`", expect_public))? .map_err(|_| format!("Invalid expected account id: `{}`", expect_public))?
.0 .0
}; };
let pair = Pair::from_string_with_seed( let pair = Pair::from_string_with_seed(
secret_uri.phrase.expose_secret().as_str(), secret_uri.phrase.expose_secret().as_str(),
password password
.or_else(|| secret_uri.password.as_ref()) .or_else(|| secret_uri.password.as_ref())
.map(|p| p.expose_secret().as_str()), .map(|p| p.expose_secret().as_str()),
) )
.map_err(|_| format!("Invalid secret uri: {}", suri))? .map_err(|_| format!("Invalid secret uri: {}", suri))?
.0; .0;
if pair.public() == expected_public { if pair.public() == expected_public {
Ok(()) Ok(())
} else { } else {
Err(format!("Expected public ({}) key does not match.", expect_public).into()) Err(format!("Expected public ({}) key does not match.", expect_public).into())
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use sp_core::crypto::{ByteArray, Pair}; use sp_core::crypto::{ByteArray, Pair};
use sp_runtime::traits::IdentifyAccount; use sp_runtime::traits::IdentifyAccount;
#[test] #[test]
fn inspect() { fn inspect() {
let words = let words =
"remember fiber forum demise paper uniform squirrel feel access exclude casual effort"; "remember fiber forum demise paper uniform squirrel feel access exclude casual effort";
let seed = "0xad1fb77243b536b90cfe5f0d351ab1b1ac40e3890b41dc64f766ee56340cfca5"; let seed = "0xad1fb77243b536b90cfe5f0d351ab1b1ac40e3890b41dc64f766ee56340cfca5";
let inspect = InspectKeyCmd::parse_from(&["inspect-key", words, "--password", "12345"]); let inspect = InspectKeyCmd::parse_from(&["inspect-key", words, "--password", "12345"]);
assert!(inspect.run().is_ok()); assert!(inspect.run().is_ok());
let inspect = InspectKeyCmd::parse_from(&["inspect-key", seed]); let inspect = InspectKeyCmd::parse_from(&["inspect-key", seed]);
assert!(inspect.run().is_ok()); assert!(inspect.run().is_ok());
} }
#[test] #[test]
fn inspect_public_key() { fn inspect_public_key() {
let public = "0x12e76e0ae8ce41b6516cce52b3f23a08dcb4cfeed53c6ee8f5eb9f7367341069"; let public = "0x12e76e0ae8ce41b6516cce52b3f23a08dcb4cfeed53c6ee8f5eb9f7367341069";
let inspect = InspectKeyCmd::parse_from(&["inspect-key", "--public", public]); let inspect = InspectKeyCmd::parse_from(&["inspect-key", "--public", public]);
assert!(inspect.run().is_ok()); assert!(inspect.run().is_ok());
} }
#[test] #[test]
fn inspect_with_expected_public_key() { fn inspect_with_expected_public_key() {
let check_cmd = |seed, expected_public, success| { let check_cmd = |seed, expected_public, success| {
let inspect = InspectKeyCmd::parse_from(&[ let inspect = InspectKeyCmd::parse_from(&[
"inspect-key", "inspect-key",
"--expect-public", "--expect-public",
expected_public, expected_public,
seed, seed,
]); ]);
let res = inspect.run(); let res = inspect.run();
if success { if success {
assert!(res.is_ok()); assert!(res.is_ok());
} else { } else {
assert!(res.unwrap_err().to_string().contains(&format!( assert!(res.unwrap_err().to_string().contains(&format!(
"Expected public ({}) key does not match.", "Expected public ({}) key does not match.",
expected_public expected_public
))); )));
} }
}; };
let seed = let seed =
"remember fiber forum demise paper uniform squirrel feel access exclude casual effort"; "remember fiber forum demise paper uniform squirrel feel access exclude casual effort";
let invalid_public = "0x12e76e0ae8ce41b6516cce52b3f23a08dcb4cfeed53c6ee8f5eb9f7367341069"; let invalid_public = "0x12e76e0ae8ce41b6516cce52b3f23a08dcb4cfeed53c6ee8f5eb9f7367341069";
let valid_public = sp_core::sr25519::Pair::from_string_with_seed(seed, None) let valid_public = sp_core::sr25519::Pair::from_string_with_seed(seed, None)
.expect("Valid") .expect("Valid")
.0 .0
.public(); .public();
let valid_public_hex = array_bytes::bytes2hex("0x", valid_public.as_slice()); let valid_public_hex = array_bytes::bytes2hex("0x", valid_public.as_slice());
let valid_accountid = format!("{}", valid_public.into_account()); let valid_accountid = format!("{}", valid_public.into_account());
// It should fail with the invalid public key // It should fail with the invalid public key
check_cmd(seed, invalid_public, false); check_cmd(seed, invalid_public, false);
// It should work with the valid public key & account id // It should work with the valid public key & account id
check_cmd(seed, &valid_public_hex, true); check_cmd(seed, &valid_public_hex, true);
check_cmd(seed, &valid_accountid, true); check_cmd(seed, &valid_accountid, true);
let password = "test12245"; let password = "test12245";
let seed_with_password = format!("{}///{}", seed, password); let seed_with_password = format!("{}///{}", seed, password);
let valid_public_with_password = let valid_public_with_password =
sp_core::sr25519::Pair::from_string_with_seed(&seed_with_password, Some(password)) sp_core::sr25519::Pair::from_string_with_seed(&seed_with_password, Some(password))
.expect("Valid") .expect("Valid")
.0 .0
.public(); .public();
let valid_public_hex_with_password = let valid_public_hex_with_password =
array_bytes::bytes2hex("0x", valid_public_with_password.as_slice()); array_bytes::bytes2hex("0x", valid_public_with_password.as_slice());
let valid_accountid_with_password = let valid_accountid_with_password =
format!("{}", &valid_public_with_password.into_account()); format!("{}", &valid_public_with_password.into_account());
// Only the public key that corresponds to the seed with password should be accepted. // Only the public key that corresponds to the seed with password should be accepted.
check_cmd(&seed_with_password, &valid_public_hex, false); check_cmd(&seed_with_password, &valid_public_hex, false);
check_cmd(&seed_with_password, &valid_accountid, false); check_cmd(&seed_with_password, &valid_accountid, false);
check_cmd(&seed_with_password, &valid_public_hex_with_password, true); check_cmd(&seed_with_password, &valid_public_hex_with_password, true);
check_cmd(&seed_with_password, &valid_accountid_with_password, true); check_cmd(&seed_with_password, &valid_accountid_with_password, true);
let seed_with_password_and_derivation = format!("{}//test//account///{}", seed, password); let seed_with_password_and_derivation = format!("{}//test//account///{}", seed, password);
let valid_public_with_password_and_derivation = let valid_public_with_password_and_derivation =
sp_core::sr25519::Pair::from_string_with_seed( sp_core::sr25519::Pair::from_string_with_seed(
&seed_with_password_and_derivation, &seed_with_password_and_derivation,
Some(password), Some(password),
) )
.expect("Valid") .expect("Valid")
.0 .0
.public(); .public();
let valid_public_hex_with_password_and_derivation = let valid_public_hex_with_password_and_derivation =
array_bytes::bytes2hex("0x", valid_public_with_password_and_derivation.as_slice()); array_bytes::bytes2hex("0x", valid_public_with_password_and_derivation.as_slice());
// They should still be valid, because we check the base secret key. // They should still be valid, because we check the base secret key.
check_cmd(&seed_with_password_and_derivation, &valid_public_hex_with_password, true); check_cmd(
check_cmd(&seed_with_password_and_derivation, &valid_accountid_with_password, true); &seed_with_password_and_derivation,
&valid_public_hex_with_password,
true,
);
check_cmd(
&seed_with_password_and_derivation,
&valid_accountid_with_password,
true,
);
// And these should be invalid. // And these should be invalid.
check_cmd(&seed_with_password_and_derivation, &valid_public_hex, false); check_cmd(&seed_with_password_and_derivation, &valid_public_hex, false);
check_cmd(&seed_with_password_and_derivation, &valid_accountid, false); check_cmd(&seed_with_password_and_derivation, &valid_accountid, false);
// The public of the derived account should fail. // The public of the derived account should fail.
check_cmd( check_cmd(
&seed_with_password_and_derivation, &seed_with_password_and_derivation,
&valid_public_hex_with_password_and_derivation, &valid_public_hex_with_password_and_derivation,
false, false,
); );
} }
} }

View File

@ -1,40 +1,37 @@
//! Key related CLI utilities //! Key related CLI utilities
use super::{generate::GenerateCmd, inspect_key::InspectKeyCmd}; use super::{generate::GenerateCmd, inspect_key::InspectKeyCmd};
use sc_cli::{ use sc_cli::{Error, GenerateKeyCmdCommon, InsertKeyCmd, InspectNodeKeyCmd, SubstrateCli};
GenerateKeyCmdCommon, InsertKeyCmd, InspectNodeKeyCmd, Error,
SubstrateCli,
};
/// Key utilities for the cli. /// Key utilities for the cli.
#[derive(Debug, clap::Subcommand)] #[derive(Debug, clap::Subcommand)]
pub enum KeySubcommand { pub enum KeySubcommand {
/// Generate a random node key, write it to a file or stdout and write the /// Generate a random node key, write it to a file or stdout and write the
/// corresponding peer-id to stderr /// corresponding peer-id to stderr
GenerateNodeKey(GenerateKeyCmdCommon), GenerateNodeKey(GenerateKeyCmdCommon),
/// Generate a Substrate-based random account /// Generate a Substrate-based random account
Generate(GenerateCmd), Generate(GenerateCmd),
/// Gets a public key and a SS58 address from the provided Secret URI /// Gets a public key and a SS58 address from the provided Secret URI
Inspect(InspectKeyCmd), Inspect(InspectKeyCmd),
/// Load a node key from a file or stdin and print the corresponding peer-id /// Load a node key from a file or stdin and print the corresponding peer-id
InspectNodeKey(InspectNodeKeyCmd), InspectNodeKey(InspectNodeKeyCmd),
/// Insert a key to the keystore of a node. /// Insert a key to the keystore of a node.
Insert(InsertKeyCmd), Insert(InsertKeyCmd),
} }
impl KeySubcommand { impl KeySubcommand {
/// run the key subcommands /// run the key subcommands
pub fn run<C: SubstrateCli>(&self, cli: &C) -> Result<(), Error> { pub fn run<C: SubstrateCli>(&self, cli: &C) -> Result<(), Error> {
match self { match self {
KeySubcommand::GenerateNodeKey(cmd) => cmd.run(), KeySubcommand::GenerateNodeKey(cmd) => cmd.run(),
KeySubcommand::Generate(cmd) => cmd.run(), KeySubcommand::Generate(cmd) => cmd.run(),
KeySubcommand::Inspect(cmd) => cmd.run(), KeySubcommand::Inspect(cmd) => cmd.run(),
KeySubcommand::Insert(cmd) => cmd.run(cli), KeySubcommand::Insert(cmd) => cmd.run(cli),
KeySubcommand::InspectNodeKey(cmd) => cmd.run(), KeySubcommand::InspectNodeKey(cmd) => cmd.run(),
} }
} }
} }

View File

@ -1,11 +1,13 @@
mod key;
mod generate; mod generate;
mod vanity;
mod inspect_key; mod inspect_key;
mod key;
mod utils; mod utils;
mod vanity;
pub use self::{ pub use self::{
key::KeySubcommand, vanity::VanityCmd, inspect_key::InspectKeyCmd,
generate::GenerateCmd, generate::GenerateCmd,
utils::{unwrap_or_default_ss58_name, print_from_uri, print_from_public}, inspect_key::InspectKeyCmd,
key::KeySubcommand,
utils::{print_from_public, print_from_uri, unwrap_or_default_ss58_name},
vanity::VanityCmd,
}; };

View File

@ -1,194 +1,206 @@
use serde_json::json;
use sc_cli::{ use sc_cli::{
OutputType,
utils::{PublicFor, SeedFor}, utils::{PublicFor, SeedFor},
OutputType,
}; };
use sp_runtime::{traits::IdentifyAccount, MultiSigner}; use serde_json::json;
use sp_core::{ use sp_core::{
crypto::{ crypto::{
unwrap_or_default_ss58_version, unwrap_or_default_ss58_version, ExposeSecret, SecretString, Ss58AddressFormat, Ss58Codec,
Ss58Codec, ExposeSecret, Ss58AddressFormat, SecretString,
}, },
hexdisplay::HexDisplay, hexdisplay::HexDisplay,
}; };
use sp_runtime::{traits::IdentifyAccount, MultiSigner};
pub fn print_from_uri<Pair>( pub fn print_from_uri<Pair>(
uri: &str, uri: &str,
password: Option<SecretString>, password: Option<SecretString>,
network_override: Option<Ss58AddressFormat>, network_override: Option<Ss58AddressFormat>,
output: OutputType, output: OutputType,
) where ) where
Pair: sp_core::Pair, Pair: sp_core::Pair,
Pair::Public: Into<MultiSigner>, Pair::Public: Into<MultiSigner>,
{ {
let password = password.as_ref().map(|s| s.expose_secret().as_str()); let password = password.as_ref().map(|s| s.expose_secret().as_str());
let network_id = unwrap_or_default_ss58_name(network_override); let network_id = unwrap_or_default_ss58_name(network_override);
if let Ok((pair, seed)) = Pair::from_phrase(uri, password) { if let Ok((pair, seed)) = Pair::from_phrase(uri, password) {
let public_key = pair.public(); let public_key = pair.public();
let network_override = unwrap_or_default_ss58_version(network_override); let network_override = unwrap_or_default_ss58_version(network_override);
match output { match output {
OutputType::Json => { OutputType::Json => {
let json = json!({ let json = json!({
"secretPhrase": uri, "secretPhrase": uri,
"networkId": network_id, "networkId": network_id,
"secretSeed": format_seed::<Pair>(seed), "secretSeed": format_seed::<Pair>(seed),
"publicKey": format_public_key::<Pair>(public_key.clone()), "publicKey": format_public_key::<Pair>(public_key.clone()),
"ss58PublicKey": public_key.to_ss58check_with_version(network_override), "ss58PublicKey": public_key.to_ss58check_with_version(network_override),
"accountId": format_account_id::<Pair>(public_key), "accountId": format_account_id::<Pair>(public_key),
"ss58Address": pair.public().into().into_account().to_ss58check_with_version(network_override), "ss58Address": pair.public().into().into_account().to_ss58check_with_version(network_override),
}); });
println!( println!(
"{}", "{}",
serde_json::to_string_pretty(&json).expect("Json pretty print failed") serde_json::to_string_pretty(&json).expect("Json pretty print failed")
); );
}, }
OutputType::Text => { OutputType::Text => {
println!( println!(
"Secret phrase: {}\n \ "Secret phrase: {}\n \
Network ID: {}\n \ Network ID: {}\n \
Secret seed: {}\n \ Secret seed: {}\n \
Public key (hex): {}\n \ Public key (hex): {}\n \
Account ID: {}\n \ Account ID: {}\n \
Public key (SS58): {}\n \ Public key (SS58): {}\n \
SS58 Address: {}", SS58 Address: {}",
uri, uri,
network_id, network_id,
format_seed::<Pair>(seed), format_seed::<Pair>(seed),
format_public_key::<Pair>(public_key.clone()), format_public_key::<Pair>(public_key.clone()),
format_account_id::<Pair>(public_key.clone()), format_account_id::<Pair>(public_key.clone()),
public_key.to_ss58check_with_version(network_override), public_key.to_ss58check_with_version(network_override),
pair.public().into().into_account().to_ss58check_with_version(network_override), pair.public()
); .into()
}, .into_account()
} .to_ss58check_with_version(network_override),
} else if let Ok((pair, seed)) = Pair::from_string_with_seed(uri, password) { );
let public_key = pair.public(); }
let network_override = unwrap_or_default_ss58_version(network_override); }
} else if let Ok((pair, seed)) = Pair::from_string_with_seed(uri, password) {
let public_key = pair.public();
let network_override = unwrap_or_default_ss58_version(network_override);
match output { match output {
OutputType::Json => { OutputType::Json => {
let json = json!({ let json = json!({
"secretKeyUri": uri, "secretKeyUri": uri,
"networkId": network_id, "networkId": network_id,
"secretSeed": if let Some(seed) = seed { format_seed::<Pair>(seed) } else { "n/a".into() }, "secretSeed": if let Some(seed) = seed { format_seed::<Pair>(seed) } else { "n/a".into() },
"publicKey": format_public_key::<Pair>(public_key.clone()), "publicKey": format_public_key::<Pair>(public_key.clone()),
"ss58PublicKey": public_key.to_ss58check_with_version(network_override), "ss58PublicKey": public_key.to_ss58check_with_version(network_override),
"accountId": format_account_id::<Pair>(public_key), "accountId": format_account_id::<Pair>(public_key),
"ss58Address": pair.public().into().into_account().to_ss58check_with_version(network_override), "ss58Address": pair.public().into().into_account().to_ss58check_with_version(network_override),
}); });
println!( println!(
"{}", "{}",
serde_json::to_string_pretty(&json).expect("Json pretty print failed") serde_json::to_string_pretty(&json).expect("Json pretty print failed")
); );
}, }
OutputType::Text => { OutputType::Text => {
println!( println!(
"Secret Key URI `{}` is account:\n \ "Secret Key URI `{}` is account:\n \
Network ID: {}\n \ Network ID: {}\n \
Secret seed: {}\n \ Secret seed: {}\n \
Public key (hex): {}\n \ Public key (hex): {}\n \
Account ID: {}\n \ Account ID: {}\n \
Public key (SS58): {}\n \ Public key (SS58): {}\n \
SS58 Address: {}", SS58 Address: {}",
uri, uri,
network_id, network_id,
if let Some(seed) = seed { format_seed::<Pair>(seed) } else { "n/a".into() }, if let Some(seed) = seed {
format_public_key::<Pair>(public_key.clone()), format_seed::<Pair>(seed)
format_account_id::<Pair>(public_key.clone()), } else {
public_key.to_ss58check_with_version(network_override), "n/a".into()
pair.public().into().into_account().to_ss58check_with_version(network_override), },
); format_public_key::<Pair>(public_key.clone()),
}, format_account_id::<Pair>(public_key.clone()),
} public_key.to_ss58check_with_version(network_override),
} else if let Ok((public_key, network)) = Pair::Public::from_string_with_version(uri) { pair.public()
let network_override = network_override.unwrap_or(network); .into()
.into_account()
.to_ss58check_with_version(network_override),
);
}
}
} else if let Ok((public_key, network)) = Pair::Public::from_string_with_version(uri) {
let network_override = network_override.unwrap_or(network);
match output { match output {
OutputType::Json => { OutputType::Json => {
let json = json!({ let json = json!({
"publicKeyUri": uri, "publicKeyUri": uri,
"networkId": String::from(network_override), "networkId": String::from(network_override),
"publicKey": format_public_key::<Pair>(public_key.clone()), "publicKey": format_public_key::<Pair>(public_key.clone()),
"accountId": format_account_id::<Pair>(public_key.clone()), "accountId": format_account_id::<Pair>(public_key.clone()),
"ss58PublicKey": public_key.to_ss58check_with_version(network_override), "ss58PublicKey": public_key.to_ss58check_with_version(network_override),
"ss58Address": public_key.to_ss58check_with_version(network_override), "ss58Address": public_key.to_ss58check_with_version(network_override),
}); });
println!( println!(
"{}", "{}",
serde_json::to_string_pretty(&json).expect("Json pretty print failed") serde_json::to_string_pretty(&json).expect("Json pretty print failed")
); );
}, }
OutputType::Text => { OutputType::Text => {
println!( println!(
"Public Key URI `{}` is account:\n \ "Public Key URI `{}` is account:\n \
Network ID/Version: {}\n \ Network ID/Version: {}\n \
Public key (hex): {}\n \ Public key (hex): {}\n \
Account ID: {}\n \ Account ID: {}\n \
Public key (SS58): {}\n \ Public key (SS58): {}\n \
SS58 Address: {}", SS58 Address: {}",
uri, uri,
String::from(network_override), String::from(network_override),
format_public_key::<Pair>(public_key.clone()), format_public_key::<Pair>(public_key.clone()),
format_account_id::<Pair>(public_key.clone()), format_account_id::<Pair>(public_key.clone()),
public_key.to_ss58check_with_version(network_override), public_key.to_ss58check_with_version(network_override),
public_key.to_ss58check_with_version(network_override), public_key.to_ss58check_with_version(network_override),
); );
}, }
} }
} else { } else {
println!("Invalid phrase/URI given"); println!("Invalid phrase/URI given");
} }
} }
/// Try to parse given `public` as hex encoded public key and print relevant information. /// Try to parse given `public` as hex encoded public key and print relevant information.
pub fn print_from_public<Pair>( pub fn print_from_public<Pair>(
public_str: &str, public_str: &str,
network_override: Option<Ss58AddressFormat>, network_override: Option<Ss58AddressFormat>,
output: OutputType, output: OutputType,
) -> Result<(), sc_cli::Error> ) -> Result<(), sc_cli::Error>
where where
Pair: sp_core::Pair, Pair: sp_core::Pair,
Pair::Public: Into<MultiSigner>, Pair::Public: Into<MultiSigner>,
{ {
let public = array_bytes::hex2bytes(public_str)?; let public = array_bytes::hex2bytes(public_str)?;
let public_key = Pair::Public::try_from(&public) let public_key = Pair::Public::try_from(&public)
.map_err(|_| "Failed to construct public key from given hex")?; .map_err(|_| "Failed to construct public key from given hex")?;
let network_override = unwrap_or_default_ss58_version(network_override); let network_override = unwrap_or_default_ss58_version(network_override);
match output { match output {
OutputType::Json => { OutputType::Json => {
let json = json!({ let json = json!({
"networkId": String::from(network_override), "networkId": String::from(network_override),
"publicKey": format_public_key::<Pair>(public_key.clone()), "publicKey": format_public_key::<Pair>(public_key.clone()),
"accountId": format_account_id::<Pair>(public_key.clone()), "accountId": format_account_id::<Pair>(public_key.clone()),
"ss58PublicKey": public_key.to_ss58check_with_version(network_override), "ss58PublicKey": public_key.to_ss58check_with_version(network_override),
"ss58Address": public_key.to_ss58check_with_version(network_override), "ss58Address": public_key.to_ss58check_with_version(network_override),
}); });
println!("{}", serde_json::to_string_pretty(&json).expect("Json pretty print failed")); println!(
}, "{}",
OutputType::Text => { serde_json::to_string_pretty(&json).expect("Json pretty print failed")
println!( );
"Network ID/Version: {}\n \ }
OutputType::Text => {
println!(
"Network ID/Version: {}\n \
Public key (hex): {}\n \ Public key (hex): {}\n \
Account ID: {}\n \ Account ID: {}\n \
Public key (SS58): {}\n \ Public key (SS58): {}\n \
SS58 Address: {}", SS58 Address: {}",
String::from(network_override), String::from(network_override),
format_public_key::<Pair>(public_key.clone()), format_public_key::<Pair>(public_key.clone()),
format_account_id::<Pair>(public_key.clone()), format_account_id::<Pair>(public_key.clone()),
public_key.to_ss58check_with_version(network_override), public_key.to_ss58check_with_version(network_override),
public_key.to_ss58check_with_version(network_override), public_key.to_ss58check_with_version(network_override),
); );
}, }
} }
Ok(()) Ok(())
} }
pub fn unwrap_or_default_ss58_name(x: Option<Ss58AddressFormat>) -> String { pub fn unwrap_or_default_ss58_name(x: Option<Ss58AddressFormat>) -> String {
@ -216,5 +228,8 @@ fn format_account_id<P: sp_core::Pair>(public_key: PublicFor<P>) -> String
where where
PublicFor<P>: Into<MultiSigner>, PublicFor<P>: Into<MultiSigner>,
{ {
format!("0x{}", HexDisplay::from(&public_key.into().into_account().as_ref())) format!(
"0x{}",
HexDisplay::from(&public_key.into().into_account().as_ref())
)
} }

View File

@ -2,213 +2,218 @@
use clap::Parser; use clap::Parser;
use rand::{rngs::OsRng, RngCore}; use rand::{rngs::OsRng, RngCore};
use sp_core::crypto::{ use sc_cli::{utils::format_seed, with_crypto_scheme, CryptoSchemeFlag, Error, OutputTypeFlag};
unwrap_or_default_ss58_version, Ss58AddressFormat, Ss58Codec, use sp_core::crypto::{unwrap_or_default_ss58_version, Ss58AddressFormat, Ss58Codec};
};
use sp_runtime::traits::IdentifyAccount; use sp_runtime::traits::IdentifyAccount;
use sc_cli::{
with_crypto_scheme, Error, OutputTypeFlag, CryptoSchemeFlag,
utils::format_seed,
};
use crate::commands::utils::print_from_uri; use crate::commands::utils::print_from_uri;
use crate::params::NetworkSchemeFlag; use crate::params::NetworkSchemeFlag;
/// The `vanity` command /// The `vanity` command
#[derive(Debug, Clone, Parser)] #[derive(Debug, Clone, Parser)]
#[command(name = "vanity", about = "Generate a seed that provides a vanity address")] #[command(
name = "vanity",
about = "Generate a seed that provides a vanity address"
)]
pub struct VanityCmd { pub struct VanityCmd {
/// Desired pattern /// Desired pattern
#[arg(long, value_parser = assert_non_empty_string)] #[arg(long, value_parser = assert_non_empty_string)]
pattern: String, pattern: String,
#[allow(missing_docs)] #[allow(missing_docs)]
#[clap(flatten)] #[clap(flatten)]
network_scheme: NetworkSchemeFlag, network_scheme: NetworkSchemeFlag,
#[allow(missing_docs)] #[allow(missing_docs)]
#[clap(flatten)] #[clap(flatten)]
output_scheme: OutputTypeFlag, output_scheme: OutputTypeFlag,
#[allow(missing_docs)] #[allow(missing_docs)]
#[clap(flatten)] #[clap(flatten)]
crypto_scheme: CryptoSchemeFlag, crypto_scheme: CryptoSchemeFlag,
} }
impl VanityCmd { impl VanityCmd {
/// Run the command /// Run the command
pub fn run(&self) -> Result<(), Error> { pub fn run(&self) -> Result<(), Error> {
let formated_seed = with_crypto_scheme!( let formated_seed = with_crypto_scheme!(
self.crypto_scheme.scheme, self.crypto_scheme.scheme,
generate_key( generate_key(
&self.pattern, &self.pattern,
unwrap_or_default_ss58_version(self.network_scheme.network) unwrap_or_default_ss58_version(self.network_scheme.network)
), ),
)?; )?;
with_crypto_scheme!( with_crypto_scheme!(
self.crypto_scheme.scheme, self.crypto_scheme.scheme,
print_from_uri( print_from_uri(
&formated_seed, &formated_seed,
None, None,
self.network_scheme.network, self.network_scheme.network,
self.output_scheme.output_type, self.output_scheme.output_type,
), ),
); );
Ok(()) Ok(())
} }
} }
/// genertae a key based on given pattern /// genertae a key based on given pattern
fn generate_key<Pair>( fn generate_key<Pair>(
desired: &str, desired: &str,
network_override: Ss58AddressFormat, network_override: Ss58AddressFormat,
) -> Result<String, &'static str> ) -> Result<String, &'static str>
where where
Pair: sp_core::Pair, Pair: sp_core::Pair,
Pair::Public: IdentifyAccount, Pair::Public: IdentifyAccount,
<Pair::Public as IdentifyAccount>::AccountId: Ss58Codec, <Pair::Public as IdentifyAccount>::AccountId: Ss58Codec,
{ {
println!("Generating key containing pattern '{}'", desired); println!("Generating key containing pattern '{}'", desired);
let top = 45 + (desired.len() * 48); let top = 45 + (desired.len() * 48);
let mut best = 0; let mut best = 0;
let mut seed = Pair::Seed::default(); let mut seed = Pair::Seed::default();
let mut done = 0; let mut done = 0;
loop { loop {
if done % 100000 == 0 { if done % 100000 == 0 {
OsRng.fill_bytes(seed.as_mut()); OsRng.fill_bytes(seed.as_mut());
} else { } else {
next_seed(seed.as_mut()); next_seed(seed.as_mut());
} }
let p = Pair::from_seed(&seed); let p = Pair::from_seed(&seed);
let ss58 = p.public().into_account().to_ss58check_with_version(network_override); let ss58 = p
.public()
.into_account()
.to_ss58check_with_version(network_override);
println!("{:?}", ss58); println!("{:?}", ss58);
let score = calculate_score(desired, &ss58); let score = calculate_score(desired, &ss58);
if score > best || desired.len() < 2 { if score > best || desired.len() < 2 {
best = score; best = score;
if best >= top { if best >= top {
println!("best: {} == top: {}", best, top); println!("best: {} == top: {}", best, top);
return Ok(format_seed::<Pair>(seed.clone())) return Ok(format_seed::<Pair>(seed.clone()));
} }
} }
done += 1; done += 1;
if done % good_waypoint(done) == 0 { if done % good_waypoint(done) == 0 {
println!("{} keys searched; best is {}/{} complete", done, best, top); println!("{} keys searched; best is {}/{} complete", done, best, top);
} }
} }
} }
fn good_waypoint(done: u64) -> u64 { fn good_waypoint(done: u64) -> u64 {
match done { match done {
0..=1_000_000 => 100_000, 0..=1_000_000 => 100_000,
1_000_001..=10_000_000 => 1_000_000, 1_000_001..=10_000_000 => 1_000_000,
10_000_001..=100_000_000 => 10_000_000, 10_000_001..=100_000_000 => 10_000_000,
100_000_001.. => 100_000_000, 100_000_001.. => 100_000_000,
} }
} }
fn next_seed(seed: &mut [u8]) { fn next_seed(seed: &mut [u8]) {
for s in seed { for s in seed {
match s { match s {
255 => { 255 => {
*s = 0; *s = 0;
}, }
_ => { _ => {
*s += 1; *s += 1;
break break;
}, }
} }
} }
} }
/// Calculate the score of a key based on the desired /// Calculate the score of a key based on the desired
/// input. /// input.
fn calculate_score(_desired: &str, key: &str) -> usize { fn calculate_score(_desired: &str, key: &str) -> usize {
for truncate in 0.._desired.len() { for truncate in 0.._desired.len() {
let snip_size = _desired.len() - truncate; let snip_size = _desired.len() - truncate;
let truncated = &_desired[0..snip_size]; let truncated = &_desired[0..snip_size];
if let Some(pos) = key.find(truncated) { if let Some(pos) = key.find(truncated) {
return (47 - pos) + (snip_size * 48) return (47 - pos) + (snip_size * 48);
} }
} }
0 0
} }
/// checks that `pattern` is non-empty /// checks that `pattern` is non-empty
fn assert_non_empty_string(pattern: &str) -> Result<String, &'static str> { fn assert_non_empty_string(pattern: &str) -> Result<String, &'static str> {
if pattern.is_empty() { if pattern.is_empty() {
Err("Pattern must not be empty") Err("Pattern must not be empty")
} else { } else {
Ok(pattern.to_string()) Ok(pattern.to_string())
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use sp_core::{ use sp_core::{
crypto::{default_ss58_version, Ss58AddressFormatRegistry, Ss58Codec}, crypto::{default_ss58_version, Ss58AddressFormatRegistry, Ss58Codec},
sr25519, Pair, sr25519, Pair,
}; };
#[test] #[test]
fn vanity() { fn vanity() {
let vanity = VanityCmd::parse_from(&["vanity", "--pattern", "j"]); let vanity = VanityCmd::parse_from(&["vanity", "--pattern", "j"]);
assert!(vanity.run().is_ok()); assert!(vanity.run().is_ok());
} }
#[test] #[test]
fn test_generation_with_single_char() { fn test_generation_with_single_char() {
let seed = generate_key::<sr25519::Pair>("ab", default_ss58_version()).unwrap(); let seed = generate_key::<sr25519::Pair>("ab", default_ss58_version()).unwrap();
assert!(sr25519::Pair::from_seed_slice(&array_bytes::hex2bytes_unchecked(&seed)) assert!(
.unwrap() sr25519::Pair::from_seed_slice(&array_bytes::hex2bytes_unchecked(&seed))
.public() .unwrap()
.to_ss58check() .public()
.contains("ab")); .to_ss58check()
} .contains("ab")
);
}
#[test] #[test]
fn generate_key_respects_network_override() { fn generate_key_respects_network_override() {
let seed = let seed =
generate_key::<sr25519::Pair>("ab", Ss58AddressFormatRegistry::PolkadotAccount.into()) generate_key::<sr25519::Pair>("ab", Ss58AddressFormatRegistry::PolkadotAccount.into())
.unwrap(); .unwrap();
assert!(sr25519::Pair::from_seed_slice(&array_bytes::hex2bytes_unchecked(&seed)) assert!(
.unwrap() sr25519::Pair::from_seed_slice(&array_bytes::hex2bytes_unchecked(&seed))
.public() .unwrap()
.to_ss58check_with_version(Ss58AddressFormatRegistry::PolkadotAccount.into()) .public()
.contains("ab")); .to_ss58check_with_version(Ss58AddressFormatRegistry::PolkadotAccount.into())
} .contains("ab")
);
}
#[test] #[test]
fn test_score_1_char_100() { fn test_score_1_char_100() {
let score = calculate_score("j", "sYrjWiZkEP1PQe2kKEZiC1Bi9L8yFSsB5RPEkzEPd5NThsB5H"); let score = calculate_score("j", "sYrjWiZkEP1PQe2kKEZiC1Bi9L8yFSsB5RPEkzEPd5NThsB5H");
assert_eq!(score, 96); assert_eq!(score, 96);
} }
#[test] #[test]
fn test_score_100() { fn test_score_100() {
let score = calculate_score("ghst", "sYsghstuhYd9p6unUC3kPxjD2gv2zRCztYQaEDCMJpYrPTqTG"); let score = calculate_score("ghst", "sYsghstuhYd9p6unUC3kPxjD2gv2zRCztYQaEDCMJpYrPTqTG");
assert_eq!(score, 246); assert_eq!(score, 246);
} }
#[test] #[test]
fn test_score_50_2() { fn test_score_50_2() {
// 50% for the position + 50% for the size // 50% for the position + 50% for the size
assert_eq!( assert_eq!(
calculate_score("ghst", "sYsghXXuhYd9p6unUC3kPxjD2gv2zRCztYQaEDCMJpYrPTqTG"), calculate_score("ghst", "sYsghXXuhYd9p6unUC3kPxjD2gv2zRCztYQaEDCMJpYrPTqTG"),
146 146
); );
} }
#[test] #[test]
fn test_score_0() { fn test_score_0() {
assert_eq!( assert_eq!(
calculate_score("ghst", "sYrjWiZkEP1PQe2kKEZiC1Bi9L8yFSsB5RPEkzEPd5NThsB5H"), calculate_score("ghst", "sYrjWiZkEP1PQe2kKEZiC1Bi9L8yFSsB5RPEkzEPd5NThsB5H"),
0 0
); );
} }
} }

View File

@ -1,5 +1,5 @@
pub mod params;
pub mod commands; pub mod commands;
pub mod params;
pub use commands::KeySubcommand; pub use commands::KeySubcommand;
pub use commands::VanityCmd; pub use commands::VanityCmd;

View File

@ -21,19 +21,25 @@ pub struct InnerSs58AddressFormat(Ss58AddressFormat);
impl InnerSs58AddressFormat { impl InnerSs58AddressFormat {
#[inline] #[inline]
pub fn custom(prefix: u16) -> Self { pub fn custom(prefix: u16) -> Self {
Self { 0: Ss58AddressFormat::custom(prefix) } Self {
0: Ss58AddressFormat::custom(prefix),
}
} }
} }
impl std::fmt::Display for InnerSs58AddressFormat { impl std::fmt::Display for InnerSs58AddressFormat {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{} network", ALL_POSSIBLE_IDS write!(
.binary_search(&u16::from(self.0)) f,
.expect("always be found")) "{} network",
ALL_POSSIBLE_IDS
.binary_search(&u16::from(self.0))
.expect("always be found")
)
} }
} }
impl<'a>TryFrom<&'a str> for InnerSs58AddressFormat { impl<'a> TryFrom<&'a str> for InnerSs58AddressFormat {
type Error = ParseError; type Error = ParseError;
fn try_from(x: &'a str) -> Result<Self, Self::Error> { fn try_from(x: &'a str) -> Result<Self, Self::Error> {
@ -44,13 +50,13 @@ impl<'a>TryFrom<&'a str> for InnerSs58AddressFormat {
} }
} }
pub fn parse_s58_prefix_address_format(x: &str,) -> Result<Ss58AddressFormat, String> { pub fn parse_s58_prefix_address_format(x: &str) -> Result<Ss58AddressFormat, String> {
match InnerSs58AddressFormat::try_from(x) { match InnerSs58AddressFormat::try_from(x) {
Ok(format_registry) => Ok(format_registry.0.into()), Ok(format_registry) => Ok(format_registry.0.into()),
Err(_) => Err(format!( Err(_) => Err(format!(
"Unable to parse variant. Known variants: {:?}", "Unable to parse variant. Known variants: {:?}",
&ALL_POSSIBLE_NAMES &ALL_POSSIBLE_NAMES
)) )),
} }
} }

6
core-primitives/Cargo.toml Executable file → Normal file
View File

@ -15,7 +15,7 @@ sp-runtime = { workspace = true }
[features] [features]
default = ["std"] default = ["std"]
std = [ std = [
"sp-core/std", "sp-core/std",
"sp-runtime/std", "sp-runtime/std",
"sp-std/std", "sp-std/std",
] ]

0
core-primitives/src/lib.rs Executable file → Normal file
View File

0
file_header.txt Executable file → Normal file
View File

BIN
images/img.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

BIN
images/img_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

BIN
images/img_10.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
images/img_11.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

BIN
images/img_12.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
images/img_13.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

BIN
images/img_14.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

BIN
images/img_15.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
images/img_16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
images/img_17.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

BIN
images/img_18.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

BIN
images/img_19.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

BIN
images/img_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

BIN
images/img_20.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
images/img_21.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

BIN
images/img_22.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
images/img_23.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
images/img_24.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
images/img_25.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
images/img_26.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
images/img_27.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
images/img_28.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
images/img_3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

BIN
images/img_30.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
images/img_31.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
images/img_32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
images/img_33.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

BIN
images/img_34.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
images/img_35.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

BIN
images/img_36.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

BIN
images/img_37.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

BIN
images/img_38.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
images/img_39.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

BIN
images/img_40.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

BIN
images/img_41.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

BIN
images/img_5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

BIN
images/img_6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
images/img_7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
images/img_8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

BIN
images/img_9.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -22,7 +22,11 @@ impl Metronome {
/// Create a new metronome source with a defined cycle duration. /// Create a new metronome source with a defined cycle duration.
pub fn new(cycle: Duration) -> Self { pub fn new(cycle: Duration) -> Self {
let period = cycle.into(); let period = cycle.into();
Self { period, delay: Delay::new(period), state: MetronomeState::Snooze } Self {
period,
delay: Delay::new(period),
state: MetronomeState::Snooze,
}
} }
} }
@ -36,14 +40,14 @@ impl futures::Stream for Metronome {
let val = self.period; let val = self.period;
self.delay.reset(val); self.delay.reset(val);
self.state = MetronomeState::Snooze; self.state = MetronomeState::Snooze;
}, }
MetronomeState::Snooze => { MetronomeState::Snooze => {
if !Pin::new(&mut self.delay).poll(cx).is_ready() { if !Pin::new(&mut self.delay).poll(cx).is_ready() {
break break;
} }
self.state = MetronomeState::SetAlarm; self.state = MetronomeState::SetAlarm;
return Poll::Ready(Some(())) return Poll::Ready(Some(()));
}, }
} }
} }
Poll::Pending Poll::Pending

View File

@ -2,19 +2,16 @@
use codec::Decode; use codec::Decode;
use core_primitives::{ use core_primitives::{
metrics_definitions::{ metrics_definitions::{CounterDefinition, CounterVecDefinition, HistogramDefinition},
CounterDefinition, CounterVecDefinition, HistogramDefinition,
},
RuntimeMetricLabelValues, RuntimeMetricOp, RuntimeMetricUpdate, RuntimeMetricLabelValues, RuntimeMetricOp, RuntimeMetricUpdate,
}; };
use prometheus_endpoint::{ use prometheus_endpoint::{
register, Counter, CounterVec, Histogram, HistogramOpts, Opts, Registry, register, Counter, CounterVec, Histogram, HistogramOpts, Opts, PrometheusError, Registry, U64,
PrometheusError, U64,
}; };
use std::{ use std::{
collections::hash_map::HashMap, collections::hash_map::HashMap,
sync::{Arc, Mutex, MutexGuard}, sync::{Arc, Mutex, MutexGuard},
} };
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Metrics { pub struct Metrics {
@ -32,13 +29,15 @@ impl RuntimeMetricsProvider {
pub fn register_countervec(&self, countervec: CounterVecDefinition) { pub fn register_countervec(&self, countervec: CounterVecDefinition) {
self.with_counter_vecs_lock_held(|mut hashmap| { self.with_counter_vecs_lock_held(|mut hashmap| {
hashmap.entry(countervec.name.to_owned()).or_insert(register( hashmap
CounterVec::new( .entry(countervec.name.to_owned())
Opts::new(countervec.name, countervec.description), .or_insert(register(
countervec.labels, CounterVec::new(
)?, Opts::new(countervec.name, countervec.description),
&self.0, countervec.labels,
)?); )?,
&self.0,
)?);
Ok(()) Ok(())
}) })
} }
@ -46,10 +45,7 @@ impl RuntimeMetricsProvider {
pub fn register_counter(&self, counter: CounterDefinition) { pub fn register_counter(&self, counter: CounterDefinition) {
self.with_counters_lock_held(|mut hashmap| { self.with_counters_lock_held(|mut hashmap| {
hashmap.entry(counter.name.to_owned()).or_insert(register( hashmap.entry(counter.name.to_owned()).or_insert(register(
Counter::new( Counter::new(counter.name, counter.description)?,
counter.name,
counter.description,
)?,
&self.0, &self.0,
)?); )?);
Ok(()) Ok(())
@ -60,8 +56,7 @@ impl RuntimeMetricsProvider {
self.with_histograms_lock_held(|mut hashmap| { self.with_histograms_lock_held(|mut hashmap| {
hashmap.entry(hist.name.to_owned()).or_insert(register( hashmap.entry(hist.name.to_owned()).or_insert(register(
Histogram::with_opts( Histogram::with_opts(
HistogramOpts::new(hist.name, hist.description) HistogramOpts::new(hist.name, hist.description).buckets(hist.buckets.to_vec()),
.buckets(hist.buckets.to_vec()),
)?, )?,
&self.0, &self.0,
)?); )?);
@ -69,15 +64,12 @@ impl RuntimeMetricsProvider {
}) })
} }
pub fn inc_counter_vec_by( pub fn inc_counter_vec_by(&self, name: &str, value: u64, labels: &RuntimeMetricsProvider) {
&self,
name: &str,
value: u64,
labels: &RuntimeMetricsProvider,
) {
self.with_counter_vecs_lock_held(|mut hashmap| { self.with_counter_vecs_lock_held(|mut hashmap| {
hashmap.entry(name.to_owned()).and_modify(|counter_vec| { hashmap.entry(name.to_owned()).and_modify(|counter_vec| {
counter_vec.with_label_values(&labels.as_str_vec()).inc_by(value) counter_vec
.with_label_values(&labels.as_str_vec())
.inc_by(value)
}); });
Ok(()) Ok(())
}) })
@ -85,7 +77,8 @@ impl RuntimeMetricsProvider {
pub fn inc_counter_by(&self, name: &str, value: u64) { pub fn inc_counter_by(&self, name: &str, value: u64) {
self.with_counters_lock_held(|mut hashmap| { self.with_counters_lock_held(|mut hashmap| {
hashmap.entry(name.to_owned()) hashmap
.entry(name.to_owned())
.and_modify(|counter_vec| counter_vec.inc_by(value)); .and_modify(|counter_vec| counter_vec.inc_by(value));
Ok(()) Ok(())
}) })
@ -93,8 +86,9 @@ impl RuntimeMetricsProvider {
pub fn observe_histogram(&self, name: &str, value: u128) { pub fn observe_histogram(&self, name: &str, value: u128) {
self.with_histograms_lock_held(|mut hashmap| { self.with_histograms_lock_held(|mut hashmap| {
hashmap.entry(name.to_owned()) hashmap
and_modify(|histogram| histogram.observe(value as f64 / 1_000_000_000.0)); .entry(name.to_owned())
.and_modify(|histogram| histogram.observe(value as f64 / 1_000_000_000.0));
Ok(()) Ok(())
}) })
} }
@ -103,21 +97,36 @@ impl RuntimeMetricsProvider {
where where
F: FnOnce(MutexGuard<'_, HashMap<String, Counter<U64>>>) -> Result<(), PrometheusError>, F: FnOnce(MutexGuard<'_, HashMap<String, Counter<U64>>>) -> Result<(), PrometheusError>,
{ {
let _ = self.1.counters.lock().map(do_something).or_else(|error| Err(error)); let _ = self
.1
.counters
.lock()
.map(do_something)
.or_else(|error| Err(error));
} }
fn with_counter_vecs_lock_held<F>(&self, do_something: F) fn with_counter_vecs_lock_held<F>(&self, do_something: F)
where where
F: FnOnce(MutexGuard<'_, HashMap<String, CounterVec<U64>>>) -> Result<(), PrometheusError>, F: FnOnce(MutexGuard<'_, HashMap<String, CounterVec<U64>>>) -> Result<(), PrometheusError>,
{ {
let _ = self.1.counter_vecs.lock().map(do_something).or_else(|error| Err(error)); let _ = self
.1
.counter_vecs
.lock()
.map(do_something)
.or_else(|error| Err(error));
} }
fn with_histograms_lock_held<F>(&self, do_something: F) fn with_histograms_lock_held<F>(&self, do_something: F)
where where
F: FnOnce(MutexGuard<'_, HashMap<String, Histogram>>) -> Result<(), PrometheusError>, F: FnOnce(MutexGuard<'_, HashMap<String, Histogram>>) -> Result<(), PrometheusError>,
{ {
let _ = self.1.histograms.lock().map(do_something).or_else(|error| Err(error)); let _ = self
.1
.histograms
.lock()
.map(do_something)
.or_else(|error| Err(error));
} }
} }
@ -131,7 +140,7 @@ impl sc_tracing::TraceHandler for RuntimeMetricsProvider {
.unwrap_or(&String::default()) .unwrap_or(&String::default())
.ne("metrics") .ne("metrics")
{ {
return return;
} }
if let Some(update_op_bs58) = event.values.string_values.get("params") { if let Some(update_op_bs58) = event.values.string_values.get("params") {
@ -141,7 +150,7 @@ impl sc_tracing::TraceHandler for RuntimeMetricsProvider {
.as_slice(), .as_slice(),
) { ) {
Ok(update_op) => self.parse_metric_update(update_op), Ok(update_op) => self.parse_metric_update(update_op),
Err(_) => {}, Err(_) => {}
} }
} }
} }
@ -150,12 +159,15 @@ impl sc_tracing::TraceHandler for RuntimeMetricsProvider {
impl RuntimeMetricsProvider { impl RuntimeMetricsProvider {
fn parse_metric_update(&self, update: RuntimeMetricUpdate) { fn parse_metric_update(&self, update: RuntimeMetricUpdate) {
match update.op { match update.op {
RuntimeMetricOp::IncrementCounterVec(value, ref labels) => RuntimeMetricOp::IncrementCounterVec(value, ref labels) => {
self.inc_counter_vec_by(update.metric_name(), value, labels), self.inc_counter_vec_by(update.metric_name(), value, labels)
RuntimeMetricOp::IncrementCounter(value) => }
self.inc_counter_by(update.metric_name(), value), RuntimeMetricOp::IncrementCounter(value) => {
RuntimeMetricOp::ObserveHistogram(value) => self.inc_counter_by(update.metric_name(), value)
self.observe_histogram(update.metric_name(), value), }
RuntimeMetricOp::ObserveHistogram(value) => {
self.observe_histogram(update.metric_name(), value)
}
} }
} }
@ -178,7 +190,7 @@ impl RuntimeMetricsProvider {
pub fn logger_hook() -> impl FnOnce(&mut sc_cli::LoggerBuilder, &sc_service::Configuration) -> () { pub fn logger_hook() -> impl FnOnce(&mut sc_cli::LoggerBuilder, &sc_service::Configuration) -> () {
|logger_builder, config| { |logger_builder, config| {
if config.prometheus_registry().is_none() { if config.prometheus_registry().is_none() {
return return;
} }
let registry = config.prometheus_registry().cloned().unwrap(); let registry = config.prometheus_registry().cloned().unwrap();

View File

@ -1,5 +1,5 @@
use hyper::{Client, Uri};
use ghost_test_service::{node_config, run_validator_node, test_prometheus_config}; use ghost_test_service::{node_config, run_validator_node, test_prometheus_config};
use hyper::{Client, Uri};
use keyring::AccountKeyring::*; use keyring::AccountKeyring::*;
use std::collections::HashMap; use std::collections::HashMap;
@ -7,8 +7,13 @@ const DEFAULT_PROMETHEUS_PORT: u16 = 9616;
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn runtime_can_publish_metrics() { async fn runtime_can_publish_metrics() {
let mut alice_config = let mut alice_config = node_config(
node_config(|| {}, tokio::runtime::Handle::current(), Alice, Vec::new(), true); || {},
tokio::runtime::Handle::current(),
Alice,
Vec::new(),
true,
);
// Enable prometheus metrics for Alice. // Enable prometheus metrics for Alice.
alice_config.prometheus_config = Some(test_prometheus_config(DEFAULT_PROMETHEUS_PORT)); alice_config.prometheus_config = Some(test_prometheus_config(DEFAULT_PROMETHEUS_PORT));
@ -26,8 +31,13 @@ async fn runtime_can_publish_metrics() {
// Start validator Alice // Start validator Alice
let alice = run_validator_node(alice_config, None); let alice = run_validator_node(alice_config, None);
let bob_config = let bob_config = node_config(
node_config(|| {}, tokio::runtime::Handle::current(), Bob, vec![alice.addr.clone()], true); || {},
tokio::runtime::Handle::current(),
Bob,
vec![alice.addr.clone()],
true,
);
// Start validator Bob // Start validator Bob
let _bob = run_validator_node(bob_config, None); let _bob = run_validator_node(bob_config, None);
@ -48,19 +58,25 @@ async fn scrape_prometheus_metrics(metrics_uri: &str) -> HashMap<String, u64> {
.expect("GET request failed"); .expect("GET request failed");
let body = String::from_utf8( let body = String::from_utf8(
hyper::body::to_bytes(res).await.expect("can't get body as bytes").to_vec(), hyper::body::to_bytes(res)
).expect("body is not an UTF8 string"); .await
.expect("can't get body as bytes")
.to_vec(),
)
.expect("body is not an UTF8 string");
let lines: Vec<_> = body.lines().map(|s| Ok(s.to_owned())).collect(); let lines: Vec<_> = body.lines().map(|s| Ok(s.to_owned())).collect();
prometheus_parse::Scrape::parse(lines.into_iter()) prometheus_parse::Scrape::parse(lines.into_iter())
.expect("Scraper failed to parse Prometheus metrics") .expect("Scraper failed to parse Prometheus metrics")
.samples .samples
.into_iter() .into_iter()
.filter_map(|prometheus_parse::Sample { metric, value, .. }| match value { .filter_map(
prometheus_parse::Value::Counter(value) => Some((metric, value as u64)), |prometheus_parse::Sample { metric, value, .. }| match value {
prometheus_parse::Value::Gauge(value) => Some((metric, value as u64)), prometheus_parse::Value::Counter(value) => Some((metric, value as u64)),
prometheus_parse::Value::Untyped(value) => Some((metric, value as u64)), prometheus_parse::Value::Gauge(value) => Some((metric, value as u64)),
_ => None, prometheus_parse::Value::Untyped(value) => Some((metric, value as u64)),
}) _ => None,
},
)
.collect() .collect()
} }

View File

@ -1,56 +0,0 @@
#![cfg(feature = "runtime-benchmarks")]
use super::*;
use frame_benchmarking::v2::*;
#[instance_benchmarks]
mod benchmarks {
use super::*;
use pallet_ranked_collective::Pallet as Club;
use frame_support::dispatch::RawOrigin;
#[benchmark]
fn claim() {
let i = 1337u32;
let ethereum_secret_key = libsecp256k1::SecretKey::parse(
&keccak_256(&i.to_le_bytes())).unwrap();
let eth_address = crate::secp_utils::eth(&ethereum_secret_key);
let balance = CurrencyOf::<T, I>::minimum_balance();
let pseudo_account: T::AccountId = Pallet::<T, I>::into_account_id(eth_address).unwrap();
let _ = CurrencyOf::<T, I>::deposit_creating(&pseudo_account, balance);
Total::<T, I>::put(balance);
let pseudo_rank = 5u16;
let _ = Club::<T, I>::do_add_member_to_rank(
pseudo_account.clone(),
pseudo_rank,
false,
);
let user_account: T::AccountId = account("user", i, 0);
let signature = crate::secp_utils::sig::<T, I>(&ethereum_secret_key, &user_account.encode());
let prev_balance = CurrencyOf::<T, I>::free_balance(&user_account);
let prev_rank = Club::<T, I>::rank_of(&user_account);
#[extrinsic_call]
claim(RawOrigin::Signed(user_account.clone()), eth_address, signature);
assert_eq!(CurrencyOf::<T, I>::free_balance(&user_account), prev_balance + balance);
assert_eq!(CurrencyOf::<T, I>::free_balance(&pseudo_account), balance - balance);
let rank = match prev_rank {
Some(current_rank) if pseudo_rank <= current_rank => Some(current_rank),
_ => Some(pseudo_rank),
};
assert_eq!(Club::<T, I>::rank_of(&user_account), rank);
assert_eq!(Club::<T, I>::rank_of(&pseudo_account), None);
}
impl_benchmark_test_suite!(
Pallet,
crate::mock::new_test_ext(),
crate::mock::Test,
);
}

View File

@ -1,313 +0,0 @@
#![cfg_attr(not(feature = "std"), no_std)]
use frame_support::{
ensure, pallet_prelude::*,
traits::{
Currency, ExistenceRequirement, Get, RankedMembers,
RankedMembersSwapHandler, VestingSchedule,
},
DefaultNoBound,
};
use frame_system::pallet_prelude::*;
use serde::{self, Deserialize, Deserializer, Serialize, Serializer};
pub use pallet::*;
use sp_io::{crypto::secp256k1_ecdsa_recover, hashing::keccak_256};
use sp_runtime::traits::{CheckedSub, CheckedDiv, BlockNumberProvider};
use sp_std::prelude::*;
extern crate alloc;
#[cfg(not(feature = "std"))]
use alloc::{format, string::String};
pub mod weights;
pub use crate::weights::WeightInfo;
mod tests;
mod mock;
mod benchmarking;
mod secp_utils;
/// An ethereum address (i.e. 20 bytes, used to represent an Ethereum account).
///
/// This gets serialized to the 0x-prefixed hex representation.
#[derive(Clone, Copy, PartialEq, Eq, Encode, Decode, Default, RuntimeDebug, TypeInfo)]
pub struct EthereumAddress(pub [u8; 20]);
impl Serialize for EthereumAddress {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let hex: String = rustc_hex::ToHex::to_hex(&self.0[..]);
serializer.serialize_str(&format!("0x{}", hex))
}
}
impl<'de> Deserialize<'de> for EthereumAddress {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let base_string = String::deserialize(deserializer)?;
let offset = if base_string.starts_with("0x") { 2 } else { 0 };
let s = &base_string[offset..];
if s.len() != 40 {
Err(serde::de::Error::custom(
"Bad length of Ethereum address (should be 42 including `0x`)",
))?;
}
let raw: Vec<u8> = rustc_hex::FromHex::from_hex(s)
.map_err(|e| serde::de::Error::custom(format!("{:?}", e)))?;
let mut r = Self::default();
r.0.copy_from_slice(&raw);
Ok(r)
}
}
#[derive(Encode, Decode, Clone, TypeInfo)]
pub struct EcdsaSignature(pub [u8; 65]);
impl PartialEq for EcdsaSignature {
fn eq(&self, other: &Self) -> bool {
&self.0[..] == &other.0[..]
}
}
impl sp_std::fmt::Debug for EcdsaSignature {
fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result {
write!(f, "EcdsaSignature({:?})", &self.0[..])
}
}
type CurrencyOf<T, I> = <<T as Config<I>>::VestingSchedule as VestingSchedule<
<T as frame_system::Config>::AccountId
>>::Currency;
type BalanceOf<T, I> = <CurrencyOf<T, I> as Currency<
<T as frame_system::Config>::AccountId>
>::Balance;
type RankOf<T, I> = <pallet_ranked_collective::Pallet::<T, I> as RankedMembers>::Rank;
type AccountIdOf<T, I> = <pallet_ranked_collective::Pallet::<T, I> as RankedMembers>::AccountId;
#[frame_support::pallet]
pub mod pallet {
use super::*;
#[pallet::pallet]
#[pallet::without_storage_info]
pub struct Pallet<T, I = ()>(_);
#[pallet::config]
pub trait Config<I: 'static = ()>: frame_system::Config + pallet_ranked_collective::Config<I> {
type RuntimeEvent: From<Event<Self, I>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
type VestingSchedule: VestingSchedule<Self::AccountId, Moment = BlockNumberFor<Self>>;
type BlockNumberProvider: BlockNumberProvider<BlockNumber = BlockNumberFor<Self>>;
type MemberSwappedHandler: RankedMembersSwapHandler<AccountIdOf<Self, I>, RankOf<Self, I>>;
#[pallet::constant]
type Prefix: Get<&'static [u8]>;
#[pallet::constant]
type MaximumWithdrawAmount: Get<BalanceOf<Self, I>>;
#[pallet::constant]
type VestingBlocks: Get<u32>;
type WeightInfo: WeightInfo;
}
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config<I>, I: 'static = ()> {
Claimed {
receiver: T::AccountId,
donor: T::AccountId,
amount: BalanceOf<T, I>,
rank: Option<RankOf<T, I>>,
},
}
#[pallet::error]
pub enum Error<T, I = ()> {
InvalidEthereumSignature,
InvalidEthereumAddress,
NoBalanceToClaim,
AddressDecodingFailed,
ArithmeticError,
PotUnderflow,
}
#[pallet::storage]
pub type Total<T: Config<I>, I: 'static = ()> = StorageValue<_, BalanceOf<T, I>, ValueQuery>;
#[pallet::genesis_config]
#[derive(DefaultNoBound)]
pub struct GenesisConfig<T: Config<I>, I: 'static = ()> {
pub total: BalanceOf<T, I>,
pub members_and_ranks: Vec<(T::AccountId, u16)>,
}
#[pallet::genesis_build]
impl<T: Config<I>, I: 'static> BuildGenesisConfig for GenesisConfig<T, I> {
fn build(&self) {
let cult_accounts: Vec<_> = self
.members_and_ranks
.iter()
.map(|(account_id, rank)| {
assert!(
pallet_ranked_collective::Pallet::<T, I>::do_add_member_to_rank(
account_id.clone(), *rank, false).is_ok(),
"error during adding and promotion"
);
account_id
})
.collect();
assert!(
self.members_and_ranks.len() == cult_accounts.len(),
"duplicates in `members_and_ranks`"
);
Total::<T, I>::put(self.total);
}
}
#[pallet::call]
impl<T: Config<I>, I: 'static> Pallet<T, I> {
#[pallet::call_index(0)]
#[pallet::weight(<T as Config<I>>::WeightInfo::claim())]
pub fn claim(
origin: OriginFor<T>,
ethereum_address: EthereumAddress,
ethereum_signature: EcdsaSignature,
) -> DispatchResult {
let who = ensure_signed(origin)?;
let data = who.using_encoded(to_ascii_hex);
let recovered_address = Self::recover_ethereum_address(
&ethereum_signature,
&data,
).ok_or(Error::<T, I>::InvalidEthereumSignature)?;
ensure!(recovered_address == ethereum_address,
Error::<T, I>::InvalidEthereumAddress);
Self::do_claim(who, ethereum_address)
}
}
}
fn to_ascii_hex(data: &[u8]) -> Vec<u8> {
let mut r = Vec::with_capacity(data.len() * 2);
let mut push_nibble = |n| r.push(if n < 10 { b'0' + n } else { b'a' - 10 + n });
for &b in data.iter() {
push_nibble(b / 16);
push_nibble(b % 16);
}
r
}
impl<T: Config<I>, I: 'static> Pallet<T, I> {
fn ethereum_signable_message(what: &[u8]) -> Vec<u8> {
let prefix = T::Prefix::get();
let mut l = prefix.len() + what.len();
let mut rev = Vec::new();
while l > 0 {
rev.push(b'0' + (l % 10) as u8);
l /= 10;
}
let mut v = b"\x19Ethereum Signed Message:\n".to_vec();
v.extend(rev.into_iter().rev());
v.extend_from_slice(prefix);
v.extend_from_slice(what);
v
}
fn recover_ethereum_address(s: &EcdsaSignature, what: &[u8]) -> Option<EthereumAddress> {
let msg = keccak_256(&Self::ethereum_signable_message(what));
let mut res = EthereumAddress::default();
res.0
.copy_from_slice(&keccak_256(&secp256k1_ecdsa_recover(&s.0, &msg).ok()?[..])[12..]);
Some(res)
}
fn into_account_id(address: EthereumAddress) -> Result<T::AccountId, codec::Error> {
let mut data = [0u8; 32];
data[0..4].copy_from_slice(b"evm:");
data[4..24].copy_from_slice(&address.0[..]);
let hash = sp_core::keccak_256(&data);
T::AccountId::decode(&mut &hash[..])
}
fn do_claim(receiver: T::AccountId, ethereum_address: EthereumAddress) -> DispatchResult {
let donor = Self::into_account_id(ethereum_address).ok()
.ok_or(Error::<T, I>::AddressDecodingFailed)?;
let balance_due = CurrencyOf::<T, I>::free_balance(&donor);
ensure!(balance_due >= CurrencyOf::<T, I>::minimum_balance(),
Error::<T, I>::NoBalanceToClaim);
let new_total = Total::<T, I>::get()
.checked_sub(&balance_due)
.ok_or(Error::<T, I>::PotUnderflow)?;
CurrencyOf::<T, I>::transfer(
&donor,
&receiver,
balance_due,
ExistenceRequirement::AllowDeath,
)?;
let max_amount = T::MaximumWithdrawAmount::get();
if balance_due > max_amount {
let vesting_balance = balance_due
.checked_sub(&max_amount)
.ok_or(Error::<T, I>::ArithmeticError)?;
let vesting_blocks = T::VestingBlocks::get();
let per_block_balance = vesting_balance
.checked_div(&vesting_blocks.into())
.ok_or(Error::<T, I>::ArithmeticError)?;
T::VestingSchedule::add_vesting_schedule(
&receiver,
vesting_balance,
per_block_balance,
T::BlockNumberProvider::current_block_number(),
)?;
}
let rank = if let Some(rank) = <pallet_ranked_collective::Pallet::<T, I> as RankedMembers>::rank_of(&donor) {
pallet_ranked_collective::Pallet::<T, I>::do_remove_member_from_rank(&donor, rank)?;
let new_rank = match <pallet_ranked_collective::Pallet::<T, I> as RankedMembers>::rank_of(&receiver) {
Some(current_rank) if current_rank >= rank => current_rank,
Some(current_rank) if current_rank < rank => {
for _ in 0..rank - current_rank {
pallet_ranked_collective::Pallet::<T, I>::do_promote_member(receiver.clone(), None, false)?;
}
rank
},
_ => {
pallet_ranked_collective::Pallet::<T, I>::do_add_member_to_rank(receiver.clone(), rank, false)?;
rank
},
};
<T as pallet::Config<I>>::MemberSwappedHandler::swapped(&donor, &receiver, new_rank);
Some(new_rank)
} else {
None
};
Total::<T, I>::put(new_total);
Self::deposit_event(Event::<T, I>::Claimed {
receiver,
donor,
amount: balance_due,
rank,
});
Ok(())
}
}

View File

@ -1,221 +0,0 @@
#![cfg(test)]
use super::*;
pub use crate as ghost_claims;
use frame_support::{
parameter_types, derive_impl,
traits::{PollStatus, Polling, WithdrawReasons, ConstU16, ConstU64},
};
use frame_system::EnsureRootWithSuccess;
use sp_runtime::{BuildStorage, traits::Convert};
pub use pallet_ranked_collective::{TallyOf, Rank};
pub mod eth_keys {
use crate::{
mock::Test,
EcdsaSignature, EthereumAddress,
};
use hex_literal::hex;
use codec::Encode;
pub fn total_claims() -> u64 { 10 + 100 + 1000 }
pub fn alice_account_id() -> <Test as frame_system::Config>::AccountId { 69 }
pub fn bob_account_id() -> <Test as frame_system::Config>::AccountId { 1337 }
pub fn first_eth_public_known() -> EthereumAddress { EthereumAddress(hex!("1A69d2D5568D1878023EeB121a73d33B9116A760")) }
pub fn second_eth_public_known() -> EthereumAddress { EthereumAddress(hex!("2f86cfBED3fbc1eCf2989B9aE5fc019a837A9C12")) }
pub fn third_eth_public_known() -> EthereumAddress { EthereumAddress(hex!("e83f67361Ac74D42A48E2DAfb6706eb047D8218D")) }
pub fn fourth_eth_public_known() -> EthereumAddress { EthereumAddress(hex!("827ee4ad9b259b6fa1390ed60921508c78befd63")) }
fn first_eth_private_key() -> libsecp256k1::SecretKey { libsecp256k1::SecretKey::parse(&hex!("01c928771aea942a1e7ac06adf2b73dfbc9a25d9eaa516e3673116af7f345198")).unwrap() }
fn second_eth_private_key() -> libsecp256k1::SecretKey { libsecp256k1::SecretKey::parse(&hex!("b19a435901872f817185f7234a1484eae837613f9d10cf21927a23c2d8cb9139")).unwrap() }
fn third_eth_private_key() -> libsecp256k1::SecretKey { libsecp256k1::SecretKey::parse(&hex!("d3baf57b74d65719b2dc33f5a464176022d0cc5edbca002234229f3e733875fc")).unwrap() }
fn fourth_eth_private_key() -> libsecp256k1::SecretKey { libsecp256k1::SecretKey::parse(&hex!("c4683d566436af6b58b4a59c8f501319226e85b21869bf93d5eeb4596d4791d4")).unwrap() }
fn wrong_eth_private_key() -> libsecp256k1::SecretKey { libsecp256k1::SecretKey::parse(&hex!("deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef")).unwrap() }
pub fn first_eth_public_key() -> EthereumAddress { crate::secp_utils::eth(&first_eth_private_key()) }
pub fn second_eth_public_key() -> EthereumAddress { crate::secp_utils::eth(&second_eth_private_key()) }
pub fn third_eth_public_key() -> EthereumAddress { crate::secp_utils::eth(&third_eth_private_key()) }
pub fn fourth_eth_public_key() -> EthereumAddress { crate::secp_utils::eth(&fourth_eth_private_key()) }
pub fn first_account_id() -> <Test as frame_system::Config>::AccountId { crate::secp_utils::into_account_id::<Test, ()>(first_eth_public_key()) }
pub fn second_account_id() -> <Test as frame_system::Config>::AccountId { crate::secp_utils::into_account_id::<Test, ()>(second_eth_public_key()) }
pub fn third_account_id() -> <Test as frame_system::Config>::AccountId { crate::secp_utils::into_account_id::<Test, ()>(third_eth_public_key()) }
pub fn fourth_account_id() -> <Test as frame_system::Config>::AccountId { crate::secp_utils::into_account_id::<Test, ()>(fourth_eth_public_key()) }
pub fn first_signature() -> EcdsaSignature { crate::secp_utils::sig::<Test, ()>(&first_eth_private_key(), &alice_account_id().encode()) }
pub fn second_signature() -> EcdsaSignature { crate::secp_utils::sig::<Test, ()>(&second_eth_private_key(), &alice_account_id().encode()) }
pub fn third_signature() -> EcdsaSignature { crate::secp_utils::sig::<Test, ()>(&third_eth_private_key(), &alice_account_id().encode()) }
pub fn fourth_signature() -> EcdsaSignature { crate::secp_utils::sig::<Test, ()>(&fourth_eth_private_key(), &bob_account_id().encode()) }
pub fn wrong_signature() -> EcdsaSignature { crate::secp_utils::sig::<Test, ()>(&wrong_eth_private_key(), &alice_account_id().encode()) }
}
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for Test {
type RuntimeOrigin = RuntimeOrigin;
type RuntimeCall = RuntimeCall;
type Block = Block;
type RuntimeEvent = RuntimeEvent;
type AccountData = pallet_balances::AccountData<u64>;
type MaxConsumers = frame_support::traits::ConstU32<16>;
}
#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
impl pallet_balances::Config for Test {
type AccountStore = System;
}
parameter_types! {
pub const MinVestedTransfer: u64 = 1;
pub UnvestedFundsAllowedWithdrawReasons: WithdrawReasons =
WithdrawReasons::except(WithdrawReasons::TRANSFER | WithdrawReasons::RESERVE);
}
impl pallet_vesting::Config for Test {
type RuntimeEvent = RuntimeEvent;
type Currency = Balances;
type BlockNumberToBalance = sp_runtime::traits::ConvertInto;
type MinVestedTransfer = MinVestedTransfer;
type WeightInfo = ();
type UnvestedFundsAllowedWithdrawReasons =
UnvestedFundsAllowedWithdrawReasons;
type BlockNumberProvider = System;
const MAX_VESTING_SCHEDULES: u32 = 28;
}
parameter_types! {
pub MinRankOfClassDelta: Rank = 1;
}
pub struct MinRankOfClass<Delta>(sp_std::marker::PhantomData<Delta>);
impl<Delta: Get<Rank>> Convert<Class, Rank> for MinRankOfClass<Delta> {
fn convert(a: Class) -> Rank {
a.saturating_sub(Delta::get())
}
}
pub struct TestPolls;
impl Polling<TallyOf<Test>> for TestPolls {
type Index = u8;
type Votes = u32;
type Moment = u64;
type Class = Class;
fn classes() -> Vec<Self::Class> {
unimplemented!()
}
fn as_ongoing(_index: u8) -> Option<(TallyOf<Test>, Self::Class)> {
unimplemented!()
}
fn access_poll<R>(
_index: Self::Index,
_f: impl FnOnce(PollStatus<&mut TallyOf<Test>, Self::Moment, Self::Class>) -> R,
) -> R {
unimplemented!()
}
fn try_access_poll<R>(
_index: Self::Index,
_f: impl FnOnce(
PollStatus<&mut TallyOf<Test>, Self::Moment, Self::Class>,
) -> Result<R, DispatchError>,
) -> Result<R, DispatchError> {
unimplemented!()
}
#[cfg(feature = "runtime-benchmarks")]
fn create_ongoing(_class: Self::Class) -> Result<Self::Index, ()> {
unimplemented!()
}
#[cfg(feature = "runtime-benchmarks")]
fn end_ongoing(_index: Self::Index, _approved: bool) -> Result<(), ()> {
unimplemented!()
}
}
impl pallet_ranked_collective::Config for Test {
type WeightInfo = ();
type RuntimeEvent = RuntimeEvent;
type AddOrigin = EnsureRootWithSuccess<Self::AccountId, ConstU16<65535>>;
type RemoveOrigin = EnsureRootWithSuccess<Self::AccountId, ConstU16<65535>>;
type PromoteOrigin = EnsureRootWithSuccess<Self::AccountId, ConstU16<65535>>;
type DemoteOrigin = EnsureRootWithSuccess<Self::AccountId, ConstU16<65535>>;
type ExchangeOrigin = EnsureRootWithSuccess<Self::AccountId, ConstU16<65535>>;
type Polls = TestPolls;
type MemberSwappedHandler = ();
type MinRankOfClass = MinRankOfClass<MinRankOfClassDelta>;
type VoteWeight = pallet_ranked_collective::Geometric;
#[cfg(feature = "runtime-benchmarks")]
type BenchmarkSetup = ();
}
parameter_types! {
pub Prefix: &'static [u8] = b"AccountId:";
}
impl Config for Test {
type RuntimeEvent = RuntimeEvent;
type VestingSchedule = Vesting;
type BlockNumberProvider = System;
type MemberSwappedHandler = ();
type Prefix = Prefix;
type MaximumWithdrawAmount = ConstU64<200>;
type VestingBlocks = ConstU32<80>;
type WeightInfo = ();
}
type Block = frame_system::mocking::MockBlock<Test>;
type Class = Rank;
frame_support::construct_runtime!(
pub enum Test
{
System: frame_system,
Balances: pallet_balances,
Vesting: pallet_vesting,
Club: pallet_ranked_collective,
Claims: ghost_claims,
}
);
pub fn new_test_ext() -> sp_io::TestExternalities {
let mut t = frame_system::GenesisConfig::<Test>::default()
.build_storage()
.unwrap();
pallet_balances::GenesisConfig::<Test> {
balances: vec![
(crate::mock::eth_keys::first_account_id(), 10),
(crate::mock::eth_keys::second_account_id(), 100),
(crate::mock::eth_keys::third_account_id(), 1000),
],
}
.assimilate_storage(&mut t)
.unwrap();
ghost_claims::GenesisConfig::<Test> {
total: crate::mock::eth_keys::total_claims(),
members_and_ranks: vec![
(crate::mock::eth_keys::second_account_id(), 1),
(crate::mock::eth_keys::third_account_id(), 3),
],
}
.assimilate_storage(&mut t)
.unwrap();
let mut ext = sp_io::TestExternalities::new(t);
ext.execute_with(|| {
System::set_block_number(1);
});
ext
}

View File

@ -1,37 +0,0 @@
#![cfg(any(test, feature = "runtime-benchmarks"))]
use crate::{keccak_256, Config, EcdsaSignature, EthereumAddress};
pub fn public(secret: &libsecp256k1::SecretKey) -> libsecp256k1::PublicKey {
libsecp256k1::PublicKey::from_secret_key(secret)
}
pub fn eth(secret: &libsecp256k1::SecretKey) -> EthereumAddress {
let mut res = EthereumAddress::default();
res.0.copy_from_slice(&keccak_256(&public(secret).serialize()[1..65])[12..]);
res
}
#[cfg(test)]
pub fn into_account_id<T: Config<I>, I: 'static>(address: EthereumAddress) -> T::AccountId {
super::Pallet::<T, I>::into_account_id(address).unwrap()
}
pub fn sig<T: Config<I>, I: 'static>(
secret: &libsecp256k1::SecretKey,
what: &[u8],
) -> EcdsaSignature {
let msg = keccak_256(&super::Pallet::<T, I>::ethereum_signable_message(
&crate::to_ascii_hex(what)[..],
));
let (sig, recovery_id) = libsecp256k1::sign(
&libsecp256k1::Message::parse(&msg),
secret,
);
let mut r = [0u8; 65];
r[0..64].copy_from_slice(&sig.serialize()[..]);
r[64] = recovery_id.serialize();
EcdsaSignature(r)
}

View File

@ -1,274 +0,0 @@
#![cfg(test)]
use mock::{
new_test_ext, ghost_claims,
Test, System, Balances, Club, Vesting, Claims, RuntimeOrigin, RuntimeEvent,
eth_keys::{
alice_account_id, total_claims, first_eth_public_known,
first_eth_public_key, first_account_id, first_signature,
second_eth_public_known, second_eth_public_key, second_account_id,
second_signature, third_eth_public_known, third_eth_public_key,
third_account_id, third_signature, fourth_eth_public_known,
fourth_eth_public_key, fourth_account_id, fourth_signature,
wrong_signature, bob_account_id,
}
};
use hex_literal::hex;
use frame_support::{assert_err, assert_ok};
use super::*;
#[test]
fn serde_works() {
let x = EthereumAddress(hex!["0123456789abcdef0123456789abcdef01234567"]);
let y = serde_json::to_string(&x).unwrap();
assert_eq!(y, "\"0x0123456789abcdef0123456789abcdef01234567\"");
let z: EthereumAddress = serde_json::from_str(&y).unwrap();
assert_eq!(x, z);
}
#[test]
fn known_eth_public_accounts_are_correct() {
assert_eq!(first_eth_public_key(), first_eth_public_known());
assert_eq!(second_eth_public_key(), second_eth_public_known());
assert_eq!(third_eth_public_key(), third_eth_public_known());
assert_eq!(fourth_eth_public_key(), fourth_eth_public_known());
}
#[test]
fn basic_setup_works() {
new_test_ext().execute_with(|| {
assert_eq!(Balances::usable_balance(&alice_account_id()), 0);
assert_eq!(Balances::usable_balance(&first_account_id()), 10);
assert_eq!(Balances::usable_balance(&second_account_id()), 100);
assert_eq!(Balances::usable_balance(&third_account_id()), 1000);
assert_eq!(Club::rank_of(&alice_account_id()), None);
assert_eq!(Club::rank_of(&first_account_id()), None);
assert_eq!(Club::rank_of(&second_account_id()), Some(1));
assert_eq!(Club::rank_of(&third_account_id()), Some(3));
assert_eq!(Vesting::vesting_balance(&alice_account_id()), None);
assert_eq!(ghost_claims::Total::<Test, ()>::get(), total_claims());
});
}
#[test]
fn small_claiming_works() {
new_test_ext().execute_with(|| {
assert_ok!(Claims::claim(
RuntimeOrigin::signed(alice_account_id()),
first_eth_public_key(),
first_signature()));
assert_eq!(Balances::usable_balance(&alice_account_id()), 10);
assert_eq!(Balances::usable_balance(&first_account_id()), 0);
assert_eq!(Balances::usable_balance(&second_account_id()), 100);
assert_eq!(Balances::usable_balance(&third_account_id()), 1000);
assert_eq!(Club::rank_of(&alice_account_id()), None);
assert_eq!(Club::rank_of(&first_account_id()), None);
assert_eq!(Vesting::vesting_balance(&alice_account_id()), None);
assert_eq!(ghost_claims::Total::<Test, ()>::get(), total_claims() - 10);
})
}
#[test]
fn medium_claiming_works() {
new_test_ext().execute_with(|| {
assert_ok!(Claims::claim(
RuntimeOrigin::signed(alice_account_id()),
second_eth_public_key(),
second_signature(),
));
assert_eq!(Balances::usable_balance(&alice_account_id()), 100);
assert_eq!(Balances::usable_balance(&first_account_id()), 10);
assert_eq!(Balances::usable_balance(&second_account_id()), 0);
assert_eq!(Balances::usable_balance(&third_account_id()), 1000);
assert_eq!(Club::rank_of(&alice_account_id()), Some(1));
assert_eq!(Club::rank_of(&second_account_id()), None);
assert_eq!(Vesting::vesting_balance(&alice_account_id()), None);
assert_eq!(ghost_claims::Total::<Test, ()>::get(), total_claims() - 100);
})
}
#[test]
fn big_claiming_works() {
new_test_ext().execute_with(|| {
assert_ok!(Claims::claim(
RuntimeOrigin::signed(alice_account_id()),
third_eth_public_key(),
third_signature(),
));
assert_eq!(Balances::usable_balance(&alice_account_id()), 200);
assert_eq!(Balances::usable_balance(&first_account_id()), 10);
assert_eq!(Balances::usable_balance(&second_account_id()), 100);
assert_eq!(Balances::usable_balance(&third_account_id()), 0);
assert_eq!(Club::rank_of(&alice_account_id()), Some(3));
assert_eq!(Club::rank_of(&third_account_id()), None);
assert_eq!(Vesting::vesting_balance(&alice_account_id()), Some(800));
assert_eq!(ghost_claims::Total::<Test, ()>::get(), total_claims() - 1000);
assert_ok!(Balances::transfer_allow_death(
RuntimeOrigin::signed(alice_account_id()),
bob_account_id(),
200));
})
}
#[test]
fn multiple_accounts_claiming_works() {
new_test_ext().execute_with(|| {
assert_ok!(Claims::claim(
RuntimeOrigin::signed(alice_account_id()),
first_eth_public_key(),
first_signature(),
));
assert_ok!(Claims::claim(
RuntimeOrigin::signed(alice_account_id()),
second_eth_public_key(),
second_signature(),
));
assert_ok!(Claims::claim(
RuntimeOrigin::signed(alice_account_id()),
third_eth_public_key(),
third_signature(),
));
assert_eq!(Balances::usable_balance(&alice_account_id()), 310);
assert_eq!(Balances::usable_balance(&first_account_id()), 0);
assert_eq!(Balances::usable_balance(&second_account_id()), 0);
assert_eq!(Balances::usable_balance(&third_account_id()), 0);
assert_eq!(Club::rank_of(&alice_account_id()), Some(3));
assert_eq!(Club::rank_of(&third_account_id()), None);
assert_eq!(Vesting::vesting_balance(&alice_account_id()), Some(800));
assert_eq!(ghost_claims::Total::<Test, ()>::get(), 0);
})
}
#[test]
fn multiple_accounts_reverese_claiming_works() {
new_test_ext().execute_with(|| {
assert_ok!(Claims::claim(
RuntimeOrigin::signed(alice_account_id()),
third_eth_public_key(),
third_signature(),
));
assert_ok!(Claims::claim(
RuntimeOrigin::signed(alice_account_id()),
second_eth_public_key(),
second_signature(),
));
assert_ok!(Claims::claim(
RuntimeOrigin::signed(alice_account_id()),
first_eth_public_key(),
first_signature(),
));
assert_eq!(Balances::usable_balance(&alice_account_id()), 310);
assert_eq!(Balances::usable_balance(&first_account_id()), 0);
assert_eq!(Balances::usable_balance(&second_account_id()), 0);
assert_eq!(Balances::usable_balance(&third_account_id()), 0);
assert_eq!(Club::rank_of(&alice_account_id()), Some(3));
assert_eq!(Club::rank_of(&third_account_id()), None);
assert_eq!(Vesting::vesting_balance(&alice_account_id()), Some(800));
assert_eq!(ghost_claims::Total::<Test, ()>::get(), 0);
})
}
#[test]
fn cannot_claim_with_bad_signature() {
new_test_ext().execute_with(|| {
assert_err!(Claims::claim(
RuntimeOrigin::signed(alice_account_id()),
first_eth_public_key(),
wrong_signature()),
crate::Error::<Test>::InvalidEthereumAddress);
assert_eq!(Balances::usable_balance(&alice_account_id()), 0);
assert_eq!(Balances::usable_balance(&first_account_id()), 10);
assert_eq!(Balances::usable_balance(&second_account_id()), 100);
assert_eq!(Balances::usable_balance(&third_account_id()), 1000);
assert_eq!(Club::rank_of(&alice_account_id()), None);
assert_eq!(Club::rank_of(&first_account_id()), None);
assert_eq!(Club::rank_of(&second_account_id()), Some(1));
assert_eq!(Club::rank_of(&third_account_id()), Some(3));
assert_eq!(Vesting::vesting_balance(&alice_account_id()), None);
assert_eq!(ghost_claims::Total::<Test, ()>::get(), total_claims());
})
}
#[test]
fn cannot_claim_with_wrong_address() {
new_test_ext().execute_with(|| {
assert_err!(Claims::claim(
RuntimeOrigin::signed(bob_account_id()),
first_eth_public_key(),
first_signature()),
crate::Error::<Test>::InvalidEthereumAddress);
assert_eq!(Balances::usable_balance(&bob_account_id()), 0);
assert_eq!(Balances::usable_balance(&alice_account_id()), 0);
assert_eq!(Balances::usable_balance(&first_account_id()), 10);
assert_eq!(Balances::usable_balance(&second_account_id()), 100);
assert_eq!(Balances::usable_balance(&third_account_id()), 1000);
assert_eq!(Club::rank_of(&alice_account_id()), None);
assert_eq!(Club::rank_of(&first_account_id()), None);
assert_eq!(Club::rank_of(&second_account_id()), Some(1));
assert_eq!(Club::rank_of(&third_account_id()), Some(3));
assert_eq!(Vesting::vesting_balance(&alice_account_id()), None);
assert_eq!(ghost_claims::Total::<Test, ()>::get(), total_claims());
})
}
#[test]
fn cannot_claim_nothing() {
new_test_ext().execute_with(|| {
assert_err!(Claims::claim(
RuntimeOrigin::signed(bob_account_id()),
fourth_eth_public_key(),
fourth_signature()),
crate::Error::<Test>::NoBalanceToClaim);
assert_eq!(Balances::usable_balance(&bob_account_id()), 0);
assert_eq!(Balances::usable_balance(&fourth_account_id()), 0);
assert_eq!(Vesting::vesting_balance(&bob_account_id()), None);
assert_eq!(ghost_claims::Total::<Test, ()>::get(), total_claims());
})
}
#[test]
fn event_emitted_during_claim() {
new_test_ext().execute_with(|| {
let account = third_account_id();
let amount = Balances::usable_balance(&account);
let rank = Club::rank_of(&account);
System::reset_events();
assert_eq!(System::event_count(), 0);
assert_ok!(Claims::claim(
RuntimeOrigin::signed(alice_account_id()),
third_eth_public_key(),
third_signature(),
));
System::assert_has_event(RuntimeEvent::Claims(
crate::Event::Claimed {
receiver: alice_account_id(),
donor: account,
amount,
rank,
}));
})
}

View File

@ -1,9 +0,0 @@
use frame_support::weights::Weight;
pub trait WeightInfo {
fn claim() -> Weight;
}
impl WeightInfo for () {
fn claim() -> Weight { Weight::zero() }
}

108
pallets/exodus/Cargo.toml Normal file
View File

@ -0,0 +1,108 @@
[package]
name = "ghost-exodus"
version = "0.0.11"
description = "Threshold signature generation with DKG included"
license.workspace = true
authors.workspace = true
edition.workspace = true
homepage.workspace = true
repository.workspace = true
[dependencies]
log = { workspace = true }
hex = { workspace = true }
codec = { workspace = true, features = ["max-encoded-len"] }
scale-info = { workspace = true, features = ["derive"] }
strum = { workspace = true, features = ["derive"] }
num-traits = { workspace = true }
frame-benchmarking = { workspace = true, optional = true }
frame-support = { workspace = true }
frame-system = { workspace = true }
sp-arithmetic = { workspace = true }
sp-application-crypto = { workspace = true }
sp-runtime = { workspace = true }
sp-staking = { workspace = true }
sp-core = { workspace = true }
sp-std = { workspace = true }
sp-io = { workspace = true }
frost-secp256k1-tr = { workspace = true }
frost-ed25519 = { workspace = true }
frost-core = { workspace = true }
rand_chacha = { workspace = true }
hkdf = { workspace = true }
chacha20poly1305 = { workspace = true }
sha2 = { workspace = true }
k256 = { workspace = true }
parking_lot = { workspace = true }
ghost-traits = { workspace = true }
ghost-helpers = { workspace = true }
pallet-balances = { workspace = true }
pallet-session = { workspace = true }
pallet-staking = { workspace = true }
ghost-networks = { workspace = true }
pallet-staking-reward-curve = { workspace = true }
[dev-dependencies]
sp-keystore = { workspace = true }
frame-executive = { workspace = true }
rand = { workspace = true }
[features]
default = ["std"]
std = [
"frame-benchmarking?/std",
"scale-info/std",
"strum/std",
"log/std",
"hex/std",
"k256/std",
"num-traits/std",
"frame-support/std",
"frame-system/std",
"sha2/std",
"hkdf/std",
"chacha20poly1305/std",
"rand_chacha/std",
"sp-arithmetic/std",
"sp-application-crypto/std",
"sp-runtime/std",
"sp-staking/std",
"sp-core/std",
"sp-std/std",
"sp-io/std",
"frost-secp256k1-tr/std",
"frost-ed25519/std",
"frost-core/std",
"ghost-traits/std",
"ghost-helpers/std",
"pallet-balances/std",
"pallet-session/std",
"pallet-staking/std",
"ghost-networks/std",
"frame-executive/std",
"sp-keystore/std",
]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
"sp-staking/runtime-benchmarks",
"pallet-balances/runtime-benchmarks",
"pallet-staking/runtime-benchmarks",
"ghost-networks/runtime-benchmarks",
]
try-runtime = [
"frame-support/try-runtime",
"frame-system/try-runtime",
"sp-runtime/try-runtime",
"pallet-balances/try-runtime",
"pallet-session/try-runtime",
"frame-executive/try-runtime",
"ghost-networks/try-runtime",
]

View File

@ -0,0 +1,20 @@
[package]
name = "ghost-exodus-runtime-api"
version = "0.0.0"
description = "RPC runtime API for ghost EXODUS pallet"
license.workspace = true
authors.workspace = true
edition.workspace = true
homepage.workspace = true
repository.workspace = true
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { workspace = true, features = ["derive"] }
sp-api = { workspace = true }
[features]
default = ["std"]
std = ["codec/std", "sp-api/std"]

View File

@ -0,0 +1,7 @@
#![cfg_attr(not(feature = "std"), no_std)]
sp_api::decl_runtime_apis! {
pub trait ExodusApi<NetworkId> {
fn verifying_key(network_id: NetworkId) -> Vec<u8>;
}
}

View File

@ -0,0 +1,853 @@
#![cfg(feature = "runtime-benchmarks")]
use super::*;
use frame_benchmarking::v2::*;
use frame_system::RawOrigin;
use ghost_helpers::networks::NetworkDataBuilder;
fn prepare_pallet<T: Config>(authorities_len: usize) -> (
PendingDkgAuthorities<ParticipantsBitmap<T>, BlockNumberFor<T>>,
NetworkCurve,
T::AuthorityId,
AuthIndex
) {
let network_curve = NetworkCurve::Secp256k1;
let authority = T::AuthorityId::generate_pair(None);
let authorities = vec![authority.clone(); authorities_len];
let bounded_authorities = WeakBoundedVec::<_, T::MaxAuthorities>::try_from(authorities.clone())
.expect("more than the maximum number of keys provided");
QualificationAuthorities::<T>::insert(network_curve, bounded_authorities);
let network_id = NetworkIdOf::<T>::default();
let network_data = NetworkDataBuilder::default()
.with_network_type(NetworkType::Evm)
.with_network_curve(NetworkCurve::Secp256k1)
.build();
T::NetworkDataHandler::register(network_id, network_data)
.expect("network should be valid for insertion");
let mut dkg_state = PendingDkgAuthorities::default();
dkg_state.new_dkg(authorities_len);
(dkg_state, network_curve, authority, 0)
}
#[benchmarks(where T: Config)]
mod benchmarks {
use super::*;
#[benchmark]
fn register_round0_package() -> Result<(), BenchmarkError> {
let dummy_hash = ExodusHash::repeat_byte(69);
let (mut dkg_state, network_curve, authority, authority_index) =
prepare_pallet::<T>(1);
dkg_state.next_phase();
QualificationDkgState::<T>::insert(&network_curve, dkg_state);
let round0_package = PackageContext::default()
.with_network_curve(NetworkCurve::Secp256k1)
.with_authority_index(0)
.with_dkg_round0(dummy_hash);
let dkg_package = DkgPackage::Round0(round0_package);
let signature = authority
.sign(&dkg_package.encode())
.expect("Signature should be created");
#[extrinsic_call]
_(RawOrigin::None, dkg_package, signature);
let expected_hash = Round0Packages::<T>::get(&network_curve, authority_index);
assert_eq!(expected_hash, dummy_hash);
Ok(())
}
#[benchmark]
fn register_round1_package(
c: Linear<4, { T::MaxAuthorities::get() }>
) -> Result<(), BenchmarkError> {
let max_signers = c as AuthIndex;
let min_signers = get_byzantium_quorum_threshold(max_signers);
let (mut dkg_state, network_curve, authority, authority_index) =
prepare_pallet::<T>(max_signers as usize);
dkg_state.next_phase();
dkg_state.next_phase();
dkg_state.insert_index(authority_index);
QualificationDkgState::<T>::insert(&network_curve, dkg_state);
let mut rng = Pallet::<T>::get_offchain_rng(authority_index);
let (_, public_package) = network_curve.dkg_part1(
authority_index,
max_signers,
min_signers,
&mut rng,
).expect("DKG part1 should work");
let package_hash = ExodusHash::from(blake2_256(&public_package));
Round0Packages::<T>::insert(network_curve, authority_index, package_hash);
let round1_package = PackageContext::default()
.with_network_curve(NetworkCurve::Secp256k1)
.with_authority_index(0)
.with_dkg_round1(public_package)
.expect("Round1 package should be valid");
let dkg_package = DkgPackage::Round1(round1_package);
let signature = authority
.sign(&dkg_package.encode())
.expect("Signature should be created");
#[extrinsic_call]
_(RawOrigin::None, dkg_package, signature);
let participants = Round1Packages::<T>::get(&network_curve);
assert!(participants.contains(authority_index));
Ok(())
}
#[benchmark]
fn register_encrypted_round2_packages(
c: Linear<1, { T::MaxAuthorities::get() / 8 }>
) -> Result<(), BenchmarkError> {
let max_signers = (c * 8) as AuthIndex;
let (mut dkg_state, network_curve, authority, authority_index) =
prepare_pallet::<T>(max_signers as usize);
dkg_state.next_phase();
dkg_state.next_phase();
dkg_state.next_phase();
(0..max_signers).for_each(|i| {
dkg_state.insert_index(i);
});
let padded_participants = dkg_state
.highest_bit::<usize>()
.map(|bit_pos| bit_pos.saturating_add(1))
.unwrap_or_default();
QualificationDkgState::<T>::insert(&network_curve, dkg_state);
let bitmask_len = padded_participants.div_ceil(8);
let blob_len = round2_encrypted_package_size(
padded_participants,
network_curve.scalar_bytes_len(),
network_curve.header_bytes_len(),
);
let mut bitmask = vec![0u8; bitmask_len];
let blob = vec![0u8; blob_len];
(0..max_signers).for_each(|i| {
if i != authority_index {
let byte_index = (i / 8) as usize;
let bit_index = (i % 8) as u8;
bitmask[byte_index] |= 1 << bit_index;
}
});
let encryption_bundle = BundledPackages { bitmask, blob };
let round2_package = PackageContext::default()
.with_network_curve(NetworkCurve::Secp256k1)
.with_authority_index(authority_index)
.with_dkg_round2(encryption_bundle)
.expect("Round2 package should be valid");
let dkg_package = DkgPackage::Round2(round2_package);
let signature = authority
.sign(&dkg_package.encode())
.expect("Signature should be created");
#[extrinsic_call]
_(RawOrigin::None, dkg_package, signature);
let participants = Round2Packages::<T>::get(&network_curve);
assert!(participants.contains(authority_index));
Ok(())
}
#[benchmark]
fn register_round3_complaints(
c: Linear<1, { T::MaxAuthorities::get() / 8 }>
) -> Result<(), BenchmarkError> {
let max_signers = (c * 8) as AuthIndex;
let (mut dkg_state, network_curve, authority, authority_index) =
prepare_pallet::<T>(max_signers as usize);
dkg_state.next_phase();
dkg_state.next_phase();
dkg_state.next_phase();
dkg_state.next_phase();
(0..max_signers).for_each(|i| {
dkg_state.insert_index(i);
});
QualificationDkgState::<T>::insert(&network_curve, dkg_state);
let bitvec_len = (max_signers + 7) / 8;
let mut accused_indexes = vec![0u8; bitvec_len as usize];
(0..max_signers).for_each(|i| {
if i != authority_index {
let byte_index = (i / 8) as usize;
let bit_index = (i % 8) as u8;
accused_indexes[byte_index] |= 1 << bit_index;
}
});
let round3_package = PackageContext::default()
.with_network_curve(NetworkCurve::Secp256k1)
.with_authority_index(authority_index)
.with_dkg_round3(accused_indexes)
.expect("Round3 package should be valid");
let dkg_package = DkgPackage::Round3(round3_package);
let signature = authority
.sign(&dkg_package.encode())
.expect("Signature should be created");
#[extrinsic_call]
_(RawOrigin::None, dkg_package, signature);
let complaints = Complaints::<T>::get(&network_curve);
let my_complaints = complaints.get(&authority_index)
.expect("Comlpaints should be registered");
assert_eq!(my_complaints.count_ones::<AuthIndex>(), max_signers - 1);
Ok(())
}
#[benchmark]
fn register_round4_justifications(
c: Linear<1, { T::MaxAuthorities::get() / 8 }>
) -> Result<(), BenchmarkError> {
let max_signers = (c * 8) as AuthIndex;
let (mut dkg_state, network_curve, authority, authority_index) =
prepare_pallet::<T>(max_signers as usize);
dkg_state.next_phase();
dkg_state.next_phase();
dkg_state.next_phase();
dkg_state.next_phase();
dkg_state.next_phase();
(0..max_signers).for_each(|i| {
dkg_state.insert_index(i);
});
let padded_participants = dkg_state
.highest_bit::<usize>()
.map(|bit_pos| bit_pos.saturating_add(1))
.unwrap_or_default();
QualificationDkgState::<T>::insert(&network_curve, dkg_state);
let bitmask_len = padded_participants.div_ceil(8);
let blob_len = round2_package_size(
padded_participants,
network_curve.scalar_bytes_len(),
network_curve.header_bytes_len(),
);
let mut bitmask = vec![0u8; bitmask_len];
let blob = vec![0u8; blob_len];
(0..max_signers).for_each(|i| {
if i != authority_index {
let byte_index = (i / 8) as usize;
let bit_index = (i % 8) as u8;
bitmask[byte_index] |= 1 << bit_index;
}
});
let justification_bundle = BundledPackages { bitmask, blob };
let round4_package = PackageContext::default()
.with_network_curve(NetworkCurve::Secp256k1)
.with_authority_index(authority_index)
.with_dkg_round4(justification_bundle)
.expect("Round4 package should be valid");
let dkg_package = DkgPackage::Round4(round4_package);
let signature = authority
.sign(&dkg_package.encode())
.expect("Signature should be created");
#[extrinsic_call]
_(RawOrigin::None, dkg_package, signature);
let justifications = Justifications::<T>::get(&network_curve);
let my_justifications = justifications.get(&authority_index)
.expect("Justifications should be registered");
assert_eq!(my_justifications.count_ones::<AuthIndex>(), max_signers - 1);
Ok(())
}
#[benchmark]
fn register_round5_verifying_package() -> Result<(), BenchmarkError> {
let max_signers = 4 as AuthIndex;
let (mut dkg_state, network_curve, authority, authority_index) =
prepare_pallet::<T>(max_signers as usize);
dkg_state.next_phase();
dkg_state.next_phase();
dkg_state.next_phase();
dkg_state.next_phase();
dkg_state.next_phase();
dkg_state.next_phase();
(0..max_signers).for_each(|i| {
dkg_state.insert_index(i);
});
let dkg_index = dkg_state.get_dkg_index();
QualificationDkgState::<T>::insert(&network_curve, dkg_state);
let dummy_merkle_root = ExodusHash::repeat_byte(69);
let dummy_verifying_key = vec![69u8; network_curve.element_bytes_len()];
let round5_package = PackageContext::default()
.with_network_curve(NetworkCurve::Secp256k1)
.with_authority_index(authority_index)
.with_dkg_round5(dummy_verifying_key.clone(), dummy_merkle_root)
.expect("Round5 package should be valid");
let dkg_package = DkgPackage::Round5(round5_package);
let signature = authority
.sign(&dkg_package.encode())
.expect("Signature should be created");
#[extrinsic_call]
_(RawOrigin::None, dkg_package, signature);
let bounded_verifying_key =
BoundedVec::<u8, ConstU32< { ELEMENT_MAX_BYTES }>>::try_from(
dummy_verifying_key,
).expect("Bounded verifying key should be correct");
let metadata_hashes = [
SubstrateBlake2Hasher::hash(dummy_merkle_root.as_ref()),
SubstrateBlake2Hasher::hash(bounded_verifying_key.as_ref()),
];
let verifying_hash = sequential_hash::<SubstrateBlake2Hasher, _>(metadata_hashes);
let verification_key = (network_curve, dkg_index, verifying_hash);
let participants = Verifications::<T>::get(verification_key);
assert!(participants.contains(authority_index));
let consensus = VerifyingKeyConsensus::<T>::get(&network_curve, dkg_index);
assert!(consensus.participants.contains(authority_index));
assert_eq!(consensus.participants.count_ones::<AuthIndex>(), 1);
assert_eq!(consensus.merkle_root, dummy_merkle_root);
assert_eq!(consensus.element_bytes, bounded_verifying_key);
Ok(())
}
#[benchmark]
fn register_round6_public_share_package(
c: Linear<1, { T::MaxAuthorities::get().next_power_of_two().trailing_zeros() }>
) -> Result<(), BenchmarkError> {
let computed_max_signers = (1u32 << c) as AuthIndex;
let constant_max_signers = T::MaxAuthorities::get() as AuthIndex;
let max_signers = sp_std::cmp::min(computed_max_signers, constant_max_signers);
let (mut dkg_state, network_curve, authority, authority_index) =
prepare_pallet::<T>(max_signers as usize);
dkg_state.next_phase();
dkg_state.next_phase();
dkg_state.next_phase();
dkg_state.next_phase();
dkg_state.next_phase();
dkg_state.next_phase();
dkg_state.next_phase();
(0..max_signers).for_each(|i| {
dkg_state.insert_index(i);
});
let dkg_index = dkg_state.get_dkg_index();
QualificationDkgState::<T>::insert(&network_curve, dkg_state);
let dummy_value = vec![69u8; network_curve.element_bytes_len()];
let (dummy_merkle_root, dummy_verifying_share, dummy_merkle_proof) =
with_ciphersuite!(network_curve, |f| {
let mut dummy_values = BTreeMap::new();
for i in 0..max_signers {
let non_zero_index = i.saturating_add(1);
let identifier: frost_core::Identifier<Ciphersuite> =
frost_core::Identifier::<Ciphersuite>::try_from(non_zero_index)
.expect("Identifier should be created");
dummy_values.insert(identifier, dummy_value.clone());
}
let merkle_tree =
NetworkCurve::build_merkle_tree(
&dummy_values,
|value: &Vec<u8>| Ok(value.clone()),
).expect("Merkle tree for verifying shares should be valid");
let merkle_root = merkle_tree.last().copied().unwrap();
let (verifying_share, merkle_proof) = NetworkCurve::generate_merkle_proof_from_tree(
&dummy_values,
&merkle_tree,
authority_index,
|value: &Vec<u8>| Ok(value.clone())
).expect("Merkle tree proof should be valid");
(merkle_root, verifying_share, merkle_proof)
});
VerifyingKeyConsensus::<T>::mutate(&network_curve, &dkg_index, |consensus| {
consensus.participants.insert(authority_index);
consensus.merkle_root = dummy_merkle_root;
});
let round6_package = PackageContext::default()
.with_network_curve(NetworkCurve::Secp256k1)
.with_authority_index(authority_index)
.with_dkg_round6(dummy_verifying_share.clone(), dummy_merkle_proof)
.expect("Round6 package should be valid");
let dkg_package = DkgPackage::Round6(round6_package);
let signature = authority
.sign(&dkg_package.encode())
.expect("Signature should be created");
#[extrinsic_call]
_(RawOrigin::None, dkg_package, signature);
let participants = VerifyingSharesParticipants::<T>::get(&network_curve, dkg_index);
assert!(participants.contains(authority_index));
let bundled_verifying_share =
BoundedVec::<u8, ConstU32<{ ELEMENT_MAX_BYTES }>>::try_from(
dummy_verifying_share,
).expect("Verifying share should be bundled");
let verifying_share_key = (network_curve, dkg_index, authority_index);
assert_eq!(
VerifyingShares::<T>::get(verifying_share_key).unwrap(),
bundled_verifying_share,
);
Ok(())
}
#[benchmark]
fn register_nonce_commitment() -> Result<(), BenchmarkError> {
let network_id: NetworkIdOf<T> = 69_420u64.unique_saturated_into();
let (mut dkg_state, network_curve, authority, authority_index) =
prepare_pallet::<T>(1);
loop {
if !dkg_state.is_dkg_pending() { break; }
dkg_state.next_phase();
}
dkg_state.insert_index(authority_index);
let exodus_session = CurrentExodus::<T>::get();
let dummy_address = EvmAddress::repeat_byte(69);
let dummy_nonce = vec![0u8; network_curve.element_bytes_len()];
let create_bond = GovernanceAction::Create {
market: [69, 420, 1337],
terms: [420, 1337],
token: dummy_address,
intervals: [34, 35],
booleans: [true, true],
};
let request = ExodusRequest::<NetworkIdOf<T>, BalanceOf<T>>::evm_governance(
exodus_session,
network_id,
dummy_address,
create_bond,
);
let authorities = QualificationAuthorities::<T>::get(&network_curve);
let active_dkg: ActivatedState<T> = (&dkg_state).into();
QualificationDkgState::<T>::insert(&network_curve, dkg_state);
ActiveDkgAuthorities::<T>::insert(&network_curve, active_dkg);
ActiveAuthorities::<T>::insert(&network_curve, authorities);
ExodusRequests::<T>::insert(network_curve, exodus_session, request);
let nonce_package = PackageContext::default()
.with_network_curve(NetworkCurve::Secp256k1)
.with_authority_index(authority_index)
.with_exodus_nonce_commitment(
exodus_session,
dummy_nonce.clone(),
dummy_nonce.clone(),
)
.expect("Nonce commitment package should be valid");
let exodus_package = ExodusPackage::NonceCommitment(nonce_package);
let signature = authority
.sign(&exodus_package.encode())
.expect("Signature should be created");
#[extrinsic_call]
_(RawOrigin::None, exodus_package, signature);
let roast_session = RoastSessions::<T>::get(&exodus_session, authority_index)
.expect("ROAST session should exist after nonce commitment");
let roast_state = RoastSessionStates::<T>::get(&exodus_session, roast_session);
assert!(roast_state.nonce_committers.contains(authority_index));
assert!(!roast_state.group_committers.contains(authority_index));
assert!(!roast_state.partial_signers.contains(authority_index));
let registered_nonces = NonceCommitments::<T>::get(
(exodus_session, roast_session),
authority_index,
).expect("Nonces should be registered during nonce commitment");
assert_eq!(registered_nonces.hiding, dummy_nonce.clone());
assert_eq!(registered_nonces.binding, dummy_nonce);
Ok(())
}
#[benchmark]
fn register_group_commitment() -> Result<(), BenchmarkError> {
let network_id: NetworkIdOf<T> = 69_420u64.unique_saturated_into();
let (mut dkg_state, network_curve, authority, authority_index) =
prepare_pallet::<T>(1);
loop {
if !dkg_state.is_dkg_pending() { break; }
dkg_state.next_phase();
}
dkg_state.insert_index(authority_index);
let exodus_session = CurrentExodus::<T>::get();
let dummy_bytes = EvmBytes32::repeat_byte(69);
let dummy_address = EvmAddress::repeat_byte(69);
let dummy_merkle_root = ExodusHash::repeat_byte(69);
let dummy_group_commitment = vec![0u8; network_curve.element_bytes_len()];
let create_bond = GovernanceAction::Create {
market: [69, 420, 1337],
terms: [420, 1337],
token: dummy_address,
intervals: [34, 35],
booleans: [true, true],
};
let request = ExodusRequest::<NetworkIdOf<T>, BalanceOf<T>>::evm_governance(
exodus_session,
network_id,
dummy_address,
create_bond,
);
let authorities = QualificationAuthorities::<T>::get(&network_curve);
let active_dkg: ActivatedState<T> = (&dkg_state).into();
QualificationDkgState::<T>::insert(&network_curve, dkg_state);
ActiveDkgAuthorities::<T>::insert(&network_curve, active_dkg);
ActiveAuthorities::<T>::insert(&network_curve, authorities);
ExodusRequests::<T>::insert(network_curve, exodus_session, request);
let mut roast_state = RoastSessionState::<ParticipantsBitmap::<T>>::default();
roast_state.status = RoastStatus::GroupCommitments;
roast_state.nonce_committers.insert(authority_index);
let roast_session = 3;
RoastSessions::<T>::insert(&exodus_session, authority_index, roast_session);
RoastSessionStates::<T>::insert(exodus_session, roast_session, roast_state);
let group_package = PackageContext::default()
.with_network_curve(NetworkCurve::Secp256k1)
.with_authority_index(authority_index)
.with_exodus_group_commitment(
exodus_session,
dummy_group_commitment.clone(),
dummy_bytes,
)
.expect("Group commitment package should be valid");
let exodus_package = ExodusPackage::GroupCommitment(group_package);
let signature = authority
.sign(&exodus_package.encode())
.expect("Signature should be created");
#[extrinsic_call]
_(RawOrigin::None, exodus_package, signature);
let bounded_group_commitment =
BoundedVec::<u8, ConstU32<{ ELEMENT_MAX_BYTES }>>::try_from(
dummy_group_commitment,
).expect("Group commtiment should be valid");
let roast_state = RoastSessionStates::<T>::get(&exodus_session, roast_session);
assert!(roast_state.nonce_committers.contains(authority_index));
assert!(roast_state.group_committers.contains(authority_index));
let consensus = GroupCommitmentsConsensus::<T>::get(exodus_session, roast_session);
assert!(consensus.participants.contains(authority_index));
assert_eq!(&consensus.element_bytes, &bounded_group_commitment);
assert_eq!(consensus.merkle_root, dummy_merkle_root);
let group_commitment_key = (exodus_session, roast_session, dummy_merkle_root, bounded_group_commitment);
let participants = GroupCommitters::<T>::get(group_commitment_key);
assert!(participants.contains(authority_index));
Ok(())
}
#[benchmark]
fn register_signature_share() -> Result<(), BenchmarkError> {
let max_signers = T::MaxAuthorities::get();
let threshold = get_byzantium_quorum_threshold(max_signers);
let network_id: NetworkIdOf<T> = 69_420u64.unique_saturated_into();
let (mut dkg_state, network_curve, authority, authority_index) =
prepare_pallet::<T>(max_signers as usize);
loop {
if !dkg_state.is_dkg_pending() { break; }
dkg_state.next_phase();
}
dkg_state.insert_index(authority_index);
let dkg_index = dkg_state.get_dkg_index();
let exodus_session = CurrentExodus::<T>::get();
let mut dummy_element = vec![0u8; network_curve.element_bytes_len()];
dummy_element[0] = 0x02;
let g_x_coords = [
0x79, 0xbe, 0x66, 0x7e, 0xf9, 0xdc, 0xbb, 0xac,
0x55, 0xa0, 0x62, 0x95, 0xce, 0x87, 0x0b, 0x07,
0x02, 0x9b, 0xfc, 0xdb, 0x2d, 0xce, 0x28, 0xd9,
0x59, 0xf2, 0x81, 0x5b, 0x16, 0xf8, 0x17, 0x98
];
dummy_element[1..33].copy_from_slice(&g_x_coords);
let dummy_address = EvmAddress::repeat_byte(69);
let create_bond = GovernanceAction::Create {
market: [69, 420, 1337],
terms: [420, 1337],
token: dummy_address,
intervals: [34, 35],
booleans: [true, true],
};
let request = ExodusRequest::<NetworkIdOf<T>, BalanceOf<T>>::evm_governance(
exodus_session,
network_id,
dummy_address,
create_bond,
);
let mut dummy_scalar = vec![0u8; network_curve.scalar_bytes_len()];
dummy_scalar[network_curve.scalar_bytes_len() - 1] = 1;
let dummy_signature_share = dummy_scalar.clone();
let dummy_binding_factor = dummy_scalar.clone();
let dummy_group_commitment = dummy_element.clone();
let (dummy_merkle_root, dummy_merkle_proof) =
with_ciphersuite!(network_curve, |f| {
let mut dummy_values = BTreeMap::new();
for i in 0..threshold {
let non_zero_index = (i as AuthIndex).saturating_add(1);
let identifier: frost_core::Identifier<Ciphersuite> =
frost_core::Identifier::<Ciphersuite>::try_from(non_zero_index)
.expect("Identifier should be created");
dummy_values.insert(identifier, dummy_binding_factor.clone());
}
let merkle_tree =
NetworkCurve::build_merkle_tree(
&dummy_values,
|value: &Vec<u8>| Ok(value.clone()),
).expect("Merkle tree for binding factor should be valid");
let merkle_root = merkle_tree.last().copied().unwrap();
let (_, merkle_proof) =
NetworkCurve::generate_merkle_proof_from_tree(
&dummy_values,
&merkle_tree,
authority_index,
|value: &Vec<u8>| Ok(value.clone())
).expect("Merkle tree proof should be valid");
(merkle_root, merkle_proof)
});
let authorities = QualificationAuthorities::<T>::get(&network_curve);
let active_dkg: ActivatedState<T> = (&dkg_state).into();
let active_dkg_index = active_dkg.get_dkg_index();
QualificationDkgState::<T>::insert(&network_curve, dkg_state);
ActiveDkgAuthorities::<T>::insert(&network_curve, active_dkg);
ActiveAuthorities::<T>::insert(&network_curve, authorities);
ExodusRequests::<T>::insert(network_curve, exodus_session, request);
let mut roast_state = RoastSessionState::<ParticipantsBitmap::<T>>::default();
roast_state.status = RoastStatus::PartialSignatures;
roast_state.nonce_committers.insert(authority_index);
roast_state.group_committers.insert(authority_index);
(0..threshold).for_each(|i| {
if i as AuthIndex == authority_index { return; }
roast_state.partial_signers.insert(i);
});
let roast_session = 3;
RoastSessions::<T>::insert(&exodus_session, authority_index, roast_session);
RoastSessionStates::<T>::insert(exodus_session, roast_session, roast_state);
let bounded_group_commitment = BoundedVec::try_from(dummy_group_commitment).unwrap();
let dummy_verifying_key = BoundedVec::try_from(dummy_element.clone()).unwrap();
let dummy_verifying_share = BoundedVec::try_from(dummy_element.clone()).unwrap();
let dummy_nonce = ExodusSeparatedNonce::new(
BoundedVec::try_from(dummy_element.clone()).unwrap(),
BoundedVec::try_from(dummy_element.clone()).unwrap(),
);
ActiveVerifyingKey::<T>::insert(&network_curve, dummy_verifying_key);
VerifyingShares::<T>::insert((&network_curve, active_dkg_index, authority_index), dummy_verifying_share);
NonceCommitments::<T>::insert((&exodus_session, &roast_session), authority_index, dummy_nonce);
GroupCommitmentsConsensus::<T>::mutate(&exodus_session, &roast_session, |consensus| {
consensus.element_bytes = bounded_group_commitment.clone();
consensus.merkle_root = dummy_merkle_root;
(0..threshold).for_each(|i| {
consensus.participants.insert(i);
});
});
let share_package = PackageContext::default()
.with_network_curve(NetworkCurve::Secp256k1)
.with_authority_index(authority_index)
.with_exodus_signature_share(
exodus_session,
dummy_signature_share.clone(),
dummy_merkle_proof,
dummy_binding_factor
)
.expect("Signature share package should be valid");
let exodus_package = ExodusPackage::SignatureShare(share_package);
let signature = authority
.sign(&exodus_package.encode())
.expect("Signature should be created");
#[extrinsic_call]
_(RawOrigin::None, exodus_package, signature);
let maybe_roast_session = RoastSessions::<T>::get(&exodus_session, authority_index);
assert!(maybe_roast_session.is_none());
let scalar_exists = SignatureScalars::<T>::contains_key(&exodus_session, &roast_session);
let consensus_exists = GroupCommitmentsConsensus::<T>::contains_key(&exodus_session, &roast_session);
let committers_exists = GroupCommitters::<T>::contains_key((
&exodus_session,
&roast_session,
&dummy_merkle_root,
&bounded_group_commitment,
));
assert!(!scalar_exists);
assert!(!consensus_exists);
assert!(!committers_exists);
assert!(!ExodusRequests::<T>::contains_key(&network_curve, &exodus_session));
assert!(ExodusSignedGovernance::<T>::contains_key((&exodus_session, network_id), dkg_index));
Ok(())
}
#[benchmark]
fn register_evm_bridge_out_exodus() -> Result<(), BenchmarkError> {
let caller: T::AccountId = frame_benchmarking::whitelisted_caller();
let (_, network_curve, _, _) = prepare_pallet::<T>(1usize);
let network_id = NetworkIdOf::<T>::default();
let amount: BalanceOf<T> = 10_000u32.into();
let bounty = Perbill::from_percent(5);
let receiver = EvmAddress::from([69u8; 20]);
QualificationDkgState::<T>::mutate(&network_curve, |state| {
loop {
if state.is_dkg_finalized() { break; }
state.next_phase();
}
});
let total_existential = T::Currency::minimum_balance();
let initial_balance = total_existential
.saturating_mul(5u32.into())
.saturating_add(amount);
let _ = T::Currency::make_free_balance_be(&caller, initial_balance);
let _ = T::NetworkDataHandler::register_incoming(&network_id, amount).unwrap();
let current_exodus_session = CurrentExodus::<T>::get();
#[extrinsic_call]
_(
RawOrigin::Signed(caller.clone()),
network_id,
amount,
bounty,
receiver,
);
assert!(ExodusRequests::<T>::contains_key(&network_curve, current_exodus_session));
assert_eq!(CurrentExodus::<T>::get(), current_exodus_session + 1);
assert_eq!(T::Currency::free_balance(&caller), initial_balance - amount);
Ok(())
}
impl_benchmark_test_suite!(
Pallet,
crate::mock::new_test_ext_benchmarks(),
crate::mock::Runtime
);
}

View File

@ -0,0 +1,94 @@
use ghost_helpers::networks::NetworkCurve;
use crate::AuthIndex;
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum ExodusError {
InvalidParticipantId,
DeserializationError,
SerializationError,
InvalidMinSigners(AuthIndex, AuthIndex),
InvalidMaxSigners(AuthIndex),
DKGNotSupported(NetworkCurve),
IncorrectNumberOfCoefficients,
IncorrectNumberOfCommitments,
IncorrectNumberOfPackages,
IncorrectPackage,
PackageNotFound,
InvalidSecretShare,
InvalidProofOfKnowledge,
MissingCommitment,
IncorrectCommitment,
UnknownIdentifier,
IdentityCommitment,
DuplicatedIdentifier,
IncorrectNumberOfIdentifiers,
InvalidSignature,
InvalidSignatureShare,
InvalidNonceCommitments,
EncryptionFailed,
DecryptionFailed,
HKDFFailed,
InvalidNonceLength,
MalformedSigningKey,
NoStoredNetworks,
OffchainTimeoutPeriod,
BadSignature(AuthIndex),
TransactionSubmissionFailed(AuthIndex),
UnknownReceiverIndex(AuthIndex),
CouldNotConstructMessage(AuthIndex),
NothingToWrite,
UnknownPrefix,
UnexpectedRound,
InvalidMerkleProof,
InvalidIdentityElement,
NoActiveAuthorities,
Unknown,
}
impl core::fmt::Debug for ExodusError {
fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
match *self {
ExodusError::InvalidParticipantId => write!(fmt, "Participant index could not be parsed to identifier."),
ExodusError::DeserializationError => write!(fmt, "Error during deserializing value."),
ExodusError::SerializationError => write!(fmt, "Error serializing value."),
ExodusError::InvalidMinSigners(min, max) => write!(fmt, "Number of min_signers ({min}) must be at least 2 and not larger than max_signers ({max})."),
ExodusError::InvalidMaxSigners(max_signers) => write!(fmt, "Number of max_signers must be at least 2, {max_signers} given."),
ExodusError::DKGNotSupported(ref curve) => write!(fmt, "The ciphersuite {curve:?} does not support DKG."),
ExodusError::IncorrectNumberOfCoefficients => write!(fmt, "Incorrect number of coefficients; should be equal to min signers."),
ExodusError::IncorrectNumberOfCommitments => write!(fmt, "Incorrect number of commitments; should be equal to min signers."),
ExodusError::InvalidNonceCommitments => write!(fmt, "Hiding nonce commitments indexes should match binding nonce commitments."),
ExodusError::InvalidProofOfKnowledge => write!(fmt, "The proof of knowledge is not valid."),
ExodusError::IncorrectNumberOfPackages => write!(fmt, "Incorrect number of packages; should be equal to max signers."),
ExodusError::IncorrectPackage => write!(fmt, "The incorrect package was specified."),
ExodusError::PackageNotFound => write!(fmt, "Round 1 package not found for Round 2 participant."),
ExodusError::InvalidSecretShare => write!(fmt, "Invalid secret share."),
ExodusError::MissingCommitment => write!(fmt, "The Signing Package must contain the participant's Commitments."),
ExodusError::IncorrectCommitment => write!(fmt, "The participant's commitment is incorrect."),
ExodusError::UnknownIdentifier => write!(fmt, "Unknown identifier."),
ExodusError::IdentityCommitment => write!(fmt, "Commitment equals the identity."),
ExodusError::DuplicatedIdentifier => write!(fmt, "Duplicated identifier."),
ExodusError::IncorrectNumberOfIdentifiers => write!(fmt, "Incorrect number of identifiers."),
ExodusError::InvalidSignature => write!(fmt, "Invalid signature."),
ExodusError::InvalidSignatureShare => write!(fmt, "Invalid signature share."),
ExodusError::EncryptionFailed => write!(fmt, "Encryption failed."),
ExodusError::DecryptionFailed => write!(fmt, "Decryption failed."),
ExodusError::HKDFFailed => write!(fmt, "HKDF failed."),
ExodusError::InvalidNonceLength => write!(fmt, "Encryption nonce has invalid length."),
ExodusError::MalformedSigningKey => write!(fmt, "Malformed signing key encoding."),
ExodusError::NoStoredNetworks => write!(fmt, "No stored networks to be worked on."),
ExodusError::OffchainTimeoutPeriod => write!(fmt, "Offchain request should be in-flight."),
ExodusError::BadSignature(auth_index) => write!(fmt, "Signature could not be created from {auth_index}."),
ExodusError::TransactionSubmissionFailed(auth_index) => write!(fmt, "Could not submit transaction from {auth_index}."),
ExodusError::UnknownReceiverIndex(receiver_index) => write!(fmt, "Unknown receiver index {receiver_index} found."),
ExodusError::CouldNotConstructMessage(auth_index) => write!(fmt, "Could not construct message during OCW from {auth_index}."),
ExodusError::NothingToWrite => write!(fmt, "Nothing to write into local storage."),
ExodusError::UnknownPrefix => write!(fmt, "Unknown prefix used for local storage."),
ExodusError::UnexpectedRound => write!(fmt, "Unexpected round provided."),
ExodusError::InvalidMerkleProof => write!(fmt, "Invalid merkle proof provided."),
ExodusError::InvalidIdentityElement => write!(fmt, "Invalid identity element provided."),
ExodusError::NoActiveAuthorities => write!(fmt, "No active authorities for signing exodus transactions."),
ExodusError::Unknown => write!(fmt, "Unknown error."),
}
}
}

View File

@ -0,0 +1,26 @@
use crate::{AuthIndex, ExodusError, NetworkCurve};
use ghost_traits::exodus::IdentifierConverter;
impl IdentifierConverter<AuthIndex, ExodusError> for NetworkCurve {
fn convert_identifier_to_index(
identifier_bytes: &[u8],
) -> Result<AuthIndex, ExodusError> {
const SIZE: usize = core::mem::size_of::<AuthIndex>();
let start = identifier_bytes.len().checked_sub(SIZE)
.ok_or(ExodusError::InvalidParticipantId)?;
let bytes_array = identifier_bytes.get(start..)
.and_then(|slice| slice.try_into().ok())
.ok_or(ExodusError::InvalidParticipantId)?;
AuthIndex::from_be_bytes(bytes_array)
.checked_sub(1)
.ok_or(ExodusError::InvalidParticipantId)
}
fn non_zero_index(index: AuthIndex) -> Result<AuthIndex, ExodusError> {
index.checked_add(1).ok_or(ExodusError::InvalidParticipantId)
}
}

View File

@ -0,0 +1,343 @@
use sp_std::{collections::btree_map::BTreeMap, vec::Vec, result::Result};
use rand_chacha::rand_core::{CryptoRng, RngCore};
use ghost_traits::exodus::{
DistributedKeyGeneration, IdentifierConverter, MerkleTreeBuilder,
};
use crate::{AuthIndex, ExodusError, NetworkCurve};
impl DistributedKeyGeneration<AuthIndex, ExodusError> for NetworkCurve {
type RoundPackages = BTreeMap<AuthIndex, Vec<u8>>;
type Part1Result = Result<(Vec<u8>, Vec<u8>), ExodusError>;
type Part2Result = Result<(Vec<u8>, BTreeMap<AuthIndex, Vec<u8>>), ExodusError>;
type Part3Result = Result<
(
Vec<u8>, Vec<u8>,
Vec<<Self as MerkleTreeBuilder<AuthIndex, ExodusError>>::Hash>,
<Self as MerkleTreeBuilder<AuthIndex, ExodusError>>::Hash,
),
ExodusError
>;
fn dkg_narrow_round_max_signers(
&self,
secret_package_bytes: &[u8],
new_max_signers: AuthIndex,
round_number: u8,
) -> Result<Vec<u8>, ExodusError> {
with_ciphersuite!(self, |f| {
macro_rules! process_secret_package {
(
$package_type:ty,
$field1:ident : $action1:tt,
$field2:ident : $action2:tt,
) => {{
let secret_package = <$package_type>::deserialize(secret_package_bytes)
.map_err(|_| ExodusError::DeserializationError)?;
let min_signers = *secret_package.min_signers();
frost_core::keys::validate_num_of_signers(min_signers, new_max_signers)
.map_err(|err| match err {
f::Error::InvalidMinSigners => {
ExodusError::InvalidMinSigners(min_signers, new_max_signers)
},
f::Error::InvalidMaxSigners => {
ExodusError::InvalidMaxSigners(new_max_signers)
},
_ => ExodusError::Unknown,
})?;
macro_rules! apply_action {
($field:ident, clone) => { secret_package.$field().clone() };
($field:ident, value) => { secret_package.$field() };
}
let new_secret_package = <$package_type>::new(
secret_package.identifier().clone(),
apply_action!($field1, $action1),
apply_action!($field2, $action2),
min_signers,
new_max_signers,
);
new_secret_package.serialize()
.map_err(|_| ExodusError::SerializationError)
}};
}
match round_number {
1 => process_secret_package!(
f::keys::dkg::round1::SecretPackage,
coefficients: value,
commitment: clone,
),
2 => process_secret_package!(
f::keys::dkg::round2::SecretPackage,
commitment: clone,
secret_share: value,
),
_ => Err(ExodusError::UnexpectedRound),
}
})
}
fn dkg_verify_proof_of_knowledge(
&self,
index: AuthIndex,
round1_package_bytes: &[u8],
) -> Result<AuthIndex, ExodusError> {
with_ciphersuite!(self, |f| {
let identifier = Self::non_zero_index(index)?
.try_into()
.map_err(|_| ExodusError::InvalidParticipantId)?;
let round1_package = f::keys::dkg::round1::Package::deserialize(round1_package_bytes)
.map_err(|_| ExodusError::DeserializationError)?;
let commitment = round1_package.commitment();
frost_core::keys::dkg::verify_proof_of_knowledge(
identifier,
&commitment,
&round1_package.proof_of_knowledge(),
).map_err(|_| ExodusError::InvalidProofOfKnowledge)?;
let coefficients = commitment.coefficients();
let coefficients_len = coefficients.len() as AuthIndex;
Ok(coefficients_len)
})
}
fn dkg_part1<R: RngCore + CryptoRng>(
&self,
index: AuthIndex,
max_signers: AuthIndex,
min_signers: AuthIndex,
mut rng: R,
) -> Self::Part1Result {
with_ciphersuite!(self, |f| {
let identifier = Self::non_zero_index(index)?
.try_into()
.map_err(|_| ExodusError::InvalidParticipantId)?;
let (round1_secret_package, round1_package) = f::keys::dkg::part1(
identifier,
max_signers,
min_signers,
&mut rng,
).map_err(|err| {
match err {
f::Error::InvalidMinSigners => ExodusError::InvalidMinSigners(min_signers, max_signers),
f::Error::InvalidMaxSigners => ExodusError::InvalidMaxSigners(max_signers),
f::Error::DKGNotSupported => ExodusError::DKGNotSupported(*self),
_ => ExodusError::Unknown,
}
})?;
let round1_secret_package_bytes = round1_secret_package.serialize()
.map_err(|_| ExodusError::SerializationError)?;
let round1_package_bytes = round1_package.serialize()
.map_err(|_| ExodusError::SerializationError)?;
Ok((round1_secret_package_bytes, round1_package_bytes))
})
}
fn dkg_part2(
&self,
round1_secret_package_bytes: &[u8],
round1_packages_bytes: &Self::RoundPackages,
) -> Self::Part2Result {
with_ciphersuite!(self, |f| {
type InnerGroup = <Ciphersuite as frost_core::Ciphersuite>::Group;
type InnerField = <InnerGroup as frost_core::Group>::Field;
let round1_secret_package = f::keys::dkg::round1::SecretPackage::deserialize(round1_secret_package_bytes)
.map_err(|_| ExodusError::DeserializationError)?;
let round1_packages = round1_packages_bytes
.iter()
.map(|(&index, round1_package_bytes)| {
let identifier = Self::non_zero_index(index)?
.try_into()
.map_err(|_| ExodusError::InvalidParticipantId)?;
let round1_package = f::keys::dkg::round1::Package::deserialize(round1_package_bytes)
.map_err(|_| ExodusError::DeserializationError)?;
Ok((identifier, round1_package))
})
.collect::<Result<BTreeMap<f::Identifier, f::keys::dkg::round1::Package>, ExodusError>>()?;
let (round2_secret_package, round2_packages) = f::keys::dkg::part2(
round1_secret_package,
&round1_packages,
).map_err(|err| match err {
f::Error::IncorrectNumberOfCommitments => ExodusError::IncorrectNumberOfCommitments,
f::Error::IncorrectNumberOfPackages => ExodusError::IncorrectNumberOfPackages,
f::Error::InvalidProofOfKnowledge { .. } => ExodusError::InvalidProofOfKnowledge,
_ => ExodusError::Unknown,
})?;
let round2_secret_package_bytes = round2_secret_package.serialize()
.map_err(|_| ExodusError::SerializationError)?;
let round2_packages_bytes = round2_packages
.iter()
.map(|(id, round2_package)| {
let id_scalar = id.to_scalar();
let id_bytes = <InnerField as frost_core::Field>::serialize(&id_scalar);
let index = Self::convert_identifier_to_index(id_bytes.as_ref())?;
let round2_package_bytes = round2_package.serialize()
.map_err(|_| ExodusError::SerializationError)?;
Ok((index, round2_package_bytes))
})
.collect::<Result<BTreeMap<AuthIndex, Vec<u8>>, ExodusError>>()?;
Ok((round2_secret_package_bytes, round2_packages_bytes))
})
}
fn dkg_verify_private_package(
&self,
index: AuthIndex,
round1_package_bytes: &[u8],
round2_package_bytes: &[u8],
) -> Result<(), ExodusError> {
with_ciphersuite!(self, |f| {
let identifier = Self::non_zero_index(index)?
.try_into()
.map_err(|_| ExodusError::InvalidParticipantId)?;
let commitment = f::keys::dkg::round1::Package::deserialize(round1_package_bytes)
.map(|round1_package| round1_package.commitment().clone())
.map_err(|_| ExodusError::DeserializationError)?;
let secret_share = f::keys::dkg::round2::Package::deserialize(round2_package_bytes)
.map(|round2_package| {
let signing_share = round2_package.signing_share().clone();
f::keys::SecretShare::new(identifier, signing_share, commitment)
})
.map_err(|_| ExodusError::DeserializationError)?;
let _ = secret_share.verify().map_err(|_| ExodusError::InvalidSecretShare)?;
Ok(())
})
}
fn dkg_part3(
&self,
authority_index: AuthIndex,
round2_secret_package_bytes: &[u8],
round1_packages_bytes: &Self::RoundPackages,
round2_packages_bytes: &Self::RoundPackages,
) -> Self::Part3Result {
with_ciphersuite!(self, |f| {
let round2_secret_package = f::keys::dkg::round2::SecretPackage::deserialize(round2_secret_package_bytes)
.map_err(|_| ExodusError::DeserializationError)?;
let round1_packages = round1_packages_bytes
.iter()
.map(|(&index, round1_package_bytes)| {
let identifier = Self::non_zero_index(index)?
.try_into()
.map_err(|_| ExodusError::InvalidParticipantId)?;
let round1_package = f::keys::dkg::round1::Package::deserialize(round1_package_bytes)
.map_err(|_| ExodusError::DeserializationError)?;
Ok((identifier, round1_package))
})
.collect::<Result<BTreeMap<f::Identifier, f::keys::dkg::round1::Package>, ExodusError>>()?;
let round2_packages = round2_packages_bytes
.iter()
.map(|(&index, round2_package_bytes)| {
let identifier = Self::non_zero_index(index)?
.try_into()
.map_err(|_| ExodusError::InvalidParticipantId)?;
let round2_package = f::keys::dkg::round2::Package::deserialize(round2_package_bytes)
.map_err(|_| ExodusError::DeserializationError)?;
Ok((identifier, round2_package))
})
.collect::<Result<BTreeMap<f::Identifier, f::keys::dkg::round2::Package>, ExodusError>>()?;
let (key_package, pubkey_package) = f::keys::dkg::part3(
&round2_secret_package,
&round1_packages,
&round2_packages,
).map_err(|err| match err {
f::Error::IncorrectNumberOfPackages => ExodusError::IncorrectNumberOfPackages,
f::Error::IncorrectPackage => ExodusError::IncorrectPackage,
f::Error::PackageNotFound => ExodusError::PackageNotFound,
f::Error::InvalidSecretShare { .. } => ExodusError::InvalidSecretShare,
f::Error::IncorrectNumberOfCommitments => ExodusError::IncorrectNumberOfCommitments,
_ => ExodusError::Unknown,
})?;
let key_package = key_package.normalize_package();
let pubkey_package = pubkey_package.normalize_package();
let key_package_bytes = key_package.serialize()
.map_err(|_| ExodusError::SerializationError)?;
let verifying_shares_merkle_tree =
Self::build_merkle_tree(
pubkey_package.verifying_shares(),
|verifying_share| verifying_share.serialize()
.map_err(|_| ExodusError::SerializationError)
)?;
let (_, merkle_proof) =
Self::generate_merkle_proof_from_tree(
pubkey_package.verifying_shares(),
&verifying_shares_merkle_tree,
authority_index,
|verifying_share| verifying_share.serialize()
.map_err(|_| ExodusError::SerializationError)
)?;
let merkle_root = verifying_shares_merkle_tree
.last()
.copied()
.ok_or(ExodusError::IncorrectNumberOfPackages)?;
let verifying_key_bytes = pubkey_package.verifying_key()
.serialize()
.map_err(|_| ExodusError::SerializationError)?;
Ok((
key_package_bytes,
verifying_key_bytes,
merkle_proof,
merkle_root,
))
})
}
fn dkg_derive_verifying_share(
&self,
secret_key_package_bytes: &[u8],
) -> Result<Vec<u8>, ExodusError> {
with_ciphersuite!(self, |f| {
let secret_key_package =
f::keys::KeyPackage::deserialize(secret_key_package_bytes)
.map_err(|_| ExodusError::DeserializationError)?;
secret_key_package
.verifying_share()
.serialize()
.map_err(|_| ExodusError::SerializationError)
})
}
}

View File

@ -0,0 +1,123 @@
use sp_std::{
vec::Vec,
result::Result,
marker::PhantomData,
};
use rand_chacha::rand_core::{CryptoRng, RngCore};
use ghost_traits::exodus::EllipticCurveDiffieHellman;
use hkdf::Hkdf as KeyDerivationFunction;
use sha2::Sha256 as Hashing;
use chacha20poly1305::{
aead::{self, Aead, KeyInit},
Key as EncryptionKey,
Nonce as EncryptionNonce,
ChaCha20Poly1305 as EncryptionCipher,
};
use crate::pallet::Config;
use crate::{AuthIndex, EncryptionData, ExodusError, NetworkCurve, DkgIndex};
impl<T: Config> EllipticCurveDiffieHellman<AuthIndex, EncryptionCipher, EncryptionData<T>, DkgIndex, ExodusError> for NetworkCurve {
fn ecdh_encrypt_package<R: RngCore + CryptoRng>(
&self,
cipher: &EncryptionCipher,
aad: &[u8],
msg: &[u8],
mut rng: R,
) -> Result<EncryptionData<T>, ExodusError> {
let payload = aead::Payload { msg, aad };
let mut nonce_bytes = [0u8; crate::ENCRYPTION_NONCE_MAX_BYTES as usize];
rng.fill_bytes(&mut nonce_bytes);
let nonce = EncryptionNonce::from_slice(&nonce_bytes);
let ciphertext = cipher.encrypt(&nonce, payload)
.map_err(|_| ExodusError::EncryptionFailed)?;
EncryptionData::try_new(ciphertext, nonce.as_slice())
}
fn ecdh_decrypt_package(
&self,
cipher: &EncryptionCipher,
encrypted_data: &EncryptionData<T>,
aad: &[u8],
) -> Result<Vec<u8>, ExodusError> {
let nonce = EncryptionNonce::from_iter(
encrypted_data.nonce.iter().cloned()
);
let payload = aead::Payload { msg: &encrypted_data.ciphertext, aad };
cipher.decrypt(&nonce, payload).map_err(|_| ExodusError::DecryptionFailed)
}
fn ecdh_prepare_additional_info(
&self,
sender_index: AuthIndex,
receiver_index: AuthIndex,
dkg_index: DkgIndex,
_marker: PhantomData<EncryptionData<T>>,
) -> Vec<u8> {
let mut additional_info = match self {
NetworkCurve::Secp256k1 => b"EXODUS-SECP256K1-DKG-V1".to_vec(),
NetworkCurve::Ed25519 => b"EXODUS-ED25519-DKG-V1".to_vec(),
};
additional_info.extend_from_slice(&sender_index.to_be_bytes());
additional_info.extend_from_slice(&receiver_index.to_be_bytes());
additional_info.extend_from_slice(&dkg_index.to_be_bytes());
additional_info
}
fn ecdh_get_cipher_from_keys(
&self,
public: &[u8],
secret: &[u8],
additional_info: &[u8],
_marker: PhantomData<EncryptionData<T>>,
) -> Result<EncryptionCipher, ExodusError> {
with_ciphersuite!(self, |f| {
let secret_package = f::keys::dkg::round1::SecretPackage::deserialize(secret)
.map_err(|_| ExodusError::DeserializationError)?;
let public_package = f::keys::dkg::round1::Package::deserialize(public)
.map_err(|_| ExodusError::DeserializationError)?;
let coefficients = secret_package.coefficients();
let local_sk = coefficients.first()
.ok_or(ExodusError::IncorrectNumberOfCoefficients)?;
let remote_pk = public_package.commitment().coefficients().first()
.ok_or(ExodusError::MissingCommitment)?.value();
let shared_secret_bytes = f::VerifyingKey::new(remote_pk * local_sk)
.serialize()
.map_err(|_| ExodusError::SerializationError)?;
let local_pk: f::VerifyingKey = f::SigningKey::from_scalar(*local_sk)
.map_err(|_| ExodusError::MalformedSigningKey)?
.into();
let local_pk_bytes = local_pk.serialize()
.map_err(|_| ExodusError::SerializationError)?;
let remote_pk_bytes = f::VerifyingKey::new(remote_pk)
.serialize()
.map_err(|_| ExodusError::SerializationError)?;
let mut keys = [local_pk_bytes, remote_pk_bytes];
keys.sort();
let salt = keys.concat();
let hk = KeyDerivationFunction::<Hashing>::new(Some(&salt), &shared_secret_bytes);
let mut encryption_key = EncryptionKey::default();
hk.expand(additional_info, &mut encryption_key)
.map_err(|_| ExodusError::HKDFFailed)?;
let cipher = EncryptionCipher::new_from_slice(&encryption_key)
.expect("could not happen; length is already correct qed");
Ok(cipher)
})
}
}

View File

@ -0,0 +1,446 @@
use ghost_traits::exodus::{
IdentifierConverter, MerkleTreeBuilder,
FlexibleRoundOptimizedSchnorrThresholdSignature,
};
use sp_std::{
vec::Vec, result::Result,
collections::{btree_set::BTreeSet, btree_map::BTreeMap},
};
use rand_chacha::rand_core::{CryptoRng, RngCore};
use ghost_helpers::networks::NetworkCurve;
use crate::{AuthIndex, ExodusHash, ExodusError};
impl FlexibleRoundOptimizedSchnorrThresholdSignature<AuthIndex, ExodusError> for NetworkCurve {
type Packages<'a> = BTreeMap<AuthIndex, &'a [u8]>;
type NoncePackages<'a> = BTreeMap<AuthIndex, (&'a [u8], &'a [u8])>;
fn header_bytes_len(&self) -> usize { 5 }
fn signature_bytes_len(&self) -> usize {
match self {
NetworkCurve::Secp256k1 => 64,
NetworkCurve::Ed25519 => 64,
}
}
fn element_bytes_len(&self) -> usize {
match self {
NetworkCurve::Secp256k1 => 33,
NetworkCurve::Ed25519 => 32,
}
}
fn scalar_bytes_len(&self) -> usize {
match self {
NetworkCurve::Secp256k1 => 32,
NetworkCurve::Ed25519 => 32,
}
}
fn verify_signature_share(
&self,
index: AuthIndex,
participants_iter: impl Iterator<Item = AuthIndex>,
signature_share_bytes: &[u8],
binding_factor_bytes: &[u8],
nonce_hiding_bytes: &[u8],
nonce_binding_bytes: &[u8],
group_commitment_bytes: &[u8],
verifying_share_bytes: &[u8],
verifying_key_bytes: &[u8],
message: &[u8],
) -> Result<(), ExodusError> {
with_ciphersuite!(self, |f| {
type InnerGroup = <Ciphersuite as frost_core::Ciphersuite>::Group;
type InnerField = <InnerGroup as frost_core::Group>::Field;
let identifier = Self::non_zero_index(index)?
.try_into()
.map_err(|_| ExodusError::InvalidParticipantId)?;
let identifier_set = participants_iter
.filter_map(|auth_index| {
let non_zero_index = Self::non_zero_index(auth_index).ok()?;
non_zero_index.try_into().ok()
})
.collect::<BTreeSet<f::Identifier>>();
let lambda_i =
frost_core::compute_lagrange_coefficient(&identifier_set, None, identifier)
.map_err(|err| match err {
f::Error::IncorrectNumberOfIdentifiers => ExodusError::IncorrectNumberOfIdentifiers,
f::Error::UnknownIdentifier => ExodusError::UnknownIdentifier,
f::Error::DuplicatedIdentifier => ExodusError::DuplicatedIdentifier,
_ => ExodusError::Unknown,
})?;
let signature_share =
f::round2::SignatureShare::deserialize(signature_share_bytes)
.map_err(|_| ExodusError::DeserializationError)?;
let group_commitment_serialization =
<InnerGroup as frost_core::Group>::Serialization::try_from(group_commitment_bytes.as_ref())
.map_err(|_| ExodusError::DeserializationError)?;
let mut group_commitment_element =
<InnerGroup as frost_core::Group>::deserialize(&group_commitment_serialization)
.map_err(|_| ExodusError::DeserializationError)?;
let binding_factor_serialization =
<InnerField as frost_core::Field>::Serialization::try_from(binding_factor_bytes.as_ref())
.map_err(|_| ExodusError::DeserializationError)?;
let binding_factor_scalar =
<InnerField as frost_core::Field>::deserialize(&binding_factor_serialization)
.map_err(|_| ExodusError::DeserializationError)?;
let verifying_share =
f::keys::VerifyingShare::deserialize(verifying_share_bytes)
.map_err(|_| ExodusError::DeserializationError)?;
let verifying_key = f::VerifyingKey::deserialize(verifying_key_bytes)
.map_err(|_| ExodusError::DeserializationError)?;
let hiding_element_serialization =
<InnerGroup as frost_core::Group>::Serialization::try_from(nonce_hiding_bytes.as_ref())
.map_err(|_| ExodusError::DeserializationError)?;
let hiding_element =
<InnerGroup as frost_core::Group>::deserialize(&hiding_element_serialization)
.map_err(|_| ExodusError::DeserializationError)?;
let binding_element_serialization =
<InnerGroup as frost_core::Group>::Serialization::try_from(nonce_binding_bytes.as_ref())
.map_err(|_| ExodusError::DeserializationError)?;
let binding_element =
<InnerGroup as frost_core::Group>::deserialize(&binding_element_serialization)
.map_err(|_| ExodusError::DeserializationError)?;
let mut r_i_element = hiding_element + (binding_element * binding_factor_scalar);
let group_commitment_wrapper =
frost_core::GroupCommitment::<Ciphersuite>::from_element(group_commitment_element);
if !group_commitment_wrapper.is_normalized() {
r_i_element = -r_i_element;
group_commitment_element = -group_commitment_element;
}
let challenge = <Ciphersuite as frost_core::Ciphersuite>::challenge(
&group_commitment_element,
&verifying_key,
message,
).map_err(|err| match err {
f::Error::SerializationError => ExodusError::SerializationError,
_ => ExodusError::Unknown,
})?;
let commitment_share =
frost_core::round1::GroupCommitmentShare::from_element(r_i_element);
signature_share.verify(
identifier,
&commitment_share,
&verifying_share,
lambda_i,
&challenge,
).map_err(|err| match err {
f::Error::InvalidSignatureShare { .. } => {
ExodusError::InvalidSignatureShare
},
_ => ExodusError::Unknown,
})
})
}
fn accumulate_signature_scalar(
&self,
signature_scalar_bytes: &[u8],
signature_share_bytes: &[u8],
) -> Result<Vec<u8>, ExodusError> {
with_ciphersuite!(self, |f| {
type InnerGroup = <Ciphersuite as frost_core::Ciphersuite>::Group;
type InnerField = <InnerGroup as frost_core::Group>::Field;
let signature_scalar_serialization =
<InnerField as frost_core::Field>::Serialization::try_from(signature_scalar_bytes)
.map_err(|_| ExodusError::DeserializationError)?;
let mut signature_scalar =
<InnerField as frost_core::Field>::deserialize(&signature_scalar_serialization)
.map_err(|_| ExodusError::DeserializationError)?;
let signature_share_serialization =
<InnerField as frost_core::Field>::Serialization::try_from(signature_share_bytes)
.map_err(|_| ExodusError::DeserializationError)?;
let signature_share =
<InnerField as frost_core::Field>::deserialize(&signature_share_serialization)
.map_err(|_| ExodusError::DeserializationError)?;
signature_scalar += signature_share;
Ok(<InnerField as frost_core::Field>::serialize(&signature_scalar).to_vec())
})
}
fn is_signature_valid(
&self,
verifying_key_bytes: &[u8],
r_element_bytes: &[u8],
z_scalar_bytes: &[u8],
message: &[u8],
) -> Result<(), ExodusError> {
with_ciphersuite!(self, |f| {
type InnerGroup = <Ciphersuite as frost_core::Ciphersuite>::Group;
type InnerField = <InnerGroup as frost_core::Group>::Field;
let verifying_key = f::VerifyingKey::deserialize(verifying_key_bytes)
.map_err(|_| ExodusError::DeserializationError)?;
let r_element_serialization =
<InnerGroup as frost_core::Group>::Serialization::try_from(r_element_bytes)
.map_err(|_| ExodusError::DeserializationError)?;
let r_element =
<InnerGroup as frost_core::Group>::deserialize(&r_element_serialization)
.map_err(|_| ExodusError::DeserializationError)?;
let z_scalar_serialization =
<InnerField as frost_core::Field>::Serialization::try_from(z_scalar_bytes)
.map_err(|_| ExodusError::DeserializationError)?;
let z_scalar =
<InnerField as frost_core::Field>::deserialize(&z_scalar_serialization)
.map_err(|_| ExodusError::DeserializationError)?;
let signature = f::Signature::new(r_element, z_scalar);
verifying_key.verify(message, &signature)
.map_err(|_| ExodusError::InvalidSignature)
})
}
fn generate_nonce_commitment<R: CryptoRng + RngCore>(
&self,
secret_key_package_bytes: &[u8],
mut rng: R
) -> Result<(Vec<u8>, Vec<u8>, Vec<u8>), ExodusError> {
with_ciphersuite!(self, |f| {
let signing_share =
f::keys::KeyPackage::deserialize(secret_key_package_bytes)
.map(|key_package| *key_package.signing_share())
.map_err(|_| ExodusError::DeserializationError)?;
let (signing_nonce, signing_commitment) =
f::round1::commit(&signing_share, &mut rng);
let signing_nonce_bytes = signing_nonce.serialize()
.map_err(|_| ExodusError::SerializationError)?;
let hiding_nonce_bytes = signing_commitment.hiding()
.serialize()
.map_err(|_| ExodusError::SerializationError)?;
let binding_nonce_bytes = signing_commitment.binding()
.serialize()
.map_err(|_| ExodusError::SerializationError)?;
Ok((signing_nonce_bytes, hiding_nonce_bytes, binding_nonce_bytes))
})
}
fn generate_group_commitment(
&self,
authority_index: AuthIndex,
nonce_commitments_bytes: &Self::NoncePackages<'_>,
verifying_key_bytes: &[u8],
message: &[u8],
) -> Result<(Vec<u8>, Vec<u8>, Vec<ExodusHash>, ExodusHash), ExodusError> {
with_ciphersuite!(self, |f| {
type InnerGroup = <Ciphersuite as frost_core::Ciphersuite>::Group;
type InnerField = <InnerGroup as frost_core::Group>::Field;
let verifying_key = f::VerifyingKey::deserialize(verifying_key_bytes)
.map_err(|_| ExodusError::DeserializationError)?;
let signing_commitments = nonce_commitments_bytes
.iter()
.filter_map(|(&index, &(hiding_bytes, binding_bytes))| {
let non_zero_index = Self::non_zero_index(index).ok()?;
let identifier = non_zero_index.try_into().ok()?;
let hiding_serialization = <InnerGroup as frost_core::Group>::Serialization::try_from(hiding_bytes.as_ref()).ok()?;
let hiding_nonce = f::round1::NonceCommitment::deserialize(&hiding_serialization).ok()?;
let binding_serialization = <InnerGroup as frost_core::Group>::Serialization::try_from((*binding_bytes).as_ref()).ok()?;
let binding_nonce = f::round1::NonceCommitment::deserialize(&binding_serialization).ok()?;
let commitments = f::round1::SigningCommitments::new(hiding_nonce, binding_nonce);
Some((identifier, commitments))
})
.collect::<BTreeMap<_, _>>();
if nonce_commitments_bytes.len() != signing_commitments.len() {
return Err(ExodusError::InvalidParticipantId);
}
let signing_package = f::SigningPackage::new(signing_commitments, message);
let binding_factors_list_wrapped =
frost_core::compute_binding_factor_list::<Ciphersuite>(&signing_package, &verifying_key, &[])
.map_err(|err| match err {
f::Error::SerializationError => ExodusError::SerializationError,
_ => ExodusError::Unknown,
})?;
let group_commitment =
frost_core::compute_group_commitment::<Ciphersuite>(&signing_package, &binding_factors_list_wrapped)
.map_err(|err| match err {
f::Error::IdentityCommitment => ExodusError::IdentityCommitment,
f::Error::UnknownIdentifier => ExodusError::UnknownIdentifier,
_ => ExodusError::Unknown,
})?;
let binding_factors_list = nonce_commitments_bytes
.iter()
.filter_map(|(&index, _)| {
let non_zero_index = Self::non_zero_index(index).ok()?;
let identifier = non_zero_index.try_into().ok()?;
let binding_factor = binding_factors_list_wrapped
.get(&identifier)
.map(|binding_factor| {
let fixed_bytes =
<InnerField as frost_core::Field>::Serialization::try_from(
binding_factor.serialize().as_slice()
).ok()?;
<InnerField as frost_core::Field>::deserialize(&fixed_bytes).ok()
})
.flatten()?;
Some((identifier, binding_factor))
})
.collect::<BTreeMap<_, _>>();
if nonce_commitments_bytes.len() != binding_factors_list.len() {
return Err(ExodusError::InvalidParticipantId);
}
let binding_factor_tree = Self::build_merkle_tree(
&binding_factors_list,
|binding_factor| {
let serialized = <InnerField as frost_core::Field>::serialize(binding_factor);
Ok(serialized.as_ref().to_vec())
}
)?;
let (self_binding_factor_bytes, binding_factors_proof) =
Self::generate_merkle_proof_from_tree(
&binding_factors_list,
&binding_factor_tree,
authority_index,
|binding_factor| {
let serialized = <InnerField as frost_core::Field>::serialize(binding_factor);
Ok(serialized.as_ref().to_vec())
}
)?;
let group_commitment_bytes =
<InnerGroup as frost_core::Group>::serialize(&group_commitment.to_element())
.map(|serialization| serialization.to_vec())
.map_err(|_| ExodusError::SerializationError)?;
let binding_factors_root = binding_factor_tree
.last()
.cloned()
.ok_or(ExodusError::IncorrectNumberOfPackages)?;
Ok((
group_commitment_bytes,
self_binding_factor_bytes,
binding_factors_proof,
binding_factors_root,
))
})
}
fn init_signing_package(
&self,
nonce_commitments_bytes: &Self::NoncePackages<'_>,
message: &[u8],
) -> Result<Vec<u8>, ExodusError> {
with_ciphersuite!(self, |f| {
type InnerGroup = <Ciphersuite as frost_core::Ciphersuite>::Group;
let signing_commitments = nonce_commitments_bytes
.iter()
.map(|(&index, &(hiding_bytes, binding_bytes))| {
let identifier = Self::non_zero_index(index)?
.try_into()
.map_err(|_| ExodusError::InvalidParticipantId)?;
let hiding_serialization =
<InnerGroup as frost_core::Group>::Serialization::try_from(hiding_bytes.as_ref())
.map_err(|_| ExodusError::DeserializationError)?;
let hiding_nonce =
f::round1::NonceCommitment::deserialize(&hiding_serialization)
.map_err(|_| ExodusError::DeserializationError)?;
let binding_serialization =
<InnerGroup as frost_core::Group>::Serialization::try_from((*binding_bytes).as_ref())
.map_err(|_| ExodusError::DeserializationError)?;
let binding_nonce =
f::round1::NonceCommitment::deserialize(&binding_serialization)
.map_err(|_| ExodusError::DeserializationError)?;
let commitments = f::round1::SigningCommitments::new(hiding_nonce, binding_nonce);
Ok((identifier, commitments))
})
.collect::<Result<BTreeMap<f::Identifier, f::round1::SigningCommitments>, ExodusError>>()?;
f::SigningPackage::new(signing_commitments, message)
.serialize()
.map_err(|_| ExodusError::SerializationError)
})
}
fn partial_sign_message(
&self,
signing_package_bytes: &[u8],
signer_nonces_bytes: &[u8],
secret_share_bytes: &[u8],
) -> Result<Vec<u8>, ExodusError> {
with_ciphersuite!(self, |f| {
let signing_package = f::SigningPackage::deserialize(signing_package_bytes)
.map_err(|_| ExodusError::DeserializationError)?;
let signer_nonces = f::round1::SigningNonces::deserialize(signer_nonces_bytes)
.map_err(|_| ExodusError::DeserializationError)?;
let secret_share = f::keys::KeyPackage::deserialize(secret_share_bytes)
.map_err(|_| ExodusError::DeserializationError)?;
let signature_shares =
f::round2::sign(&signing_package, &signer_nonces, &secret_share)
.map_err(|err| match err {
f::Error::IncorrectNumberOfCommitments => ExodusError::IncorrectNumberOfCommitments,
f::Error::MissingCommitment => ExodusError::MissingCommitment,
f::Error::IncorrectCommitment => ExodusError::IncorrectCommitment,
f::Error::SerializationError => ExodusError::SerializationError,
f::Error::DuplicatedIdentifier => ExodusError::DuplicatedIdentifier,
f::Error::UnknownIdentifier => ExodusError::UnknownIdentifier,
f::Error::IncorrectNumberOfIdentifiers => ExodusError::IncorrectNumberOfIdentifiers,
_ => ExodusError::Unknown,
})?;
Ok(signature_shares.serialize())
})
}
}

View File

@ -0,0 +1,127 @@
use sp_std::{collections::btree_map::BTreeMap, vec::Vec, result::Result};
use frost_core::{Ciphersuite, Group, Identifier};
use ghost_helpers::SubstrateBlake2Hasher;
use ghost_traits::{
exodus::{MerkleTreeBuilder, IdentifierConverter},
hashing::GhostHasher,
};
use crate::{AuthIndex, ExodusError, NetworkCurve};
impl MerkleTreeBuilder<AuthIndex, ExodusError> for NetworkCurve {
type Hash = <SubstrateBlake2Hasher as GhostHasher>::Hash;
fn build_merkle_tree<C, V, F>(
values: &BTreeMap<Identifier<C>, V>,
serialize_fn: F,
) -> Result<Vec<Self::Hash>, ExodusError>
where
C: frost_core::Ciphersuite,
<<C as Ciphersuite>::Group as Group>::Field: frost_core::Field,
F: Fn(&V) -> Result<Vec<u8>, ExodusError>
{
use ghost_helpers::merkle_tree::generate_tree;
type CField<C> = <<C as Ciphersuite>::Group as Group>::Field;
let max_index = values.keys()
.next_back()
.and_then(|id| {
let id_scalar = id.to_scalar();
let id_bytes = <CField<C> as frost_core::Field>::serialize(&id_scalar);
Self::convert_identifier_to_index(id_bytes.as_ref()).ok()
})
.ok_or(ExodusError::IncorrectNumberOfIdentifiers)?;
generate_tree::<SubstrateBlake2Hasher, _, _, _, _>(
max_index,
values.iter(),
|(identifier, value)| {
let id_scalar = identifier.to_scalar();
let id_bytes = <CField<C> as frost_core::Field>::serialize(&id_scalar);
let index = Self::convert_identifier_to_index(id_bytes.as_ref())?;
let value_bytes = serialize_fn(value).map_err(|_| ExodusError::SerializationError)?;
// NOTE: revisit
// Is it possible to use [u8; CONSTANT] where constant is
// purely dependant on provided generic.
let mut preimage = Vec::<u8>::with_capacity(id_bytes.as_ref().len() + value_bytes.len());
preimage.extend_from_slice(id_bytes.as_ref());
preimage.extend_from_slice(value_bytes.as_ref());
Ok((index as usize, preimage))
})
}
fn generate_merkle_proof_from_tree<C, V, F>(
values: &BTreeMap<Identifier<C>, V>,
merkle_tree: &[Self::Hash],
authority_index: AuthIndex,
serialize_fn: F,
) -> Result<(Vec<u8>, Vec<Self::Hash>), ExodusError>
where
C: frost_core::Ciphersuite,
<<C as Ciphersuite>::Group as Group>::Field: frost_core::Field,
F: Fn(&V) -> Result<Vec<u8>, ExodusError>
{
use ghost_helpers::merkle_tree::generate_proof;
type CField<C> = <<C as Ciphersuite>::Group as Group>::Field;
let non_zero_index = Self::non_zero_index(authority_index)?;
let identifier = Identifier::try_from(non_zero_index)
.map_err(|_| ExodusError::InvalidParticipantId)?;
let value = values.get(&identifier).ok_or(ExodusError::InvalidParticipantId)?;
let value_bytes = serialize_fn(&value).map_err(|_| ExodusError::SerializationError)?;
let max_index = values.keys()
.next_back()
.and_then(|id| {
let id_scalar = id.to_scalar();
let id_bytes = <CField<C> as frost_core::Field>::serialize(&id_scalar);
Self::convert_identifier_to_index(id_bytes.as_ref()).ok()
})
.ok_or(ExodusError::IncorrectNumberOfIdentifiers)?;
let proof = generate_proof::<SubstrateBlake2Hasher, _>(
merkle_tree,
max_index,
authority_index,
);
Ok((value_bytes, proof))
}
fn verify_merkle_proof(
&self,
values: &[u8],
merkle_proof: &[Self::Hash],
merkle_root: Self::Hash,
authority_index: AuthIndex,
) -> Result<(), ExodusError> {
use ghost_helpers::merkle_tree::verify_tree_proof;
let preimage = with_ciphersuite!(self, |f| {
let non_zero_index = Self::non_zero_index(authority_index)?;
let identifier = f::Identifier::try_from(non_zero_index)
.map_err(|_| ExodusError::InvalidParticipantId)?;
let mut preimage = identifier.serialize();
preimage.extend_from_slice(values);
Ok(preimage)
})?;
let is_verified = verify_tree_proof::<SubstrateBlake2Hasher, _>(
&preimage,
merkle_proof,
merkle_root,
authority_index,
);
is_verified.then(|| ()).ok_or(ExodusError::InvalidMerkleProof)
}
}

View File

@ -0,0 +1,74 @@
#[macro_export]
macro_rules! with_ciphersuite {
($curve:expr, |$alias:ident| $body:block) => {
match $curve {
NetworkCurve::Secp256k1 => {
#[allow(unused_imports)]
use frost_secp256k1_tr as $alias;
#[allow(unused_imports)]
use frost_secp256k1_tr::Secp256K1Sha256TR as Ciphersuite;
use $alias::keys::EvenY;
#[allow(dead_code)]
trait UniversalNormalizer {
fn normalize_package(self) -> Self;
fn is_normalized(&self) -> bool;
}
impl<T: EvenY> UniversalNormalizer for T {
#[inline]
fn normalize_package(self) -> Self {
self.into_even_y(None)
}
#[inline]
fn is_normalized(&self) -> bool {
self.has_even_y()
}
}
$body
},
NetworkCurve::Ed25519 => {
#[allow(unused_imports)]
use frost_ed25519 as $alias;
#[allow(unused_imports)]
use frost_ed25519::Ed25519Sha512 as Ciphersuite;
#[allow(dead_code)]
trait UniversalNormalizer {
fn normalize_package(self) -> Self;
fn is_normalized(&self) -> bool;
}
impl<T> UniversalNormalizer for T {
#[inline]
fn normalize_package(self) -> Self { self }
#[inline]
fn is_normalized(&self) -> bool { true }
}
$body
},
}
}
}
#[macro_use]
mod converter;
#[macro_use]
mod merkle;
#[macro_use]
mod dkg;
#[macro_use]
mod ecdh;
#[macro_use]
mod frost;

4957
pallets/exodus/src/lib.rs Normal file

File diff suppressed because it is too large Load Diff

216
pallets/exodus/src/mock.rs Normal file
View File

@ -0,0 +1,216 @@
#![cfg(any(test, feature = "runtime-benchmarks"))]
use frame_system::EnsureRoot;
use frame_support::{
derive_impl, parameter_types,
traits::{ConstU32, ConstU64},
};
use crate as ghost_exodus;
use pallet_session::historical as pallet_session_historical;
use sp_runtime::{
BuildStorage, curve::PiecewiseLinear, traits::ConvertInto,
testing::{TestXt, UintAuthorityId},
};
use sp_staking::SessionIndex;
#[cfg(test)]
use ghost_helpers::networks::{NetworkInitiationBuilder, NetworkDataBuilder};
pub type MockedBalance = u64;
pub type MockedNetworkId = u64;
type Block = frame_system::mocking::MockBlock<Runtime>;
frame_support::construct_runtime!(
pub enum Runtime {
System: frame_system,
Session: pallet_session,
Historical: pallet_session_historical,
Balances: pallet_balances,
Networks: ghost_networks,
Exodus: ghost_exodus,
}
);
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for Runtime {
type Block = Block;
type AccountData = pallet_balances::AccountData<MockedBalance>;
}
parameter_types! {
pub static FixedAuthorities: Vec<u64> = vec![0, 1, 2, 3];
}
pub struct TestSessionManager;
impl pallet_session::SessionManager<u64> for TestSessionManager {
fn new_session(_new_index: SessionIndex) -> Option<Vec<u64>> {
Some(FixedAuthorities::get())
}
fn end_session(_: SessionIndex) {}
fn start_session(_: SessionIndex) {}
}
impl pallet_session::historical::SessionManager<u64, u64> for TestSessionManager {
fn new_session(_new_index: SessionIndex) -> Option<Vec<(u64, u64)>> {
Some(FixedAuthorities::get().iter().map(|l| (*l, *l)).collect())
}
fn end_session(_: SessionIndex) {}
fn start_session(_: SessionIndex) {}
}
parameter_types! {
pub const Period: u64 = 1;
pub const Offset: u64 = 0;
}
impl pallet_session::Config for Runtime {
type ShouldEndSession = pallet_session::PeriodicSessions<Period, Offset>;
type SessionManager =
pallet_session::historical::NoteHistoricalRoot<Runtime, TestSessionManager>;
type SessionHandler = (Exodus,);
type ValidatorId = u64;
type ValidatorIdOf = ConvertInto;
type Keys = UintAuthorityId;
type RuntimeEvent = RuntimeEvent;
type NextSessionRotation = pallet_session::PeriodicSessions<Period, Offset>;
type WeightInfo = ();
}
impl pallet_session::historical::Config for Runtime {
type FullIdentification = u64;
type FullIdentificationOf = ConvertInto;
}
parameter_types! {
pub const MaxNetworks: u32 = 5;
}
impl ghost_networks::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type Currency = Balances;
type NetworkId = MockedNetworkId;
type RegisterOrigin = EnsureRoot<Self::AccountId>;
type UpdateOrigin = EnsureRoot<Self::AccountId>;
type RemoveOrigin = EnsureRoot<Self::AccountId>;
type MaxNetworks = MaxNetworks;
type WeightInfo = ();
}
pallet_staking_reward_curve::build! {
const REWARD_CURVE: PiecewiseLinear<'static> = curve!(
min_inflation: 0_006_000,
max_inflation: 1_000_000,
ideal_stake: 0_690_000,
falloff: 0_050_000,
max_piece_count: 100,
test_precision: 0_005_000,
);
}
parameter_types! {
pub static ExistentialDeposit: u64 = 2;
pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE;
pub const HistoryDepth: u32 = 10;
pub const EpochDuration: u64 = 80;
}
#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
impl pallet_balances::Config for Runtime {
type ExistentialDeposit = ExistentialDeposit;
type MaxReserves = ConstU32<2>;
type AccountStore = System;
type WeightInfo = ();
}
impl ghost_exodus::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type AuthorityId = UintAuthorityId;
type ValidatorSet = Historical;
type Currency = Balances;
type NetworkDataHandler = Networks;
type BlockNumberProvider = System;
type DisabledValidators = Session;
type UnsignedPriority = ConstU64<{ 1 << 20 }>;
type UnsignedLongevity = ConstU64<1>;
type MaxAuthorities = ConstU32<10>;
type MaxAuthoritiesChunks = ConstU32<1>;
type DkgRoundPeriod = ConstU64<100>;
type RemovalLimit = ConstU32<20>;
type MaxCursorLen = ConstU32<20>;
type WeightInfo = ();
}
pub type Extrinsic<Call> = TestXt<Call, ()>;
impl<LocalCall> frame_system::offchain::SendTransactionTypes<LocalCall> for Runtime
where
RuntimeCall: From<LocalCall>,
{
type OverarchingCall = RuntimeCall;
type Extrinsic = Extrinsic<RuntimeCall>;
}
#[allow(dead_code)]
pub fn new_test_ext_benchmarks() -> sp_io::TestExternalities {
let t = frame_system::GenesisConfig::<Runtime>::default()
.build_storage()
.unwrap();
sp_io::TestExternalities::new(t)
}
#[cfg(test)]
pub fn new_test_ext() -> (
sp_io::TestExternalities,
std::sync::Arc<parking_lot::RwLock<sp_core::offchain::testing::PoolState>>,
) {
let mut t = frame_system::GenesisConfig::<Runtime>::default()
.build_storage()
.unwrap();
let network_data = NetworkDataBuilder::default()
.with_block_deviation(69)
.build();
let networks = vec![
NetworkInitiationBuilder::<MockedNetworkId, u64>::default()
.with_network_id(420)
.with_gatekeeper_amount(0)
.with_network_data(network_data)
.build(),
];
ghost_networks::GenesisConfig::<Runtime> { networks }
.assimilate_storage(&mut t)
.unwrap();
let mut ext = sp_io::TestExternalities::new(t);
ext.execute_with(|| {
for &id in FixedAuthorities::get().iter() {
System::inc_providers(&id);
assert_eq!(
Session::set_keys(RuntimeOrigin::signed(id), id.into(), vec![],),
Ok(())
);
}
});
let (offchain, _) = sp_core::offchain::testing::TestOffchainExt::with_offchain_db(ext.offchain_db());
let (tx_pool, tx_pool_state) = sp_core::offchain::testing::TestTransactionPoolExt::new();
ext.register_extension(sp_core::offchain::OffchainDbExt::new(offchain.clone()));
ext.register_extension(sp_core::offchain::OffchainWorkerExt::new(offchain));
ext.register_extension(sp_core::offchain::TransactionPoolExt::new(tx_pool));
ext.execute_with(|| System::set_block_number(1));
(ext, tx_pool_state)
}

2437
pallets/exodus/src/tests.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,24 @@
use codec::{Encode, Decode};
use scale_info::TypeInfo;
use sp_runtime::RuntimeDebug;
use frame_support::{BoundedVec, traits::ConstU32};
use crate::{ExodusHash, ELEMENT_MAX_BYTES};
#[derive(Default, Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
pub struct ConsensusState<Bitmap> {
pub element_bytes: BoundedVec<u8, ConstU32<ELEMENT_MAX_BYTES>>,
pub participants: Bitmap,
pub merkle_root: ExodusHash,
}
impl<Bitmap> ConsensusState<Bitmap> {
pub fn new(
element_bytes: BoundedVec<u8, ConstU32<ELEMENT_MAX_BYTES>>,
participants: Bitmap,
merkle_root: ExodusHash,
) -> Self {
Self { element_bytes, participants, merkle_root }
}
}

View File

@ -0,0 +1,295 @@
use sp_runtime::{RuntimeDebug, traits::UniqueSaturatedInto};
use codec::{Encode, Decode};
use scale_info::TypeInfo;
use strum::EnumCount;
use frame_support::{BoundedVec, traits::ConstU32};
use ghost_helpers::networks::NetworkType;
use ghost_traits::bounded_bitmap::{
BoundedBitmapGenerator, BoundedBitmapReader, BoundedBitmapWriter,
};
use crate::{ELEMENT_MAX_BYTES, ExodusSession, DkgIndex};
type RotationSessions = BoundedVec<RotationSession, ConstU32<{ NetworkType::COUNT as u32 }>>;
#[derive(Default, Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo)]
pub struct ReadyDkgAuthorities<AuthoritiesBitmap> {
authorities_indexes: AuthoritiesBitmap,
dkg_index: DkgIndex,
}
impl<AuthoritiesBitmap> ReadyDkgAuthorities<AuthoritiesBitmap>
where
AuthoritiesBitmap: BoundedBitmapReader,
{
pub fn contains_index<N>(&self, index: N) -> bool
where N: UniqueSaturatedInto<usize>,
{
self.authorities_indexes.contains(index)
}
pub fn get_indexes(&self) -> &AuthoritiesBitmap {
&self.authorities_indexes
}
pub fn get_dkg_index(&self) -> DkgIndex {
self.dkg_index
}
}
impl<AuthoritiesBitmap, Block>
From<&PendingDkgAuthorities<AuthoritiesBitmap, Block>>
for ReadyDkgAuthorities<AuthoritiesBitmap>
where
Block: Copy,
AuthoritiesBitmap: BoundedBitmapWriter + BoundedBitmapReader + BoundedBitmapGenerator + Clone,
for<'a> AuthoritiesBitmap: core::ops::BitAndAssign<&'a AuthoritiesBitmap>,
{
fn from(pending: &PendingDkgAuthorities<AuthoritiesBitmap, Block>) -> Self {
Self {
authorities_indexes: pending.get_indexes().clone(),
dkg_index: pending.get_dkg_index(),
}
}
}
#[derive(Default, Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo)]
pub struct RotationSession {
pub exodus_session: ExodusSession,
pub network_type: NetworkType
}
#[derive(Default, Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
pub struct PendingDkgAuthorities<AuthoritiesBitmap, Block> {
verifying_key: BoundedVec<u8, ConstU32<ELEMENT_MAX_BYTES>>,
authorities_indexes: AuthoritiesBitmap,
rotation_sessions: RotationSessions,
dkg_state: DkgState<Block>,
}
impl<AuthoritiesBitmap, Block> PendingDkgAuthorities<AuthoritiesBitmap, Block>
where
Block: Copy,
AuthoritiesBitmap: BoundedBitmapWriter + BoundedBitmapReader + BoundedBitmapGenerator + Clone,
for<'a> AuthoritiesBitmap: core::ops::BitAndAssign<&'a AuthoritiesBitmap>,
{
pub fn new_dkg(&mut self, authorities_len: usize) {
self.authorities_indexes = AuthoritiesBitmap::empty(authorities_len);
self.dkg_state.restart();
self.rotation_sessions = Default::default();
}
pub fn set_block(&mut self, block: Block) {
self.dkg_state.set_block(block);
}
pub fn set_verifying_key(
&mut self,
verifying_key: BoundedVec<u8, ConstU32<ELEMENT_MAX_BYTES>>,
) {
self.verifying_key = verifying_key;
}
pub fn nullify_indexes(&mut self) {
self.verifying_key = Default::default();
self.authorities_indexes.nullify();
}
pub fn restart_dkg(&mut self) {
self.verifying_key = Default::default();
self.authorities_indexes.nullify();
self.dkg_state.restart();
self.rotation_sessions = Default::default();
}
pub fn next_phase(&mut self) {
self.dkg_state.next_phase()
}
#[inline(always)]
pub fn intersect_indexes(&mut self, mask: &AuthoritiesBitmap) {
self.authorities_indexes &= mask;
}
pub fn is_dkg_pending(&self) -> bool {
self.dkg_state.is_dkg_pending()
}
pub fn is_dkg_vacant(&self) -> bool {
self.dkg_state.is_dkg_vacant()
}
pub fn get_dkg_index(&self) -> DkgIndex {
self.dkg_state.get_dkg_index()
}
pub fn get_block(&self) -> Block {
self.dkg_state.get_block()
}
pub fn get_phase(&self) -> DkgPhase {
self.dkg_state.get_phase()
}
pub fn get_verifying_key(
&self
) -> BoundedVec<u8, ConstU32<ELEMENT_MAX_BYTES>> {
self.verifying_key.clone()
}
pub fn is_zero_phase(&self) -> bool {
self.dkg_state.get_phase() == DkgPhase::Round0
}
pub fn is_first_phase(&self) -> bool {
self.dkg_state.get_phase() == DkgPhase::Round1
}
pub fn is_second_phase(&self) -> bool {
self.dkg_state.get_phase() == DkgPhase::Round2
}
pub fn is_third_phase(&self) -> bool {
self.dkg_state.get_phase() == DkgPhase::Round3
}
pub fn is_fourth_phase(&self) -> bool {
self.dkg_state.get_phase() == DkgPhase::Round4
}
pub fn is_fifth_phase(&self) -> bool {
self.dkg_state.get_phase() == DkgPhase::Round5
}
pub fn is_sixth_phase(&self) -> bool {
self.dkg_state.get_phase() == DkgPhase::Round6
}
pub fn is_dkg_finalized(&self) -> bool {
self.dkg_state.get_phase() == DkgPhase::Finalized
}
pub fn contains_index<N>(&self, index: N) -> bool
where N: UniqueSaturatedInto<usize>,
{
self.authorities_indexes.contains(index)
}
pub fn insert_index<N>(&mut self, i: N) -> Option<N>
where N: Copy + TryInto<usize>
{
self.authorities_indexes.insert(i)
}
pub fn try_push_rotation_session(
&mut self,
exodus_session: ExodusSession,
network_type: NetworkType,
) -> Option<()> {
let rotation_session = RotationSession {
exodus_session,
network_type,
};
self.rotation_sessions.try_push(rotation_session).ok()
}
pub fn count_ones<N>(&self) -> N
where u32: UniqueSaturatedInto<N>,
{
self.authorities_indexes.count_ones()
}
pub fn highest_bit<N>(&self) -> Option<N>
where usize: UniqueSaturatedInto<N>,
{
self.authorities_indexes.highest_bit()
}
pub fn get_indexes(&self) -> &AuthoritiesBitmap {
&self.authorities_indexes
}
pub fn iter_rotation_sessions(&self) -> impl Iterator<Item = &RotationSession> {
self.rotation_sessions.iter()
}
}
#[derive(Default, Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo)]
pub struct DkgState<Block> {
block: Block,
index: DkgIndex,
phase: DkgPhase,
}
impl<Block: Copy> DkgState<Block> {
fn is_dkg_pending(&self) -> bool { self.phase.is_dkg_pending() }
fn is_dkg_vacant(&self) -> bool { self.phase.is_dkg_vacant() }
fn get_dkg_index(&self) -> DkgIndex { self.index }
fn get_phase(&self) -> DkgPhase { self.phase }
fn get_block(&self) -> Block { self.block }
fn restart(&mut self) {
self.phase = Default::default();
self.index = self.index.checked_add(1)
.unwrap_or_default();
}
fn next_phase(&mut self) {
self.phase.next_phase()
}
fn set_block(&mut self, block: Block) {
self.block = block;
}
}
#[derive(Default, Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo)]
pub enum DkgPhase {
#[default]
Vacant,
Round0,
Round1,
Round2,
Round3,
Round4,
Round5,
Round6,
Round7,
Finalized,
}
impl DkgPhase {
fn is_dkg_pending(&self) -> bool {
match &self {
Self::Finalized | Self::Round7 => false,
_ => true
}
}
fn is_dkg_vacant(&self) -> bool {
match &self {
Self::Vacant => true,
_ => false,
}
}
fn next_phase(&mut self) {
*self = match *self {
Self::Vacant => Self::Round0,
Self::Round0 => Self::Round1,
Self::Round1 => Self::Round2,
Self::Round2 => Self::Round3,
Self::Round3 => Self::Round4,
Self::Round4 => Self::Round5,
Self::Round5 => Self::Round6,
Self::Round6 => Self::Round7,
Self::Round7 => Self::Finalized,
Self::Finalized => Self::Finalized,
};
}
}

View File

@ -0,0 +1,34 @@
use sp_std::vec::Vec;
use scale_info::TypeInfo;
use sp_runtime::RuntimeDebug;
use codec::{Encode, Decode};
use frame_support::BoundedVec;
use crate::ExodusError;
#[derive(Default, Encode, Decode, Clone, PartialEq, Eq, Ord, PartialOrd, RuntimeDebug, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct EncryptionData<T: crate::pallet::Config> {
pub ciphertext: BoundedVec<u8, crate::CiphertextMaxBytes<T>>,
pub nonce: [u8; crate::ENCRYPTION_NONCE_MAX_BYTES as usize],
}
impl<T: crate::pallet::Config> EncryptionData<T> {
pub fn try_new(
ciphertext_vec: Vec<u8>,
nonce_bytes: &[u8],
) -> Result<Self, ExodusError> {
if nonce_bytes.len() != crate::ENCRYPTION_NONCE_MAX_BYTES as usize {
return Err(ExodusError::EncryptionFailed);
}
let ciphertext =
BoundedVec::<u8, crate::CiphertextMaxBytes<T>>::try_from(ciphertext_vec)
.map_err(|_| ExodusError::EncryptionFailed)?;
let mut nonce = [0u8; crate::ENCRYPTION_NONCE_MAX_BYTES as usize];
nonce.copy_from_slice(nonce_bytes);
Ok(Self { ciphertext, nonce })
}
}

View File

@ -0,0 +1,732 @@
use scale_info::TypeInfo;
use codec::{Encode, Decode};
use sp_runtime::{
Perbill, RuntimeDebug, traits::UniqueSaturatedInto,
helpers_128bit::multiply_by_rational_with_rounding,
};
use crate::{
EvmAddress, EvmBytes32, ExodusSession, RoastSession,
MAX_MESSAGE_SIZE,
};
pub trait EvmAbiEncode {
fn size(&self) -> usize;
fn abi_encode(&self, buffer: &mut [u8], cursor: &mut usize) -> Option<()>;
}
macro_rules! impl_evm_encodable {
(numeric: $( $ty:ty ),* ) => {
$(
impl EvmAbiEncode for $ty {
#[inline(always)]
fn size(&self) -> usize {
core::mem::size_of::<Self>()
}
#[inline(always)]
fn abi_encode(&self, buffer: &mut [u8], cursor: &mut usize) -> Option<()> {
let val_len = self.size();
let padding = 32 - val_len;
let padding_slice = buffer.get_mut(*cursor..*cursor + padding)?;
padding_slice.fill(0);
*cursor += padding;
let value_slice = buffer.get_mut(*cursor..*cursor + val_len)?;
value_slice.copy_from_slice(&self.to_be_bytes());
*cursor += val_len;
Some(())
}
}
)*
};
(bytes_fixed: $( $ty:ty ),* ) => {
$(
impl EvmAbiEncode for $ty {
#[inline(always)]
fn size(&self) -> usize {
<Self as AsRef<[u8]>>::as_ref(self).len()
}
#[inline(always)]
fn abi_encode(&self, buffer: &mut [u8], cursor: &mut usize) -> Option<()> {
if self.size() > 32 { return None; }
let val_len = self.size();
let padding = 32 - val_len;
let padding_slice = buffer.get_mut(*cursor..*cursor + padding)?;
padding_slice.fill(0);
*cursor += padding;
let value_slice = buffer.get_mut(*cursor..*cursor + val_len)?;
value_slice.copy_from_slice(self.as_ref());
*cursor += val_len;
Some(())
}
}
)*
};
(const_array: $( [ $inner_ty:ty ; $len:expr ] ),* $(,)? ) => {
$(
impl EvmAbiEncode for [ $inner_ty ; $len ] {
#[inline(always)]
fn size(&self) -> usize {
$len * 32
}
#[inline(always)]
fn abi_encode(&self, buffer: &mut [u8], cursor: &mut usize) -> Option<()> {
for item in self.iter() {
EvmAbiEncode::abi_encode(item, buffer, cursor)?;
}
Some(())
}
}
)*
};
(bytes_slice: ) => {
impl EvmAbiEncode for [u8] {
#[inline(always)]
fn size(&self) -> usize {
let content_len = self.len();
let remainder = content_len % 32;
let alignment = if remainder == 0 { 0 } else { 32 - remainder };
32 + content_len + alignment
}
#[inline(always)]
fn abi_encode(&self, buffer: &mut [u8], cursor: &mut usize) -> Option<()> {
if self.size() >= MAX_MESSAGE_SIZE as usize { return None; }
let pointer = cursor.checked_sub(4).map(|x| x.saturating_add(32))?;
let head_pointer = pointer as u32;
EvmAbiEncode::abi_encode(&head_pointer, buffer, cursor);
let content_len = self.len() as u32;
EvmAbiEncode::abi_encode(&content_len, buffer, cursor)?;
let value_slice = buffer.get_mut(*cursor..*cursor + self.len())?;
value_slice.copy_from_slice(self);
*cursor += self.len();
let remainder = self.len() % 32;
if remainder != 0 {
let alignment = 32 - remainder;
let alignment_slice = buffer.get_mut(*cursor..*cursor + alignment)?;
alignment_slice.fill(0);
*cursor += alignment;
}
Some(())
}
}
};
}
impl_evm_encodable!(numeric: u8, u16, u32, u64, u128);
impl_evm_encodable!(bytes_fixed: EvmAddress, EvmBytes32);
impl_evm_encodable!(const_array: [u128; 3], [u128; 2], [u32; 2], [u8; 2]);
impl_evm_encodable!(bytes_slice:);
macro_rules! abi_encode_evm_message {
($buffer:expr, $sig:expr, [ $( $val:expr ),* $(,)? ]) => {{
let mut cursor = 0;
let mut no_overflow = true;
let hash = sp_io::hashing::keccak_256($sig.as_bytes());
if let Some(selector_slice) = $buffer.get_mut(0..4) {
selector_slice.copy_from_slice(&hash[0..4]);
cursor += 4;
$(
if no_overflow {
if EvmAbiEncode::abi_encode($val, $buffer, &mut cursor).is_none() {
no_overflow = false;
}
}
)*
} else {
no_overflow = false;
}
no_overflow.then(|| &$buffer[0..cursor])
}};
}
#[derive(Encode, Decode, Clone, Copy, PartialEq, TypeInfo, RuntimeDebug)]
#[repr(u8)]
pub enum GovernanceStatus {
ReserveDepositor = 0,
ReserveToken = 1,
LiquidityDepositor = 2,
LiquidityToken = 3,
RewardManager = 4,
}
#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, RuntimeDebug)]
pub enum GovernanceAction {
SetDistributor { distributor: EvmAddress },
SetWarmupPeriod { warmup_period: u128 },
UpdateGatekeeperAddress { new_gatekeeper: EvmAddress },
SetBounty { bounty: u128 },
AddPool { address: EvmAddress },
RemovePool { index: u128 },
SetAdjustment { rate: u128, target: u128, add: bool },
Enable { status: GovernanceStatus, address: EvmAddress, calculator: EvmAddress },
Disable { status: GovernanceStatus, address: EvmAddress },
ForfeitReserves { router: EvmAddress, liquidity: u128, destroyer_mode: bool },
RedeemReserves { router: EvmAddress, amount: u128 },
Withdraw { token: EvmAddress, amount: u128 },
AuditReserves,
Close { id: u128 },
Create {
market: [u128; 3],
terms: [u128; 2],
token: EvmAddress,
intervals: [u32; 2],
booleans: [bool; 2],
},
}
impl GovernanceAction {
pub fn get_message<'a>(
&self,
message_buffer: &'a mut [u8; MAX_MESSAGE_SIZE as usize],
) -> Option<&'a [u8]> {
match &self {
Self::SetDistributor { distributor } => {
abi_encode_evm_message!(
message_buffer,
"setDistributor(address)",
[distributor]
)
},
Self::SetWarmupPeriod { warmup_period } => {
abi_encode_evm_message!(
message_buffer,
"setWarmupPeriod(uint256)",
[warmup_period]
)
},
Self::UpdateGatekeeperAddress { new_gatekeeper } => {
abi_encode_evm_message!(
message_buffer,
"updateGatekeeperAddress(address)",
[new_gatekeeper]
)
},
Self::SetBounty { bounty } => {
abi_encode_evm_message!(
message_buffer,
"setBounty(uint256)",
[bounty]
)
},
Self::AddPool { address } => {
abi_encode_evm_message!(
message_buffer,
"addPool(address)",
[address]
)
},
Self::RemovePool { index } => {
abi_encode_evm_message!(
message_buffer,
"removePool(uint256)",
[index]
)
},
Self::SetAdjustment { rate, target, add } => {
abi_encode_evm_message!(
message_buffer,
"setAdjustment(uint256,uint256,bool)",
[rate, target, &(*add as u8)]
)
},
Self::Enable { status, address, calculator } => {
abi_encode_evm_message!(
message_buffer,
"enable(uint8,address,address)",
[&(*status as u8), address, calculator]
)
},
Self::Disable { status, address } => {
abi_encode_evm_message!(
message_buffer,
"disable(uint8,address)",
[&(*status as u8), address]
)
},
Self::ForfeitReserves { router, liquidity, destroyer_mode } => {
abi_encode_evm_message!(
message_buffer,
"forfeitReserves(address,uint256,bool)",
[router, liquidity, &(*destroyer_mode as u8)]
)
},
Self::RedeemReserves { router, amount } => {
abi_encode_evm_message!(
message_buffer,
"redeemReserve(address,uint256)",
[router, amount]
)
},
Self::Withdraw { token, amount } => {
abi_encode_evm_message!(
message_buffer,
"withdraw(address,uint256)",
[token, amount]
)
},
Self::AuditReserves => {
abi_encode_evm_message!(message_buffer, "auditReserves()", [])
},
Self::Close { id } => {
abi_encode_evm_message!(message_buffer, "close(uint256)", [id])
},
Self::Create { market, terms, token, intervals, booleans } => {
let booleans_u8 = [booleans[0] as u8, booleans[1] as u8];
abi_encode_evm_message!(
message_buffer,
"create(uint256[3],uint256[2],address,uint32[2],bool[2])",
[market, terms, token, intervals, &booleans_u8]
)
},
}
}
}
#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, RuntimeDebug)]
pub struct ExodusRequest<NetworkId, Balance> {
pub r#type: ExodusRequestType<NetworkId, Balance>,
pub next_roast_session: RoastSession,
}
#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, RuntimeDebug)]
pub enum ExodusRequestType<NetworkId, Balance> {
EvmRotation {
exodus_session: ExodusSession,
public_key: EvmBytes32,
parity: u8,
},
UtxoRotation,
EvmBridgeOut {
exodus_session: ExodusSession,
network_id: NetworkId,
amount: Balance,
bounty: Perbill,
receiver: EvmAddress,
},
EvmGovernance {
exodus_session: ExodusSession,
network_id: NetworkId,
target: EvmAddress,
action: GovernanceAction,
},
UtxoBridgeOut { network_id: NetworkId },
}
impl<NetworkId, Balance> ExodusRequestType<NetworkId, Balance>
where
NetworkId: UniqueSaturatedInto<u64> + Copy,
Balance: UniqueSaturatedInto<u128> + Copy,
{
pub fn get_message<'a>(
&self,
message_buffer: &'a mut [u8; MAX_MESSAGE_SIZE as usize],
) -> Option<&'a [u8]> {
match &self {
ExodusRequestType::EvmRotation { exodus_session, public_key, parity } => {
abi_encode_evm_message!(
message_buffer,
"rotate(uint256,bytes32,uint8)",
[exodus_session, public_key, parity]
)
},
ExodusRequestType::EvmGovernance { exodus_session, network_id, target, action } => {
let mut action_buffer = [0u8; MAX_MESSAGE_SIZE as usize];
let action_payload = action.get_message(&mut action_buffer)?;
let mut metadata_buffer = [0u8; 32];
let chain_id_u64: u64 = (*network_id).unique_saturated_into();
metadata_buffer[0..8].copy_from_slice(&chain_id_u64.to_be_bytes());
metadata_buffer[8..28].copy_from_slice(target.as_ref());
let packed_metadata = EvmBytes32::from(&metadata_buffer);
abi_encode_evm_message!(
message_buffer,
"govern(uint256,uint256,bytes)",
[exodus_session, &packed_metadata, action_payload]
)
},
ExodusRequestType::EvmBridgeOut { exodus_session, network_id, amount, bounty, receiver } => {
let mut buffer = [0u8; 32];
buffer[0..20].copy_from_slice(receiver.as_ref());
let chain_id_u64: u64 = (*network_id).unique_saturated_into();
buffer[20..28].copy_from_slice(&chain_id_u64.to_be_bytes());
let scaled_bounty = multiply_by_rational_with_rounding(
bounty.deconstruct() as u128,
1u128 << 32,
1_000_000_000,
sp_runtime::Rounding::Down
)?;
let bounty_uint32 = scaled_bounty as u32;
buffer[28..32].copy_from_slice(&bounty_uint32.to_be_bytes());
let mut amount_buffer = [0u8; 32];
let amount_u128: u128 = (*amount).unique_saturated_into();
amount_buffer[16..32].copy_from_slice(&amount_u128.to_be_bytes());
let packed_metadata = EvmBytes32::from(buffer);
let amount_u256 = EvmBytes32::from(amount_buffer);
abi_encode_evm_message!(
message_buffer,
"recall(uint256,uint256,uint256)",
[exodus_session, &amount_u256, &packed_metadata]
)
}
_ => None
}
}
}
impl<NetworkId, Balance> ExodusRequest<NetworkId, Balance>
where
NetworkId: UniqueSaturatedInto<u64> + Copy,
Balance: UniqueSaturatedInto<u128> + Copy,
{
pub fn get_message<'a>(
&self,
message_buffer: &'a mut [u8; MAX_MESSAGE_SIZE as usize],
) -> Option<&'a [u8]> {
self.r#type.get_message(message_buffer)
}
pub fn evm_rotation(
exodus_session: ExodusSession,
public_key: EvmBytes32,
parity: u8,
) -> Self {
let rotation = ExodusRequestType::EvmRotation {
exodus_session,
public_key,
parity,
};
Self::default_request_for(rotation)
}
pub fn evm_bridge_out(
exodus_session: ExodusSession,
network_id: NetworkId,
amount: Balance,
bounty: Perbill,
receiver: EvmAddress,
) -> Self {
let evm_bridge_out = ExodusRequestType::EvmBridgeOut {
exodus_session,
network_id,
amount,
bounty,
receiver,
};
Self::default_request_for(evm_bridge_out)
}
pub fn evm_governance(
exodus_session: ExodusSession,
network_id: NetworkId,
target: EvmAddress,
action: GovernanceAction,
) -> Self {
let evm_governance = ExodusRequestType::EvmGovernance {
exodus_session,
network_id,
target,
action,
};
Self::default_request_for(evm_governance)
}
pub fn is_rotation(&self) -> bool {
match &self.r#type {
ExodusRequestType::EvmRotation { .. } | ExodusRequestType::UtxoRotation { .. } => true,
_ => false,
}
}
fn default_request_for(
request_type: ExodusRequestType<NetworkId, Balance>,
) -> Self {
Self { r#type: request_type, next_roast_session: Default::default() }
}
}
#[cfg(test)]
mod tests {
use super::*;
use sp_io::hashing::keccak_256;
#[test]
fn test_rotation_message_valid() {
let mut buffer = [0u8; MAX_MESSAGE_SIZE as usize];
let public_key = EvmBytes32::repeat_byte(0x11);
let session = 69u64;
let rotation_request = ExodusRequest::<u64, u128>::evm_rotation(
session,
public_key.clone(),
1,
);
let calldata = rotation_request.get_message(&mut buffer).unwrap();
assert_eq!(calldata.len(), 100);
let expected_hash = keccak_256("rotate(uint256,bytes32,uint8)".as_bytes());
assert_eq!(calldata[0..4], expected_hash[0..4]);
assert_eq!(calldata[35], 0x45);
assert_eq!(&calldata[4..35], &[0u8; 31]);
assert_eq!(&calldata[36..68], public_key.as_ref());
assert_eq!(calldata[99], 1);
assert_eq!(&calldata[68..99], &[0u8; 31]);
}
#[test]
fn test_bridge_out_message_valid() {
let mut buffer = [0u8; MAX_MESSAGE_SIZE as usize];
let receiver = EvmAddress::repeat_byte(0xAA);
let network_id = 420u64;
let session = 69u64;
let bridge_request = ExodusRequest::<u64, u128>::evm_bridge_out(
session,
network_id,
1_000_000u128,
sp_runtime::Perbill::from_percent(1),
receiver.clone()
);
let calldata = bridge_request.get_message(&mut buffer).unwrap();
assert_eq!(calldata.len(), 100);
let expected_hash = keccak_256("recall(uint256,uint256,uint256)".as_bytes());
assert_eq!(calldata[0..4], expected_hash[0..4]);
assert_eq!(calldata[35], 69);
assert_eq!(calldata[67], 0x40);
assert_eq!(calldata[66], 0x42);
assert_eq!(calldata[65], 0x0F);
let metadata_slot = &calldata[68..100];
assert_eq!(&metadata_slot[0..20], receiver.as_ref());
assert_eq!(metadata_slot[27], 0xA4);
assert_eq!(metadata_slot[26], 0x01);
assert_eq!(metadata_slot[31], 0x28);
assert_eq!(metadata_slot[30], 0x5C);
assert_eq!(metadata_slot[29], 0x8F);
assert_eq!(metadata_slot[28], 0x02);
}
#[test]
fn test_governance_message_valid() {
let mut buffer = [0u8; MAX_MESSAGE_SIZE as usize];
let target = EvmAddress::repeat_byte(0xAA);
let network_id = 420u64;
let session = 69u64;
let actions = vec![
GovernanceAction::SetDistributor { distributor: EvmAddress::repeat_byte(0x11) },
GovernanceAction::SetWarmupPeriod { warmup_period: 3600 },
GovernanceAction::UpdateGatekeeperAddress { new_gatekeeper: EvmAddress::repeat_byte(0x22) },
GovernanceAction::SetBounty { bounty: 500 },
GovernanceAction::AddPool { address: EvmAddress::repeat_byte(0x22) },
GovernanceAction::RemovePool { index: 2 },
GovernanceAction::SetAdjustment { rate: 10, target: 100, add: true },
GovernanceAction::Enable {
status: GovernanceStatus::ReserveDepositor,
address: EvmAddress::repeat_byte(0x33),
calculator: EvmAddress::repeat_byte(0x44)
},
GovernanceAction::Disable {
status: GovernanceStatus::ReserveToken,
address: EvmAddress::repeat_byte(0x55)
},
GovernanceAction::ForfeitReserves {
router: EvmAddress::repeat_byte(0x66),
liquidity: 5000,
destroyer_mode: false
},
GovernanceAction::RedeemReserves { router: EvmAddress::repeat_byte(0x77), amount: 10000 },
GovernanceAction::Withdraw { token: EvmAddress::repeat_byte(0x88), amount: 2500 },
GovernanceAction::AuditReserves,
GovernanceAction::Close { id: 99 },
GovernanceAction::Create {
market: [69, 420, 1337],
terms: [69, 420],
token: EvmAddress::repeat_byte(0x99),
intervals: [69, 1337],
booleans: [true, false],
},
];
for (index, action) in actions.into_iter().enumerate() {
let action_clone = action.clone();
let governance_request = ExodusRequest::<u64, u128>::evm_governance(
session,
network_id,
target,
action,
);
let message = governance_request
.get_message(&mut buffer)
.get_or_insert(&[])
.to_vec();
assert!(!message.is_empty(), "Failed to encode action at index {}", index);
let expected_master_hash = keccak_256("govern(uint256,uint256,bytes)".as_bytes());
assert_eq!(message[0..4], expected_master_hash[0..4]);
assert_eq!(message[35], 69);
assert_eq!(&message[4..35], &[0u8; 31]);
assert_eq!(message[43], 0xA4);
assert_eq!(message[42], 0x01);
assert_eq!(&message[44..64], target.as_ref());
assert_eq!(&message[64..68], &[0u8; 4]);
assert_eq!(message[99], 96);
let inner_payload_len = u32::from_be_bytes(message[128..132].try_into().unwrap()) as usize;
let inner_payload = &message[132..132 + inner_payload_len];
assert_eq!((message.len() - 4) % 32, 0);
match action_clone {
GovernanceAction::SetDistributor { distributor } => {
let hash = keccak_256("setDistributor(address)".as_bytes());
assert_eq!(inner_payload[0..4], hash[0..4]);
assert_eq!(&inner_payload[16..36], distributor.as_ref());
},
GovernanceAction::SetWarmupPeriod { warmup_period } => {
let hash = keccak_256("setWarmupPeriod(uint256)".as_bytes());
assert_eq!(inner_payload[0..4], hash[0..4]);
assert_eq!(inner_payload[inner_payload.len() - 1], warmup_period as u8);
},
GovernanceAction::UpdateGatekeeperAddress { new_gatekeeper } => {
let hash = keccak_256("updateGatekeeperAddress(address)".as_bytes());
assert_eq!(inner_payload[0..4], hash[0..4]);
assert_eq!(&inner_payload[16..36], new_gatekeeper.as_ref());
},
GovernanceAction::SetBounty { bounty } => {
let hash = keccak_256("setBounty(uint256)".as_bytes());
assert_eq!(inner_payload[0..4], hash[0..4]);
assert_eq!(inner_payload[inner_payload.len() - 1], bounty as u8);
},
GovernanceAction::AddPool { address } => {
let hash = keccak_256("addPool(address)".as_bytes());
assert_eq!(inner_payload[0..4], hash[0..4]);
assert_eq!(&inner_payload[16..36], address.as_ref());
},
GovernanceAction::RemovePool { index } => {
let hash = keccak_256("removePool(uint256)".as_bytes());
assert_eq!(inner_payload[0..4], hash[0..4]);
assert_eq!(inner_payload[inner_payload.len() - 1], index as u8);
},
GovernanceAction::SetAdjustment { rate, target, add } => {
let hash = keccak_256("setAdjustment(uint256,uint256,bool)".as_bytes());
assert_eq!(inner_payload[0..4], hash[0..4]);
assert_eq!(inner_payload[35], rate as u8);
assert_eq!(inner_payload[67], target as u8);
assert_eq!(inner_payload[99], add as u8);
},
GovernanceAction::Enable { status, address, calculator } => {
let hash = keccak_256("enable(uint8,address,address)".as_bytes());
assert_eq!(inner_payload[0..4], hash[0..4]);
assert_eq!(inner_payload[35], status as u8);
assert_eq!(&inner_payload[48..68], address.as_ref());
assert_eq!(&inner_payload[80..100], calculator.as_ref());
},
GovernanceAction::Disable { status, address } => {
let hash = keccak_256("disable(uint8,address)".as_bytes());
assert_eq!(inner_payload[0..4], hash[0..4]);
assert_eq!(inner_payload[35], status as u8);
assert_eq!(&inner_payload[48..68], address.as_ref());
},
GovernanceAction::ForfeitReserves { router, liquidity, destroyer_mode } => {
let hash = keccak_256("forfeitReserves(address,uint256,bool)".as_bytes());
assert_eq!(inner_payload[0..4], hash[0..4]);
assert_eq!(&inner_payload[16..36], router.as_ref());
assert_eq!(inner_payload[67], liquidity as u8);
assert_eq!(inner_payload[99], destroyer_mode as u8);
},
GovernanceAction::RedeemReserves { router, amount } => {
let hash = keccak_256("redeemReserve(address,uint256)".as_bytes());
assert_eq!(inner_payload[0..4], hash[0..4]);
assert_eq!(&inner_payload[16..36], router.as_ref());
assert_eq!(inner_payload[67], amount as u8);
},
GovernanceAction::Withdraw { token, amount } => {
let hash = keccak_256("withdraw(address,uint256)".as_bytes());
assert_eq!(inner_payload[0..4], hash[0..4]);
assert_eq!(&inner_payload[16..36], token.as_ref());
assert_eq!(inner_payload[67], amount as u8);
},
GovernanceAction::AuditReserves => {
let hash = keccak_256("auditReserves()".as_bytes());
assert_eq!(inner_payload[0..4], hash[0..4]);
assert_eq!(inner_payload.len(), 4);
},
GovernanceAction::Close { id } => {
let hash = keccak_256("close(uint256)".as_bytes());
assert_eq!(inner_payload[0..4], hash[0..4]);
assert_eq!(inner_payload[inner_payload.len() - 1], id as u8);
},
GovernanceAction::Create { market, terms, token, intervals, booleans } => {
let hash = keccak_256("create(uint256[3],uint256[2],address,uint32[2],bool[2])".as_bytes());
assert_eq!(inner_payload[0..4], hash[0..4]);
assert_eq!(inner_payload.len(), 324);
assert_eq!(inner_payload[35], 69);
assert_eq!(u128::from_be_bytes(inner_payload[20..36].try_into().unwrap()), market[0]);
assert_eq!(u128::from_be_bytes(inner_payload[52..68].try_into().unwrap()), market[1]);
assert_eq!(u128::from_be_bytes(inner_payload[84..100].try_into().unwrap()), market[2]);
assert_eq!(u128::from_be_bytes(inner_payload[116..132].try_into().unwrap()), terms[0]);
assert_eq!(u128::from_be_bytes(inner_payload[148..164].try_into().unwrap()), terms[1]);
assert_eq!(&inner_payload[176..196], token.as_ref());
assert_eq!(u32::from_be_bytes(inner_payload[224..228].try_into().unwrap()), intervals[0]);
assert_eq!(u32::from_be_bytes(inner_payload[256..260].try_into().unwrap()), intervals[1]);
assert_eq!(inner_payload[291], booleans[0] as u8);
assert_eq!(inner_payload[323], booleans[1] as u8);
}
}
}
}
}

View File

@ -0,0 +1,611 @@
use codec::{Encode, Decode};
use sp_std::{vec::Vec, collections::btree_map::BTreeMap};
use sp_runtime::{
RuntimeDebug, RuntimeAppPublic,
traits::{AtLeast32BitUnsigned, Saturating},
offchain::storage::StorageValueRef,
};
use super::{DkgPackage, ExodusPackage, ExodusOk, PackageContext};
use crate::{AuthIndex, ExodusSession, ExodusHash, ExodusError};
fn create_storage_key(prefix: &[u8], index: &[u8], network: &[u8]) -> Vec<u8> {
let mut key = crate::DB_STORAGE_PREFIX.to_vec();
key.extend(prefix);
key.extend(index);
key.extend(network);
key
}
#[derive(Default, Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)]
pub struct PersistentData<BlockNumber, DataType> {
pub release_block: BlockNumber,
pub data: DataType,
}
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)]
pub enum DkgStorageEnum {
Round0 { package_hash: ExodusHash },
Round1 {
secret_package: Vec<u8>,
public_package: Vec<u8>,
},
Round2 {
secret_package: Vec<u8>,
public_packages: BTreeMap<AuthIndex, Vec<u8>>,
},
Round3 { decrypted: BTreeMap<AuthIndex, Vec<u8>> },
Round4,
Round5 {
secret_share: Vec<u8>,
merkle_proof: Vec<ExodusHash>,
},
Round6,
}
#[derive(Default, Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)]
pub struct ExodusNonce(Vec<u8>);
#[derive(Default, Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)]
pub struct ExodusGroup {
pub self_binding_factor: Vec<u8>,
pub binding_factors_proof: Vec<ExodusHash>,
}
#[derive(Default, Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)]
pub struct ExodusShare;
#[derive(Default, Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)]
pub struct ExodusExile;
type ExodusNonces<BlockNumber> = BTreeMap<ExodusSession, PersistentData<BlockNumber, ExodusNonce>>;
type ExodusGroups<BlockNumber> = BTreeMap<ExodusSession, PersistentData<BlockNumber, ExodusGroup>>;
type ExodusShares<BlockNumber> = BTreeMap<ExodusSession, PersistentData<BlockNumber, ExodusShare>>;
type ExodusExiles<BlockNumber> = BTreeMap<ExodusSession, PersistentData<BlockNumber, ExodusExile>>;
pub struct ExodusStorageWrapper<ExodusInnerType> {
pub inner: ExodusInnerType,
pub data_changed: bool,
}
impl<ExodusInnerType> ExodusStorageWrapper<ExodusInnerType> {
pub fn new(inner: ExodusInnerType) -> Self {
Self { inner, data_changed: false }
}
}
#[derive(Default)]
pub struct ExodusStorage<BlockNumber, NetworkCurve>
where
BlockNumber: Default,
NetworkCurve: Default,
{
current_block: BlockNumber,
block_longevity: BlockNumber,
pub network_curve: NetworkCurve,
pub authority_index: AuthIndex,
}
impl<B, NC> ExodusStorage<B, NC>
where
B: AtLeast32BitUnsigned + Copy,
B: Encode + Decode + Default,
NC: Encode + Decode + Default + Copy,
{
pub fn with_network_curve(mut self, network_curve: NC) -> Self {
self.network_curve = network_curve;
self
}
pub fn with_current_block(mut self, current_block: B) -> Self {
self.current_block = current_block;
self
}
pub fn with_block_longevity(mut self, block_longevity: B) -> Self {
self.block_longevity = block_longevity;
self
}
pub fn with_authority_index(mut self, authority_index: AuthIndex) -> Self {
self.authority_index = authority_index;
self
}
pub fn read_local_storage(&self) -> ExodusRecords<B> {
let encoded_index = self.authority_index.encode();
let encoded_network = self.network_curve.encode();
let nonces_key = create_storage_key(crate::EXODUS_PREFIX_NONCES, &encoded_index, &encoded_network);
let exodus_nonces = StorageValueRef::persistent(&nonces_key)
.get::<ExodusNonces<B>>()
.ok()
.flatten()
.unwrap_or_default();
let shares_key = create_storage_key(crate::EXODUS_PREFIX_SHARES, &encoded_index, &encoded_network);
let exodus_shares = StorageValueRef::persistent(&shares_key)
.get::<ExodusShares<B>>()
.ok()
.flatten()
.unwrap_or_default();
let groups_key = create_storage_key(crate::EXODUS_PREFIX_BLENDS, &encoded_index, &encoded_network);
let exodus_groups = StorageValueRef::persistent(&groups_key)
.get::<ExodusGroups<B>>()
.ok()
.flatten()
.unwrap_or_default();
let exiles_key = create_storage_key(crate::EXODUS_PREFIX_EXILES, &encoded_index, &encoded_network);
let exodus_exiles = StorageValueRef::persistent(&exiles_key)
.get::<ExodusExiles<B>>()
.ok()
.flatten()
.unwrap_or_default();
ExodusRecords {
exodus_nonces: ExodusStorageWrapper::new(exodus_nonces),
exodus_shares: ExodusStorageWrapper::new(exodus_shares),
exodus_groups: ExodusStorageWrapper::new(exodus_groups),
exodus_exiles: ExodusStorageWrapper::new(exodus_exiles),
}
}
pub fn prepare_signed_call_with_context<T, A, F1, F2, F3>(
&self,
authority_key: &A,
exodus_records: &mut ExodusRecords<B>,
make_data: F1,
submit_call: F2,
insert_data: F3,
) -> Result<(), ExodusError>
where
T: crate::pallet::Config,
A: RuntimeAppPublic,
F1: FnOnce(PackageContext<NC>) -> Result<ExodusPackage<NC, T>, ExodusError>,
F2: FnOnce(ExodusPackage<NC, T>, A::Signature) -> Result<(), ExodusError>,
F3: FnOnce(&mut ExodusRecords<B>, B),
{
let context = PackageContext::default()
.with_authority_index(self.authority_index)
.with_network_curve(self.network_curve);
let package = make_data(context)?;
let signature = authority_key
.sign(&package.encode())
.ok_or(ExodusError::BadSignature(self.authority_index))?;
submit_call(package, signature)?;
let next_release_block = self.current_block.saturating_add(self.block_longevity);
insert_data(exodus_records, next_release_block);
self.try_write_local_storage(exodus_records);
Ok(())
}
pub fn force_clear_local_storage(&self) {
let encoded_index = self.authority_index.encode();
let encoded_network = self.network_curve.encode();
let nonces_key = create_storage_key(crate::EXODUS_PREFIX_NONCES, &encoded_index, &encoded_network);
let shares_key = create_storage_key(crate::EXODUS_PREFIX_SHARES, &encoded_index, &encoded_network);
let groups_key = create_storage_key(crate::EXODUS_PREFIX_BLENDS, &encoded_index, &encoded_network);
let exiles_key = create_storage_key(crate::EXODUS_PREFIX_EXILES, &encoded_index, &encoded_network);
StorageValueRef::persistent(&nonces_key).clear();
StorageValueRef::persistent(&shares_key).clear();
StorageValueRef::persistent(&groups_key).clear();
StorageValueRef::persistent(&exiles_key).clear();
}
pub fn try_write_local_storage(&self, records: &ExodusRecords<B>) {
let encoded_index = self.authority_index.encode();
let encoded_network = self.network_curve.encode();
if records.exodus_nonces.data_changed {
let nonces_key = create_storage_key(crate::EXODUS_PREFIX_NONCES, &encoded_index, &encoded_network);
StorageValueRef::persistent(&nonces_key).set(&records.exodus_nonces.inner);
}
if records.exodus_shares.data_changed {
let shares_key = create_storage_key(crate::EXODUS_PREFIX_SHARES, &encoded_index, &encoded_network);
StorageValueRef::persistent(&shares_key).set(&records.exodus_shares.inner);
}
if records.exodus_groups.data_changed {
let groups_key = create_storage_key(crate::EXODUS_PREFIX_BLENDS, &encoded_index, &encoded_network);
StorageValueRef::persistent(&groups_key).set(&records.exodus_groups.inner);
}
if records.exodus_exiles.data_changed {
let exiles_key = create_storage_key(crate::EXODUS_PREFIX_EXILES, &encoded_index, &encoded_network);
StorageValueRef::persistent(&exiles_key).set(&records.exodus_exiles.inner);
}
}
}
pub struct ExodusRecords<BlockNumber>
where
BlockNumber: AtLeast32BitUnsigned,
{
exodus_nonces: ExodusStorageWrapper<ExodusNonces<BlockNumber>>,
exodus_shares: ExodusStorageWrapper<ExodusShares<BlockNumber>>,
exodus_groups: ExodusStorageWrapper<ExodusGroups<BlockNumber>>,
exodus_exiles: ExodusStorageWrapper<ExodusExiles<BlockNumber>>,
}
impl<BlockNumber> ExodusRecords<BlockNumber>
where
BlockNumber: AtLeast32BitUnsigned + Default,
{
pub fn get_secret_nonce(&self, exodus_session: &ExodusSession) -> Option<&[u8]> {
self.exodus_nonces.inner
.get(exodus_session)
.map(|v| v.data.0.as_slice())
}
pub fn get_group_record(
&self,
exodus_session: &ExodusSession,
) -> Option<ExodusGroup> {
self.exodus_groups.inner
.get(exodus_session)
.cloned()
.map(|storage| storage.data)
}
pub fn is_exodus_session_banned(
&self,
exodus_session: &ExodusSession,
) -> bool {
self.exodus_exiles.inner.contains_key(exodus_session)
}
pub fn is_group_registered(
&self,
exodus_session: &ExodusSession,
) -> bool {
self.exodus_groups.inner.contains_key(exodus_session)
}
pub fn is_waiting_period_for_nonce(
&self,
exodus_session: &ExodusSession,
current_block: &BlockNumber
) -> bool {
self.exodus_nonces.inner
.get(exodus_session)
.map(|data| data.release_block.gt(current_block))
.unwrap_or(false)
}
pub fn is_waiting_period_for_share(
&self,
exodus_session: &ExodusSession,
current_block: &BlockNumber,
) -> bool {
self.exodus_shares.inner
.get(exodus_session)
.map(|data| data.release_block.gt(&current_block))
.unwrap_or(false)
}
pub fn is_waiting_period_for_group(
&self,
exodus_session: &ExodusSession,
current_block: &BlockNumber,
) -> bool {
self.exodus_groups.inner
.get(exodus_session)
.map(|data| data.release_block.gt(&current_block))
.unwrap_or(false)
}
pub fn exclude_exodus_session(
&mut self,
exodus_session: ExodusSession,
) {
let exodus_exile = PersistentData {
data: ExodusExile,
release_block: Default::default(),
};
self.exodus_exiles.inner.insert(exodus_session, exodus_exile);
self.exodus_exiles.data_changed = true;
}
pub fn insert_exodus_nonce(
&mut self,
exodus_session: ExodusSession,
next_release_block: BlockNumber,
nonce_commitment: Vec<u8>,
) {
let exodus_nonce = PersistentData {
data: ExodusNonce(nonce_commitment),
release_block: next_release_block,
};
self.exodus_nonces.inner.insert(exodus_session, exodus_nonce);
self.exodus_nonces.data_changed = true;
}
pub fn insert_exodus_share(
&mut self,
exodus_session: ExodusSession,
next_release_block: BlockNumber,
) {
let exodus_share = PersistentData {
data: ExodusShare,
release_block: next_release_block,
};
self.exodus_shares.inner.insert(exodus_session, exodus_share);
self.exodus_shares.data_changed = true;
}
pub fn insert_exodus_group(
&mut self,
exodus_session: ExodusSession,
next_release_block: BlockNumber,
self_binding_factor: Vec<u8>,
binding_factors_proof: Vec<ExodusHash>,
) {
let exodus_group = PersistentData {
data: ExodusGroup { self_binding_factor, binding_factors_proof },
release_block: next_release_block,
};
self.exodus_groups.inner.insert(exodus_session, exodus_group);
self.exodus_groups.data_changed = true;
}
}
#[derive(Default, Clone, Copy)]
pub struct DkgStorage<BlockNumber, NetworkCurve>
where
BlockNumber: Copy + Clone,
NetworkCurve: Copy + Clone,
{
block_longevity: BlockNumber,
current_block: BlockNumber,
pub network_curve: NetworkCurve,
pub authority_index: AuthIndex,
}
impl<B, NC> DkgStorage<B, NC>
where
B: Saturating + Encode + Decode + Copy + Default + PartialOrd,
NC: Encode + Copy + Default,
{
pub fn with_network_curve(mut self, network_curve: NC) -> Self {
self.network_curve = network_curve;
self
}
pub fn with_current_block(mut self, current_block: B) -> Self {
self.current_block = current_block;
self
}
pub fn with_block_longevity(mut self, block_longevity: B) -> Self {
self.block_longevity = block_longevity;
self
}
pub fn with_authority_index(mut self, authority_index: AuthIndex) -> Self {
self.authority_index = authority_index;
self
}
pub fn get_release_block(&self) -> B {
self.current_block.saturating_add(self.block_longevity)
}
pub fn get_dkg_result(
&self,
dkg_record: DkgRecord<B>,
) -> Result<ExodusOk<NC>, ExodusError> {
match dkg_record.prefix_slice {
crate::DKG_ROUND0_PREFIX => return Ok(ExodusOk::DkgRoundRegistered(0u8, self.authority_index, self.network_curve)),
crate::DKG_ROUND1_PREFIX => return Ok(ExodusOk::DkgRoundRegistered(1u8, self.authority_index, self.network_curve)),
crate::DKG_ROUND2_PREFIX => return Ok(ExodusOk::DkgRoundRegistered(2u8, self.authority_index, self.network_curve)),
crate::DKG_ROUND3_PREFIX => return Ok(ExodusOk::DkgRoundRegistered(3u8, self.authority_index, self.network_curve)),
crate::DKG_ROUND4_PREFIX => return Ok(ExodusOk::DkgRoundRegistered(4u8, self.authority_index, self.network_curve)),
_ => {}
}
if !dkg_record.prefix_slice.starts_with(crate::DKG_SECRET_PREFIX) {
return Err(ExodusError::UnknownPrefix);
}
match &dkg_record.maybe_stored_data {
Some(PersistentData { data: DkgStorageEnum::Round5 { .. }, .. }) => {
Ok(ExodusOk::DkgRoundRegistered(5u8, self.authority_index, self.network_curve))
},
Some(PersistentData { data: DkgStorageEnum::Round6 { .. }, .. }) => {
Ok(ExodusOk::DkgRoundRegistered(6u8, self.authority_index, self.network_curve))
},
_ => Err(ExodusError::UnknownPrefix),
}
}
pub fn read_local_storage<'a>(
&self,
prefix_slice: &'a [u8],
authority_index: AuthIndex,
) -> DkgRecord<'a, B> {
let encoded_index = authority_index.encode();
let encoded_network = self.network_curve.encode();
let key = create_storage_key(prefix_slice, &encoded_index, &encoded_network);
let maybe_stored_data = StorageValueRef::persistent(&key)
.get::<PersistentData<B, DkgStorageEnum>>()
.ok()
.flatten();
DkgRecord { prefix_slice, maybe_stored_data, current_block: self.current_block }
}
pub fn prepare_signed_call_with_context<T, A, F1, F2>(
&self,
authority_key: &A,
dkg_record: &mut DkgRecord<B>,
make_data: F1,
submit_call: F2,
) -> Result<(), ExodusError>
where
T: crate::pallet::Config,
A: RuntimeAppPublic,
F1: FnOnce(PackageContext<NC>) -> Result<DkgPackage<NC, T>, ExodusError>,
F2: FnOnce(DkgPackage<NC, T>, A::Signature) -> Result<(), ExodusError>,
{
let context = PackageContext::default()
.with_authority_index(self.authority_index)
.with_network_curve(self.network_curve);
let package = make_data(context)?;
let signature = authority_key
.sign(&package.encode())
.ok_or(ExodusError::BadSignature(self.authority_index))?;
submit_call(package, signature)?;
self.try_write_local_storage(dkg_record);
Ok(())
}
pub fn try_write_local_storage(
&self,
dkg_record: &DkgRecord<B>,
) {
let encoded_index = self.authority_index.encode();
let encoded_network = self.network_curve.encode();
let key = create_storage_key(
dkg_record.prefix_slice,
&encoded_index,
&encoded_network,
);
match &dkg_record.maybe_stored_data {
Some(to_store) => StorageValueRef::persistent(&key).set(&to_store),
None => StorageValueRef::persistent(&key).clear(),
};
}
pub fn clear_local_storage<'a>(
&self,
prefix: &'a [u8],
) {
let encoded_index = self.authority_index.encode();
let encoded_network = self.network_curve.encode();
let key = create_storage_key(prefix, &encoded_index, &encoded_network);
StorageValueRef::persistent(&key).clear();
}
}
pub struct DkgRecord<'a, BlockNumber> {
maybe_stored_data: Option<PersistentData<BlockNumber, DkgStorageEnum>>,
current_block: BlockNumber,
prefix_slice: &'a [u8],
}
impl<'a, BlockNumber> DkgRecord<'a, BlockNumber>
where
BlockNumber: Default + Copy + PartialOrd,
{
pub fn is_waiting_period(&self) -> bool {
self.maybe_stored_data
.as_ref()
.map(|data| data.release_block.gt(&self.current_block))
.unwrap_or(false)
}
pub fn storage_exists(&self) -> bool {
self.maybe_stored_data.is_some()
}
pub fn into_inner(&self) -> Option<&DkgStorageEnum> {
self.maybe_stored_data.as_ref().map(|stored| &stored.data)
}
pub fn into_inner_mut(&mut self) -> Option<&mut DkgStorageEnum> {
self.maybe_stored_data.as_mut().map(|stored| &mut stored.data)
}
pub fn try_update_max_signers<F>(
&mut self,
get_new_secret: F
) -> Result<&Vec<u8>, ExodusError>
where
F: FnOnce(&[u8]) -> Result<Vec<u8>, ExodusError>,
{
enum RoundType<'a> {
R1 { public_package: &'a Vec<u8> },
R2 { public_packages: &'a BTreeMap<AuthIndex, Vec<u8>> },
}
let (old_secret, round_type) = self.maybe_stored_data.as_ref()
.map(|persistent| match &persistent.data {
DkgStorageEnum::Round1 { secret_package, public_package } => {
Ok((secret_package, RoundType::R1 { public_package }))
},
DkgStorageEnum::Round2 { secret_package, public_packages } => {
Ok((secret_package, RoundType::R2 { public_packages }))
},
_ => Err(ExodusError::NothingToWrite),
})
.ok_or(ExodusError::NothingToWrite)??;
let new_secret = get_new_secret(old_secret)?;
let new_data = match round_type {
RoundType::R1 { public_package } => DkgStorageEnum::Round1 {
secret_package: new_secret,
public_package: public_package.clone(),
},
RoundType::R2 { public_packages } => DkgStorageEnum::Round2 {
secret_package: new_secret,
public_packages: public_packages.clone(),
},
};
self.update_with_block(None, new_data)?;
self.maybe_stored_data.as_ref()
.map(|persistent| match &persistent.data {
DkgStorageEnum::Round1 { secret_package, .. } |
DkgStorageEnum::Round2 { secret_package, .. } => Ok(secret_package),
_ => Err(ExodusError::NothingToWrite),
})
.ok_or(ExodusError::NothingToWrite)?
}
pub fn update_with_block(
&mut self,
maybe_release_block: Option<BlockNumber>,
new_data: DkgStorageEnum,
) -> Result<(), ExodusError> {
use core::mem::discriminant as d;
let release_block = match &self.maybe_stored_data {
Some(existing) => {
if d(&existing.data) != d(&new_data) {
return Err(ExodusError::NothingToWrite);
}
maybe_release_block.unwrap_or(existing.release_block)
}
None => maybe_release_block.unwrap_or_default(),
};
self.maybe_stored_data = Some(PersistentData { release_block, data: new_data });
Ok(())
}
}

View File

@ -0,0 +1,23 @@
mod exodus_request;
mod local_storage;
mod encryption;
mod consensus;
mod signature;
mod packages;
mod success;
mod removal;
mod nonce;
mod roast;
mod dkg;
pub use exodus_request::*;
pub use local_storage::*;
pub use encryption::*;
pub use consensus::*;
pub use signature::*;
pub use packages::*;
pub use success::*;
pub use removal::*;
pub use nonce::*;
pub use roast::*;
pub use dkg::*;

View File

@ -0,0 +1,20 @@
use frame_support::{BoundedVec, traits::ConstU32};
use codec::{Encode, Decode};
use scale_info::TypeInfo;
use sp_runtime::RuntimeDebug;
use crate::ELEMENT_MAX_BYTES;
type NonceType = BoundedVec<u8, ConstU32<{ ELEMENT_MAX_BYTES }>>;
#[derive(Default, Encode, Decode, Clone, PartialEq, TypeInfo, RuntimeDebug)]
pub struct ExodusSeparatedNonce {
pub hiding: NonceType,
pub binding: NonceType,
}
impl ExodusSeparatedNonce {
pub fn new(hiding: NonceType, binding: NonceType) -> Self {
Self { hiding, binding }
}
}

View File

@ -0,0 +1,466 @@
use sp_std::vec::Vec;
use scale_info::TypeInfo;
use sp_runtime::RuntimeDebug;
use codec::{Encode, Decode};
use frame_support::{BoundedVec, traits::ConstU32};
use crate::{AuthIndex, ExodusSession, ExodusHash, ExodusError};
pub trait PackageMetadata<NetworkCurve>
where
NetworkCurve: Copy,
{
fn context(&self) -> &PackageContext<NetworkCurve>;
fn get_authority_index(&self) -> AuthIndex {
self.context().get_authority_index()
}
fn get_network_curve(&self) -> NetworkCurve {
self.context().get_network_curve()
}
}
#[derive(Encode, Decode, Clone, RuntimeDebug, TypeInfo, PartialEq, Eq)]
#[scale_info(skip_type_params(T))]
pub enum ExodusPackage<NetworkCurve, T>
where
T: crate::pallet::Config,
{
NonceCommitment(ExodusNonceCommitment<NetworkCurve>),
GroupCommitment(ExodusGroupCommitment<NetworkCurve>),
SignatureShare(ExodusSignatureShare<NetworkCurve, T>),
}
impl<NetworkCurve, T> ExodusPackage<NetworkCurve, T>
where
T: crate::pallet::Config,
{
pub fn bytes_len(&self) -> u32 {
let length = match self {
Self::GroupCommitment(package) => package.group_commitment.len(),
Self::SignatureShare(package) => {
(package.binding_factors_proof.len() * ExodusHash::len_bytes())
+ package.self_binding_factor.len()
+ package.signature_share.len()
},
Self::NonceCommitment(package) => {
package.hiding_commitment.len()
+ package.binding_commitment.len()
},
};
length as u32
}
pub fn get_exodus_session(&self) -> ExodusSession {
match &self {
Self::SignatureShare(package) => package.session,
Self::NonceCommitment(package) => package.session,
Self::GroupCommitment(package) => package.session,
}
}
}
#[derive(Encode, Decode, Clone, RuntimeDebug, TypeInfo, PartialEq, Eq)]
#[scale_info(skip_type_params(T))]
pub enum DkgPackage<NetworkCurve, T>
where
T: crate::pallet::Config,
{
Round0(Round0Package<NetworkCurve>),
Round1(Round1Package<NetworkCurve, T>),
Round2(Round2Package<NetworkCurve, T>),
Round3(Round3Package<NetworkCurve, T>),
Round4(Round4Package<NetworkCurve, T>),
Round5(Round5Package<NetworkCurve>),
Round6(Round6Package<NetworkCurve, T>),
}
impl<NetworkCurve, T> DkgPackage<NetworkCurve, T>
where
T: crate::pallet::Config,
{
pub fn bytes_len(&self) -> u32 {
let length = match self {
Self::Round0(_) => ExodusHash::len_bytes(),
Self::Round1(round1) => round1.package.len(),
Self::Round2(round2) => round2.bundle.bitmask.len(),
Self::Round3(round3) => round3.indices.len(),
Self::Round4(round4) => round4.bundle.bitmask.len(),
Self::Round5(round5) => {
round5.verifying_key.len()
.saturating_add(ExodusHash::len_bytes())
},
Self::Round6(round6) => round6.merkle_proof.len(),
};
length as u32
}
}
#[derive(Default, RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
pub struct PackageContext<NetworkCurve> {
authority_index: AuthIndex,
network_curve: NetworkCurve
}
impl<NetworkCurve> PackageContext<NetworkCurve>
where
NetworkCurve: Copy,
{
pub fn get_authority_index(&self) -> AuthIndex {
self.authority_index
}
pub fn get_network_curve(&self) -> NetworkCurve {
self.network_curve
}
pub fn with_network_curve(mut self, network_curve: NetworkCurve) -> Self {
self.network_curve = network_curve;
self
}
pub fn with_authority_index(mut self, authority_index: AuthIndex) -> Self {
self.authority_index = authority_index;
self
}
}
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
pub struct BundledPackages {
pub blob: Vec<u8>,
pub bitmask: Vec<u8>,
}
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct EncryptionBundledPackages<T>
where
T: crate::pallet::Config,
{
pub blob: BoundedVec<u8, crate::EncryptedRound2BlobMaxBytes<T>>,
pub bitmask: BoundedVec<u8, crate::MaxAuthoritiesBitmaskSize<T>>
}
impl<T: crate::pallet::Config> EncryptionBundledPackages<T> {
pub fn len(&self) -> usize {
let blob_len = self.blob.len();
let bitmask_len = self.bitmask.len();
bitmask_len.saturating_add(blob_len)
}
}
impl<T: crate::pallet::Config> TryFrom<BundledPackages> for EncryptionBundledPackages<T> {
type Error = ExodusError;
fn try_from(source: BundledPackages) -> Result<Self, Self::Error> {
let blob =
BoundedVec::<u8, crate::EncryptedRound2BlobMaxBytes<T>>::try_from(source.blob)
.map_err(|_| ExodusError::DeserializationError)?;
let bitmask = BoundedVec::<u8, crate::MaxAuthoritiesBitmaskSize<T>>::try_from(source.bitmask)
.map_err(|_| ExodusError::DeserializationError)?;
Ok(Self { blob, bitmask })
}
}
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct JustificationsBundledPackages<T>
where
T: crate::pallet::Config,
{
pub blob: BoundedVec<u8, crate::Round2BlobMaxBytes<T>>,
pub bitmask: BoundedVec<u8, crate::MaxAuthoritiesBitmaskSize<T>>
}
impl<T: crate::pallet::Config> JustificationsBundledPackages<T> {
pub fn len(&self) -> usize {
let blob_len = self.blob.len();
let bitmask_len = self.bitmask.len();
bitmask_len.saturating_add(blob_len)
}
}
impl<T: crate::pallet::Config> TryFrom<BundledPackages> for JustificationsBundledPackages<T> {
type Error = ExodusError;
fn try_from(source: BundledPackages) -> Result<Self, Self::Error> {
let blob =
BoundedVec::<u8, crate::Round2BlobMaxBytes<T>>::try_from(source.blob)
.map_err(|_| ExodusError::DeserializationError)?;
let bitmask = BoundedVec::<u8, crate::MaxAuthoritiesBitmaskSize<T>>::try_from(source.bitmask)
.map_err(|_| ExodusError::DeserializationError)?;
Ok(Self { blob, bitmask })
}
}
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct ExodusNonceCommitment<NetworkCurve> {
context: PackageContext<NetworkCurve>,
pub hiding_commitment: BoundedVec<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>,
pub binding_commitment: BoundedVec<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>,
pub session: ExodusSession,
}
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
pub struct ExodusGroupCommitment<NetworkCurve> {
context: PackageContext<NetworkCurve>,
pub group_commitment: BoundedVec<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>,
pub binding_factors_root: ExodusHash,
pub session: ExodusSession,
}
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct ExodusSignatureShare<NetworkCurve, T>
where
T: crate::pallet::Config,
{
context: PackageContext<NetworkCurve>,
pub binding_factors_proof: BoundedVec<ExodusHash, crate::BindingFactorsProof<T>>,
pub signature_share: BoundedVec<u8, ConstU32<{ crate::SCALAR_MAX_BYTES }>>,
pub self_binding_factor: BoundedVec<u8, ConstU32<{ crate::SCALAR_MAX_BYTES }>>,
pub session: ExodusSession,
}
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
pub struct Round0Package<NetworkCurve> {
context: PackageContext<NetworkCurve>,
pub package_hash: ExodusHash,
}
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct Round1Package<NetworkCurve, T>
where
T: crate::pallet::Config,
{
context: PackageContext<NetworkCurve>,
pub package: BoundedVec<u8, crate::Round1MaxBytes<T>>,
}
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct Round2Package<NetworkCurve, T>
where
T: crate::pallet::Config,
{
context: PackageContext<NetworkCurve>,
pub bundle: EncryptionBundledPackages<T>,
}
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct Round3Package<NetworkCurve, T>
where
T: crate::pallet::Config,
{
context: PackageContext<NetworkCurve>,
pub indices: BoundedVec<u8, crate::AccusedIndicesMaxBytes<T>>,
}
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct Round4Package<NetworkCurve, T>
where
T: crate::pallet::Config,
{
context: PackageContext<NetworkCurve>,
pub bundle: JustificationsBundledPackages<T>,
}
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
pub struct Round5Package<NetworkCurve> {
context: PackageContext<NetworkCurve>,
pub verifying_key: BoundedVec<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>,
pub merkle_root: ExodusHash,
}
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct Round6Package<NetworkCurve, T>
where
T: crate::pallet::Config,
{
context: PackageContext<NetworkCurve>,
pub verifying_share: BoundedVec<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>,
pub merkle_proof: BoundedVec<ExodusHash, crate::MerkleProofMaxSize<T>>,
}
impl<NetworkCurve> PackageContext<NetworkCurve> {
pub fn with_exodus_nonce_commitment(
self,
session: ExodusSession,
hiding_commitment_vec: Vec<u8>,
binding_commitment_vec: Vec<u8>,
) -> Result<ExodusNonceCommitment<NetworkCurve>, ExodusError> {
let hiding_commitment =
BoundedVec::<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>::try_from(hiding_commitment_vec)
.map_err(|_| ExodusError::DeserializationError)?;
let binding_commitment =
BoundedVec::<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>::try_from(binding_commitment_vec)
.map_err(|_| ExodusError::DeserializationError)?;
Ok(ExodusNonceCommitment { context: self, hiding_commitment, binding_commitment, session })
}
pub fn with_exodus_group_commitment(
self,
session: ExodusSession,
commitment_vec: Vec<u8>,
binding_factors_root: ExodusHash,
) -> Result<ExodusGroupCommitment<NetworkCurve>, ExodusError> {
let group_commitment =
BoundedVec::<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>::try_from(commitment_vec)
.map_err(|_| ExodusError::DeserializationError)?;
Ok(ExodusGroupCommitment {
context: self,
group_commitment,
binding_factors_root,
session,
})
}
pub fn with_exodus_signature_share<T: crate::pallet::Config>(
self,
session: ExodusSession,
signature_share_vec: Vec<u8>,
binding_factors_proof_vec: Vec<ExodusHash>,
self_binding_factor_vec: Vec<u8>,
) -> Result<ExodusSignatureShare<NetworkCurve, T>, ExodusError> {
let signature_share =
BoundedVec::<u8, ConstU32<{ crate::SCALAR_MAX_BYTES }>>::try_from(signature_share_vec)
.map_err(|_| ExodusError::DeserializationError)?;
let binding_factors_proof =
BoundedVec::<ExodusHash, crate::BindingFactorsProof<T>>::try_from(binding_factors_proof_vec)
.map_err(|_| ExodusError::DeserializationError)?;
let self_binding_factor =
BoundedVec::<u8, ConstU32<{ crate::SCALAR_MAX_BYTES }>>::try_from(self_binding_factor_vec)
.map_err(|_| ExodusError::DeserializationError)?;
Ok(ExodusSignatureShare {
context: self,
binding_factors_proof,
self_binding_factor,
signature_share,
session,
})
}
pub fn with_dkg_round0(
self,
package_hash: ExodusHash,
) -> Round0Package<NetworkCurve> {
Round0Package { context: self, package_hash }
}
pub fn with_dkg_round1<T: crate::pallet::Config>(
self,
package_vec: Vec<u8>,
) -> Result<Round1Package<NetworkCurve, T>, ExodusError> {
let package =
BoundedVec::<u8, crate::Round1MaxBytes<T>>::try_from(package_vec)
.map_err(|_| ExodusError::DeserializationError)?;
Ok(Round1Package { context: self, package })
}
pub fn with_dkg_round2<T: crate::pallet::Config>(
self,
bundle_raw: BundledPackages,
) -> Result<Round2Package<NetworkCurve, T>, ExodusError> {
let bundle: EncryptionBundledPackages<T> = bundle_raw.try_into()?;
Ok(Round2Package { context: self, bundle })
}
pub fn with_dkg_round3<T: crate::pallet::Config>(
self,
indices_vec: Vec<u8>
) -> Result<Round3Package<NetworkCurve, T>, ExodusError> {
let indices =
BoundedVec::<u8, crate::AccusedIndicesMaxBytes<T>>::try_from(indices_vec)
.map_err(|_| ExodusError::DeserializationError)?;
Ok(Round3Package { context: self, indices })
}
pub fn with_dkg_round4<T: crate::pallet::Config>(
self,
bundle_raw: BundledPackages,
) -> Result<Round4Package<NetworkCurve, T>, ExodusError> {
let bundle: JustificationsBundledPackages<T> = bundle_raw.try_into()?;
Ok(Round4Package { context: self, bundle })
}
pub fn with_dkg_round5(
self,
verifying_key_vec: Vec<u8>,
merkle_root: ExodusHash,
) -> Result<Round5Package<NetworkCurve>, ExodusError> {
let verifying_key =
BoundedVec::<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>::try_from(verifying_key_vec)
.map_err(|_| ExodusError::DeserializationError)?;
Ok(Round5Package { context: self, verifying_key, merkle_root })
}
pub fn with_dkg_round6<T: crate::pallet::Config>(
self,
verifying_share_vec: Vec<u8>,
merkle_proof_vec: Vec<ExodusHash>,
) -> Result<Round6Package<NetworkCurve, T>, ExodusError> {
let verifying_share =
BoundedVec::<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>::try_from(verifying_share_vec)
.map_err(|_| ExodusError::DeserializationError)?;
let merkle_proof =
BoundedVec::<ExodusHash, crate::MerkleProofMaxSize<T>>::try_from(merkle_proof_vec)
.map_err(|_| ExodusError::DeserializationError)?;
Ok(Round6Package { context: self, verifying_share, merkle_proof })
}
}
impl<NC, T> PackageMetadata<NC> for ExodusPackage<NC, T>
where
NC: Copy,
T: crate::pallet::Config,
{
fn context(&self) -> &PackageContext<NC> {
match self {
Self::NonceCommitment(package) => &package.context,
Self::GroupCommitment(package) => &package.context,
Self::SignatureShare(package) => &package.context,
}
}
}
impl<NC, T> PackageMetadata<NC> for DkgPackage<NC, T>
where
NC: Copy,
T: crate::pallet::Config,
{
fn context(&self) -> &PackageContext<NC> {
match self {
Self::Round0(package) => &package.context,
Self::Round1(package) => &package.context,
Self::Round2(package) => &package.context,
Self::Round3(package) => &package.context,
Self::Round4(package) => &package.context,
Self::Round5(package) => &package.context,
Self::Round6(package) => &package.context,
}
}
}

View File

@ -0,0 +1,24 @@
use frame_support::weights::Weight;
pub struct DkgRemovalResult {
weight: Weight,
cleared: bool,
}
impl DkgRemovalResult {
pub fn new<F>(try_clean_storage: F) -> Self
where
F: FnOnce() -> (Weight, bool)
{
let (weight, cleared) = try_clean_storage();
Self { weight, cleared }
}
pub fn fully_cleared(&self) -> bool {
self.cleared
}
pub fn used_weight(&self) -> Weight {
self.weight
}
}

View File

@ -0,0 +1,22 @@
use codec::{Encode, Decode};
use scale_info::TypeInfo;
use sp_runtime::RuntimeDebug;
#[derive(Default, Encode, Decode, Clone, Copy, PartialEq, TypeInfo, RuntimeDebug)]
pub enum RoastStatus {
#[default]
NonceCommitments,
GroupCommitments,
PartialSignatures,
}
#[derive(Default, Encode, Decode, Clone, PartialEq, TypeInfo, RuntimeDebug)]
pub struct RoastSessionState<Bitmap>
where
Bitmap: Default,
{
pub status: RoastStatus,
pub nonce_committers: Bitmap,
pub group_committers: Bitmap,
pub partial_signers: Bitmap,
}

View File

@ -0,0 +1,46 @@
use scale_info::TypeInfo;
use codec::{Encode, Decode};
use frame_support::{BoundedVec, traits::ConstU32};
use sp_runtime::{RuntimeDebug, traits::UniqueSaturatedInto};
type ExodusNonceT = BoundedVec<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>;
type ExodusSignatureT = BoundedVec<u8, ConstU32<{ crate::SCALAR_MAX_BYTES }>>;
type Type<NetworkId, Balance> = super::ExodusRequestType<NetworkId, Balance>;
#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, RuntimeDebug)]
pub struct ExodusSignedMessage<NetworkId: Default, Balance: Default> {
pub nonce: ExodusNonceT,
pub signature: ExodusSignatureT,
pub r#type: Type<NetworkId, Balance>,
}
impl<NetworkId, Balance> ExodusSignedMessage<NetworkId, Balance>
where
NetworkId: UniqueSaturatedInto<u64> + Copy + Default,
Balance: UniqueSaturatedInto<u128> + Copy + Default,
{
pub fn new<E, S>(
nonce_iter: E,
signature_iter: S,
r#type: Type<NetworkId, Balance>
) -> Option<Self>
where
E: Iterator<Item = u8> + ExactSizeIterator,
S: Iterator<Item = u8> + ExactSizeIterator,
{
let mut nonce = ExodusNonceT::new();
nonce.try_extend(nonce_iter).ok()?;
let mut signature = ExodusSignatureT::new();
signature.try_extend(signature_iter).ok()?;
Some(Self { nonce, signature, r#type })
}
pub fn get_message<'a>(
&self,
message_buffer: &'a mut [u8; crate::MAX_MESSAGE_SIZE as usize],
) -> Option<&'a [u8]> {
self.r#type.get_message(message_buffer)
}
}

View File

@ -0,0 +1,44 @@
use crate::{AuthIndex, ExodusSession, DkgIndex, RoastSession};
pub enum ExodusOk<NetworkCurve> {
ExodusEmptyRequests(AuthIndex, NetworkCurve),
ExodusWaitingState(AuthIndex, NetworkCurve),
ExodusNonceCommitted(ExodusSession, RoastSession, AuthIndex, NetworkCurve),
ExodusGroupCommitted(ExodusSession, RoastSession, AuthIndex, NetworkCurve),
ExodusSignedPartially(ExodusSession, RoastSession, AuthIndex, NetworkCurve),
ExodusVerifyingShareMissed(AuthIndex, DkgIndex, NetworkCurve),
DkgNotPartOfRound(u8, AuthIndex, NetworkCurve),
DkgRoundRegistered(u8, AuthIndex, NetworkCurve),
DkgPreviousPurged(AuthIndex, NetworkCurve),
DkgRoundsCleared(AuthIndex, NetworkCurve),
DkgRoundAlreadyPassed(u8, AuthIndex, NetworkCurve),
DkgRoundWaitingPeriod(u8, AuthIndex, NetworkCurve),
DkgRoundNothingStored(u8, AuthIndex, NetworkCurve),
DkgRoundMaxSignersNarrowed(u8, AuthIndex, AuthIndex, AuthIndex, NetworkCurve),
}
impl<NetworkCurve> core::fmt::Display for ExodusOk<NetworkCurve>
where
NetworkCurve: core::fmt::Display
{
fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
match self {
ExodusOk::ExodusEmptyRequests(auth_index, curve) => write!(fmt, "No EXODUS requests found by authority #{auth_index} for curve {curve}."),
ExodusOk::ExodusWaitingState(auth_index, curve) => write!(fmt, "Waiting for EXODUS requests to be processed by authority #{auth_index} for curve {curve}."),
ExodusOk::ExodusNonceCommitted(exodus, roast, auth_index, curve) => write!(fmt, "Nonce committed for EXODUS {exodus:?} on ROAST session {roast:?} by authority #{auth_index} for curve {curve}."),
ExodusOk::ExodusGroupCommitted(exodus, roast, auth_index, curve) => write!(fmt, "Aggregated nonce and binding factor proof committed for EXODUS {exodus:?} on ROAST session {roast:?} by authority #{auth_index} for curve {curve}."),
ExodusOk::ExodusSignedPartially(exodus, roast, auth_index, curve) => write!(fmt, "Partial signature created for EXODUS {exodus:?} on ROAST session {roast:?} by authority #{auth_index} for curve {curve}."),
ExodusOk::ExodusVerifyingShareMissed(auth_index, dkg_index, curve) => write!(fmt, "EXODUS Round no verifying share for authority #{auth_index} based on DKG session #{dkg_index} for curve {curve}."),
ExodusOk::DkgNotPartOfRound(round, auth_index, curve) => write!(fmt, "DKG authority #{auth_index} for network curve {curve} is not part of Round{round}."),
ExodusOk::DkgRoundRegistered(round, auth_index, curve) => write!(fmt, "DKG Round{round} registered by authority #{auth_index} for network curve {curve}."),
ExodusOk::DkgPreviousPurged(auth_index, curve) => write!(fmt, "Storage prepared for new DKG session by authority #{auth_index} for network curve {curve}."),
ExodusOk::DkgRoundsCleared(auth_index, curve) => write!(fmt, "DKG Rounds cleared from storage by authority #{auth_index} for network curve {curve}."),
ExodusOk::DkgRoundAlreadyPassed(round, auth_index, curve) => write!(fmt, "DKG Round{round} already done by authority #{auth_index} for network curve {curve}."),
ExodusOk::DkgRoundWaitingPeriod(round, auth_index, curve) => write!(fmt, "DKG Round{round} waiting to be applied by authority #{auth_index} for network curve {curve}."),
ExodusOk::DkgRoundNothingStored(round, auth_index, curve) => write!(fmt, "DKG nothing stored from Round{round} by authority #{auth_index} for network curve {curve}."),
ExodusOk::DkgRoundMaxSignersNarrowed(round, auth_index, prev_max, curr_max, curve) => write!(fmt, "DKG Round{round} narrowed max_signers from {prev_max} to {curr_max} by {auth_index} for network curve {curve}."),
}
}
}

View File

@ -0,0 +1,354 @@
// This file is part of Ghost Network.
// Ghost Network is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Ghost Network 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 General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Ghost Network. If not, see <http://www.gnu.org/licenses/>.
//! Autogenerated weights for `ghost_exodus`
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0
//! DATE: 2026-09-10, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! WORST CASE MAP SIZE: `1000000`
//! HOSTNAME: `ghostown`, CPU: `Intel(R) Core(TM) i3-2310M CPU @ 2.10GHz`
//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("casper-dev")`, DB CACHE: 1024
// Executed Command:
// ./target/release/ghost
// benchmark
// pallet
// --chain=casper-dev
// --steps=50
// --repeat=20
// --pallet=ghost_exodus
// --extrinsic=*
// --wasm-execution=compiled
// --heap-pages=4096
// --header=./file_header.txt
// --output=./runtime/casper/src/weights/ghost_exodus.rs
#![cfg_attr(rustfmt, rustfmt_skip)]
#![allow(unused_parens)]
#![allow(unused_imports)]
#![allow(missing_docs)]
use frame_support::{
traits::Get,
weights::{Weight, constants::RocksDbWeight},
};
use core::marker::PhantomData;
pub trait WeightInfo {
fn register_round0_package() -> Weight;
fn register_round1_package(package_len: u32) -> Weight;
fn register_encrypted_round2_packages(packages: u32,) -> Weight;
fn register_round3_complaints(indices: u32) -> Weight;
fn register_round4_justifications(packages: u32) -> Weight;
fn register_round5_verifying_package() -> Weight;
fn register_round6_public_share_package(packages: u32) -> Weight;
fn register_nonce_commitment() -> Weight;
fn register_group_commitment() -> Weight;
fn register_signature_share() -> Weight;
fn register_evm_bridge_out_exodus() -> Weight;
}
/// Weight functions for `ghost_exodus`.
impl WeightInfo for () {
/// Storage: `GhostNetworks::NetworkCurves` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkCurves` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationDkgState` (r:1 w:1)
/// Proof: `GhostExodus::QualificationDkgState` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::QualificationAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusActivity` (r:1 w:1)
/// Proof: `GhostExodus::ExodusActivity` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::Round0Packages` (r:0 w:1)
/// Proof: `GhostExodus::Round0Packages` (`max_values`: None, `max_size`: None, mode: `Measured`)
fn register_round0_package() -> Weight {
// Proof Size summary in bytes:
// Measured: `351`
// Estimated: `3816`
// Minimum execution time: 239_012_000 picoseconds.
Weight::from_parts(241_388_000, 0)
.saturating_add(Weight::from_parts(0, 3816))
.saturating_add(RocksDbWeight::get().reads(4))
.saturating_add(RocksDbWeight::get().writes(3))
}
/// Storage: `GhostNetworks::NetworkCurves` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkCurves` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationDkgState` (r:1 w:0)
/// Proof: `GhostExodus::QualificationDkgState` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::QualificationAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusActivity` (r:1 w:1)
/// Proof: `GhostExodus::ExodusActivity` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::Round0Packages` (r:1 w:0)
/// Proof: `GhostExodus::Round0Packages` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::Round1Packages` (r:1 w:1)
/// Proof: `GhostExodus::Round1Packages` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// The range of component `c` is `[4, 300]`.
fn register_round1_package(c: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `391 + c * (32 ±0)`
// Estimated: `3856 + c * (32 ±0)`
// Minimum execution time: 1_339_828_000 picoseconds.
Weight::from_parts(1_212_625_790, 0)
.saturating_add(Weight::from_parts(0, 3856))
// Standard Error: 11_831
.saturating_add(Weight::from_parts(33_120_868, 0).saturating_mul(c.into()))
.saturating_add(RocksDbWeight::get().reads(6))
.saturating_add(RocksDbWeight::get().writes(2))
.saturating_add(Weight::from_parts(0, 32).saturating_mul(c.into()))
}
/// Storage: `GhostNetworks::NetworkCurves` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkCurves` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationDkgState` (r:1 w:0)
/// Proof: `GhostExodus::QualificationDkgState` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::QualificationAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusActivity` (r:1 w:1)
/// Proof: `GhostExodus::ExodusActivity` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::Round2Packages` (r:1 w:1)
/// Proof: `GhostExodus::Round2Packages` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// The range of component `c` is `[1, 37]`.
fn register_encrypted_round2_packages(c: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `317 + c * (257 ±0)`
// Estimated: `3783 + c * (257 ±0)`
// Minimum execution time: 251_478_000 picoseconds.
Weight::from_parts(254_144_621, 0)
.saturating_add(Weight::from_parts(0, 3783))
// Standard Error: 24_613
.saturating_add(Weight::from_parts(6_595_819, 0).saturating_mul(c.into()))
.saturating_add(RocksDbWeight::get().reads(5))
.saturating_add(RocksDbWeight::get().writes(2))
.saturating_add(Weight::from_parts(0, 257).saturating_mul(c.into()))
}
/// Storage: `GhostNetworks::NetworkCurves` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkCurves` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationDkgState` (r:1 w:0)
/// Proof: `GhostExodus::QualificationDkgState` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::QualificationAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusActivity` (r:1 w:1)
/// Proof: `GhostExodus::ExodusActivity` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::Complaints` (r:1 w:1)
/// Proof: `GhostExodus::Complaints` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// The range of component `c` is `[1, 37]`.
fn register_round3_complaints(c: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `317 + c * (257 ±0)`
// Estimated: `3783 + c * (257 ±0)`
// Minimum execution time: 236_155_000 picoseconds.
Weight::from_parts(242_055_948, 0)
.saturating_add(Weight::from_parts(0, 3783))
// Standard Error: 15_623
.saturating_add(Weight::from_parts(360_411, 0).saturating_mul(c.into()))
.saturating_add(RocksDbWeight::get().reads(5))
.saturating_add(RocksDbWeight::get().writes(2))
.saturating_add(Weight::from_parts(0, 257).saturating_mul(c.into()))
}
/// Storage: `GhostNetworks::NetworkCurves` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkCurves` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationDkgState` (r:1 w:0)
/// Proof: `GhostExodus::QualificationDkgState` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::QualificationAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusActivity` (r:1 w:1)
/// Proof: `GhostExodus::ExodusActivity` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::Justifications` (r:1 w:1)
/// Proof: `GhostExodus::Justifications` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// The range of component `c` is `[1, 37]`.
fn register_round4_justifications(c: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `317 + c * (257 ±0)`
// Estimated: `3783 + c * (257 ±0)`
// Minimum execution time: 249_507_000 picoseconds.
Weight::from_parts(248_505_595, 0)
.saturating_add(Weight::from_parts(0, 3783))
// Standard Error: 20_302
.saturating_add(Weight::from_parts(4_191_526, 0).saturating_mul(c.into()))
.saturating_add(RocksDbWeight::get().reads(5))
.saturating_add(RocksDbWeight::get().writes(2))
.saturating_add(Weight::from_parts(0, 257).saturating_mul(c.into()))
}
/// Storage: `GhostNetworks::NetworkCurves` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkCurves` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationDkgState` (r:1 w:0)
/// Proof: `GhostExodus::QualificationDkgState` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::QualificationAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusActivity` (r:1 w:1)
/// Proof: `GhostExodus::ExodusActivity` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::Verifications` (r:1 w:1)
/// Proof: `GhostExodus::Verifications` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::VerifyingKeyConsensus` (r:1 w:1)
/// Proof: `GhostExodus::VerifyingKeyConsensus` (`max_values`: None, `max_size`: None, mode: `Measured`)
fn register_round5_verifying_package() -> Weight {
// Proof Size summary in bytes:
// Measured: `448`
// Estimated: `3913`
// Minimum execution time: 264_214_000 picoseconds.
Weight::from_parts(266_905_000, 0)
.saturating_add(Weight::from_parts(0, 3913))
.saturating_add(RocksDbWeight::get().reads(6))
.saturating_add(RocksDbWeight::get().writes(3))
}
/// Storage: `GhostNetworks::NetworkCurves` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkCurves` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationDkgState` (r:1 w:0)
/// Proof: `GhostExodus::QualificationDkgState` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::QualificationAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusActivity` (r:1 w:1)
/// Proof: `GhostExodus::ExodusActivity` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::VerifyingKeyConsensus` (r:1 w:0)
/// Proof: `GhostExodus::VerifyingKeyConsensus` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::VerifyingShares` (r:1 w:1)
/// Proof: `GhostExodus::VerifyingShares` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::VerifyingSharesParticipants` (r:1 w:1)
/// Proof: `GhostExodus::VerifyingSharesParticipants` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// The range of component `c` is `[1, 9]`.
fn register_round6_public_share_package(c: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `0 + c * (770 ±0)`
// Estimated: `1551 + c * (1019 ±27)`
// Minimum execution time: 281_073_000 picoseconds.
Weight::from_parts(280_778_987, 0)
.saturating_add(Weight::from_parts(0, 1551))
// Standard Error: 92_951
.saturating_add(Weight::from_parts(3_489_552, 0).saturating_mul(c.into()))
.saturating_add(RocksDbWeight::get().reads(7))
.saturating_add(RocksDbWeight::get().writes(3))
.saturating_add(Weight::from_parts(0, 1019).saturating_mul(c.into()))
}
/// Storage: `GhostNetworks::NetworkCurves` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkCurves` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationDkgState` (r:1 w:0)
/// Proof: `GhostExodus::QualificationDkgState` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ActiveDkgAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::ActiveDkgAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ActiveAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::ActiveAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusRequests` (r:1 w:1)
/// Proof: `GhostExodus::ExodusRequests` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::RoastSessions` (r:1 w:1)
/// Proof: `GhostExodus::RoastSessions` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusActivity` (r:1 w:1)
/// Proof: `GhostExodus::ExodusActivity` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::RoastSessionStates` (r:1 w:1)
/// Proof: `GhostExodus::RoastSessionStates` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::NonceCommitments` (r:0 w:1)
/// Proof: `GhostExodus::NonceCommitments` (`max_values`: None, `max_size`: None, mode: `Measured`)
fn register_nonce_commitment() -> Weight {
// Proof Size summary in bytes:
// Measured: `588`
// Estimated: `4053`
// Minimum execution time: 291_972_000 picoseconds.
Weight::from_parts(295_122_000, 0)
.saturating_add(Weight::from_parts(0, 4053))
.saturating_add(RocksDbWeight::get().reads(8))
.saturating_add(RocksDbWeight::get().writes(5))
}
/// Storage: `GhostNetworks::NetworkCurves` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkCurves` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationDkgState` (r:1 w:0)
/// Proof: `GhostExodus::QualificationDkgState` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ActiveDkgAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::ActiveDkgAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ActiveAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::ActiveAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::RoastSessions` (r:1 w:0)
/// Proof: `GhostExodus::RoastSessions` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusActivity` (r:1 w:1)
/// Proof: `GhostExodus::ExodusActivity` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusRequests` (r:1 w:0)
/// Proof: `GhostExodus::ExodusRequests` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::GroupCommitters` (r:1 w:1)
/// Proof: `GhostExodus::GroupCommitters` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::RoastSessionStates` (r:1 w:1)
/// Proof: `GhostExodus::RoastSessionStates` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::GroupCommitmentsConsensus` (r:1 w:1)
/// Proof: `GhostExodus::GroupCommitmentsConsensus` (`max_values`: None, `max_size`: None, mode: `Measured`)
fn register_group_commitment() -> Weight {
// Proof Size summary in bytes:
// Measured: `968`
// Estimated: `4433`
// Minimum execution time: 331_528_000 picoseconds.
Weight::from_parts(333_974_000, 0)
.saturating_add(Weight::from_parts(0, 4433))
.saturating_add(RocksDbWeight::get().reads(10))
.saturating_add(RocksDbWeight::get().writes(4))
}
/// Storage: `GhostNetworks::NetworkCurves` (r:1 w:0)
/// Proof: `GhostNetworks::NetworkCurves` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationDkgState` (r:1 w:0)
/// Proof: `GhostExodus::QualificationDkgState` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ActiveDkgAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::ActiveDkgAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ActiveAuthorities` (r:1 w:0)
/// Proof: `GhostExodus::ActiveAuthorities` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::RoastSessions` (r:1 w:1)
/// Proof: `GhostExodus::RoastSessions` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusActivity` (r:1 w:1)
/// Proof: `GhostExodus::ExodusActivity` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::GroupCommitmentsConsensus` (r:1 w:1)
/// Proof: `GhostExodus::GroupCommitmentsConsensus` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusRequests` (r:1 w:1)
/// Proof: `GhostExodus::ExodusRequests` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusSignedGovernance` (r:1 w:1)
/// Proof: `GhostExodus::ExodusSignedGovernance` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::RoastSessionStates` (r:1 w:1)
/// Proof: `GhostExodus::RoastSessionStates` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::NonceCommitments` (r:1 w:0)
/// Proof: `GhostExodus::NonceCommitments` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::VerifyingShares` (r:1 w:0)
/// Proof: `GhostExodus::VerifyingShares` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ActiveVerifyingKey` (r:1 w:0)
/// Proof: `GhostExodus::ActiveVerifyingKey` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::SignatureScalars` (r:1 w:1)
/// Proof: `GhostExodus::SignatureScalars` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::GroupCommitters` (r:0 w:1)
/// Proof: `GhostExodus::GroupCommitters` (`max_values`: None, `max_size`: None, mode: `Measured`)
fn register_signature_share() -> Weight {
// Proof Size summary in bytes:
// Measured: `11259`
// Estimated: `14724`
// Minimum execution time: 3_842_259_000 picoseconds.
Weight::from_parts(3_985_167_000, 0)
.saturating_add(Weight::from_parts(0, 14724))
.saturating_add(RocksDbWeight::get().reads(14))
.saturating_add(RocksDbWeight::get().writes(8))
}
/// Storage: `GhostNetworks::Networks` (r:1 w:0)
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostNetworks::GatekeeperAmounts` (r:1 w:1)
/// Proof: `GhostNetworks::GatekeeperAmounts` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostNetworks::NetworkImbalance` (r:1 w:1)
/// Proof: `GhostNetworks::NetworkImbalance` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::QualificationDkgState` (r:1 w:0)
/// Proof: `GhostExodus::QualificationDkgState` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::CurrentExodus` (r:1 w:1)
/// Proof: `GhostExodus::CurrentExodus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `GhostExodus::ExodusRequests` (r:0 w:1)
/// Proof: `GhostExodus::ExodusRequests` (`max_values`: None, `max_size`: None, mode: `Measured`)
fn register_evm_bridge_out_exodus() -> Weight {
// Proof Size summary in bytes:
// Measured: `448`
// Estimated: `3913`
// Minimum execution time: 137_053_000 picoseconds.
Weight::from_parts(138_693_000, 0)
.saturating_add(Weight::from_parts(0, 3913))
.saturating_add(RocksDbWeight::get().reads(5))
.saturating_add(RocksDbWeight::get().writes(4))
}
}

View File

@ -1,7 +1,7 @@
[package] [package]
name = "ghost-claims" name = "ghost-governance"
version = "0.2.2" version = "0.3.7"
description = "Ghost balance and rank claims based on EVM actions" description = "Full-chain and cross-chain governance pallet with early adopter share claims"
license.workspace = true license.workspace = true
authors.workspace = true authors.workspace = true
edition.workspace = true edition.workspace = true
@ -20,8 +20,6 @@ frame-benchmarking = { workspace = true, optional = true }
frame-support = { workspace = true } frame-support = { workspace = true }
frame-system = { workspace = true } frame-system = { workspace = true }
pallet-ranked-collective = { workspace = true }
pallet-vesting = { workspace = true }
pallet-balances = { workspace = true } pallet-balances = { workspace = true }
sp-core = { features = ["serde"], workspace = true } sp-core = { features = ["serde"], workspace = true }
@ -29,46 +27,47 @@ sp-runtime = { features = ["serde"], workspace = true }
sp-io = { workspace = true } sp-io = { workspace = true }
sp-std = { workspace = true } sp-std = { workspace = true }
ghost-traits = { workspace = true }
ghost-helpers = { workspace = true }
ghost-networks = { workspace = true }
[dev-dependencies] [dev-dependencies]
hex-literal = { workspace = true, default-features = true }
libsecp256k1 = { workspace = true, default-features = true } libsecp256k1 = { workspace = true, default-features = true }
serde_json = { workspace = true, default-features = true } hex-literal = { workspace = true, default-features = true }
[features] [features]
default = ["std"] default = ["std"]
std = [ std = [
"frame-benchmarking?/std", "frame-benchmarking?/std",
"serde/std", "serde/std",
"codec/std", "codec/std",
"scale-info/std", "scale-info/std",
"libsecp256k1/std", "libsecp256k1/std",
"frame-support/std", "frame-support/std",
"frame-system/std", "frame-system/std",
"sp-core/std", "sp-core/std",
"sp-runtime/std", "sp-runtime/std",
"sp-io/std", "sp-io/std",
"sp-std/std", "sp-std/std",
"pallet-ranked-collective/std",
"pallet-vesting/std",
"pallet-balances/std", "pallet-balances/std",
"rustc-hex/std", "rustc-hex/std",
"ghost-traits/std",
"ghost-helpers/std",
"ghost-networks/std",
] ]
runtime-benchmarks = [ runtime-benchmarks = [
"libsecp256k1/hmac", "libsecp256k1/hmac",
"libsecp256k1/static-context", "libsecp256k1/static-context",
"frame-benchmarking/runtime-benchmarks", "frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks", "frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks", "frame-system/runtime-benchmarks",
"pallet-ranked-collective/runtime-benchmarks", "pallet-balances/runtime-benchmarks",
"pallet-vesting/runtime-benchmarks", "sp-runtime/runtime-benchmarks",
"pallet-balances/runtime-benchmarks", "ghost-networks/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
] ]
try-runtime = [ try-runtime = [
"frame-support/try-runtime", "frame-support/try-runtime",
"frame-system/try-runtime", "frame-system/try-runtime",
"pallet-ranked-collective/try-runtime", "pallet-balances/try-runtime",
"pallet-vesting/try-runtime", "sp-runtime/try-runtime",
"pallet-balances/try-runtime",
"sp-runtime/try-runtime",
] ]

View File

@ -0,0 +1,147 @@
#![cfg(feature = "runtime-benchmarks")]
use super::*;
use frame_benchmarking::v2::*;
use frame_support::dispatch::RawOrigin;
#[benchmarks(where T: Config)]
mod benchmarks {
use super::*;
#[benchmark]
fn claim(
p: Linear<1, { T::MaxProofDepth::get() }>
) -> Result<(), BenchmarkError> {
let network_id = Default::default();
let current_account: T::AccountId = frame_benchmarking::whitelisted_caller();
let max_proof_depth = p;
let minimum_donation_u128 = <<T as Config>::MinimumDonation as Get<u128>>::get();
let minimum_donation: BalanceOf<T> = minimum_donation_u128.unique_saturated_into();
let dummy_shares_u128 = 420u128;
let dummy_shares: BalanceOf<T> = dummy_shares_u128.unique_saturated_into();
let secret_key = libsecp256k1::SecretKey::parse(&[69u8; 32]).unwrap();
let public_key = libsecp256k1::PublicKey::from_secret_key(&secret_key);
let pubkey_raw = &public_key.serialize()[1..65];
let pubkey_hash = SubstrateKeccakHasher::hash(pubkey_raw);
let mut evm_addr_bytes = [0u8; 20];
evm_addr_bytes.copy_from_slice(&pubkey_hash.as_ref()[12..32]);
let dummy_evm_address = EvmAddress::from(evm_addr_bytes);
let target_index = max_proof_depth - 1;
let claim_package_init = ClaimPackage {
shares: dummy_shares,
merkle_proof: Default::default(),
token_id: 0,
index: target_index,
};
let preimage_init = claim_package_init.get_preimage(&dummy_evm_address, network_id);
let mut leaf_combined = sp_std::vec![0u8; 1 + preimage_init.len()];
leaf_combined[1..].copy_from_slice(&preimage_init);
let some_hash = SubstrateKeccakHasher::hash(&leaf_combined);
let mut current_hash = some_hash;
let mut proof_hashes = Vec::with_capacity(max_proof_depth as usize);
let mut current_index = target_index as usize;
let hash_len = SubstrateKeccakHasher::hash_len();
let mut combined = sp_std::vec![1u8; 1 + hash_len * 2];
for _ in 0..max_proof_depth {
proof_hashes.push(some_hash);
if current_index % 2 == 0 {
combined[1..1 + hash_len].copy_from_slice(&current_hash.to_fixed_bytes());
combined[1 + hash_len..1 + hash_len * 2].copy_from_slice(&some_hash.to_fixed_bytes());
} else {
combined[1..1 + hash_len].copy_from_slice(&some_hash.to_fixed_bytes());
combined[1 + hash_len..1 + hash_len * 2].copy_from_slice(&current_hash.to_fixed_bytes());
}
current_hash = SubstrateKeccakHasher::hash(&combined);
current_index >>= 1;
}
let merkle_root = current_hash;
let member_share = MemberShare {
initiated_network: None,
activated_shares: Default::default(),
locked_balance: minimum_donation,
};
MemberShares::<T>::insert(&current_account, member_share);
let network_state = NetworkShare::new(
dummy_shares,
merkle_root,
max_proof_depth,
);
NetworkShares::<T>::insert(&network_id, network_state);
let global_state = ShareState::new(dummy_shares);
GlobalShares::<T>::put(global_state);
let claim_package = ClaimPackage {
index: target_index,
merkle_proof: BoundedVec::try_from(proof_hashes).unwrap(),
shares: dummy_shares,
token_id: 0
};
let preimage = claim_package.get_preimage(&dummy_evm_address, network_id);
let preimage_hash = SubstrateKeccakHasher::hash(&preimage);
let message_string_bytes = Pallet::<T>::ethereum_signable_message(
&current_account,
&network_id,
&preimage_hash
);
let message_hash = SubstrateKeccakHasher::hash(&message_string_bytes);
let mut message_bytes = [0u8; 32];
message_bytes.copy_from_slice(message_hash.as_ref());
let msg_to_sign = libsecp256k1::Message::parse(&message_bytes);
let (sig, recovery_id) = libsecp256k1::sign(&msg_to_sign, &secret_key);
let mut signature_raw = [0u8; 65];
signature_raw[0..32].copy_from_slice(&sig.r.b32());
signature_raw[32..64].copy_from_slice(&sig.s.b32());
signature_raw[64] = recovery_id.serialize();
let evm_signature = EvmSignature::from(signature_raw);
#[extrinsic_call]
claim(
RawOrigin::Signed(current_account.clone()),
network_id,
claim_package,
dummy_evm_address,
evm_signature,
);
assert_eq!(dummy_shares, GlobalShares::<T>::get().claimed_shares);
assert_eq!(
dummy_shares,
MemberShares::<T>::get(&current_account).activated_shares
);
assert_eq!(
dummy_shares,
NetworkShares::<T>::get(&network_id).claimed_shares(),
);
Ok(())
}
impl_benchmark_test_suite!(
Pallet,
crate::mock::new_test_ext(),
crate::mock::Test,
);
}

Some files were not shown because too many files have changed in this diff Show More