feat(slashing): double-sign fraud proof via nu_reportDoubleSign

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Mukan Erkin TÖRÜK 2026-04-25 10:14:29 +03:00
parent 6bc6e90114
commit d2adf9381a
7 changed files with 370 additions and 3 deletions

View file

@ -7,6 +7,13 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
## [Unreleased] ## [Unreleased]
## [0.12.0] — 2026-04-25
### Added
- `nu-state/slash_evidence.rs``SlashEvidence` struct; DB key `slash_evidence:<validator>:<slot>`
- `nu_reportDoubleSign` RPC handler — iki farklı block imzasını k256 ile doğrular, geçerliyse DB'ye yazar; idempotent (aynı kanıt tekrar kabul edilmez)
- `block_loop::apply_pending_slashes` — her slot'ta uygulanmamış kanıtları işler: `slash_double_sign` çağırır, slashed stake'in %50'si BURN_WALLET'a, %50'si reporter'a akar
## [0.11.0] — 2026-04-25 ## [0.11.0] — 2026-04-25
### Added ### Added

185
Cargo.lock generated
View file

@ -154,12 +154,24 @@ dependencies = [
"tracing", "tracing",
] ]
[[package]]
name = "base16ct"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
[[package]] [[package]]
name = "base64" name = "base64"
version = "0.22.1" version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "base64ct"
version = "1.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
[[package]] [[package]]
name = "bindgen" name = "bindgen"
version = "0.69.5" version = "0.69.5"
@ -333,6 +345,12 @@ version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
[[package]]
name = "const-oid"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
[[package]] [[package]]
name = "core-foundation" name = "core-foundation"
version = "0.9.4" version = "0.9.4"
@ -368,6 +386,18 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "crypto-bigint"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76"
dependencies = [
"generic-array",
"rand_core",
"subtle",
"zeroize",
]
[[package]] [[package]]
name = "crypto-common" name = "crypto-common"
version = "0.1.7" version = "0.1.7"
@ -378,6 +408,16 @@ dependencies = [
"typenum", "typenum",
] ]
[[package]]
name = "der"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
dependencies = [
"const-oid",
"zeroize",
]
[[package]] [[package]]
name = "digest" name = "digest"
version = "0.10.7" version = "0.10.7"
@ -385,7 +425,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [ dependencies = [
"block-buffer", "block-buffer",
"const-oid",
"crypto-common", "crypto-common",
"subtle",
] ]
[[package]] [[package]]
@ -399,12 +441,45 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "ecdsa"
version = "0.16.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca"
dependencies = [
"der",
"digest",
"elliptic-curve",
"rfc6979",
"signature",
"spki",
]
[[package]] [[package]]
name = "either" name = "either"
version = "1.15.0" version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "elliptic-curve"
version = "0.13.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
dependencies = [
"base16ct",
"crypto-bigint",
"digest",
"ff",
"generic-array",
"group",
"pkcs8",
"rand_core",
"sec1",
"subtle",
"zeroize",
]
[[package]] [[package]]
name = "encoding_rs" name = "encoding_rs"
version = "0.8.35" version = "0.8.35"
@ -436,6 +511,16 @@ version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6"
[[package]]
name = "ff"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393"
dependencies = [
"rand_core",
"subtle",
]
[[package]] [[package]]
name = "find-msvc-tools" name = "find-msvc-tools"
version = "0.1.9" version = "0.1.9"
@ -519,6 +604,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [ dependencies = [
"typenum", "typenum",
"version_check", "version_check",
"zeroize",
] ]
[[package]] [[package]]
@ -550,6 +636,17 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
[[package]]
name = "group"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63"
dependencies = [
"ff",
"rand_core",
"subtle",
]
[[package]] [[package]]
name = "h2" name = "h2"
version = "0.4.13" version = "0.4.13"
@ -587,6 +684,15 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "hmac"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
dependencies = [
"digest",
]
[[package]] [[package]]
name = "http" name = "http"
version = "1.4.0" version = "1.4.0"
@ -915,6 +1021,20 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "k256"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b"
dependencies = [
"cfg-if",
"ecdsa",
"elliptic-curve",
"once_cell",
"sha2",
"signature",
]
[[package]] [[package]]
name = "lazy_static" name = "lazy_static"
version = "1.5.0" version = "1.5.0"
@ -1157,6 +1277,8 @@ dependencies = [
"anyhow", "anyhow",
"axum", "axum",
"chrono", "chrono",
"hex",
"k256",
"nu-block", "nu-block",
"nu-mempool", "nu-mempool",
"nu-state", "nu-state",
@ -1293,6 +1415,16 @@ version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[[package]]
name = "pkcs8"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
dependencies = [
"der",
"spki",
]
[[package]] [[package]]
name = "pkg-config" name = "pkg-config"
version = "0.3.33" version = "0.3.33"
@ -1332,6 +1464,15 @@ version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom 0.2.17",
]
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.5.18" version = "0.5.18"
@ -1410,6 +1551,16 @@ dependencies = [
"web-sys", "web-sys",
] ]
[[package]]
name = "rfc6979"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2"
dependencies = [
"hmac",
"subtle",
]
[[package]] [[package]]
name = "ring" name = "ring"
version = "0.17.14" version = "0.17.14"
@ -1519,6 +1670,20 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "sec1"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"
dependencies = [
"base16ct",
"der",
"generic-array",
"pkcs8",
"subtle",
"zeroize",
]
[[package]] [[package]]
name = "security-framework" name = "security-framework"
version = "3.7.0" version = "3.7.0"
@ -1644,6 +1809,16 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "signature"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
dependencies = [
"digest",
"rand_core",
]
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.12" version = "0.4.12"
@ -1666,6 +1841,16 @@ dependencies = [
"windows-sys 0.61.2", "windows-sys 0.61.2",
] ]
[[package]]
name = "spki"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
dependencies = [
"base64ct",
"der",
]
[[package]] [[package]]
name = "stable_deref_trait" name = "stable_deref_trait"
version = "1.2.1" version = "1.2.1"

View file

@ -11,6 +11,8 @@ anyhow.workspace = true
tracing.workspace = true tracing.workspace = true
axum.workspace = true axum.workspace = true
chrono.workspace = true chrono.workspace = true
k256 = { version = "0.13", features = ["ecdsa"] }
hex.workspace = true
nu-state = { path = "../nu-state" } nu-state = { path = "../nu-state" }
nu-mempool = { path = "../nu-mempool" } nu-mempool = { path = "../nu-mempool" }
nu-block = { path = "../nu-block" } nu-block = { path = "../nu-block" }

View file

@ -4,8 +4,9 @@ use crate::{
server::AppState, server::AppState,
types::{JsonRpcRequest, JsonRpcResponse}, types::{JsonRpcRequest, JsonRpcResponse},
}; };
use k256::ecdsa::{signature::Verifier, Signature, VerifyingKey};
use nu_block::types::{Block, RawTransaction}; use nu_block::types::{Block, RawTransaction};
use nu_state::{account::AccountState, ValidatorState}; use nu_state::{account::AccountState, SlashEvidence, ValidatorState};
pub async fn dispatch(req: JsonRpcRequest, state: &AppState) -> JsonRpcResponse { pub async fn dispatch(req: JsonRpcRequest, state: &AppState) -> JsonRpcResponse {
match req.method.as_str() { match req.method.as_str() {
@ -15,6 +16,7 @@ pub async fn dispatch(req: JsonRpcRequest, state: &AppState) -> JsonRpcResponse
"nu_getBlock" => handle_get_block(&req, state).await, "nu_getBlock" => handle_get_block(&req, state).await,
"nu_getValidator" => handle_get_validator(&req, state).await, "nu_getValidator" => handle_get_validator(&req, state).await,
"nu_listValidators" => handle_list_validators(&req, state).await, "nu_listValidators" => handle_list_validators(&req, state).await,
"nu_reportDoubleSign" => handle_report_double_sign(&req, state).await,
"nu_getTx" => not_implemented(&req, "nu_getTx"), "nu_getTx" => not_implemented(&req, "nu_getTx"),
"nu_getStory" => not_implemented(&req, "nu_getStory"), "nu_getStory" => not_implemented(&req, "nu_getStory"),
"nu_getNode" => not_implemented(&req, "nu_getNode"), "nu_getNode" => not_implemented(&req, "nu_getNode"),
@ -137,6 +139,83 @@ async fn handle_list_validators(req: &JsonRpcRequest, state: &AppState) -> JsonR
JsonRpcResponse::ok(req.id.clone(), serde_json::to_value(validators).unwrap()) JsonRpcResponse::ok(req.id.clone(), serde_json::to_value(validators).unwrap())
} }
async fn handle_report_double_sign(req: &JsonRpcRequest, state: &AppState) -> JsonRpcResponse {
// params: [validator, slot, block_hash_1, sig_1_hex, block_hash_2, sig_2_hex, reporter]
let get_str = |i: usize| req.params.get(i).and_then(|v| v.as_str()).map(String::from);
let get_u32 = |i: usize| req.params.get(i).and_then(|v| v.as_u64()).map(|n| n as u32);
let (Some(validator), Some(slot), Some(hash1), Some(sig1_hex), Some(hash2), Some(sig2_hex), Some(reporter)) = (
get_str(0), get_u32(1), get_str(2), get_str(3), get_str(4), get_str(5), get_str(6),
) else {
return JsonRpcResponse::err(req.id.clone(), -32602, "Missing params".into());
};
if hash1 == hash2 {
return JsonRpcResponse::err(req.id.clone(), -32602, "Block hashes must differ".into());
}
// Verify both signatures are from the same validator key
let sig1_bytes = match hex::decode(&sig1_hex) {
Ok(b) => b,
Err(_) => return JsonRpcResponse::err(req.id.clone(), -32602, "Invalid sig_1 hex".into()),
};
let sig2_bytes = match hex::decode(&sig2_hex) {
Ok(b) => b,
Err(_) => return JsonRpcResponse::err(req.id.clone(), -32602, "Invalid sig_2 hex".into()),
};
let val_pubkey_bytes = match hex::decode(&validator) {
Ok(b) => b,
Err(_) => return JsonRpcResponse::err(req.id.clone(), -32602, "Invalid validator address".into()),
};
let verifying_key = match VerifyingKey::from_sec1_bytes(&val_pubkey_bytes) {
Ok(k) => k,
Err(_) => return JsonRpcResponse::err(req.id.clone(), -32602, "Invalid validator pubkey".into()),
};
let sig1 = match Signature::from_slice(&sig1_bytes) {
Ok(s) => s,
Err(_) => return JsonRpcResponse::err(req.id.clone(), -32602, "Invalid sig_1".into()),
};
let sig2 = match Signature::from_slice(&sig2_bytes) {
Ok(s) => s,
Err(_) => return JsonRpcResponse::err(req.id.clone(), -32602, "Invalid sig_2".into()),
};
if verifying_key.verify(hash1.as_bytes(), &sig1).is_err() {
return JsonRpcResponse::err(req.id.clone(), -32602, "sig_1 does not verify".into());
}
if verifying_key.verify(hash2.as_bytes(), &sig2).is_err() {
return JsonRpcResponse::err(req.id.clone(), -32602, "sig_2 does not verify".into());
}
let evidence = SlashEvidence {
validator: validator.clone(),
slot,
block_hash_1: hash1,
sig_1: sig1_bytes,
block_hash_2: hash2,
sig_2: sig2_bytes,
reporter,
submitted_at: chrono::Utc::now().timestamp_millis(),
applied: false,
};
let key = SlashEvidence::db_key(&validator, slot);
let db = state.db.lock().await;
// Idempotent — don't store duplicates
if let Ok(Some(_)) = db.get::<SlashEvidence>(&key) {
return JsonRpcResponse::ok(req.id.clone(), json!({ "status": "already_reported" }));
}
match db.put(&key, &evidence) {
Ok(_) => {
tracing::warn!(validator = %validator, slot, "double-sign evidence submitted");
JsonRpcResponse::ok(req.id.clone(), json!({ "status": "accepted", "key": key }))
}
Err(e) => JsonRpcResponse::err(req.id.clone(), -32000, e.to_string()),
}
}
fn not_implemented(req: &JsonRpcRequest, method: &str) -> JsonRpcResponse { fn not_implemented(req: &JsonRpcRequest, method: &str) -> JsonRpcResponse {
JsonRpcResponse::err(req.id.clone(), -32000, format!("{method} not implemented yet")) JsonRpcResponse::err(req.id.clone(), -32000, format!("{method} not implemented yet"))
} }

View file

@ -2,11 +2,13 @@ pub mod accessor;
pub mod account; pub mod account;
pub mod db; pub mod db;
pub mod nft; pub mod nft;
pub mod slash_evidence;
pub mod story_node; pub mod story_node;
pub mod validator; pub mod validator;
pub use accessor::StateAccessor; pub use accessor::StateAccessor;
pub use db::StateDb; pub use db::StateDb;
pub use nft::NftState; pub use nft::NftState;
pub use slash_evidence::SlashEvidence;
pub use story_node::{NodeStatus, StoryNodeState, WeightedVote}; pub use story_node::{NodeStatus, StoryNodeState, WeightedVote};
pub use validator::ValidatorState; pub use validator::ValidatorState;

View file

@ -0,0 +1,22 @@
use serde::{Deserialize, Serialize};
/// Double-sign fraud proof submitted via nu_reportDoubleSign RPC.
/// Stored as "slash_evidence:<validator_addr>:<slot>" in DB.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SlashEvidence {
pub validator: String,
pub slot: u32,
pub block_hash_1: String,
pub sig_1: Vec<u8>,
pub block_hash_2: String,
pub sig_2: Vec<u8>,
pub reporter: String,
pub submitted_at: i64,
pub applied: bool,
}
impl SlashEvidence {
pub fn db_key(validator: &str, slot: u32) -> String {
format!("slash_evidence:{validator}:{slot:010}")
}
}

View file

@ -9,12 +9,12 @@ use nu_block::{
}; };
use nu_consensus::{ use nu_consensus::{
slot::current_slot, slot::current_slot,
slashing::record_skip, slashing::{record_skip, slash_double_sign},
types::ValidatorRecord, types::ValidatorRecord,
validator_set::ValidatorSet, validator_set::ValidatorSet,
}; };
use nu_mempool::Mempool; use nu_mempool::Mempool;
use nu_state::{story_node::StoryNodeState, ValidatorState, StateDb}; use nu_state::{account::AccountState, story_node::StoryNodeState, SlashEvidence, ValidatorState, StateDb};
use nu_vm::execute_block; use nu_vm::execute_block;
use crate::p2p::P2pSender; use crate::p2p::P2pSender;
@ -86,6 +86,12 @@ pub async fn run(
} }
} }
// Apply any pending double-sign slash evidence
{
let db_guard = db.lock().await;
apply_pending_slashes(&db_guard, slot);
}
// Scheduler: inject auto-txs for pending nodes // Scheduler: inject auto-txs for pending nodes
{ {
let db_guard = db.lock().await; let db_guard = db.lock().await;
@ -168,6 +174,70 @@ pub async fn run(
} }
} }
fn apply_pending_slashes(db: &StateDb, current_slot: u32) {
let burn_wallet = std::env::var("BURN_WALLET").unwrap_or_default();
let evidences: Vec<SlashEvidence> = db.scan_prefix("slash_evidence:");
for mut ev in evidences {
if ev.applied {
continue;
}
let vs_key = format!("validator:{}", ev.validator);
let mut vs = match db.get::<ValidatorState>(&vs_key).ok().flatten() {
Some(v) => v,
None => continue,
};
let mut record = ValidatorRecord {
address: vs.address.clone(),
stake: vs.stake,
pon_score: vs.pon_score,
is_active: vs.is_active,
last_block: vs.last_block,
slash_count: vs.slash_count,
skip_count: vs.skip_count,
consecutive_blocks: vs.consecutive_blocks,
ban_until_slot: vs.ban_until_slot,
};
let result = slash_double_sign(&mut record, current_slot);
vs.stake = record.stake;
vs.pon_score = record.pon_score;
vs.is_active = record.is_active;
vs.ban_until_slot = record.ban_until_slot;
vs.slash_count = record.slash_count;
let _ = db.put(&vs_key, &vs);
// Burn half
if !burn_wallet.is_empty() {
if let Ok(Some(mut burn_acc)) = db.get::<AccountState>(&format!("account:{burn_wallet}")) {
burn_acc.balance = burn_acc.balance.saturating_add(result.burn_amount);
let _ = db.put(&format!("account:{burn_wallet}"), &burn_acc);
}
}
// Reporter reward
if !ev.reporter.is_empty() {
if let Ok(Some(mut reporter_acc)) = db.get::<AccountState>(&format!("account:{}", ev.reporter)) {
reporter_acc.balance = reporter_acc.balance.saturating_add(result.reporter_reward);
let _ = db.put(&format!("account:{}", ev.reporter), &reporter_acc);
}
}
ev.applied = true;
let _ = db.put(&SlashEvidence::db_key(&ev.validator, ev.slot), &ev);
tracing::warn!(
validator = %ev.validator,
slot = ev.slot,
slashed = result.slashed_amount,
"double-sign slash applied"
);
}
}
fn unban_expired_validators(db: &StateDb, current_slot: u32) { fn unban_expired_validators(db: &StateDb, current_slot: u32) {
let records: Vec<ValidatorState> = db.scan_prefix("validator:"); let records: Vec<ValidatorState> = db.scan_prefix("validator:");
for mut vs in records { for mut vs in records {