Skip to content

Commit b07cc98

Browse files
committed
feat(slasher): add stub contract
1 parent 1ef5f51 commit b07cc98

9 files changed

Lines changed: 124 additions & 22 deletions

File tree

cli/src/node/config.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use tycho_control::ControlServerConfig;
1010
use tycho_core::node::NodeBaseConfig;
1111
use tycho_crypto::ed25519;
1212
use tycho_rpc::RpcConfig;
13+
use tycho_slasher::SlasherConfig;
1314
use tycho_types::cell::HashBytes;
1415
use tycho_types::models::StdAddr;
1516
use tycho_util::cli::config::ThreadPoolConfig;
@@ -165,6 +166,9 @@ pub struct NodeConfig {
165166

166167
pub validator: ValidatorStdImplConfig,
167168

169+
#[partial]
170+
pub slasher: SlasherConfig,
171+
168172
#[partial]
169173
pub rpc: Option<RpcConfig>,
170174

@@ -191,6 +195,7 @@ impl Default for NodeConfig {
191195
mempool: Default::default(),
192196
internal_queue: Default::default(),
193197
validator: Default::default(),
198+
slasher: Default::default(),
194199
rpc: Some(Default::default()),
195200
control: Default::default(),
196201
metrics: Some(Default::default()),

cli/src/node/mod.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ use tycho_core::node::{NodeBase, NodeKeys};
3131
use tycho_core::storage::NodeSyncState;
3232
use tycho_network::InboundRequestMeta;
3333
use tycho_rpc::{NodeBaseInitRpc, RpcConfig};
34+
use tycho_slasher::SlasherConfig;
3435
use tycho_types::models::*;
3536
use tycho_util::futures::JoinTask;
3637
use tycho_wu_tuner::service::WuTunerServiceBuilder;
@@ -54,6 +55,7 @@ pub struct Node {
5455
collator_config: CollatorConfig,
5556
validator_config: ValidatorStdImplConfig,
5657
internal_queue_config: QueueConfig,
58+
slasher_config: SlasherConfig,
5759
mempool_config_override: Option<MempoolGlobalConfig>,
5860

5961
/// Path to the work units tuner config.
@@ -114,6 +116,7 @@ impl Node {
114116
collator_config: node_config.collator,
115117
validator_config: node_config.validator,
116118
internal_queue_config: node_config.internal_queue,
119+
slasher_config: node_config.slasher,
117120
mempool_config_override: global_config.mempool,
118121
wu_tuner_config_path,
119122
})
@@ -202,7 +205,12 @@ impl Node {
202205
message_queue_adapter.clear_uncommitted_state(&top_shards)?;
203206

204207
// NOTE: Stub
205-
let slasher = tycho_slasher::Slasher::new(base.keypair.clone());
208+
let slasher = tycho_slasher::Slasher::new(
209+
base.keypair.clone(),
210+
tycho_slasher::StubSlasherContract,
211+
base.blockchain_rpc_client.clone(),
212+
self.slasher_config,
213+
);
206214

207215
let validator = ValidatorStdImpl::new(
208216
ValidatorNetworkContext {

contracts/src/slasher-stub.tolk

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import "@stdlib/gas-payments"
2+
import "lib/config-params"
3+
4+
const ERROR_INVALID_SIGNATURE = 40
5+
const ERROR_VALIDATOR_NOT_FOUND = 50
6+
const ERROR_REPLAY_PROTECTION = 52
7+
const ERROR_MESSAGE_EXPIRED = 57
8+
9+
const REPLAY_OFFSET_MS = 5000
10+
const FUTURE_OFFSET_SEC = 60
11+
12+
struct Storage {
13+
updatedAtMs: uint64
14+
}
15+
16+
fun Storage.load(): Storage {
17+
return Storage.fromCell(contract.getData());
18+
}
19+
20+
fun Storage.save(self) {
21+
contract.setData(self.toCell());
22+
}
23+
24+
fun onInternalMessage(_in: InMessage) {}
25+
26+
fun onExternalMessage(inMsg: slice) {
27+
val signature = inMsg.loadBits(512);
28+
val signedBody = inMsg;
29+
val createdAtMs = inMsg.loadUint(64);
30+
val expireAtSec = inMsg.loadUint(32);
31+
val validatorIdx = inMsg.loadUint(16);
32+
inMsg.assertEnd();
33+
assert(blockchain.now() <= expireAtSec, ERROR_MESSAGE_EXPIRED);
34+
35+
var data = Storage.load();
36+
assert(createdAtMs > (data.updatedAtMs - REPLAY_OFFSET_MS) &&
37+
createdAtMs <= (blockchain.now() + FUTURE_OFFSET_SEC) * 1000, ERROR_REPLAY_PROTECTION);
38+
39+
var validatorCs = CurrentVset.getValidatorDescription(validatorIdx);
40+
assert(validatorCs != null, ERROR_VALIDATOR_NOT_FOUND);
41+
val validator = ValidatorDescr.readFromSlice(mutate validatorCs);
42+
43+
val toSign = beginCell().storeSlice(signedBody).endCell();
44+
assert(isSignatureValid(toSign.hash(), signature, validator.pubkey), ERROR_INVALID_SIGNATURE);
45+
46+
data.updatedAtMs = max(createdAtMs, data.updatedAtMs);
47+
data.save();
48+
49+
acceptExternalMessage();
50+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { CompilerConfig } from "@ton/blueprint";
2+
3+
export const compile: CompilerConfig = {
4+
lang: "tolk",
5+
entrypoint: "src/slasher-stub.tolk",
6+
withStackComments: true,
7+
withSrcLineComments: true,
8+
experimentalOptions: "",
9+
};

scripts/build-contracts.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ yarn build --all
1919
copy_code Elector
2020
copy_code ElectorPoA
2121
copy_code Config
22+
copy_code SlasherStub

slasher/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ tokio = { workspace = true, features = ["sync"] }
2323
tokio-util = { workspace = true }
2424
tracing = { workspace = true }
2525
tycho-crypto = { workspace = true }
26-
tycho-types = { workspace = true }
26+
tycho-types = { workspace = true, features = ["abi", "models"] }
2727

2828
# local deps
2929
tycho-block-util = { workspace = true }

slasher/src/bc/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@ use tycho_types::models::{
1111
};
1212
use tycho_util::FastDashMap;
1313

14+
pub use self::stub_contract::StubSlasherContract;
1415
use crate::util::AtomicBitSet;
1516

1617
mod stub_contract;
1718

1819
#[derive(Clone, Copy)]
1920
pub struct EncodeBlocksBatchMessage<'a> {
21+
pub address: &'a StdAddr,
2022
pub session_id: ValidationSessionId,
2123
pub batch: &'a BlocksBatch,
2224
pub validator_idx: u16,

slasher/src/bc/stub_contract.rs

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::num::NonZeroU32;
22

33
use anyhow::{Context, Result};
4+
use tycho_types::abi::extend_signature_with_id;
45
use tycho_types::cell::Lazy;
56
use tycho_types::dict;
67
use tycho_types::models::{
@@ -10,11 +11,17 @@ use tycho_types::prelude::*;
1011

1112
use super::{BlocksBatch, SignedMessage, SlasherContract};
1213

13-
pub struct StubContract;
14+
const PARAM_IDX: u32 = 666;
1415

15-
impl SlasherContract for StubContract {
16-
fn find_account_address(&self, _config: &BlockchainConfigParams) -> Result<Option<StdAddr>> {
17-
Ok(None)
16+
pub struct StubSlasherContract;
17+
18+
impl SlasherContract for StubSlasherContract {
19+
fn find_account_address(&self, config: &BlockchainConfigParams) -> Result<Option<StdAddr>> {
20+
let Some(raw) = config.get_raw_cell_ref(PARAM_IDX)? else {
21+
return Ok(None);
22+
};
23+
let address = raw.parse::<HashBytes>()?;
24+
Ok(Some(StdAddr::new(-1, address)))
1825
}
1926

2027
fn default_batch_size(&self) -> NonZeroU32 {
@@ -33,16 +40,35 @@ impl SlasherContract for StubContract {
3340
.context("failed to serialize blocks batch")?;
3441

3542
let now = tycho_util::time::now_millis();
36-
3743
let expire_at = (now / 1000).saturating_add(params.ttl.as_secs()) as u32;
44+
let body_to_sign = {
45+
let mut b = CellBuilder::new();
46+
b.store_u64(now)?;
47+
b.store_u32(expire_at)?;
48+
b.store_u16(params.validator_idx)?;
49+
b.store_reference(cell)?;
50+
b.build()?
51+
};
52+
53+
// TODO: Add support for signature id.
54+
let signature = params.keypair.sign_raw(&extend_signature_with_id(
55+
body_to_sign.repr_hash().as_array(),
56+
None,
57+
));
58+
let body = {
59+
let mut b = CellBuilder::new();
60+
b.store_raw(&signature, 512)?;
61+
b.store_slice(body_to_sign.as_slice()?)?;
62+
b.build()?
63+
};
64+
3865
let message = Lazy::new(&OwnedMessage {
3966
info: MsgInfo::ExtIn(ExtInMsgInfo {
40-
// Stub address.
41-
dst: StdAddr::new(-1, HashBytes::ZERO).into(),
67+
dst: params.address.clone().into(),
4268
..Default::default()
4369
}),
4470
init: None,
45-
body: cell.into(),
71+
body: body.into(),
4672
layout: None,
4773
})?;
4874

slasher/src/lib.rs

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@ use tycho_core::blockchain_rpc::BlockchainRpcClient;
1414
use tycho_crypto::ed25519;
1515
use tycho_slasher_traits::{ValidationSessionId, ValidatorEventsListener};
1616
use tycho_types::boc::Boc;
17+
use tycho_util::config::PartialConfig;
1718
use tycho_util::futures::JoinTask;
1819
use tycho_util::serde_helpers;
1920

20-
use self::bc::MessageDeliveryStatus;
2121
pub use self::bc::{
22-
BlocksBatch, ContractSubscription, EncodeBlocksBatchMessage, SignatureHistory, SignedMessage,
23-
SlasherContract,
22+
BlocksBatch, ContractSubscription, EncodeBlocksBatchMessage, MessageDeliveryStatus,
23+
SignatureHistory, SignedMessage, SlasherContract, StubSlasherContract,
2424
};
2525
use self::collector::{ValidatorEventsCollector, ValidatorSessionInfo};
2626

@@ -34,7 +34,7 @@ pub mod collector {
3434
mod bc;
3535
mod util;
3636

37-
#[derive(Debug, Clone, Serialize, Deserialize)]
37+
#[derive(Debug, Clone, Serialize, Deserialize, PartialConfig)]
3838
pub struct SlasherConfig {
3939
/// TTL of messages to the slasher contract.
4040
///
@@ -220,20 +220,21 @@ impl SlasherSharedState {
220220
validator_idx: u16,
221221
batch: BlocksBatch,
222222
) {
223-
let params = EncodeBlocksBatchMessage {
224-
session_id,
225-
batch: &batch,
226-
validator_idx,
227-
keypair: &self.node_keys,
228-
ttl: self.config.message_ttl,
229-
};
230-
231223
loop {
232224
let Some(subscription) = self.subscription.load_full() else {
233225
tracing::warn!("no slasher contract subscription");
234226
break;
235227
};
236228

229+
let params = EncodeBlocksBatchMessage {
230+
address: subscription.address(),
231+
session_id,
232+
batch: &batch,
233+
validator_idx,
234+
keypair: &self.node_keys,
235+
ttl: self.config.message_ttl,
236+
};
237+
237238
let signed = match self.contract.encode_blocks_batch_message(&params) {
238239
Ok(signed) => signed,
239240
Err(e) => {

0 commit comments

Comments
 (0)