The Builder simulates bundles and transactions against the latest chain state to create valid Signet rollup blocks and submits them to the configured host chain as an EIP-4844 transaction.
Bundles are treated as MEV-style bundles, meaning that the Builder should respect transaction ordering, bundle atomicity, and the specified revertability.
🚀 System Design
The Builder orchestrates a series of asynchronous actors that work together to build blocks for every assigned slot.
- Env - watches the latest host and rollup blocks to monitor gas rates and block updates.
- Cache - polls bundle and transaction caches and adds them to the cache.
- Simulator - simulates transactions and bundles against rollup state and block environment to build them into a cohesive block.
- Submit - handles preparing and submitting the simulated block to all configured MEV relay/builder endpoints concurrently.
- Metrics - records block and tx data over time.
%%{ init : { "theme" : "dark" } }%%
flowchart TD
%% Initialization
Start(["Start"]) --> Init["Init tracing & logging"]
Init --> Config["Load BuilderConfig from env"]
Inbound["📥 Transactions &
📦 Bundles"] --> Cache[" Cache Task"]
Quincey["🖊️ Quincey (Signer)"]
%% Block building loop
subgraph BuilderLoop["Block Building Loop"]
Env["🔢 Env Task"]
Cache[" Cache Task"]
Simulator["💾 Simulator Task"]
Metrics["📏 Metrics Task"]
SubmitTask["📥 Submit Task"]
end
%% Signing
SubmitTask --hash--> Quincey
Quincey -- signature --> SubmitTask
%% Config wiring
Config -.rollup rpc.-> Env
Config -.host rpc.-> Env
Config -.host rpc.-> Simulator
Config -.rollup rpc.-> Simulator
Config -.host rpc.-> Metrics
Config -.host rpc.-> SubmitTask
%% Core flow
Env ==block env==> Simulator
Cache ==sim cache==> Simulator
Simulator ==built block==> SubmitTask
%% Network submission
SubmitTask ==>|"tx bundle"| Relays["🛡️ MEV Relays / Builders"]
Relays ==> Ethereum
%% Metrics
SubmitTask ==rollup block tx hash==> Metrics
💾 Simulation Task
The block building loop waits until a new block environment has been received, and then kicks off the next attempt.
When the Builder receives a new block, it takes a reference to the transaction cache, calculates a simulation deadline for the current slot with a buffer of 1.5 seconds, and begins constructing a block for the current slot.
Transactions enter through the cache, and then they're sent to the simulator, where they're run against the latest chain state and block environment. If they're successfully applied, they're added to the block. If a transaction fails to be applied, it is simply ignored.
When the deadline is reached, the simulator is stopped, and all open simulation threads are cancelled. The built block is then bundled with the block environment and the previous host header that it was simulated against, and all three are passed along to the submit task.
✨ Submit Task
The submit task prepares a MEV bundle from the Signet block and its host transactions, then fans it out to all configured relay/builder endpoints concurrently (SUBMIT_ENDPOINTS). At least one successful relay acceptance is required; individual relay failures are tolerated and logged. The blob sidecar is always forwarded to Pylon regardless of relay outcome.
If the block received from simulation is empty, the submit task will ignore it.
If it's non-empty, the submit task attempts to get a signature for the block, and if it fails due to a 403 error, it will skip the current slot and begin waiting for the next block.
⚙️ Configuration
The Builder is configured via environment variables. The following values are supported for configuration.
| Key | Required | Description |
|---|---|---|
RUST_LOG |
No | The log level of the builder |
CHAIN_NAME |
No | The chain name ("pecorino", or the corresponding name) |
HOST_RPC_URL |
Yes | RPC endpoint for the host chain |
ROLLUP_RPC_URL |
Yes | RPC endpoint for the rollup chain |
QUINCEY_URL |
Yes | Remote sequencer signing endpoint |
SEQUENCER_KEY |
No | AWS Key ID OR local private key for the Sequencer; set IFF using local Sequencer signing instead of remote (via QUINCEY_URL) Quincey signing |
TX_POOL_URL |
Yes | Transaction pool URL |
SUBMIT_ENDPOINTS |
Yes | Comma-separated list of MEV relay/builder RPC endpoints for bundle submission (e.g. https://rpc.flashbots.net,https://rpc.titanbuilder.xyz) |
ROLLUP_BLOCK_GAS_LIMIT |
No | Override for rollup block gas limit |
MAX_HOST_GAS_COEFFICIENT |
No | Optional maximum host gas coefficient, as a percentage, to use when building blocks |
BUILDER_KEY |
Yes | AWS KMS key ID or local private key for builder signing |
BLOCK_QUERY_CUTOFF_BUFFER |
Yes | Number of milliseconds before the end of the slot to stop querying for new transactions and start the block signing and submission process |
AWS_ACCESS_KEY_ID |
No | AWS secret access key ID (required if not using BUILDER_KEY) |
AWS_SECRET_ACCESS_KEY |
No | AWS secret access key (required if not using BUILDER_KEY) |
AWS_DEFAULT_REGION |
No | AWS region for the KMS key in question (required if not using BUILDER_KEY) |
BUILDER_PORT |
Yes | HTTP port for the Builder (default: 8080) |
BUILDER_REWARDS_ADDRESS |
Yes | Address receiving builder rewards |
CONCURRENCY_LIMIT |
No | Optional max number of concurrent tasks the simulator uses. Defaults to a system call to determine optimal parallelism |
OAUTH_CLIENT_ID |
Yes | OAuth client ID for the builder |
OAUTH_CLIENT_SECRET |
Yes | OAuth client secret for the builder |
OAUTH_AUTHENTICATE_URL |
Yes | OAuth authenticate URL for the builder for performing OAuth logins |
OAUTH_TOKEN_URL |
Yes | OAuth token URL for the builder to get an OAuth2 access token |
AUTH_TOKEN_REFRESH_INTERVAL |
Yes | The OAuth token refresh interval in seconds |
💻 Recommended Specs
| Key | Minimum | Recommended |
|---|---|---|
| CPU | 0.1 vCPU | 0.5 vCPU |
| Memory | 256MB | 512MB |
Note: Builder prefers clock speed over core count, recommended 2.8Ghz+
💾 EVM Behavior
🗿 Inherited Header Values
PREVRANDAO is set to a random byte string for each block.
// `src/tasks/env.rs` prevrandao: Some(B256::random()),
TIMESTAMP - Block timestamps are set to the same value as the current Ethereum block.
Blob gas values excess_blob_gas and blob_gasprice are also set to 0 for all Signet blocks.
🔢 Disabled Opcodes
BLOBHASH - EIP-4844 is not supported on Signet. BLOBBASEFEE - EIP4844 is not supported.
⛽ Transaction Submission
When a completed, non-empty Signet block is received by the Submit task, it prepares the block data into a blob transaction and submits it to the network.
If it fails, it will retry up to 3 times with a 12.5% bump on each retry.
The previous header's basefee is tracked through the build loop and used for gas estimation purposes in the Submit Task.
🛠️ Development
Requirements
- Rust ≥ 1.85
- AWS CLI
- A private key or AWS KMS key for signing transactions
✅ Testing
Unit Tests
Integration Tests
Integration tests require network access (RPC endpoints or Anvil) and are gated behind the test-utils Cargo feature. They are not compiled by default.
make test-all # Run all tests (unit + integration) cargo test --features test-utils --test block_builder_test # Run a specific integration test
Deployment Verification
- Build the Docker image:
docker build -t builder:latest .- Push to your container registry:
docker push <registry>/builder:latest
-
Update your deployment manifests with the new image.
-
Verify expected behavior in your target network.
- This should typically include sending a test transaction and verifying it is simulated and built into a block.
🪪 License
This project is licensed under the MIT License.