mirror of
https://github.com/datahaven-xyz/datahaven
synced 2026-05-24 09:50:01 +00:00
Merge branch 'main' into feat/add-validator-submitter-ci-job
This commit is contained in:
commit
9931759467
16 changed files with 1044 additions and 176 deletions
148
operator/Cargo.lock
generated
148
operator/Cargo.lock
generated
|
|
@ -8642,8 +8642,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pallet-bucket-nfts"
|
||||
version = "0.4.2"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.2#5b52af21ca6c60db96bb7c3fe7c069075e941614"
|
||||
version = "0.4.3"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.3#0176413b97dbbacb9148f1134e2797e174afa713"
|
||||
dependencies = [
|
||||
"frame-benchmarking",
|
||||
"frame-support",
|
||||
|
|
@ -8699,8 +8699,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pallet-cr-randomness"
|
||||
version = "0.4.2"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.2#5b52af21ca6c60db96bb7c3fe7c069075e941614"
|
||||
version = "0.4.3"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.3#0176413b97dbbacb9148f1134e2797e174afa713"
|
||||
dependencies = [
|
||||
"frame-support",
|
||||
"frame-system",
|
||||
|
|
@ -8983,8 +8983,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pallet-evm-precompile-file-system"
|
||||
version = "0.4.2"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.2#5b52af21ca6c60db96bb7c3fe7c069075e941614"
|
||||
version = "0.4.3"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.3#0176413b97dbbacb9148f1134e2797e174afa713"
|
||||
dependencies = [
|
||||
"fp-account",
|
||||
"fp-evm",
|
||||
|
|
@ -9222,8 +9222,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pallet-file-system"
|
||||
version = "0.4.2"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.2#5b52af21ca6c60db96bb7c3fe7c069075e941614"
|
||||
version = "0.4.3"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.3#0176413b97dbbacb9148f1134e2797e174afa713"
|
||||
dependencies = [
|
||||
"frame-benchmarking",
|
||||
"frame-support",
|
||||
|
|
@ -9251,8 +9251,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pallet-file-system-runtime-api"
|
||||
version = "0.4.2"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.2#5b52af21ca6c60db96bb7c3fe7c069075e941614"
|
||||
version = "0.4.3"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.3#0176413b97dbbacb9148f1134e2797e174afa713"
|
||||
dependencies = [
|
||||
"parity-scale-codec",
|
||||
"scale-info",
|
||||
|
|
@ -9466,8 +9466,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pallet-payment-streams"
|
||||
version = "0.4.2"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.2#5b52af21ca6c60db96bb7c3fe7c069075e941614"
|
||||
version = "0.4.3"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.3#0176413b97dbbacb9148f1134e2797e174afa713"
|
||||
dependencies = [
|
||||
"frame-benchmarking",
|
||||
"frame-support",
|
||||
|
|
@ -9486,8 +9486,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pallet-payment-streams-runtime-api"
|
||||
version = "0.4.2"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.2#5b52af21ca6c60db96bb7c3fe7c069075e941614"
|
||||
version = "0.4.3"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.3#0176413b97dbbacb9148f1134e2797e174afa713"
|
||||
dependencies = [
|
||||
"parity-scale-codec",
|
||||
"scale-info",
|
||||
|
|
@ -9514,8 +9514,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pallet-proofs-dealer"
|
||||
version = "0.4.2"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.2#5b52af21ca6c60db96bb7c3fe7c069075e941614"
|
||||
version = "0.4.3"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.3#0176413b97dbbacb9148f1134e2797e174afa713"
|
||||
dependencies = [
|
||||
"frame-benchmarking",
|
||||
"frame-support",
|
||||
|
|
@ -9540,8 +9540,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pallet-proofs-dealer-runtime-api"
|
||||
version = "0.4.2"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.2#5b52af21ca6c60db96bb7c3fe7c069075e941614"
|
||||
version = "0.4.3"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.3#0176413b97dbbacb9148f1134e2797e174afa713"
|
||||
dependencies = [
|
||||
"parity-scale-codec",
|
||||
"scale-info",
|
||||
|
|
@ -9579,8 +9579,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pallet-randomness"
|
||||
version = "0.4.2"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.2#5b52af21ca6c60db96bb7c3fe7c069075e941614"
|
||||
version = "0.4.3"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.3#0176413b97dbbacb9148f1134e2797e174afa713"
|
||||
dependencies = [
|
||||
"frame-benchmarking",
|
||||
"frame-support",
|
||||
|
|
@ -9717,8 +9717,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pallet-storage-providers"
|
||||
version = "0.4.2"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.2#5b52af21ca6c60db96bb7c3fe7c069075e941614"
|
||||
version = "0.4.3"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.3#0176413b97dbbacb9148f1134e2797e174afa713"
|
||||
dependencies = [
|
||||
"frame-benchmarking",
|
||||
"frame-support",
|
||||
|
|
@ -9739,8 +9739,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pallet-storage-providers-runtime-api"
|
||||
version = "0.4.2"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.2#5b52af21ca6c60db96bb7c3fe7c069075e941614"
|
||||
version = "0.4.3"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.3#0176413b97dbbacb9148f1134e2797e174afa713"
|
||||
dependencies = [
|
||||
"parity-scale-codec",
|
||||
"scale-info",
|
||||
|
|
@ -13902,8 +13902,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "shc-actors-derive"
|
||||
version = "0.4.2"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.2#5b52af21ca6c60db96bb7c3fe7c069075e941614"
|
||||
version = "0.4.3"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.3#0176413b97dbbacb9148f1134e2797e174afa713"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
|
|
@ -13915,8 +13915,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "shc-actors-framework"
|
||||
version = "0.4.2"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.2#5b52af21ca6c60db96bb7c3fe7c069075e941614"
|
||||
version = "0.4.3"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.3#0176413b97dbbacb9148f1134e2797e174afa713"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
|
|
@ -13934,8 +13934,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "shc-blockchain-service"
|
||||
version = "0.4.2"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.2#5b52af21ca6c60db96bb7c3fe7c069075e941614"
|
||||
version = "0.4.3"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.3#0176413b97dbbacb9148f1134e2797e174afa713"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"array-bytes",
|
||||
|
|
@ -13990,8 +13990,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "shc-blockchain-service-db"
|
||||
version = "0.4.2"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.2#5b52af21ca6c60db96bb7c3fe7c069075e941614"
|
||||
version = "0.4.3"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.3#0176413b97dbbacb9148f1134e2797e174afa713"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"diesel",
|
||||
|
|
@ -14014,8 +14014,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "shc-client"
|
||||
version = "0.4.2"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.2#5b52af21ca6c60db96bb7c3fe7c069075e941614"
|
||||
version = "0.4.3"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.3#0176413b97dbbacb9148f1134e2797e174afa713"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"array-bytes",
|
||||
|
|
@ -14089,8 +14089,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "shc-common"
|
||||
version = "0.4.2"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.2#5b52af21ca6c60db96bb7c3fe7c069075e941614"
|
||||
version = "0.4.3"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.3#0176413b97dbbacb9148f1134e2797e174afa713"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bigdecimal",
|
||||
|
|
@ -14154,8 +14154,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "shc-file-manager"
|
||||
version = "0.4.2"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.2#5b52af21ca6c60db96bb7c3fe7c069075e941614"
|
||||
version = "0.4.3"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.3#0176413b97dbbacb9148f1134e2797e174afa713"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"hash-db",
|
||||
|
|
@ -14179,8 +14179,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "shc-file-transfer-service"
|
||||
version = "0.4.2"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.2#5b52af21ca6c60db96bb7c3fe7c069075e941614"
|
||||
version = "0.4.3"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.3#0176413b97dbbacb9148f1134e2797e174afa713"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"array-bytes",
|
||||
|
|
@ -14210,8 +14210,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "shc-fisherman-service"
|
||||
version = "0.4.2"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.2#5b52af21ca6c60db96bb7c3fe7c069075e941614"
|
||||
version = "0.4.3"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.3#0176413b97dbbacb9148f1134e2797e174afa713"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"diesel",
|
||||
|
|
@ -14241,8 +14241,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "shc-forest-manager"
|
||||
version = "0.4.2"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.2#5b52af21ca6c60db96bb7c3fe7c069075e941614"
|
||||
version = "0.4.3"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.3#0176413b97dbbacb9148f1134e2797e174afa713"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
|
|
@ -14267,8 +14267,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "shc-indexer-db"
|
||||
version = "0.4.2"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.2#5b52af21ca6c60db96bb7c3fe7c069075e941614"
|
||||
version = "0.4.3"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.3#0176413b97dbbacb9148f1134e2797e174afa713"
|
||||
dependencies = [
|
||||
"bigdecimal",
|
||||
"chrono",
|
||||
|
|
@ -14295,8 +14295,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "shc-indexer-service"
|
||||
version = "0.4.2"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.2#5b52af21ca6c60db96bb7c3fe7c069075e941614"
|
||||
version = "0.4.3"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.3#0176413b97dbbacb9148f1134e2797e174afa713"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"array-bytes",
|
||||
|
|
@ -14346,8 +14346,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "shc-rpc"
|
||||
version = "0.4.2"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.2#5b52af21ca6c60db96bb7c3fe7c069075e941614"
|
||||
version = "0.4.3"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.3#0176413b97dbbacb9148f1134e2797e174afa713"
|
||||
dependencies = [
|
||||
"array-bytes",
|
||||
"async-trait",
|
||||
|
|
@ -14392,8 +14392,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "shc-telemetry"
|
||||
version = "0.4.2"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.2#5b52af21ca6c60db96bb7c3fe7c069075e941614"
|
||||
version = "0.4.3"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.3#0176413b97dbbacb9148f1134e2797e174afa713"
|
||||
dependencies = [
|
||||
"log",
|
||||
"substrate-prometheus-endpoint",
|
||||
|
|
@ -14409,8 +14409,8 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
|||
|
||||
[[package]]
|
||||
name = "shp-constants"
|
||||
version = "0.4.2"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.2#5b52af21ca6c60db96bb7c3fe7c069075e941614"
|
||||
version = "0.4.3"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.3#0176413b97dbbacb9148f1134e2797e174afa713"
|
||||
dependencies = [
|
||||
"sp-core",
|
||||
"sp-runtime",
|
||||
|
|
@ -14418,8 +14418,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "shp-data-price-updater"
|
||||
version = "0.4.2"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.2#5b52af21ca6c60db96bb7c3fe7c069075e941614"
|
||||
version = "0.4.3"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.3#0176413b97dbbacb9148f1134e2797e174afa713"
|
||||
dependencies = [
|
||||
"frame-support",
|
||||
"parity-scale-codec",
|
||||
|
|
@ -14433,8 +14433,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "shp-file-key-verifier"
|
||||
version = "0.4.2"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.2#5b52af21ca6c60db96bb7c3fe7c069075e941614"
|
||||
version = "0.4.3"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.3#0176413b97dbbacb9148f1134e2797e174afa713"
|
||||
dependencies = [
|
||||
"frame-support",
|
||||
"parity-scale-codec",
|
||||
|
|
@ -14451,8 +14451,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "shp-file-metadata"
|
||||
version = "0.4.2"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.2#5b52af21ca6c60db96bb7c3fe7c069075e941614"
|
||||
version = "0.4.3"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.3#0176413b97dbbacb9148f1134e2797e174afa713"
|
||||
dependencies = [
|
||||
"hex",
|
||||
"num-bigint",
|
||||
|
|
@ -14467,8 +14467,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "shp-forest-verifier"
|
||||
version = "0.4.2"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.2#5b52af21ca6c60db96bb7c3fe7c069075e941614"
|
||||
version = "0.4.3"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.3#0176413b97dbbacb9148f1134e2797e174afa713"
|
||||
dependencies = [
|
||||
"frame-support",
|
||||
"parity-scale-codec",
|
||||
|
|
@ -14484,16 +14484,16 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "shp-opaque"
|
||||
version = "0.4.2"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.2#5b52af21ca6c60db96bb7c3fe7c069075e941614"
|
||||
version = "0.4.3"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.3#0176413b97dbbacb9148f1134e2797e174afa713"
|
||||
dependencies = [
|
||||
"sp-runtime",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shp-session-keys"
|
||||
version = "0.4.2"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.2#5b52af21ca6c60db96bb7c3fe7c069075e941614"
|
||||
version = "0.4.3"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.3#0176413b97dbbacb9148f1134e2797e174afa713"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"parity-scale-codec",
|
||||
|
|
@ -14507,8 +14507,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "shp-traits"
|
||||
version = "0.4.2"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.2#5b52af21ca6c60db96bb7c3fe7c069075e941614"
|
||||
version = "0.4.3"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.3#0176413b97dbbacb9148f1134e2797e174afa713"
|
||||
dependencies = [
|
||||
"frame-support",
|
||||
"parity-scale-codec",
|
||||
|
|
@ -14521,8 +14521,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "shp-treasury-funding"
|
||||
version = "0.4.2"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.2#5b52af21ca6c60db96bb7c3fe7c069075e941614"
|
||||
version = "0.4.3"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.3#0176413b97dbbacb9148f1134e2797e174afa713"
|
||||
dependencies = [
|
||||
"log",
|
||||
"shp-traits",
|
||||
|
|
@ -14532,8 +14532,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "shp-tx-implicits-runtime-api"
|
||||
version = "0.4.2"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.2#5b52af21ca6c60db96bb7c3fe7c069075e941614"
|
||||
version = "0.4.3"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.3#0176413b97dbbacb9148f1134e2797e174afa713"
|
||||
dependencies = [
|
||||
"parity-scale-codec",
|
||||
"scale-info",
|
||||
|
|
@ -14545,8 +14545,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "shp-types"
|
||||
version = "0.4.2"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.2#5b52af21ca6c60db96bb7c3fe7c069075e941614"
|
||||
version = "0.4.3"
|
||||
source = "git+https://github.com/Moonsong-Labs/storage-hub.git?tag=v0.4.3#0176413b97dbbacb9148f1134e2797e174afa713"
|
||||
dependencies = [
|
||||
"sp-core",
|
||||
"sp-runtime",
|
||||
|
|
|
|||
|
|
@ -272,42 +272,42 @@ fc-storage = { git = "https://github.com/polkadot-evm/frontier", branch = "stabl
|
|||
|
||||
# StorageHub
|
||||
## Runtime
|
||||
pallet-bucket-nfts = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.2", default-features = false }
|
||||
pallet-cr-randomness = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.2", default-features = false }
|
||||
pallet-file-system = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.2", default-features = false }
|
||||
pallet-file-system-runtime-api = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.2", default-features = false }
|
||||
pallet-payment-streams = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.2", default-features = false }
|
||||
pallet-payment-streams-runtime-api = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.2", default-features = false }
|
||||
pallet-proofs-dealer = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.2", default-features = false }
|
||||
pallet-proofs-dealer-runtime-api = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.2", default-features = false }
|
||||
pallet-randomness = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.2", default-features = false }
|
||||
pallet-storage-providers = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.2", default-features = false }
|
||||
pallet-storage-providers-runtime-api = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.2", default-features = false }
|
||||
shp-constants = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.2", default-features = false }
|
||||
shp-data-price-updater = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.2", default-features = false }
|
||||
shp-file-key-verifier = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.2", default-features = false }
|
||||
shp-file-metadata = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.2", default-features = false }
|
||||
shp-forest-verifier = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.2", default-features = false }
|
||||
shp-traits = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.2", default-features = false }
|
||||
shp-treasury-funding = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.2", default-features = false }
|
||||
pallet-bucket-nfts = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
|
||||
pallet-cr-randomness = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
|
||||
pallet-file-system = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
|
||||
pallet-file-system-runtime-api = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
|
||||
pallet-payment-streams = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
|
||||
pallet-payment-streams-runtime-api = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
|
||||
pallet-proofs-dealer = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
|
||||
pallet-proofs-dealer-runtime-api = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
|
||||
pallet-randomness = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
|
||||
pallet-storage-providers = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
|
||||
pallet-storage-providers-runtime-api = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
|
||||
shp-constants = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
|
||||
shp-data-price-updater = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
|
||||
shp-file-key-verifier = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
|
||||
shp-file-metadata = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
|
||||
shp-forest-verifier = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
|
||||
shp-traits = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
|
||||
shp-treasury-funding = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
|
||||
## Client
|
||||
shc-actors-derive = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.2", default-features = false }
|
||||
shc-actors-framework = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.2", default-features = false }
|
||||
shc-blockchain-service = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.2", default-features = false }
|
||||
shc-client = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.2", default-features = false }
|
||||
shc-common = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.2", default-features = false }
|
||||
shc-file-manager = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.2", default-features = false }
|
||||
shc-file-transfer-service = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.2", default-features = false }
|
||||
shc-fisherman-service = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.2", default-features = false }
|
||||
shc-forest-manager = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.2", default-features = false }
|
||||
shc-indexer-db = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.2", default-features = false }
|
||||
shc-indexer-service = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.2", default-features = false }
|
||||
shc-rpc = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.2", default-features = false }
|
||||
shp-opaque = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.2", default-features = false }
|
||||
shp-tx-implicits-runtime-api = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.2", default-features = false }
|
||||
shp-types = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.2", default-features = false }
|
||||
shc-actors-derive = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
|
||||
shc-actors-framework = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
|
||||
shc-blockchain-service = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
|
||||
shc-client = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
|
||||
shc-common = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
|
||||
shc-file-manager = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
|
||||
shc-file-transfer-service = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
|
||||
shc-fisherman-service = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
|
||||
shc-forest-manager = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
|
||||
shc-indexer-db = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
|
||||
shc-indexer-service = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
|
||||
shc-rpc = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
|
||||
shp-opaque = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
|
||||
shp-tx-implicits-runtime-api = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
|
||||
shp-types = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
|
||||
## Precompiles
|
||||
pallet-evm-precompile-file-system = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.2", default-features = false }
|
||||
pallet-evm-precompile-file-system = { git = "https://github.com/Moonsong-Labs/storage-hub.git", tag = "v0.4.3", default-features = false }
|
||||
|
||||
|
||||
# Static linking
|
||||
|
|
|
|||
|
|
@ -41,7 +41,14 @@ mod benchmarks {
|
|||
let era = T::EraIndexProvider::active_era().index;
|
||||
let dummy = || T::AccountId::decode(&mut TrailingZeroInput::zeroes()).unwrap();
|
||||
for _ in 0..MAX_SLASHES {
|
||||
existing_slashes.push(Slash::<T::AccountId, T::SlashId>::default_from(dummy()));
|
||||
existing_slashes.push(Slash {
|
||||
validator: dummy(),
|
||||
reporters: vec![],
|
||||
slash_id: One::one(),
|
||||
percentage: Perbill::from_percent(1),
|
||||
confirmed: false,
|
||||
offence_kind: OffenceKind::LivenessOffence,
|
||||
});
|
||||
}
|
||||
Slashes::<T>::insert(
|
||||
era.saturating_add(T::SlashDeferDuration::get())
|
||||
|
|
@ -74,7 +81,13 @@ mod benchmarks {
|
|||
let era = T::EraIndexProvider::active_era().index;
|
||||
let dummy = || T::AccountId::decode(&mut TrailingZeroInput::zeroes()).unwrap();
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Root, era, dummy(), Perbill::from_percent(50));
|
||||
_(
|
||||
RawOrigin::Root,
|
||||
era,
|
||||
dummy(),
|
||||
Perbill::from_percent(50),
|
||||
OffenceKind::LivenessOffence,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Slashes::<T>::get(
|
||||
|
|
@ -93,7 +106,14 @@ mod benchmarks {
|
|||
let dummy = || T::AccountId::decode(&mut TrailingZeroInput::zeroes()).unwrap();
|
||||
|
||||
for _ in 0..(s + 1) {
|
||||
queue.push_back(Slash::<T::AccountId, T::SlashId>::default_from(dummy()));
|
||||
queue.push_back(Slash {
|
||||
validator: dummy(),
|
||||
reporters: vec![],
|
||||
slash_id: One::one(),
|
||||
percentage: Perbill::from_percent(1),
|
||||
confirmed: false,
|
||||
offence_kind: OffenceKind::LivenessOffence,
|
||||
});
|
||||
}
|
||||
|
||||
UnreportedSlashesQueue::<T>::set(queue);
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ extern crate alloc;
|
|||
use pallet_external_validators::apply;
|
||||
use snowbridge_outbound_queue_primitives::SendError;
|
||||
use {
|
||||
alloc::{collections::vec_deque::VecDeque, vec, vec::Vec},
|
||||
alloc::{collections::vec_deque::VecDeque, string::String, vec, vec::Vec},
|
||||
frame_support::{pallet_prelude::*, traits::DefensiveSaturating},
|
||||
frame_system::pallet_prelude::*,
|
||||
log::log,
|
||||
|
|
@ -46,7 +46,7 @@ use {
|
|||
DispatchResult, Perbill,
|
||||
},
|
||||
sp_staking::{
|
||||
offence::{OffenceDetails, OnOffenceHandler},
|
||||
offence::{Offence, OffenceDetails, OffenceError, OnOffenceHandler, ReportOffence},
|
||||
EraIndex, SessionIndex,
|
||||
},
|
||||
};
|
||||
|
|
@ -63,10 +63,45 @@ mod tests;
|
|||
mod benchmarking;
|
||||
pub mod weights;
|
||||
|
||||
/// Identifies the type of consensus offence for EigenLayer slash reporting.
|
||||
#[derive(
|
||||
Encode,
|
||||
Decode,
|
||||
DecodeWithMemTracking,
|
||||
RuntimeDebug,
|
||||
TypeInfo,
|
||||
Clone,
|
||||
PartialEq,
|
||||
Eq,
|
||||
MaxEncodedLen,
|
||||
)]
|
||||
pub enum OffenceKind {
|
||||
/// Liveness offence (i.e. Unresponsiveness)
|
||||
LivenessOffence,
|
||||
BabeEquivocation,
|
||||
GrandpaEquivocation,
|
||||
BeefyEquivocation,
|
||||
Custom(BoundedVec<u8, ConstU32<256>>),
|
||||
}
|
||||
|
||||
impl OffenceKind {
|
||||
pub fn to_description(&self) -> String {
|
||||
match self {
|
||||
Self::LivenessOffence => "Liveness offence".into(),
|
||||
Self::BabeEquivocation => "BABE equivocation".into(),
|
||||
Self::GrandpaEquivocation => "GRANDPA equivocation".into(),
|
||||
Self::BeefyEquivocation => "BEEFY equivocation".into(),
|
||||
Self::Custom(desc) => String::from_utf8(desc.to_vec())
|
||||
.unwrap_or_else(|e| String::from_utf8_lossy(e.as_bytes()).into_owned()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct SlashData<AccountId> {
|
||||
pub validator: AccountId,
|
||||
pub wad_to_slash: u128,
|
||||
pub description: String,
|
||||
}
|
||||
|
||||
// FIXME (nice to have): Merge with SendMessage trait from pallet external-validator-reward (similar trait)
|
||||
|
|
@ -153,6 +188,11 @@ pub mod pallet {
|
|||
/// Provider to retrieve the current external index of validators
|
||||
type ExternalIndexProvider: ExternalIndexProvider;
|
||||
|
||||
/// Maximum WAD value for EigenLayer slashing. Maps Perbill(100%) to this value.
|
||||
/// Default: 5e16 = 5% in WAD format (1e18 = 100%).
|
||||
#[pallet::constant]
|
||||
type MaxSlashWad: Get<u128>;
|
||||
|
||||
/// How many queued slashes are being processed per block.
|
||||
#[pallet::constant]
|
||||
type QueuedSlashesProcessedPerBlock: Get<u32>;
|
||||
|
|
@ -183,6 +223,9 @@ pub mod pallet {
|
|||
EthereumDeliverFail,
|
||||
/// Invalid params for root_test_send_msg_to_eth
|
||||
RootTestInvalidParams,
|
||||
/// No PendingOffenceKind found for (session, validator) — offence was not
|
||||
/// reported through EquivocationReportWrapper, so the offence kind is unknown.
|
||||
MissingOffenceKind,
|
||||
}
|
||||
|
||||
#[apply(derive_storage_traits)]
|
||||
|
|
@ -237,6 +280,27 @@ pub mod pallet {
|
|||
#[pallet::storage]
|
||||
pub type SlashingMode<T: Config> = StorageValue<_, SlashingModeOption, ValueQuery>;
|
||||
|
||||
/// Temporarily stores the offence kind per (session, offender), set by
|
||||
/// `EquivocationReportWrapper` before `on_offence` is called synchronously within
|
||||
/// the same block. Keyed by session index and validator ID so that offences from
|
||||
/// different sessions or for different validators cannot interfere with each other.
|
||||
///
|
||||
/// SAFETY: relies on `pallet_offences::report_offence` calling `on_offence`
|
||||
/// synchronously in the same block. Entries are cleaned up via `take()` in
|
||||
/// `on_offence` on success, or explicit `remove()` in the wrapper on error.
|
||||
/// If the offence pipeline ever becomes asynchronous, this storage should be
|
||||
/// replaced with an offence-payload-based approach.
|
||||
#[pallet::storage]
|
||||
pub type PendingOffenceKind<T: Config> = StorageDoubleMap<
|
||||
_,
|
||||
Twox64Concat,
|
||||
SessionIndex,
|
||||
Twox64Concat,
|
||||
T::ValidatorId,
|
||||
OffenceKind,
|
||||
OptionQuery,
|
||||
>;
|
||||
|
||||
#[pallet::genesis_config]
|
||||
#[derive(frame_support::DefaultNoBound)]
|
||||
pub struct GenesisConfig<T: Config> {
|
||||
|
|
@ -305,6 +369,7 @@ pub mod pallet {
|
|||
era: EraIndex,
|
||||
validator: T::AccountId,
|
||||
percentage: Perbill,
|
||||
offence_kind: OffenceKind,
|
||||
) -> DispatchResult {
|
||||
ensure_root(origin)?;
|
||||
let active_era = T::EraIndexProvider::active_era().index;
|
||||
|
|
@ -324,6 +389,7 @@ pub mod pallet {
|
|||
era,
|
||||
validator,
|
||||
slash_defer_duration,
|
||||
offence_kind,
|
||||
)
|
||||
.ok_or(Error::<T>::ErrorComputingSlash)?;
|
||||
|
||||
|
|
@ -374,7 +440,8 @@ pub mod pallet {
|
|||
}
|
||||
}
|
||||
|
||||
/// This is intended to be used with `FilterHistoricalOffences`.
|
||||
/// This is intended to be used with `EquivocationReportWrapper`, which filters
|
||||
/// out historical offences (before the bonding period) and tags the offence kind.
|
||||
impl<T: Config>
|
||||
OnOffenceHandler<T::AccountId, pallet_session::historical::IdentificationTuple<T>, Weight>
|
||||
for Pallet<T>
|
||||
|
|
@ -453,6 +520,25 @@ where
|
|||
for (details, slash_fraction) in offenders.iter().zip(slash_fraction) {
|
||||
let (stash, _) = &details.offender;
|
||||
|
||||
// Read the per-(session, offender) offence kind set by EquivocationReportWrapper.
|
||||
// This is set synchronously before on_offence is called, so take() clears it.
|
||||
// Type safety: `stash` is T::ValidatorId (from IdentificationTuple), matching
|
||||
// the key used by the wrapper. The trait bounds above enforce ValidatorId == AccountId.
|
||||
let offence_kind = match pallet::PendingOffenceKind::<T>::take(slash_session, stash) {
|
||||
Some(kind) => kind,
|
||||
None => {
|
||||
log!(
|
||||
log::Level::Error,
|
||||
"MissingOffenceKind for session {:?}, validator {:?} — skipping slash",
|
||||
slash_session,
|
||||
stash,
|
||||
);
|
||||
add_db_reads_writes(1, 1);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
add_db_reads_writes(1, 1);
|
||||
|
||||
// Skip if the validator is invulnerable.
|
||||
if invulnerables.contains(stash) {
|
||||
continue;
|
||||
|
|
@ -477,6 +563,7 @@ where
|
|||
slash_era,
|
||||
stash.clone(),
|
||||
slash_defer_duration,
|
||||
offence_kind.clone(),
|
||||
);
|
||||
|
||||
if let Some(mut slash) = slash {
|
||||
|
|
@ -594,9 +681,22 @@ impl<T: Config> Pallet<T> {
|
|||
break;
|
||||
};
|
||||
|
||||
// Convert Perbill to EigenLayer WAD format with linear mapping.
|
||||
// Perbill(100%) → MaxSlashWad (e.g. 5% WAD = 5e16).
|
||||
// Formula: perbill_inner * MaxSlashWad / 1e9
|
||||
// Clamp to MaxSlashWad to guard against overflow if governance
|
||||
// sets MaxSlashWad high enough for saturating_mul to hit u128::MAX.
|
||||
let max_wad = T::MaxSlashWad::get();
|
||||
let wad_to_slash = (slash.percentage.deconstruct() as u128)
|
||||
.saturating_mul(max_wad)
|
||||
.checked_div(1_000_000_000u128)
|
||||
.unwrap_or(0)
|
||||
.min(max_wad);
|
||||
|
||||
slashes_to_send.push(SlashData {
|
||||
validator: slash.validator,
|
||||
wad_to_slash: u128::from_str_radix("10000000000000000", 10).unwrap(), // TODO: need to compute how much we slash (for now it is 1e16)
|
||||
wad_to_slash,
|
||||
description: slash.offence_kind.to_description(),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
@ -655,19 +755,8 @@ pub struct Slash<AccountId, SlashId> {
|
|||
pub percentage: Perbill,
|
||||
// Whether the slash is confirmed or still needs to go through deferred period
|
||||
pub confirmed: bool,
|
||||
}
|
||||
|
||||
impl<AccountId, SlashId: One> Slash<AccountId, SlashId> {
|
||||
/// Initializes the default object using the given `validator`.
|
||||
pub fn default_from(validator: AccountId) -> Self {
|
||||
Self {
|
||||
validator,
|
||||
reporters: vec![],
|
||||
slash_id: One::one(),
|
||||
percentage: Perbill::from_percent(50),
|
||||
confirmed: false,
|
||||
}
|
||||
}
|
||||
/// The type of consensus offence (relayed to EigenLayer as a description string).
|
||||
pub offence_kind: OffenceKind,
|
||||
}
|
||||
|
||||
/// Computes a slash of a validator and nominators. It returns an unapplied
|
||||
|
|
@ -682,6 +771,7 @@ pub(crate) fn compute_slash<T: Config>(
|
|||
slash_era: EraIndex,
|
||||
stash: T::AccountId,
|
||||
slash_defer_duration: EraIndex,
|
||||
offence_kind: OffenceKind,
|
||||
) -> Option<Slash<T::AccountId, T::SlashId>> {
|
||||
let prior_slash_p = ValidatorSlashInEra::<T>::get(slash_era, &stash).unwrap_or(Zero::zero());
|
||||
|
||||
|
|
@ -707,6 +797,7 @@ pub(crate) fn compute_slash<T: Config>(
|
|||
slash_id,
|
||||
reporters: Vec::new(),
|
||||
confirmed,
|
||||
offence_kind,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -714,3 +805,107 @@ pub(crate) fn compute_slash<T: Config>(
|
|||
fn is_sorted_and_unique(list: &[u32]) -> bool {
|
||||
list.windows(2).all(|w| w[0] < w[1])
|
||||
}
|
||||
|
||||
/// Trait for associating an `OffenceKind` with a reporter type.
|
||||
pub trait OffenceKindProvider {
|
||||
fn kind() -> OffenceKind;
|
||||
}
|
||||
|
||||
/// Extracts the validator (account) ID from an offender identification tuple.
|
||||
pub trait HasValidatorId<ValidatorId> {
|
||||
fn validator_id(&self) -> &ValidatorId;
|
||||
}
|
||||
|
||||
impl<V, F> HasValidatorId<V> for (V, F) {
|
||||
fn validator_id(&self) -> &V {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Wraps a `ReportOffence` implementation to:
|
||||
/// 1. **Filter historical offences**: discard reports whose session predates the bonding
|
||||
/// period (similar to `FilterHistoricalOffences` in `pallet_staking`, but using this
|
||||
/// pallet's own `BondedEras` storage instead of staking eras).
|
||||
/// 2. **Tag offence kind**: store the `OffenceKind` per offender in `PendingOffenceKind`
|
||||
/// before delegating to the inner reporter, so that `on_offence` can read it via
|
||||
/// `PendingOffenceKind::take()`.
|
||||
///
|
||||
/// If the inner `report_offence` fails (e.g. duplicate report), stale `PendingOffenceKind`
|
||||
/// entries are cleaned up to prevent leaking into unrelated future offences.
|
||||
pub struct EquivocationReportWrapper<T, Inner, Kind>(PhantomData<(T, Inner, Kind)>);
|
||||
|
||||
impl<T, Inner, Kind, R, O, Id> ReportOffence<R, Id, O> for EquivocationReportWrapper<T, Inner, Kind>
|
||||
where
|
||||
T: Config,
|
||||
Inner: ReportOffence<R, Id, O>,
|
||||
O: Offence<Id>,
|
||||
Kind: OffenceKindProvider,
|
||||
Id: HasValidatorId<T::ValidatorId>,
|
||||
{
|
||||
fn report_offence(reporters: Vec<R>, offence: O) -> Result<(), OffenceError> {
|
||||
// Discard offences from before the bonding period.
|
||||
let offence_session = offence.session_index();
|
||||
let bonded_eras = pallet::BondedEras::<T>::get();
|
||||
if bonded_eras
|
||||
.first()
|
||||
.filter(|(_, start, _)| offence_session >= *start)
|
||||
.is_none()
|
||||
{
|
||||
log!(
|
||||
log::Level::Debug,
|
||||
"discarding offence from session {} — predates bonded eras {:?}",
|
||||
offence_session,
|
||||
bonded_eras.first(),
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let offenders = offence.offenders();
|
||||
for offender in &offenders {
|
||||
pallet::PendingOffenceKind::<T>::insert(
|
||||
offence_session,
|
||||
offender.validator_id(),
|
||||
Kind::kind(),
|
||||
);
|
||||
}
|
||||
let result = Inner::report_offence(reporters, offence);
|
||||
if result.is_err() {
|
||||
for offender in &offenders {
|
||||
pallet::PendingOffenceKind::<T>::remove(offence_session, offender.validator_id());
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn is_known_offence(offenders: &[Id], time_slot: &O::TimeSlot) -> bool {
|
||||
Inner::is_known_offence(offenders, time_slot)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BabeEquivocation;
|
||||
impl OffenceKindProvider for BabeEquivocation {
|
||||
fn kind() -> OffenceKind {
|
||||
OffenceKind::BabeEquivocation
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GrandpaEquivocation;
|
||||
impl OffenceKindProvider for GrandpaEquivocation {
|
||||
fn kind() -> OffenceKind {
|
||||
OffenceKind::GrandpaEquivocation
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BeefyEquivocation;
|
||||
impl OffenceKindProvider for BeefyEquivocation {
|
||||
fn kind() -> OffenceKind {
|
||||
OffenceKind::BeefyEquivocation
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ImOnlineUnresponsive;
|
||||
impl OffenceKindProvider for ImOnlineUnresponsive {
|
||||
fn kind() -> OffenceKind {
|
||||
OffenceKind::LivenessOffence
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ use {
|
|||
core::cell::RefCell,
|
||||
frame_support::{
|
||||
parameter_types,
|
||||
traits::{ConstU16, ConstU32, ConstU64, Get},
|
||||
traits::{ConstU128, ConstU16, ConstU32, ConstU64, Get},
|
||||
weights::constants::RocksDbWeight,
|
||||
},
|
||||
frame_system as system,
|
||||
|
|
@ -132,7 +132,9 @@ thread_local! {
|
|||
pub static ERA_INDEX: RefCell<EraIndex> = const { RefCell::new(0) };
|
||||
pub static DEFER_PERIOD: RefCell<EraIndex> = const { RefCell::new(2) };
|
||||
pub static SENT_ETHEREUM_MESSAGE_NONCE: RefCell<u64> = const { RefCell::new(0) };
|
||||
|
||||
pub static MOCK_REPORT_OFFENCE_SHOULD_FAIL: RefCell<bool> = const { RefCell::new(false) };
|
||||
pub static MOCK_REPORT_OFFENCE_CALLED: RefCell<bool> = const { RefCell::new(false) };
|
||||
pub static LAST_SENT_SLASHES: RefCell<Vec<crate::SlashData<AccountId>>> = RefCell::new(Vec::new());
|
||||
}
|
||||
|
||||
impl MockEraIndexProvider {
|
||||
|
|
@ -215,10 +217,16 @@ impl DeferPeriodGetter {
|
|||
}
|
||||
|
||||
pub struct MockOkOutboundQueue;
|
||||
impl MockOkOutboundQueue {
|
||||
pub fn last_sent_slashes() -> Vec<crate::SlashData<AccountId>> {
|
||||
LAST_SENT_SLASHES.with(|r| r.borrow().clone())
|
||||
}
|
||||
}
|
||||
impl crate::SendMessage<AccountId> for MockOkOutboundQueue {
|
||||
type Ticket = ();
|
||||
type Message = ();
|
||||
fn build(_: &Vec<crate::SlashData<AccountId>>, _: u32) -> Option<Self::Ticket> {
|
||||
fn build(slashes: &Vec<crate::SlashData<AccountId>>, _: u32) -> Option<Self::Ticket> {
|
||||
LAST_SENT_SLASHES.with(|r| *r.borrow_mut() = slashes.clone());
|
||||
Some(())
|
||||
}
|
||||
fn validate(_: Self::Ticket) -> Result<Self::Ticket, SendError> {
|
||||
|
|
@ -258,6 +266,7 @@ impl external_validator_slashes::Config for Test {
|
|||
type EraIndexProvider = MockEraIndexProvider;
|
||||
type InvulnerablesProvider = MockInvulnerableProvider;
|
||||
type ExternalIndexProvider = TimestampProvider;
|
||||
type MaxSlashWad = ConstU128<50_000_000_000_000_000>;
|
||||
type QueuedSlashesProcessedPerBlock = ConstU32<20>;
|
||||
type WeightInfo = ();
|
||||
type SendMessage = MockOkOutboundQueue;
|
||||
|
|
@ -289,6 +298,75 @@ impl sp_runtime::traits::Convert<u64, Option<u64>> for IdentityValidator {
|
|||
}
|
||||
}
|
||||
|
||||
// --- Mock infrastructure for testing EquivocationReportWrapper ---
|
||||
|
||||
use sp_staking::offence::{Offence, OffenceError, ReportOffence};
|
||||
|
||||
/// A mock inner ReportOffence that can be configured to succeed or fail.
|
||||
pub struct MockInnerReporter;
|
||||
|
||||
impl MockInnerReporter {
|
||||
pub fn set_should_fail(fail: bool) {
|
||||
MOCK_REPORT_OFFENCE_SHOULD_FAIL.with(|r| *r.borrow_mut() = fail);
|
||||
}
|
||||
pub fn was_called() -> bool {
|
||||
MOCK_REPORT_OFFENCE_CALLED.with(|r| *r.borrow())
|
||||
}
|
||||
pub fn reset() {
|
||||
MOCK_REPORT_OFFENCE_SHOULD_FAIL.with(|r| *r.borrow_mut() = false);
|
||||
MOCK_REPORT_OFFENCE_CALLED.with(|r| *r.borrow_mut() = false);
|
||||
}
|
||||
}
|
||||
|
||||
impl<R, Id, O: Offence<Id>> ReportOffence<R, Id, O> for MockInnerReporter {
|
||||
fn report_offence(_reporters: Vec<R>, _offence: O) -> Result<(), OffenceError> {
|
||||
MOCK_REPORT_OFFENCE_CALLED.with(|r| *r.borrow_mut() = true);
|
||||
if MOCK_REPORT_OFFENCE_SHOULD_FAIL.with(|r| *r.borrow()) {
|
||||
Err(OffenceError::DuplicateReport)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
fn is_known_offence(_offenders: &[Id], _time_slot: &O::TimeSlot) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// A minimal mock Offence for testing the wrapper.
|
||||
pub struct MockOffence {
|
||||
pub session_index: SessionIndex,
|
||||
pub offenders: Vec<(u64, ())>,
|
||||
}
|
||||
|
||||
impl Offence<(u64, ())> for MockOffence {
|
||||
const ID: sp_staking::offence::Kind = *b"mock:offence0000";
|
||||
type TimeSlot = u128;
|
||||
|
||||
fn offenders(&self) -> Vec<(u64, ())> {
|
||||
self.offenders.clone()
|
||||
}
|
||||
fn session_index(&self) -> SessionIndex {
|
||||
self.session_index
|
||||
}
|
||||
fn validator_set_count(&self) -> u32 {
|
||||
3
|
||||
}
|
||||
fn time_slot(&self) -> Self::TimeSlot {
|
||||
self.session_index as u128
|
||||
}
|
||||
fn slash_fraction(&self, _offenders_count: u32) -> sp_runtime::Perbill {
|
||||
sp_runtime::Perbill::from_percent(50)
|
||||
}
|
||||
}
|
||||
|
||||
/// Type alias for the wrapper using the mock reporter with BabeEquivocation kind.
|
||||
pub type MockBabeWrapper =
|
||||
crate::EquivocationReportWrapper<Test, MockInnerReporter, crate::BabeEquivocation>;
|
||||
|
||||
/// Type alias for the wrapper using the mock reporter with GrandpaEquivocation kind.
|
||||
pub type MockGrandpaWrapper =
|
||||
crate::EquivocationReportWrapper<Test, MockInnerReporter, crate::GrandpaEquivocation>;
|
||||
|
||||
pub fn run_block() {
|
||||
run_to_block(System::block_number() + 1);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,12 +18,14 @@ use {
|
|||
super::*,
|
||||
crate::{
|
||||
mock::{
|
||||
new_test_ext, run_block, DeferPeriodGetter, ExternalValidatorSlashes,
|
||||
MockEraIndexProvider, RuntimeEvent, RuntimeOrigin, System, Test,
|
||||
new_test_ext, run_block, DeferPeriodGetter, ExternalValidatorSlashes, MockBabeWrapper,
|
||||
MockEraIndexProvider, MockGrandpaWrapper, MockInnerReporter, MockOffence,
|
||||
MockOkOutboundQueue, RuntimeEvent, RuntimeOrigin, System, Test,
|
||||
},
|
||||
Slash,
|
||||
OffenceKind, Slash,
|
||||
},
|
||||
frame_support::{assert_noop, assert_ok},
|
||||
frame_support::{assert_noop, assert_ok, BoundedVec},
|
||||
sp_staking::offence::ReportOffence,
|
||||
};
|
||||
|
||||
#[test]
|
||||
|
|
@ -35,6 +37,7 @@ fn root_can_inject_manual_offence() {
|
|||
0,
|
||||
1u64,
|
||||
Perbill::from_percent(75),
|
||||
OffenceKind::Custom(BoundedVec::truncate_from(b"Test slash".to_vec())),
|
||||
));
|
||||
assert_eq!(
|
||||
Slashes::<Test>::get(get_slashing_era(0)),
|
||||
|
|
@ -43,7 +46,10 @@ fn root_can_inject_manual_offence() {
|
|||
percentage: Perbill::from_percent(75),
|
||||
confirmed: false,
|
||||
reporters: vec![],
|
||||
slash_id: 0
|
||||
slash_id: 0,
|
||||
offence_kind: OffenceKind::Custom(BoundedVec::truncate_from(
|
||||
b"Test slash".to_vec()
|
||||
)),
|
||||
}]
|
||||
);
|
||||
assert_eq!(NextSlashId::<Test>::get(), 1);
|
||||
|
|
@ -59,7 +65,8 @@ fn cannot_inject_future_era_offence() {
|
|||
RuntimeOrigin::root(),
|
||||
1,
|
||||
1u64,
|
||||
Perbill::from_percent(75)
|
||||
Perbill::from_percent(75),
|
||||
OffenceKind::Custom(BoundedVec::truncate_from(b"Test slash".to_vec())),
|
||||
),
|
||||
Error::<Test>::ProvidedFutureEra
|
||||
);
|
||||
|
|
@ -76,7 +83,8 @@ fn cannot_inject_era_offence_too_far_in_the_past() {
|
|||
RuntimeOrigin::root(),
|
||||
1,
|
||||
4u64,
|
||||
Perbill::from_percent(75)
|
||||
Perbill::from_percent(75),
|
||||
OffenceKind::Custom(BoundedVec::truncate_from(b"Test slash".to_vec())),
|
||||
),
|
||||
Error::<Test>::ProvidedNonSlashableEra
|
||||
);
|
||||
|
|
@ -91,7 +99,8 @@ fn root_can_cancel_deferred_slash() {
|
|||
RuntimeOrigin::root(),
|
||||
0,
|
||||
1u64,
|
||||
Perbill::from_percent(75)
|
||||
Perbill::from_percent(75),
|
||||
OffenceKind::Custom(BoundedVec::truncate_from(b"Test slash".to_vec())),
|
||||
));
|
||||
assert_ok!(ExternalValidatorSlashes::cancel_deferred_slash(
|
||||
RuntimeOrigin::root(),
|
||||
|
|
@ -111,7 +120,8 @@ fn root_cannot_cancel_deferred_slash_if_outside_deferring_period() {
|
|||
RuntimeOrigin::root(),
|
||||
0,
|
||||
1u64,
|
||||
Perbill::from_percent(75)
|
||||
Perbill::from_percent(75),
|
||||
OffenceKind::Custom(BoundedVec::truncate_from(b"Test slash".to_vec())),
|
||||
));
|
||||
|
||||
start_era(4, 0, 4);
|
||||
|
|
@ -131,7 +141,8 @@ fn root_cannot_cancel_out_of_bounds() {
|
|||
RuntimeOrigin::root(),
|
||||
0,
|
||||
1u64,
|
||||
Perbill::from_percent(75)
|
||||
Perbill::from_percent(75),
|
||||
OffenceKind::Custom(BoundedVec::truncate_from(b"Test slash".to_vec())),
|
||||
));
|
||||
assert_noop!(
|
||||
ExternalValidatorSlashes::cancel_deferred_slash(
|
||||
|
|
@ -152,7 +163,8 @@ fn root_cannot_cancel_duplicates() {
|
|||
RuntimeOrigin::root(),
|
||||
0,
|
||||
1u64,
|
||||
Perbill::from_percent(75)
|
||||
Perbill::from_percent(75),
|
||||
OffenceKind::Custom(BoundedVec::truncate_from(b"Test slash".to_vec())),
|
||||
));
|
||||
assert_noop!(
|
||||
ExternalValidatorSlashes::cancel_deferred_slash(RuntimeOrigin::root(), 3, vec![0, 0]),
|
||||
|
|
@ -169,13 +181,15 @@ fn root_cannot_cancel_if_not_sorted() {
|
|||
RuntimeOrigin::root(),
|
||||
0,
|
||||
1u64,
|
||||
Perbill::from_percent(75)
|
||||
Perbill::from_percent(75),
|
||||
OffenceKind::Custom(BoundedVec::truncate_from(b"Test slash".to_vec())),
|
||||
));
|
||||
assert_ok!(ExternalValidatorSlashes::force_inject_slash(
|
||||
RuntimeOrigin::root(),
|
||||
0,
|
||||
2u64,
|
||||
Perbill::from_percent(75)
|
||||
Perbill::from_percent(75),
|
||||
OffenceKind::Custom(BoundedVec::truncate_from(b"Test slash".to_vec())),
|
||||
));
|
||||
assert_noop!(
|
||||
ExternalValidatorSlashes::cancel_deferred_slash(RuntimeOrigin::root(), 3, vec![1, 0]),
|
||||
|
|
@ -196,7 +210,8 @@ fn test_after_bonding_period_we_can_remove_slashes() {
|
|||
RuntimeOrigin::root(),
|
||||
0,
|
||||
1u64,
|
||||
Perbill::from_percent(75)
|
||||
Perbill::from_percent(75),
|
||||
OffenceKind::Custom(BoundedVec::truncate_from(b"Test slash".to_vec())),
|
||||
));
|
||||
|
||||
assert_eq!(
|
||||
|
|
@ -206,7 +221,10 @@ fn test_after_bonding_period_we_can_remove_slashes() {
|
|||
percentage: Perbill::from_percent(75),
|
||||
confirmed: false,
|
||||
reporters: vec![],
|
||||
slash_id: 0
|
||||
slash_id: 0,
|
||||
offence_kind: OffenceKind::Custom(BoundedVec::truncate_from(
|
||||
b"Test slash".to_vec()
|
||||
)),
|
||||
}]
|
||||
);
|
||||
|
||||
|
|
@ -226,6 +244,7 @@ fn test_on_offence_injects_offences() {
|
|||
new_test_ext().execute_with(|| {
|
||||
start_era(0, 0, 0);
|
||||
start_era(1, 1, 1);
|
||||
PendingOffenceKind::<Test>::insert(0, 3u64, OffenceKind::LivenessOffence);
|
||||
Pallet::<Test>::on_offence(
|
||||
&[OffenceDetails {
|
||||
// 1 and 2 are invulnerables
|
||||
|
|
@ -242,7 +261,8 @@ fn test_on_offence_injects_offences() {
|
|||
percentage: Perbill::from_percent(75),
|
||||
confirmed: false,
|
||||
reporters: vec![],
|
||||
slash_id: 0
|
||||
slash_id: 0,
|
||||
offence_kind: OffenceKind::LivenessOffence,
|
||||
}]
|
||||
);
|
||||
});
|
||||
|
|
@ -253,7 +273,8 @@ fn test_on_offence_does_not_work_for_invulnerables() {
|
|||
new_test_ext().execute_with(|| {
|
||||
start_era(0, 0, 0);
|
||||
start_era(1, 1, 1);
|
||||
// account 1 invulnerable
|
||||
// account 1 invulnerable — populate kind so we test the invulnerable check, not missing kind
|
||||
PendingOffenceKind::<Test>::insert(0, 1u64, OffenceKind::LivenessOffence);
|
||||
Pallet::<Test>::on_offence(
|
||||
&[OffenceDetails {
|
||||
offender: (1, ()),
|
||||
|
|
@ -276,6 +297,7 @@ fn test_on_offence_does_not_work_if_slashing_disabled() {
|
|||
RuntimeOrigin::root(),
|
||||
SlashingModeOption::Disabled,
|
||||
));
|
||||
PendingOffenceKind::<Test>::insert(0, 3u64, OffenceKind::LivenessOffence);
|
||||
let weight = Pallet::<Test>::on_offence(
|
||||
&[OffenceDetails {
|
||||
// 1 and 2 are invulnerables
|
||||
|
|
@ -303,7 +325,8 @@ fn defer_period_of_zero_confirms_immediately_slashes() {
|
|||
RuntimeOrigin::root(),
|
||||
0,
|
||||
1u64,
|
||||
Perbill::from_percent(75)
|
||||
Perbill::from_percent(75),
|
||||
OffenceKind::Custom(BoundedVec::truncate_from(b"Test slash".to_vec())),
|
||||
));
|
||||
assert_eq!(
|
||||
Slashes::<Test>::get(get_slashing_era(0)),
|
||||
|
|
@ -312,7 +335,10 @@ fn defer_period_of_zero_confirms_immediately_slashes() {
|
|||
percentage: Perbill::from_percent(75),
|
||||
confirmed: true,
|
||||
reporters: vec![],
|
||||
slash_id: 0
|
||||
slash_id: 0,
|
||||
offence_kind: OffenceKind::Custom(BoundedVec::truncate_from(
|
||||
b"Test slash".to_vec()
|
||||
)),
|
||||
}]
|
||||
);
|
||||
});
|
||||
|
|
@ -327,7 +353,8 @@ fn we_cannot_cancel_anything_with_defer_period_zero() {
|
|||
RuntimeOrigin::root(),
|
||||
0,
|
||||
1u64,
|
||||
Perbill::from_percent(75)
|
||||
Perbill::from_percent(75),
|
||||
OffenceKind::Custom(BoundedVec::truncate_from(b"Test slash".to_vec())),
|
||||
));
|
||||
assert_noop!(
|
||||
ExternalValidatorSlashes::cancel_deferred_slash(RuntimeOrigin::root(), 0, vec![0]),
|
||||
|
|
@ -342,6 +369,7 @@ fn test_on_offence_defer_period_0() {
|
|||
crate::mock::DeferPeriodGetter::with_defer_period(0);
|
||||
start_era(0, 0, 0);
|
||||
start_era(1, 1, 1);
|
||||
PendingOffenceKind::<Test>::insert(0, 3u64, OffenceKind::LivenessOffence);
|
||||
Pallet::<Test>::on_offence(
|
||||
&[OffenceDetails {
|
||||
// 1 and 2 are invulnerables
|
||||
|
|
@ -359,7 +387,8 @@ fn test_on_offence_defer_period_0() {
|
|||
percentage: Perbill::from_percent(75),
|
||||
confirmed: true,
|
||||
reporters: vec![],
|
||||
slash_id: 0
|
||||
slash_id: 0,
|
||||
offence_kind: OffenceKind::LivenessOffence,
|
||||
}]
|
||||
);
|
||||
start_era(2, 2, 2);
|
||||
|
|
@ -373,6 +402,7 @@ fn test_slashes_command_matches_event() {
|
|||
crate::mock::DeferPeriodGetter::with_defer_period(0);
|
||||
start_era(0, 0, 0);
|
||||
start_era(1, 1, 1);
|
||||
PendingOffenceKind::<Test>::insert(0, 3u64, OffenceKind::LivenessOffence);
|
||||
Pallet::<Test>::on_offence(
|
||||
&[OffenceDetails {
|
||||
// 1 and 2 are invulnerables
|
||||
|
|
@ -391,7 +421,8 @@ fn test_slashes_command_matches_event() {
|
|||
percentage: Perbill::from_percent(75),
|
||||
confirmed: true,
|
||||
reporters: vec![],
|
||||
slash_id: 0
|
||||
slash_id: 0,
|
||||
offence_kind: OffenceKind::LivenessOffence,
|
||||
}]
|
||||
);
|
||||
start_era(2, 2, 2);
|
||||
|
|
@ -405,6 +436,122 @@ fn test_slashes_command_matches_event() {
|
|||
});
|
||||
}
|
||||
|
||||
// ── WAD conversion tests ──
|
||||
// MaxSlashWad in mock = 50_000_000_000_000_000 (5e16 = 5% in WAD format).
|
||||
// Perbill(100%) = 1_000_000_000 inner.
|
||||
// Formula: wad = perbill_inner * MaxSlashWad / 1e9
|
||||
|
||||
#[test]
|
||||
fn wad_conversion_100_percent_slash_maps_to_max_slash_wad() {
|
||||
new_test_ext().execute_with(|| {
|
||||
crate::mock::DeferPeriodGetter::with_defer_period(0);
|
||||
start_era(0, 0, 0);
|
||||
start_era(1, 1, 1);
|
||||
|
||||
PendingOffenceKind::<Test>::insert(0, 3u64, OffenceKind::LivenessOffence);
|
||||
Pallet::<Test>::on_offence(
|
||||
&[OffenceDetails {
|
||||
offender: (3, ()),
|
||||
reporters: vec![],
|
||||
}],
|
||||
&[Perbill::from_percent(100)],
|
||||
0,
|
||||
);
|
||||
|
||||
start_era(2, 2, 2);
|
||||
run_block();
|
||||
|
||||
let sent = MockOkOutboundQueue::last_sent_slashes();
|
||||
assert_eq!(sent.len(), 1);
|
||||
// 100% → full MaxSlashWad = 5e16
|
||||
assert_eq!(sent[0].wad_to_slash, 50_000_000_000_000_000u128);
|
||||
assert_eq!(sent[0].validator, 3);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wad_conversion_50_percent_slash_maps_to_half_max_slash_wad() {
|
||||
new_test_ext().execute_with(|| {
|
||||
crate::mock::DeferPeriodGetter::with_defer_period(0);
|
||||
start_era(0, 0, 0);
|
||||
start_era(1, 1, 1);
|
||||
|
||||
PendingOffenceKind::<Test>::insert(0, 3u64, OffenceKind::LivenessOffence);
|
||||
Pallet::<Test>::on_offence(
|
||||
&[OffenceDetails {
|
||||
offender: (3, ()),
|
||||
reporters: vec![],
|
||||
}],
|
||||
&[Perbill::from_percent(50)],
|
||||
0,
|
||||
);
|
||||
|
||||
start_era(2, 2, 2);
|
||||
run_block();
|
||||
|
||||
let sent = MockOkOutboundQueue::last_sent_slashes();
|
||||
assert_eq!(sent.len(), 1);
|
||||
// 50% → MaxSlashWad / 2 = 2.5e16
|
||||
assert_eq!(sent[0].wad_to_slash, 25_000_000_000_000_000u128);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wad_conversion_zero_percent_slash_maps_to_zero() {
|
||||
new_test_ext().execute_with(|| {
|
||||
crate::mock::DeferPeriodGetter::with_defer_period(0);
|
||||
start_era(0, 0, 0);
|
||||
start_era(1, 1, 1);
|
||||
|
||||
PendingOffenceKind::<Test>::insert(0, 3u64, OffenceKind::LivenessOffence);
|
||||
Pallet::<Test>::on_offence(
|
||||
&[OffenceDetails {
|
||||
offender: (3, ()),
|
||||
reporters: vec![],
|
||||
}],
|
||||
&[Perbill::from_percent(0)],
|
||||
0,
|
||||
);
|
||||
|
||||
start_era(2, 2, 2);
|
||||
run_block();
|
||||
|
||||
// 0% slash → no slash recorded (compute_slash returns None for 0%)
|
||||
let sent = MockOkOutboundQueue::last_sent_slashes();
|
||||
assert_eq!(sent.len(), 0);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wad_conversion_carries_offence_kind_description() {
|
||||
new_test_ext().execute_with(|| {
|
||||
crate::mock::DeferPeriodGetter::with_defer_period(0);
|
||||
start_era(0, 0, 0);
|
||||
start_era(1, 1, 1);
|
||||
|
||||
// Pre-populate a BabeEquivocation kind for session 0, validator 3.
|
||||
PendingOffenceKind::<Test>::insert(0, 3u64, OffenceKind::BabeEquivocation);
|
||||
|
||||
Pallet::<Test>::on_offence(
|
||||
&[OffenceDetails {
|
||||
offender: (3, ()),
|
||||
reporters: vec![],
|
||||
}],
|
||||
&[Perbill::from_percent(75)],
|
||||
0,
|
||||
);
|
||||
|
||||
start_era(2, 2, 2);
|
||||
run_block();
|
||||
|
||||
let sent = MockOkOutboundQueue::last_sent_slashes();
|
||||
assert_eq!(sent.len(), 1);
|
||||
// 75% → 75% of MaxSlashWad = 3.75e16
|
||||
assert_eq!(sent[0].wad_to_slash, 37_500_000_000_000_000u128);
|
||||
assert_eq!(sent[0].description, "BABE equivocation");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_on_offence_defer_period_0_messages_get_queued() {
|
||||
new_test_ext().execute_with(|| {
|
||||
|
|
@ -413,6 +560,7 @@ fn test_on_offence_defer_period_0_messages_get_queued() {
|
|||
start_era(1, 1, 1);
|
||||
// The limit is 20,
|
||||
for i in 0..25 {
|
||||
PendingOffenceKind::<Test>::insert(0, 3 + i, OffenceKind::LivenessOffence);
|
||||
Pallet::<Test>::on_offence(
|
||||
&[OffenceDetails {
|
||||
// 1 and 2 are invulnerables
|
||||
|
|
@ -450,6 +598,7 @@ fn test_account_id_encoding() {
|
|||
slash_id: 1,
|
||||
percentage: Perbill::default(),
|
||||
confirmed: true,
|
||||
offence_kind: OffenceKind::LivenessOffence,
|
||||
};
|
||||
|
||||
let encoded_account = slash.validator.encode();
|
||||
|
|
@ -466,6 +615,7 @@ fn test_on_offence_defer_period_0_messages_get_queued_across_eras() {
|
|||
start_era(1, 1, 1);
|
||||
// The limit is 20,
|
||||
for i in 0..25 {
|
||||
PendingOffenceKind::<Test>::insert(0, 3 + i, OffenceKind::LivenessOffence);
|
||||
Pallet::<Test>::on_offence(
|
||||
&[OffenceDetails {
|
||||
// 1 and 2 are invulnerables
|
||||
|
|
@ -487,6 +637,7 @@ fn test_on_offence_defer_period_0_messages_get_queued_across_eras() {
|
|||
// We have 5 non-dispatched, which should accumulate
|
||||
// We shoulld have 30 after we initialie era 3
|
||||
for i in 0..25 {
|
||||
PendingOffenceKind::<Test>::insert(2, 3 + i, OffenceKind::LivenessOffence);
|
||||
Pallet::<Test>::on_offence(
|
||||
&[OffenceDetails {
|
||||
// 1 and 2 are invulnerables
|
||||
|
|
@ -512,6 +663,213 @@ fn test_on_offence_defer_period_0_messages_get_queued_across_eras() {
|
|||
});
|
||||
}
|
||||
|
||||
// ── PendingOffenceKind & EquivocationReportWrapper tests ──
|
||||
|
||||
#[test]
|
||||
fn on_offence_reads_pending_offence_kind_from_double_map() {
|
||||
new_test_ext().execute_with(|| {
|
||||
start_era(0, 0, 0);
|
||||
start_era(1, 1, 1);
|
||||
|
||||
// Pre-populate PendingOffenceKind for validator 3 at session 0.
|
||||
PendingOffenceKind::<Test>::insert(0, 3u64, OffenceKind::BabeEquivocation);
|
||||
|
||||
Pallet::<Test>::on_offence(
|
||||
&[OffenceDetails {
|
||||
offender: (3, ()),
|
||||
reporters: vec![],
|
||||
}],
|
||||
&[Perbill::from_percent(75)],
|
||||
0,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Slashes::<Test>::get(get_slashing_era(0)),
|
||||
vec![Slash {
|
||||
validator: 3,
|
||||
percentage: Perbill::from_percent(75),
|
||||
confirmed: false,
|
||||
reporters: vec![],
|
||||
slash_id: 0,
|
||||
offence_kind: OffenceKind::BabeEquivocation,
|
||||
}]
|
||||
);
|
||||
|
||||
// Entry should have been consumed.
|
||||
assert_eq!(PendingOffenceKind::<Test>::get(0, 3u64), None);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pending_offence_kind_is_session_isolated() {
|
||||
new_test_ext().execute_with(|| {
|
||||
start_era(0, 0, 0);
|
||||
start_era(1, 1, 1);
|
||||
|
||||
// Same validator, different kinds in different sessions.
|
||||
PendingOffenceKind::<Test>::insert(0, 3u64, OffenceKind::BabeEquivocation);
|
||||
PendingOffenceKind::<Test>::insert(1, 3u64, OffenceKind::GrandpaEquivocation);
|
||||
|
||||
// Report at session 0 — should use BabeEquivocation.
|
||||
Pallet::<Test>::on_offence(
|
||||
&[OffenceDetails {
|
||||
offender: (3, ()),
|
||||
reporters: vec![],
|
||||
}],
|
||||
&[Perbill::from_percent(50)],
|
||||
0,
|
||||
);
|
||||
|
||||
// Session 0 consumed, session 1 untouched.
|
||||
assert_eq!(PendingOffenceKind::<Test>::get(0, 3u64), None);
|
||||
assert_eq!(
|
||||
PendingOffenceKind::<Test>::get(1, 3u64),
|
||||
Some(OffenceKind::GrandpaEquivocation),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrapper_filters_historical_offence_before_bonding_period() {
|
||||
new_test_ext().execute_with(|| {
|
||||
start_era(0, 0, 0);
|
||||
start_era(1, 1, 1);
|
||||
MockInnerReporter::reset();
|
||||
|
||||
// BondedEras now contains [(0,0,0), (1,1,1)].
|
||||
// An offence at session 0 is within the bonding period — should pass.
|
||||
let result = MockBabeWrapper::report_offence(
|
||||
Vec::<u64>::new(),
|
||||
MockOffence {
|
||||
session_index: 0,
|
||||
offenders: vec![(3, ())],
|
||||
},
|
||||
);
|
||||
assert!(result.is_ok());
|
||||
assert!(MockInnerReporter::was_called());
|
||||
|
||||
// The mock reporter doesn't trigger on_offence, so manually consume the entry.
|
||||
assert_eq!(
|
||||
PendingOffenceKind::<Test>::take(0, 3u64),
|
||||
Some(OffenceKind::BabeEquivocation),
|
||||
);
|
||||
|
||||
// Advance eras until era 0 drops out of BondedEras.
|
||||
// BondingDuration = 5, so after era 6 starts, era 0 is pruned.
|
||||
for i in 2..=7 {
|
||||
start_era(i, i, i as u64);
|
||||
}
|
||||
|
||||
MockInnerReporter::reset();
|
||||
|
||||
// Session 0 now predates the bonding period — should be silently discarded.
|
||||
let result = MockBabeWrapper::report_offence(
|
||||
Vec::<u64>::new(),
|
||||
MockOffence {
|
||||
session_index: 0,
|
||||
offenders: vec![(3, ())],
|
||||
},
|
||||
);
|
||||
assert!(result.is_ok());
|
||||
assert!(!MockInnerReporter::was_called());
|
||||
|
||||
// No PendingOffenceKind should have been written.
|
||||
assert_eq!(PendingOffenceKind::<Test>::get(0, 3u64), None);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrapper_sets_pending_offence_kind_per_session_and_offender() {
|
||||
new_test_ext().execute_with(|| {
|
||||
start_era(0, 0, 0);
|
||||
start_era(1, 1, 1);
|
||||
MockInnerReporter::reset();
|
||||
|
||||
let _ = MockBabeWrapper::report_offence(
|
||||
Vec::<u64>::new(),
|
||||
MockOffence {
|
||||
session_index: 0,
|
||||
offenders: vec![(3, ()), (4, ())],
|
||||
},
|
||||
);
|
||||
|
||||
// Both offenders should have entries at session 0.
|
||||
assert_eq!(
|
||||
PendingOffenceKind::<Test>::get(0, 3u64),
|
||||
Some(OffenceKind::BabeEquivocation),
|
||||
);
|
||||
assert_eq!(
|
||||
PendingOffenceKind::<Test>::get(0, 4u64),
|
||||
Some(OffenceKind::BabeEquivocation),
|
||||
);
|
||||
// No entry at a different session.
|
||||
assert_eq!(PendingOffenceKind::<Test>::get(1, 3u64), None);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrapper_cleans_up_pending_offence_kind_on_error() {
|
||||
new_test_ext().execute_with(|| {
|
||||
start_era(0, 0, 0);
|
||||
start_era(1, 1, 1);
|
||||
MockInnerReporter::reset();
|
||||
MockInnerReporter::set_should_fail(true);
|
||||
|
||||
let result = MockBabeWrapper::report_offence(
|
||||
Vec::<u64>::new(),
|
||||
MockOffence {
|
||||
session_index: 0,
|
||||
offenders: vec![(3, ()), (4, ())],
|
||||
},
|
||||
);
|
||||
|
||||
assert!(result.is_err());
|
||||
// Entries should have been cleaned up.
|
||||
assert_eq!(PendingOffenceKind::<Test>::get(0, 3u64), None);
|
||||
assert_eq!(PendingOffenceKind::<Test>::get(0, 4u64), None);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrapper_error_cleanup_does_not_affect_other_sessions() {
|
||||
new_test_ext().execute_with(|| {
|
||||
start_era(0, 0, 0);
|
||||
start_era(1, 1, 1);
|
||||
MockInnerReporter::reset();
|
||||
|
||||
// Successfully report at session 0.
|
||||
let _ = MockGrandpaWrapper::report_offence(
|
||||
Vec::<u64>::new(),
|
||||
MockOffence {
|
||||
session_index: 0,
|
||||
offenders: vec![(3, ())],
|
||||
},
|
||||
);
|
||||
assert_eq!(
|
||||
PendingOffenceKind::<Test>::get(0, 3u64),
|
||||
Some(OffenceKind::GrandpaEquivocation),
|
||||
);
|
||||
|
||||
// Now fail a report at session 1 for the same validator.
|
||||
MockInnerReporter::set_should_fail(true);
|
||||
let result = MockBabeWrapper::report_offence(
|
||||
Vec::<u64>::new(),
|
||||
MockOffence {
|
||||
session_index: 1,
|
||||
offenders: vec![(3, ())],
|
||||
},
|
||||
);
|
||||
assert!(result.is_err());
|
||||
|
||||
// Session 1 cleaned up, session 0 untouched.
|
||||
assert_eq!(PendingOffenceKind::<Test>::get(1, 3u64), None);
|
||||
assert_eq!(
|
||||
PendingOffenceKind::<Test>::get(0, 3u64),
|
||||
Some(OffenceKind::GrandpaEquivocation),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
fn start_era(era_index: EraIndex, session_index: SessionIndex, external_idx: u64) {
|
||||
Pallet::<Test>::on_era_start(era_index, session_index, external_idx);
|
||||
crate::mock::MockEraIndexProvider::with_era(era_index);
|
||||
|
|
|
|||
|
|
@ -111,8 +111,8 @@ fn encode_slashing_request(
|
|||
let slashing_request = SlashingRequest {
|
||||
operator: Address::from(slash_operator.validator.0),
|
||||
strategies: strategies.clone(),
|
||||
wadsToSlash: wads_to_slash, // We only have one strategy deployed
|
||||
description: "Slashing validator".into(),
|
||||
wadsToSlash: wads_to_slash,
|
||||
description: slash_operator.description.clone().into(),
|
||||
};
|
||||
|
||||
slashings.push(slashing_request);
|
||||
|
|
|
|||
|
|
@ -321,8 +321,16 @@ impl pallet_babe::Config for Runtime {
|
|||
type KeyOwnerProof =
|
||||
<Historical as KeyOwnerProofSystem<(KeyTypeId, pallet_babe::AuthorityId)>>::Proof;
|
||||
|
||||
type EquivocationReportSystem =
|
||||
pallet_babe::EquivocationReportSystem<Self, Offences, Historical, ReportLongevity>;
|
||||
type EquivocationReportSystem = pallet_babe::EquivocationReportSystem<
|
||||
Self,
|
||||
pallet_external_validator_slashes::EquivocationReportWrapper<
|
||||
Runtime,
|
||||
Offences,
|
||||
pallet_external_validator_slashes::BabeEquivocation,
|
||||
>,
|
||||
Historical,
|
||||
ReportLongevity,
|
||||
>;
|
||||
}
|
||||
|
||||
impl pallet_timestamp::Config for Runtime {
|
||||
|
|
@ -401,7 +409,11 @@ impl pallet_im_online::Config for Runtime {
|
|||
type RuntimeEvent = RuntimeEvent;
|
||||
type ValidatorSet = Historical;
|
||||
type NextSessionRotation = Babe;
|
||||
type ReportUnresponsiveness = Offences;
|
||||
type ReportUnresponsiveness = pallet_external_validator_slashes::EquivocationReportWrapper<
|
||||
Runtime,
|
||||
Offences,
|
||||
pallet_external_validator_slashes::ImOnlineUnresponsive,
|
||||
>;
|
||||
type UnsignedPriority = ImOnlineUnsignedPriority;
|
||||
type WeightInfo = crate::weights::pallet_im_online::WeightInfo<Runtime>;
|
||||
}
|
||||
|
|
@ -424,7 +436,11 @@ impl pallet_grandpa::Config for Runtime {
|
|||
type KeyOwnerProof = <Historical as KeyOwnerProofSystem<(KeyTypeId, GrandpaId)>>::Proof;
|
||||
type EquivocationReportSystem = pallet_grandpa::EquivocationReportSystem<
|
||||
Self,
|
||||
Offences,
|
||||
pallet_external_validator_slashes::EquivocationReportWrapper<
|
||||
Runtime,
|
||||
Offences,
|
||||
pallet_external_validator_slashes::GrandpaEquivocation,
|
||||
>,
|
||||
Historical,
|
||||
EquivocationReportPeriodInBlocks,
|
||||
>;
|
||||
|
|
@ -501,8 +517,16 @@ impl pallet_beefy::Config for Runtime {
|
|||
type AncestryHelper = BeefyMmrLeaf;
|
||||
type WeightInfo = ();
|
||||
type KeyOwnerProof = <Historical as KeyOwnerProofSystem<(KeyTypeId, BeefyId)>>::Proof;
|
||||
type EquivocationReportSystem =
|
||||
pallet_beefy::EquivocationReportSystem<Self, Offences, Historical, ReportLongevity>;
|
||||
type EquivocationReportSystem = pallet_beefy::EquivocationReportSystem<
|
||||
Self,
|
||||
pallet_external_validator_slashes::EquivocationReportWrapper<
|
||||
Runtime,
|
||||
Offences,
|
||||
pallet_external_validator_slashes::BeefyEquivocation,
|
||||
>,
|
||||
Historical,
|
||||
ReportLongevity,
|
||||
>;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
|
|
@ -1701,6 +1725,7 @@ impl pallet_external_validator_slashes::Config for Runtime {
|
|||
type EraIndexProvider = ExternalValidators;
|
||||
type InvulnerablesProvider = ExternalValidators;
|
||||
type ExternalIndexProvider = ExternalValidators;
|
||||
type MaxSlashWad = runtime_params::dynamic_params::runtime_config::MaxSlashWad;
|
||||
type QueuedSlashesProcessedPerBlock = ConstU32<10>;
|
||||
type WeightInfo = mainnet_weights::pallet_external_validator_slashes::WeightInfo<Runtime>;
|
||||
type SendMessage = SlashesSendAdapter;
|
||||
|
|
|
|||
|
|
@ -417,6 +417,16 @@ pub mod dynamic_params {
|
|||
BoundedVec::truncate_from(vec![]);
|
||||
|
||||
// ╚══════════════════════ EigenLayer Rewards V2 ═══════════════════════╝
|
||||
|
||||
// ╔══════════════════════ EigenLayer Slashing ═══════════════════════╗
|
||||
|
||||
#[codec(index = 46)]
|
||||
#[allow(non_upper_case_globals)]
|
||||
/// Maximum WAD value for EigenLayer slashing. Maps Perbill(100%) to this value.
|
||||
/// 5e16 = 5% in WAD format (1e18 = 100%).
|
||||
pub static MaxSlashWad: u128 = 50_000_000_000_000_000u128;
|
||||
|
||||
// ╚══════════════════════ EigenLayer Slashing ═══════════════════════╝
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -321,8 +321,16 @@ impl pallet_babe::Config for Runtime {
|
|||
type KeyOwnerProof =
|
||||
<Historical as KeyOwnerProofSystem<(KeyTypeId, pallet_babe::AuthorityId)>>::Proof;
|
||||
|
||||
type EquivocationReportSystem =
|
||||
pallet_babe::EquivocationReportSystem<Self, Offences, Historical, ReportLongevity>;
|
||||
type EquivocationReportSystem = pallet_babe::EquivocationReportSystem<
|
||||
Self,
|
||||
pallet_external_validator_slashes::EquivocationReportWrapper<
|
||||
Runtime,
|
||||
Offences,
|
||||
pallet_external_validator_slashes::BabeEquivocation,
|
||||
>,
|
||||
Historical,
|
||||
ReportLongevity,
|
||||
>;
|
||||
}
|
||||
|
||||
impl pallet_timestamp::Config for Runtime {
|
||||
|
|
@ -400,7 +408,11 @@ impl pallet_im_online::Config for Runtime {
|
|||
type RuntimeEvent = RuntimeEvent;
|
||||
type ValidatorSet = Historical;
|
||||
type NextSessionRotation = Babe;
|
||||
type ReportUnresponsiveness = Offences;
|
||||
type ReportUnresponsiveness = pallet_external_validator_slashes::EquivocationReportWrapper<
|
||||
Runtime,
|
||||
Offences,
|
||||
pallet_external_validator_slashes::ImOnlineUnresponsive,
|
||||
>;
|
||||
type UnsignedPriority = ImOnlineUnsignedPriority;
|
||||
type WeightInfo = crate::weights::pallet_im_online::WeightInfo<Runtime>;
|
||||
}
|
||||
|
|
@ -423,7 +435,11 @@ impl pallet_grandpa::Config for Runtime {
|
|||
type KeyOwnerProof = <Historical as KeyOwnerProofSystem<(KeyTypeId, GrandpaId)>>::Proof;
|
||||
type EquivocationReportSystem = pallet_grandpa::EquivocationReportSystem<
|
||||
Self,
|
||||
Offences,
|
||||
pallet_external_validator_slashes::EquivocationReportWrapper<
|
||||
Runtime,
|
||||
Offences,
|
||||
pallet_external_validator_slashes::GrandpaEquivocation,
|
||||
>,
|
||||
Historical,
|
||||
EquivocationReportPeriodInBlocks,
|
||||
>;
|
||||
|
|
@ -498,8 +514,16 @@ impl pallet_beefy::Config for Runtime {
|
|||
type AncestryHelper = BeefyMmrLeaf;
|
||||
type WeightInfo = ();
|
||||
type KeyOwnerProof = <Historical as KeyOwnerProofSystem<(KeyTypeId, BeefyId)>>::Proof;
|
||||
type EquivocationReportSystem =
|
||||
pallet_beefy::EquivocationReportSystem<Self, Offences, Historical, ReportLongevity>;
|
||||
type EquivocationReportSystem = pallet_beefy::EquivocationReportSystem<
|
||||
Self,
|
||||
pallet_external_validator_slashes::EquivocationReportWrapper<
|
||||
Runtime,
|
||||
Offences,
|
||||
pallet_external_validator_slashes::BeefyEquivocation,
|
||||
>,
|
||||
Historical,
|
||||
ReportLongevity,
|
||||
>;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
|
|
@ -1697,6 +1721,7 @@ impl pallet_external_validator_slashes::Config for Runtime {
|
|||
type EraIndexProvider = ExternalValidators;
|
||||
type InvulnerablesProvider = ExternalValidators;
|
||||
type ExternalIndexProvider = ExternalValidators;
|
||||
type MaxSlashWad = runtime_params::dynamic_params::runtime_config::MaxSlashWad;
|
||||
type QueuedSlashesProcessedPerBlock = ConstU32<10>;
|
||||
type WeightInfo = stagenet_weights::pallet_external_validator_slashes::WeightInfo<Runtime>;
|
||||
type SendMessage = SlashesSendAdapter;
|
||||
|
|
|
|||
|
|
@ -424,6 +424,16 @@ pub mod dynamic_params {
|
|||
BoundedVec::truncate_from(vec![]);
|
||||
|
||||
// ╚══════════════════════ EigenLayer Rewards V2 ═══════════════════════╝
|
||||
|
||||
// ╔══════════════════════ EigenLayer Slashing ═══════════════════════╗
|
||||
|
||||
#[codec(index = 46)]
|
||||
#[allow(non_upper_case_globals)]
|
||||
/// Maximum WAD value for EigenLayer slashing. Maps Perbill(100%) to this value.
|
||||
/// 5e16 = 5% in WAD format (1e18 = 100%).
|
||||
pub static MaxSlashWad: u128 = 50_000_000_000_000_000u128;
|
||||
|
||||
// ╚══════════════════════ EigenLayer Slashing ═══════════════════════╝
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -321,8 +321,16 @@ impl pallet_babe::Config for Runtime {
|
|||
type KeyOwnerProof =
|
||||
<Historical as KeyOwnerProofSystem<(KeyTypeId, pallet_babe::AuthorityId)>>::Proof;
|
||||
|
||||
type EquivocationReportSystem =
|
||||
pallet_babe::EquivocationReportSystem<Self, Offences, Historical, ReportLongevity>;
|
||||
type EquivocationReportSystem = pallet_babe::EquivocationReportSystem<
|
||||
Self,
|
||||
pallet_external_validator_slashes::EquivocationReportWrapper<
|
||||
Runtime,
|
||||
Offences,
|
||||
pallet_external_validator_slashes::BabeEquivocation,
|
||||
>,
|
||||
Historical,
|
||||
ReportLongevity,
|
||||
>;
|
||||
}
|
||||
|
||||
impl pallet_timestamp::Config for Runtime {
|
||||
|
|
@ -400,7 +408,11 @@ impl pallet_im_online::Config for Runtime {
|
|||
type RuntimeEvent = RuntimeEvent;
|
||||
type ValidatorSet = Historical;
|
||||
type NextSessionRotation = Babe;
|
||||
type ReportUnresponsiveness = Offences;
|
||||
type ReportUnresponsiveness = pallet_external_validator_slashes::EquivocationReportWrapper<
|
||||
Runtime,
|
||||
Offences,
|
||||
pallet_external_validator_slashes::ImOnlineUnresponsive,
|
||||
>;
|
||||
type UnsignedPriority = ImOnlineUnsignedPriority;
|
||||
type WeightInfo = crate::weights::pallet_im_online::WeightInfo<Runtime>;
|
||||
}
|
||||
|
|
@ -423,7 +435,11 @@ impl pallet_grandpa::Config for Runtime {
|
|||
type KeyOwnerProof = <Historical as KeyOwnerProofSystem<(KeyTypeId, GrandpaId)>>::Proof;
|
||||
type EquivocationReportSystem = pallet_grandpa::EquivocationReportSystem<
|
||||
Self,
|
||||
Offences,
|
||||
pallet_external_validator_slashes::EquivocationReportWrapper<
|
||||
Runtime,
|
||||
Offences,
|
||||
pallet_external_validator_slashes::GrandpaEquivocation,
|
||||
>,
|
||||
Historical,
|
||||
EquivocationReportPeriodInBlocks,
|
||||
>;
|
||||
|
|
@ -501,8 +517,16 @@ impl pallet_beefy::Config for Runtime {
|
|||
type AncestryHelper = BeefyMmrLeaf;
|
||||
type WeightInfo = ();
|
||||
type KeyOwnerProof = <Historical as KeyOwnerProofSystem<(KeyTypeId, BeefyId)>>::Proof;
|
||||
type EquivocationReportSystem =
|
||||
pallet_beefy::EquivocationReportSystem<Self, Offences, Historical, ReportLongevity>;
|
||||
type EquivocationReportSystem = pallet_beefy::EquivocationReportSystem<
|
||||
Self,
|
||||
pallet_external_validator_slashes::EquivocationReportWrapper<
|
||||
Runtime,
|
||||
Offences,
|
||||
pallet_external_validator_slashes::BeefyEquivocation,
|
||||
>,
|
||||
Historical,
|
||||
ReportLongevity,
|
||||
>;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
|
|
@ -1700,6 +1724,7 @@ impl pallet_external_validator_slashes::Config for Runtime {
|
|||
type EraIndexProvider = ExternalValidators;
|
||||
type InvulnerablesProvider = ExternalValidators;
|
||||
type ExternalIndexProvider = ExternalValidators;
|
||||
type MaxSlashWad = runtime_params::dynamic_params::runtime_config::MaxSlashWad;
|
||||
type QueuedSlashesProcessedPerBlock = ConstU32<10>;
|
||||
type WeightInfo = testnet_weights::pallet_external_validator_slashes::WeightInfo<Runtime>;
|
||||
type SendMessage = SlashesSendAdapter;
|
||||
|
|
|
|||
|
|
@ -419,6 +419,16 @@ pub mod dynamic_params {
|
|||
BoundedVec::truncate_from(vec![]);
|
||||
|
||||
// ╚══════════════════════ EigenLayer Rewards V2 ═══════════════════════╝
|
||||
|
||||
// ╔══════════════════════ EigenLayer Slashing ═══════════════════════╗
|
||||
|
||||
#[codec(index = 46)]
|
||||
#[allow(non_upper_case_globals)]
|
||||
/// Maximum WAD value for EigenLayer slashing. Maps Perbill(100%) to this value.
|
||||
/// 5e16 = 5% in WAD format (1e18 = 100%).
|
||||
pub static MaxSlashWad: u128 = 50_000_000_000_000_000u128;
|
||||
|
||||
// ╚══════════════════════ EigenLayer Slashing ═══════════════════════╝
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": "0.1.0-autogenerated.13357056092938763018",
|
||||
"version": "0.1.0-autogenerated.18296316742446681711",
|
||||
"name": "@polkadot-api/descriptors",
|
||||
"files": [
|
||||
"dist"
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -1,9 +1,11 @@
|
|||
import { beforeAll, describe, expect, it } from "bun:test";
|
||||
import { FixedSizeBinary } from "polkadot-api";
|
||||
import { $ } from "bun";
|
||||
import { Binary, FixedSizeBinary } from "polkadot-api";
|
||||
import { CROSS_CHAIN_TIMEOUTS, getPapiSigner, logger } from "utils";
|
||||
import type { Address } from "viem";
|
||||
import { getContractInstance, parseDeploymentsFile } from "../../utils/contracts";
|
||||
import { waitForDataHavenEvent } from "../../utils/events";
|
||||
import { waitFor } from "../../utils/waits";
|
||||
import { BaseTestSuite } from "../framework";
|
||||
|
||||
class SlashTestSuite extends BaseTestSuite {
|
||||
|
|
@ -15,6 +17,10 @@ class SlashTestSuite extends BaseTestSuite {
|
|||
// Set up hooks in constructor
|
||||
this.setupHooks();
|
||||
}
|
||||
|
||||
getNetworkId(): string {
|
||||
return this.networkId;
|
||||
}
|
||||
}
|
||||
|
||||
// Create the test suite instance
|
||||
|
|
@ -129,7 +135,11 @@ describe("Should slash an operator", () => {
|
|||
const sudoSlashCall = dhApi.tx.ExternalValidatorsSlashes.force_inject_slash({
|
||||
validator,
|
||||
era: activeEra?.index || 0, // Will fail if active era is 0. !! Important !! Sometimes for the inject to work (because of some latency) we need to inject in the `activeEra.index + 1`
|
||||
percentage: 20
|
||||
percentage: 20,
|
||||
offence_kind: {
|
||||
type: "Custom",
|
||||
value: Binary.fromText("Manual slash: E2E test")
|
||||
}
|
||||
});
|
||||
const sudoTx = dhApi.tx.Sudo.sudo({
|
||||
call: sudoSlashCall.decodedCall
|
||||
|
|
@ -159,4 +169,106 @@ describe("Should slash an operator", () => {
|
|||
}
|
||||
logger.info("Slashes message sent");
|
||||
}, 560000);
|
||||
|
||||
it("should detect and slash an unresponsive validator (liveness)", async () => {
|
||||
const networkId = suite.getNetworkId();
|
||||
const bobContainer = `datahaven-bob-${networkId}`;
|
||||
|
||||
// Drain any prior SlashReported events so we only see events from this test.
|
||||
await dhApi.event.ExternalValidatorsSlashes.SlashReported.pull();
|
||||
|
||||
const activeEra = await dhApi.query.ExternalValidators.ActiveEra.getValue({ at: "best" });
|
||||
const eraAtStart = activeEra?.index ?? 0;
|
||||
const sessionAtStart = await dhApi.query.Session.CurrentIndex.getValue({ at: "best" });
|
||||
logger.info(`Liveness test start — era: ${eraAtStart}, session: ${sessionAtStart}`);
|
||||
|
||||
// Pause bob to simulate a liveness failure (missed heartbeats).
|
||||
// Using pause/unpause instead of stop/start preserves bob's process
|
||||
// state (GRANDPA voter, peer connections, keystore) so finality can
|
||||
// resume quickly once unpaused.
|
||||
logger.info(`Pausing bob container: ${bobContainer}`);
|
||||
await $`docker pause ${bobContainer}`.quiet();
|
||||
logger.info("Bob container paused. Waiting for sessions to elapse...");
|
||||
|
||||
let slashReportedEvent: any;
|
||||
try {
|
||||
// Wait for at least TWO full sessions so pallet_im_online detects bob's
|
||||
// missing heartbeats at a session boundary.
|
||||
//
|
||||
// Why two sessions:
|
||||
// 1. Bob may have already sent his heartbeat for the current session
|
||||
// before being paused, so the first session boundary won't report him.
|
||||
// 2. The NEXT full session (where bob is offline from start to finish)
|
||||
// will trigger the unresponsiveness report at its boundary.
|
||||
//
|
||||
// Timing with BABE c=(1,4) and PrimaryAndSecondaryVRFSlots:
|
||||
// - Alice alone produces ~62.5% of blocks → ~9.6s per block on average.
|
||||
// - 10 blocks/session → ~96s per session with alice only.
|
||||
// - 2 sessions = ~192s. We use 200s for margin.
|
||||
await Bun.sleep(200_000);
|
||||
|
||||
// Unpause bob to restore GRANDPA finality (needs 2/2 validators).
|
||||
// Bob's process resumes immediately with full state, so he can vote
|
||||
// on the pending blocks that alice produced while he was paused.
|
||||
logger.info("Unpausing bob container...");
|
||||
await $`docker unpause ${bobContainer}`.quiet();
|
||||
logger.info("Bob unpaused. Waiting for finality and SlashReported event...");
|
||||
|
||||
// Poll for a SlashReported event from the ExternalValidatorsSlashes pallet.
|
||||
//
|
||||
// Why SlashReported instead of Slashes storage:
|
||||
// pallet_im_online's UnresponsivenessOffence::slash_fraction() formula
|
||||
// gives 0% for small validator sets (1 out of 2 offline is below the
|
||||
// 10%+1 threshold). With 0% fraction, compute_slash returns None and
|
||||
// no Slashes entry is created. However, on_offence still emits the
|
||||
// SlashReported event, which proves the full detection pipeline works:
|
||||
// pallet_im_online → EquivocationReportWrapper → pallet_offences → on_offence.
|
||||
//
|
||||
// After unpausing bob, GRANDPA finality catches up and the events from
|
||||
// alice's blocks (produced during the pause) become finalized and visible
|
||||
// to the PAPI event subscription.
|
||||
let pollCount = 0;
|
||||
const collectedEvents: any[] = [];
|
||||
await waitFor({
|
||||
lambda: async () => {
|
||||
pollCount++;
|
||||
const events = await dhApi.event.ExternalValidatorsSlashes.SlashReported.pull();
|
||||
for (const event of events) {
|
||||
const payload = (event as any)?.payload ?? event;
|
||||
collectedEvents.push(payload);
|
||||
logger.info(
|
||||
`[poll ${pollCount}] SlashReported event: validator=${payload?.validator}, ` +
|
||||
`fraction=${JSON.stringify(payload?.fraction)}, slash_era=${payload?.slash_era}`
|
||||
);
|
||||
}
|
||||
if (collectedEvents.length > 0) {
|
||||
slashReportedEvent = collectedEvents[0];
|
||||
return true;
|
||||
}
|
||||
if (pollCount % 10 === 0) {
|
||||
const curEra = await dhApi.query.ExternalValidators.ActiveEra.getValue({ at: "best" });
|
||||
const curSession = await dhApi.query.Session.CurrentIndex.getValue({ at: "best" });
|
||||
logger.info(
|
||||
`[poll ${pollCount}] era: ${curEra?.index}, session: ${curSession} (started at era=${eraAtStart}, session=${sessionAtStart})`
|
||||
);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
iterations: 60,
|
||||
delay: 5000,
|
||||
errorMessage: "SlashReported event not found after pausing bob for liveness detection"
|
||||
});
|
||||
} finally {
|
||||
// Ensure bob is always unpaused so the network stays healthy for teardown
|
||||
await $`docker unpause ${bobContainer}`.nothrow().quiet();
|
||||
}
|
||||
|
||||
expect(slashReportedEvent).toBeDefined();
|
||||
logger.info(
|
||||
"Liveness offence confirmed via SlashReported: " +
|
||||
`validator=${slashReportedEvent.validator}, ` +
|
||||
`fraction=${JSON.stringify(slashReportedEvent.fraction)}, ` +
|
||||
`slash_era=${slashReportedEvent.slash_era}`
|
||||
);
|
||||
}, 600_000);
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue