Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
c995f2b
wip
EclesioMeloJunior Aug 20, 2026
f113fa5
chore: implement gosssip subscription + watchdog
EclesioMeloJunior Aug 24, 2026
360d542
chore: rever the metrics changes
EclesioMeloJunior Aug 24, 2026
e5c47e3
chore: rever the metrics changes
EclesioMeloJunior Aug 24, 2026
377152b
chore: use block delay in secs
EclesioMeloJunior Aug 25, 2026
cd68fd4
Merge branch 'fetch-beacon-gossipsub' of github.com:ChainSafe/forest …
EclesioMeloJunior Aug 25, 2026
bd4985a
Merge branch 'main' of github.com:ChainSafe/forest into fetch-beacon-…
EclesioMeloJunior Aug 25, 2026
ac6700e
chore: introduce resubscription
EclesioMeloJunior Aug 25, 2026
0f17015
chore: add tests
EclesioMeloJunior Aug 26, 2026
f146c60
chore: update changelog
EclesioMeloJunior Aug 26, 2026
3d19243
chore: fix lint
EclesioMeloJunior Aug 26, 2026
030e607
Merge branch 'main' of github.com:ChainSafe/forest into fetch-beacon-…
EclesioMeloJunior Aug 31, 2026
67c88e3
Merge branch 'main' of github.com:ChainSafe/forest into fetch-beacon-…
EclesioMeloJunior Aug 31, 2026
2031c75
Merge branch 'main' into fetch-beacon-gossipsub
EclesioMeloJunior Aug 31, 2026
2f8061a
chore: remove changelog entry, place proto permalink, scoped const,
EclesioMeloJunior Aug 31, 2026
426f2b0
chore: run `beacon.entry(...)` under cancellation_token.run_until_can…
EclesioMeloJunior Aug 31, 2026
5533930
Merge branch 'fetch-beacon-gossipsub' of github.com:ChainSafe/forest …
EclesioMeloJunior Aug 31, 2026
ab1030f
chore: start the watchdog ticker correctly
EclesioMeloJunior Aug 31, 2026
370cee6
Merge branch 'main' into fetch-beacon-gossipsub
EclesioMeloJunior Aug 31, 2026
5296c11
Merge branch 'main' into fetch-beacon-gossipsub
EclesioMeloJunior Aug 31, 2026
7012c15
Merge branch 'main' into fetch-beacon-gossipsub
EclesioMeloJunior Sep 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/docs/users/reference/env_variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ process.
| `FOREST_MAX_CONCURRENT_INBOUND_CHAIN_EXCHANGE_REQUESTS` | positive integer | 32 | 32 | Maximum number of inbound chain exchange requests Forest will service concurrently. Excess requests are rejected with a `GoAway` response |
| `FOREST_MAX_CONCURRENT_INBOUND_CHAIN_EXCHANGE_REQUESTS_PER_PEER` | positive integer | 4 | 4 | Per-peer cap on concurrent inbound chain exchange requests. Excess requests from a single peer are rejected with a `GoAway` response |
| `FOREST_MAX_CONCURRENT_HELLO_TRIGGERED_FETCHES` | positive integer | 16 | 16 | Bounds tipset fetches triggered by inbound `hello` requests that run concurrently; each chain-exchanges the peer's claimed head. Excess triggers are dropped, not queued. |
| `FOREST_MAX_CONCURRENT_DRAND_VERIFICATIONS` | positive integer | 4 | 4 | Bounds drand beacon entries from gossipsub verified concurrently; each costs a BLS pairing. Excess entries are dropped, not queued. |
| `FOREST_MAX_OUTBOUND_CHAIN_EXCHANGE_RESPONSE_BYTES` | positive integer (bytes) | 10485760 (10 MiB) | 10485760 | Cap on the encoded byte size of a chain exchange response Forest serves to peers. Building stops as soon as the running encoded size would exceed this cap and the response is returned with `PartialResponse` status |
| `FOREST_ETH_RPC_COMPUTE_STATE_ON_INDEX_MISS` | 1 or true | false | 1 | Allows Ethereum RPC methods to compute state trees on index miss |
| `FOREST_ETH_RPC_COMPUTE_BLOOM_ON_MISS` | 1 or true | false | 1 | Allows `eth` block RPC methods to compute (and store) the block `logsBloom` when it is not already stored, otherwise such blocks report an all-ones bloom |
Expand Down
9 changes: 9 additions & 0 deletions proto/drand_pb.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
syntax = "proto3";

package drand_pb;
Comment thread
EclesioMeloJunior marked this conversation as resolved.

// https://github.com/drand/drand/blob/v2.1.7/protobuf/drand/api.proto#L42-L53
message PublicRandResponse {
Comment thread
EclesioMeloJunior marked this conversation as resolved.
uint64 round = 1;
bytes signature = 2;
}
7 changes: 7 additions & 0 deletions src/beacon/drand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,13 @@ impl BeaconSchedule {
}
}

pub fn unchained_beacon(&self) -> Option<&BeaconImpl> {
self.0
.iter()
.map(|point| &point.beacon)
.find(|beacon| beacon.network().is_unchained())
}

pub fn beacon_for_epoch(&self, epoch: ChainEpoch) -> anyhow::Result<(ChainEpoch, &BeaconImpl)> {
// Iterate over beacon schedule to find the latest randomness beacon to use.
self.0
Expand Down
55 changes: 55 additions & 0 deletions src/beacon/drand_pb.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright 2019-2026 ChainSafe Systems
// SPDX-License-Identifier: Apache-2.0, MIT
// Automatically generated rust module for 'drand_pb.proto' file
// Command: `pb-rs -s -D proto/drand_pb.proto`, See <https://crates.io/crates/pb-rs>

#![allow(non_snake_case)]
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(unused_imports)]
#![allow(unknown_lints)]
#![allow(clippy::all)]
#![allow(clippy::assigning_clones)]
#![cfg_attr(rustfmt, rustfmt_skip)]


use quick_protobuf::{MessageInfo, MessageRead, MessageWrite, BytesReader, Writer, WriterBackend, Result};
use quick_protobuf::sizeofs::*;
use super::*;

#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Debug, Default, PartialEq, Clone)]
pub struct PublicRandResponse {
pub round: u64,
pub signature: Vec<u8>,
}

impl<'a> MessageRead<'a> for PublicRandResponse {
fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result<Self> {
let mut msg = Self::default();
while !r.is_eof() {
match r.next_tag(bytes) {
Ok(8) => msg.round = r.read_uint64(bytes)?,
Ok(18) => msg.signature = r.read_bytes(bytes)?.to_owned(),
Ok(t) => { r.read_unknown(bytes, t)?; }
Err(e) => return Err(e),
}
}
Ok(msg)
}
}

impl MessageWrite for PublicRandResponse {
fn get_size(&self) -> usize {
0
+ if self.round == 0u64 { 0 } else { 1 + sizeof_varint(*(&self.round) as u64) }
+ if self.signature.is_empty() { 0 } else { 1 + sizeof_len((&self.signature).len()) }
}

fn write_message<W: WriterBackend>(&self, w: &mut Writer<W>) -> Result<()> {
if self.round != 0u64 { w.write_with_tag(8, |w| w.write_uint64(*&self.round))?; }
if !self.signature.is_empty() { w.write_with_tag(18, |w| w.write_bytes(&**&self.signature))?; }
Ok(())
}
}

3 changes: 3 additions & 0 deletions src/beacon/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@

pub mod beacon_entries;
mod drand;
mod drand_pb;
pub mod metrics;
pub mod signatures;
pub use beacon_entries::*;
pub use drand::*;
pub use drand_pb::PublicRandResponse;

#[cfg(test)]
pub mod mock_beacon;
Expand All @@ -15,4 +17,5 @@ pub mod tests {
// `pub` so that helpers such as `drand::new_beacon_quicknet` can be shared with
// tests in other modules.
pub mod drand;
pub mod fake_drand;
}
2 changes: 1 addition & 1 deletion src/beacon/signatures/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use rayon::prelude::*;
pub use bls_signatures::{PublicKey as PublicKeyOnG1, Signature as SignatureOnG2};

// See <https://www.ietf.org/archive/id/draft-irtf-cfrg-bls-signature-05.html#name-basic>
const CSUITE_G1: &[u8] = b"BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_";
pub(crate) const CSUITE_G1: &[u8] = b"BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_";
const CSUITE_G2: &[u8] = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_";

#[derive(Debug, Clone, Eq, PartialEq, derive_more::Deref)]
Expand Down
95 changes: 95 additions & 0 deletions src/beacon/tests/fake_drand.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
use crate::beacon::{Beacon, BeaconEntry, ChainInfo, DrandBeacon, DrandConfig, DrandNetwork};
use blstrs::{G1Projective, G2Projective, Scalar};
use group::{Curve, Group};

pub const FAKE_DRAND_GENESIS_TIME: i32 = 1_692_803_367;
pub const FAKE_DRAND_PERIOD: i32 = 3;

pub const TEST_FIL_GENESIS_TIME: u64 = 1_598_306_400;
pub const TEST_FIL_BLOCK_DELAY: u64 = 30;

pub struct FakeDrand {
secret: Scalar,
config: DrandConfig<'static>,
}

impl FakeDrand {
pub fn new(servers: Vec<url::Url>, period: i32, genesis_time: i32) -> Self {
let secret = Scalar::from(0xC0FFEEu64);
let public = G2Projective::generator() * secret;
let public_key = hex::encode(public.to_affine().to_compressed());
Self {
secret,
config: DrandConfig {
servers,
chain_info: ChainInfo {
public_key: public_key.into(),
period,
genesis_time,
hash: "0011".repeat(16).into(),
group_hash: "00".repeat(32).into(),
},
network_type: DrandNetwork::Quicknet, // unchained
// The fixture builds beacons directly; registering a collector here
// would clash with the one the real quicknet config registers.
register_metrics: false,
},
}
}

// sign H(round) on G1, exactly what `verify_entries` checks for unchained.
pub fn entry(&self, round: u64) -> BeaconEntry {
let msg = BeaconEntry::message_unchained(round);
let point =
G1Projective::hash_to_curve(msg.as_ref(), crate::beacon::signatures::CSUITE_G1, &[]);
let point = point * self.secret;
BeaconEntry::new(round, point.to_affine().to_compressed().to_vec())
}

// encode PublicRandResponse to protobuf
pub fn to_protobuf(&self, round: u64) -> Vec<u8> {
let entry = self.entry(round);
let mut out = Vec::new();
let mut w = quick_protobuf::Writer::new(&mut out);
quick_protobuf::MessageWrite::write_message(
&crate::beacon::drand_pb::PublicRandResponse {
round,
signature: entry.signature().to_vec(),
},
&mut w,
)
.unwrap();
out
}

pub fn to_json(&self, round: u64) -> serde_json::Value {
let entry = self.entry(round);
serde_json::json!({
"round": round,
"randomness": "00".repeat(32),
"signature": hex::encode(entry.signature()),
"previous_signature": null,
})
}

pub fn beacon(&self, genesis_ts: u64, block_delay: u64) -> DrandBeacon {
DrandBeacon::new(genesis_ts, block_delay, &self.config)
}

pub fn chain_info_hash(&self) -> String {
self.config.chain_info.hash.to_string()
}
}

// just test the secret and public keys are correctly validating
#[test]
fn fake_drand_entries_verify() {
let d = FakeDrand::new(vec![], FAKE_DRAND_PERIOD, FAKE_DRAND_GENESIS_TIME);
let beacon = d.beacon(TEST_FIL_GENESIS_TIME, TEST_FIL_BLOCK_DELAY);
let entries: Vec<_> = (1..=5).map(|r| d.entry(r)).collect();
assert!(
beacon
.verify_entries(&entries, &BeaconEntry::default())
.unwrap()
);
}
Loading
Loading