Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
116 commits
Select commit Hold shift + click to select a range
147c01f
wip
0xForerunner Jul 9, 2025
14c4297
wip
0xForerunner Jul 9, 2025
54af8b1
wip
0xForerunner Jul 9, 2025
9966ecb
wip
0xForerunner Jul 9, 2025
cddf97f
wip
0xForerunner Jul 10, 2025
89f5ee6
add capability to node
0xForerunner Jul 10, 2025
c3ad860
feat: add validation to node overlay
0xForerunner Jul 10, 2025
36201ba
FlashblocksNetworkBuilder
0xForerunner Jul 10, 2025
0dc7064
Pull out flashblocks-node
0xForerunner Jul 10, 2025
e94925e
add timestamp to Authorization
0xForerunner Jul 10, 2025
dc15ab1
wip
0xForerunner Jul 11, 2025
677574a
wip
0xForerunner Jul 11, 2025
9c3667a
wip
0xForerunner Jul 11, 2025
9944798
wip
0xForerunner Jul 11, 2025
1d0e8ad
wip
0xForerunner Jul 11, 2025
782b951
update vis
0xForerunner Jul 11, 2025
f225b5e
fix node
0xForerunner Jul 11, 2025
1fa5b68
rename vars
0xForerunner Jul 11, 2025
e6b0e9d
switch to Mutex for p2p state
0xForerunner Jul 14, 2025
4ac4633
update protocol to accept new blocks
0xForerunner Jul 14, 2025
0cbf403
move tests directory
0xForerunner Jul 14, 2025
dcf2a43
getting integration tests working
0xForerunner Jul 14, 2025
485adf0
get tests passing
0xForerunner Jul 14, 2025
6d252fe
get rid of duplicate code
0xForerunner Jul 15, 2025
a476ee9
rlp encoding
0xForerunner Jul 16, 2025
6c60ddd
fix rlpx message header
0xForerunner Jul 16, 2025
463c2f7
handle maximum flashblocks message size
0xForerunner Jul 16, 2025
8298f29
cleanup p2p handler
0xForerunner Jul 16, 2025
fd198aa
Don't resend flashblocks to the originating peer
0xForerunner Jul 16, 2025
fd06a6c
clear up comments
0xForerunner Jul 16, 2025
495ab62
remove stray if let
0xForerunner Jul 16, 2025
65125aa
reexport ed25519
0xForerunner Jul 16, 2025
7f82992
use mpsc for publish channel
0xForerunner Jul 17, 2025
ba36548
feat: reth 1.5.1
0xForerunner Jul 17, 2025
a7b4973
chore: cargo update
0xForerunner Jul 17, 2025
7e36324
chore: udeps
0xForerunner Jul 17, 2025
ad658fe
feat: flashblock size metric
0xForerunner Jul 17, 2025
69f50d3
fix: target in events
0xForerunner Jul 17, 2025
b40a313
feat: flashblocks node args
0xForerunner Jul 18, 2025
2f3a210
feat: check peer reputation
0xForerunner Jul 19, 2025
05b6eba
switch to broadcast
0xForerunner Jul 21, 2025
bf980dd
feat: switch to broadcast for publish chan
0xForerunner Jul 21, 2025
0abf5e8
target: flashblocks::p2p
0xForerunner Jul 21, 2025
d1f3ed1
feat: Authorized Msg work
0xForerunner Jul 22, 2025
36559b8
feat: improve rlp testing
0xForerunner Jul 22, 2025
dd3db0f
wip
0xForerunner Jul 23, 2025
ffb966e
wip
0xForerunner Jul 23, 2025
6f4ab14
finish change to p2p leader protocol
0xForerunner Jul 23, 2025
80f24e1
wip: fix rlp encoding
0xForerunner Jul 23, 2025
70d37d7
set block_number
0xForerunner Jul 23, 2025
72b2bc6
feat: working demo!
0xForerunner Jul 24, 2025
d6d129e
feat: authorized verification
0xForerunner Jul 24, 2025
9a8b031
feat: flashblocks_p2p spec
0xForerunner Jul 24, 2025
aa2f722
chore: rename
0xForerunner Jul 24, 2025
83cb7ef
todo: handle replay attacks
0xForerunner Jul 24, 2025
d24dcb5
feat: mitigate Start/StopPublish replay attacks
0xForerunner Jul 24, 2025
6b45336
add doc commends to p2p.rs
0xForerunner Jul 24, 2025
7680cb7
add failover tests
0xForerunner Jul 24, 2025
bd451a1
feat: Add doc comments
0xForerunner Jul 24, 2025
fd99315
feat: remove block_number in favour of timestamp
0xForerunner Jul 24, 2025
4a862cc
feat: pass in handler
0xForerunner Jul 24, 2025
3aa5090
for_leyton
0xForerunner Jul 25, 2025
822f0bc
feat: rename
0xForerunner Jul 25, 2025
481893e
feat: watch channel for publishing state
0xForerunner Jul 25, 2025
e0a40c6
feat: guage for flashblocks peers
0xForerunner Jul 28, 2025
888bd1b
chore: unused error messages
0xForerunner Jul 28, 2025
8dc7540
feat: fn flashblock_stream
0xForerunner Jul 28, 2025
360158f
feat: Add more integration tests
0xForerunner Jul 28, 2025
bb57a44
feat: check MAX_FLASHBLOCK_INDEX with peers
0xForerunner Jul 28, 2025
d8249da
feat: additional doc comments
0xForerunner Jul 28, 2025
528cac3
feat: switch to adding param to FCU for authorization
0xForerunner Jul 28, 2025
93456df
feat: add handle to flashblocks sender
0xOsiris Aug 5, 2025
7ab74a1
feat: internalize flashblocks chan
0xForerunner Aug 6, 2025
36926ad
feat: update tests
0xForerunner Aug 6, 2025
c0cc1e2
chore: cleanup
0xForerunner Aug 6, 2025
f634e4b
feat: test_peer_reputation
0xForerunner Aug 6, 2025
12c65df
chore: stray comments
0xForerunner Aug 6, 2025
d84a2da
feat: flashblocks_forkchoiceUpdatedV3
0xForerunner Aug 14, 2025
378fd99
feat: move rpc-layer
0xForerunner Aug 15, 2025
0a0079d
wip
0xForerunner Aug 15, 2025
171cfbc
fixed
0xForerunner Aug 15, 2025
bf86104
feat: cleanup flashblocks keys
0xForerunner Aug 15, 2025
e46bf80
feat: parse args tests
0xForerunner Aug 15, 2025
c7130aa
remove websocket config
0xForerunner Aug 15, 2025
101ffc0
feat: update rust version
0xForerunner Aug 18, 2025
fe634cb
feat: remove websocket proxy
0xForerunner Aug 19, 2025
ba6b3e4
feat: remove unused stuff
0xForerunner Aug 19, 2025
54d8745
fix: image build configuration
0xOsiris Aug 20, 2025
58feb62
feat: reth update
0xForerunner Aug 29, 2025
bb873b2
feat: publish vs subscribe client types
0xForerunner Sep 5, 2025
39d6a27
feat: fn subscriber()
0xForerunner Sep 5, 2025
dc3f48c
Revert "feat: fn subscriber()"
0xForerunner Sep 8, 2025
075b9c6
Revert "feat: publish vs subscribe client types"
0xForerunner Sep 8, 2025
40774a2
feat: Option builder_sk
0xForerunner Sep 8, 2025
a236f55
feat: reth v1.7.0
0xForerunner Sep 10, 2025
4ea4ba1
chore: upgrade reth
0xOsiris Sep 12, 2025
513d85f
chore: extricate p2p crates and move to world-chain
0xForerunner Sep 17, 2025
5c9dbbc
Merge branch 'main' into forerunner/p2p-flashblocks
0xForerunner Sep 17, 2025
e690dec
chore: add websocket-proxy back
0xForerunner Sep 17, 2025
6355b88
ws vs p2p args
0xForerunner Sep 17, 2025
6be9b9d
chore: refactoring
0xForerunner Sep 17, 2025
46fd26d
chore: more refactoring
0xForerunner Sep 17, 2025
afeed2d
ws
0xForerunner Nov 5, 2025
53838ee
wip merge
0xForerunner Dec 16, 2025
26a342e
chore: remove unused types
0xForerunner Dec 16, 2025
f929970
chore: cleanup FlashblocksError
0xForerunner Dec 16, 2025
39b4d24
chore: fixup tests
0xForerunner Dec 16, 2025
40cf2e1
chore: move Authorization to rollup-boost-types
0xForerunner Dec 16, 2025
02ac0d5
chore: cleanup
0xForerunner Dec 16, 2025
b565106
chore: satisfy clippy
0xForerunner Dec 17, 2025
2eb0895
chore: prefer workspace defined deps
0xForerunner Dec 17, 2025
55060fc
Update crates/rollup-boost/src/server.rs
0xForerunner Dec 17, 2025
0117c0d
chore: prefer workspace deps
0xForerunner Dec 17, 2025
79bcd17
chore: fix comment
0xForerunner Dec 17, 2025
ba36d45
feat: Cleanup RcpClient generics + don't send flashblocks authorizati…
0xForerunner Dec 19, 2025
fc0f983
fix: extract lock
0xForerunner Dec 19, 2025
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
2,825 changes: 1,932 additions & 893 deletions Cargo.lock

Large diffs are not rendered by default.

17 changes: 13 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ resolver = "3"
members = [
"crates/rollup-boost",
"crates/websocket-proxy",
"crates/flashblocks-rpc",
"crates/flashblocks-rpc",
"crates/rollup-boost-types",
]

[workspace.dependencies]
rollup-boost = { path = "crates/rollup-boost" }
flashblocks-rpc = { path = "crates/flashblocks-rpc" }
rollup-boost-types = { path = "crates/rollup-boost-types" }

backoff = "0.4.0"
Expand All @@ -27,6 +28,13 @@ tokio = { version = "1", features = ["full"] }
tracing = "0.1.4"
tracing-subscriber = { version = "0.3.20", features = ["env-filter", "json"] }
url = "2.2.0"
ed25519-dalek = { version = "2", features = ["serde"] }
blake3 = "1"
hex = "0.4"

# Reth deps, use 4231f4b to get latest op-alloy
reth-optimism-payload-builder = { git = "https://github.com/paradigmxyz/reth", rev = "4231f4b" }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These dependencies should be pinned to v1.9.3

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1.9.3 doesn't have the latest op-alloy which is causing problems

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can't we just match the alloy versions from reth?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd have to downgrade op_alloy_rpc_types_engine since we're already depending on the newer version here.

reth-rpc-layer = { git = "https://github.com/paradigmxyz/reth", rev = "4231f4b" }

# Alloy libraries
alloy-rpc-types-engine = "1.0.41"
Expand All @@ -39,11 +47,12 @@ alloy-consensus = "1.0.41"
alloy-rpc-types = "1.0.41"
alloy-genesis = "1.0.41"
alloy-rpc-client = "1.0.41"
alloy-rlp = "0.3.12"
alloy-provider = "1.0.41"
op-alloy-network = "0.23.0"
op-alloy-rpc-types-engine = "0.23.0"
op-alloy-consensus = "0.23.0"
op-alloy-rpc-types = "0.23.0"
op-alloy-rpc-types-engine = "0.23.1"
op-alloy-consensus = "0.23.1"
op-alloy-rpc-types = "0.23.1"
tokio-tungstenite = { version = "0.26.2", features = ["native-tls"] }
testcontainers = "0.23"
jsonrpsee = "0.26.0"
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ RUN cargo install sccache --version ^0.9
RUN cargo install cargo-chef --version ^0.1

RUN apt-get update \
&& apt-get install -y clang libclang-dev
Copy link
Collaborator

@0xOsiris 0xOsiris Dec 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this necessary

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have this now:

RUN apt-get update \
    && apt-get install -y clang libclang-dev gcc

Should be fine.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I'm asking is this a necessary addition. It was working previously, so what addition made this a necessary change

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TBH it was a while ago and I'm not sure :p

&& apt-get install -y clang libclang-dev gcc

ENV CARGO_HOME=/usr/local/cargo
ENV RUSTC_WRAPPER=sccache
Expand Down
4 changes: 4 additions & 0 deletions crates/rollup-boost-types/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ license = "MIT"

[dependencies]
alloy-primitives = { workspace = true }
alloy-rlp = { workspace = true }
alloy-rpc-types-engine = { workspace = true }
alloy-rpc-types-eth = { workspace = true }
alloy-serde = { workspace = true }
Expand All @@ -15,3 +16,6 @@ op-alloy-rpc-types-engine = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
tracing = { workspace = true }
thiserror = { workspace = true }
ed25519-dalek = { workspace = true }
blake3 = { workspace = true }
222 changes: 222 additions & 0 deletions crates/rollup-boost-types/src/authorization.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
use alloy_primitives::{B64, Bytes};
use alloy_rlp::{Decodable, Encodable, Header};
use alloy_rpc_types_engine::PayloadId;
use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
use serde::{Deserialize, Serialize};
use thiserror::Error;

/// An authorization token that grants a builder permission to publish flashblocks for a specific payload.
///
/// The `authorizer_sig` is made over the `payload_id`, `timestamp`, and `builder_vk`. This is
/// useful because it allows the authorizer to control which builders can publish flashblocks in
/// real time, without relying on consumers to verify the builder's public key against a
/// pre-defined list.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Authorization {
/// The unique identifier of the payload this authorization applies to
pub payload_id: PayloadId,
/// Unix timestamp when this authorization was created
pub timestamp: u64,
/// The public key of the builder who is authorized to sign messages
pub builder_vk: VerifyingKey,
/// The authorizer's signature over the payload_id, timestamp, and builder_vk
pub authorizer_sig: Signature,
}

#[derive(Debug, Error, PartialEq)]
pub enum AuthorizationError {
#[error("invalid authorizer signature")]
InvalidAuthorizerSig,
}

impl Authorization {
/// Creates a new authorization token for a builder to publish messages for a specific payload.
///
/// This function creates a cryptographic authorization by signing a message containing the
/// payload ID, timestamp, and builder's public key using the authorizer's signing key.
///
/// # Arguments
///
/// * `payload_id` - The unique identifier of the payload this authorization applies to
/// * `timestamp` - Unix timestamp associated with this `payload_id`
/// * `authorizer_sk` - The authorizer's signing key used to create the signature
/// * `actor_vk` - The verifying key of the actor being authorized
///
/// # Returns
///
/// A new `Authorization` instance with the generated signature
pub fn new(
payload_id: PayloadId,
timestamp: u64,
authorizer_sk: &SigningKey,
actor_vk: VerifyingKey,
) -> Self {
let mut msg = payload_id.0.to_vec();
msg.extend_from_slice(&timestamp.to_le_bytes());
msg.extend_from_slice(actor_vk.as_bytes());
let hash = blake3::hash(&msg);
let sig = authorizer_sk.sign(hash.as_bytes());

Self {
payload_id,
timestamp,
builder_vk: actor_vk,
authorizer_sig: sig,
}
}

/// Verifies the authorization signature against the provided authorizer's verifying key.
///
/// This function reconstructs the signed message from the authorization data and verifies
/// that the signature was created by the holder of the authorizer's private key.
///
/// # Arguments
///
/// * `authorizer_sk` - The verifying key of the authorizer to verify against
///
/// # Returns
///
/// * `Ok(())` if the signature is valid
/// * `Err(FlashblocksP2PError::InvalidAuthorizerSig)` if the signature is invalid
pub fn verify(&self, authorizer_sk: VerifyingKey) -> Result<(), AuthorizationError> {
let mut msg = self.payload_id.0.to_vec();
msg.extend_from_slice(&self.timestamp.to_le_bytes());
msg.extend_from_slice(self.builder_vk.as_bytes());
let hash = blake3::hash(&msg);
authorizer_sk
.verify(hash.as_bytes(), &self.authorizer_sig)
.map_err(|_| AuthorizationError::InvalidAuthorizerSig)
}
}

impl Encodable for Authorization {
fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
// pre-serialize the key & sig once so we can reuse the bytes & lengths
let pub_bytes = Bytes::copy_from_slice(self.builder_vk.as_bytes()); // 33 bytes
let sig_bytes = Bytes::copy_from_slice(&self.authorizer_sig.to_bytes()); // 64 bytes

let payload_len = self.payload_id.0.length()
+ self.timestamp.length()
+ pub_bytes.length()
+ sig_bytes.length();

Header {
list: true,
payload_length: payload_len,
}
.encode(out);

// 1. payload_id (inner B64 already Encodable)
self.payload_id.0.encode(out);
// 2. timestamp
self.timestamp.encode(out);
// 3. builder_pub
pub_bytes.encode(out);
// 4. authorizer_sig
sig_bytes.encode(out);
}

fn length(&self) -> usize {
let pub_bytes = Bytes::copy_from_slice(self.builder_vk.as_bytes());
let sig_bytes = Bytes::copy_from_slice(&self.authorizer_sig.to_bytes());

let payload_len = self.payload_id.0.length()
+ self.timestamp.length()
+ pub_bytes.length()
+ sig_bytes.length();

Header {
list: true,
payload_length: payload_len,
}
.length()
+ payload_len
}
}

impl Decodable for Authorization {
fn decode(buf: &mut &[u8]) -> Result<Self, alloy_rlp::Error> {
let header = Header::decode(buf)?;
if !header.list {
return Err(alloy_rlp::Error::UnexpectedString);
}
let mut body = &buf[..header.payload_length];

// 1. payload_id
let payload_id = alloy_rpc_types_engine::PayloadId(B64::decode(&mut body)?);

// 2. timestamp
let timestamp = u64::decode(&mut body)?;

// 3. builder_pub
let pub_bytes = Bytes::decode(&mut body)?;
let builder_pub = VerifyingKey::try_from(pub_bytes.as_ref())
.map_err(|_| alloy_rlp::Error::Custom("bad builder_pub"))?;

// 4. authorizer_sig
let sig_bytes = Bytes::decode(&mut body)?;
let authorizer_sig = Signature::try_from(sig_bytes.as_ref())
.map_err(|_| alloy_rlp::Error::Custom("bad signature"))?;

// advance caller’s slice cursor
*buf = &buf[header.payload_length..];

Ok(Self {
payload_id,
timestamp,
builder_vk: builder_pub,
authorizer_sig,
})
}
}

#[cfg(test)]
mod tests {
use super::*;
use alloy_rlp::{Decodable, Encodable, encode};

fn key_pair(seed: u8) -> (SigningKey, VerifyingKey) {
let bytes = [seed; 32];
let sk = SigningKey::from_bytes(&bytes);
let vk = sk.verifying_key();
(sk, vk)
}

#[test]
fn authorization_rlp_roundtrip_and_verify() {
let (authorizer_sk, authorizer_vk) = key_pair(1);
let (_, builder_vk) = key_pair(2);

let auth = Authorization::new(
PayloadId::default(),
1_700_000_123,
&authorizer_sk,
builder_vk,
);

let encoded = encode(auth);
assert_eq!(encoded.len(), auth.length(), "length impl correct");

let mut slice = encoded.as_ref();
let decoded = Authorization::decode(&mut slice).expect("decoding succeeds");
assert!(slice.is_empty(), "decoder consumed all bytes");
assert_eq!(decoded, auth, "round-trip preserves value");

// Signature is valid
decoded.verify(authorizer_vk).expect("signature verifies");
}

#[test]
fn authorization_signature_tamper_is_detected() {
let (authorizer_sk, authorizer_vk) = key_pair(1);
let (_, builder_vk) = key_pair(2);

let mut auth = Authorization::new(PayloadId::default(), 42, &authorizer_sk, builder_vk);

let mut sig_bytes = auth.authorizer_sig.to_bytes();
sig_bytes[0] ^= 1;
auth.authorizer_sig = Signature::try_from(sig_bytes.as_ref()).unwrap();

assert!(auth.verify(authorizer_vk).is_err());
}
}
Loading