Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
contrib/
docs/
localnet/
scripts/
scripts/*
!scripts/bundle-history.py
target/
17 changes: 16 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,24 @@ jobs:
- uses: tempoxyz/gh-actions/vendor/mozilla-actions/sccache-action@e813067e922303348ee5aad965f745803cc24f1f # 2026-10-02T09-56-31Z-e813067e
- name: Build
run: cargo build --bin ${{ matrix.binary }} --profile ${{ inputs.profile || 'dev' }}
- name: Prepare artifact
env:
BUILD_PROFILE: ${{ inputs.profile || 'dev' }}
BIN_NAME: ${{ matrix.binary }}
CARGO_TARGET_DIR: ${{ github.workspace }}/target
run: |
profile_dir="$BUILD_PROFILE"
[ "$profile_dir" != dev ] || profile_dir=debug
output="dist/$BIN_NAME"
mkdir -p "$output"
cp "target/$profile_dir/$BIN_NAME" "$output/"
if [ "$BIN_NAME" = tempo ]; then
python3 scripts/bundle-history.py --output "$output" --profile "$BUILD_PROFILE"
fi
tar czf "dist/$BIN_NAME.tar.gz" -C "$output" .
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
id: binary_upload
with:
if-no-files-found: "error"
name: ${{ matrix.binary }}
path: target/${{ inputs.profile || 'debug' }}/${{ matrix.binary }}
path: dist/${{ matrix.binary }}.tar.gz
8 changes: 7 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ jobs:
TARGET: ${{ matrix.platform.target }}
BIN_NAME: ${{ matrix.binary.name }}
VERSION: ${{ needs.get-version.outputs.version }}
CARGO_TARGET_DIR: ${{ github.workspace }}/target
run: |
PROFILE="$BUILD_PROFILE"
if [ "$PROFILE" = "dev" ]; then
Expand All @@ -171,7 +172,12 @@ jobs:
ARCHIVE_NAME="${BIN_NAME}-${VERSION}-${TARGET}.tar.gz"

cp "$BINARY_PATH" "$BINARY_NAME"
tar czf "$ARCHIVE_NAME" "$BINARY_NAME"
archive_files=("$BINARY_NAME")
if [ "$BIN_NAME" = tempo ]; then
python3 scripts/bundle-history.py --output . --profile "$BUILD_PROFILE"
archive_files+=(eras tempo-eras.json history/genesis-t10)
fi
tar czf "$ARCHIVE_NAME" "${archive_files[@]}"

{
echo "archive_name=$ARCHIVE_NAME"
Expand Down
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

47 changes: 31 additions & 16 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,19 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry,sharing=locked,id=cargo-
--bin tempo --features "${RUST_FEATURES},localnet" \
--bin tempo-localnet --features "${RUST_FEATURES},localnet" \
--bin tempo-sidecar \
--bin tempo-xtask
--bin tempo-xtask && \
profile_dir="${RUST_PROFILE}" && \
if [ "$profile_dir" = dev ]; then profile_dir=debug; fi && \
mkdir -p dist && \
cp target/"$profile_dir"/tempo target/"$profile_dir"/tempo-localnet \
target/"$profile_dir"/tempo-sidecar target/"$profile_dir"/tempo-xtask dist/

FROM builder AS history-builder
RUN --mount=type=cache,target=/usr/local/cargo/registry,sharing=locked,id=cargo-registry-${TARGETARCH} \
--mount=type=cache,target=/usr/local/cargo/git,sharing=locked,id=cargo-git-${TARGETARCH} \
--mount=type=cache,target=$SCCACHE_DIR,sharing=locked,id=sccache-${TARGETARCH} \
RUSTFLAGS="-C link-arg=-fuse-ld=mold ${EXTRA_RUSTFLAGS}" \
python3 scripts/bundle-history.py --output dist --profile ${RUST_PROFILE}

# Reuse the regular build artifacts, enabling custom PCRs only for the devnet binary.
FROM builder AS devnet-builder
Expand All @@ -29,7 +41,10 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry,sharing=locked,id=cargo-
--mount=type=cache,target=$SCCACHE_DIR,sharing=locked,id=sccache-${TARGETARCH} \
RUSTFLAGS="-C link-arg=-fuse-ld=mold ${EXTRA_RUSTFLAGS}" \
cargo build --profile ${RUST_PROFILE} \
--bin tempo --features "${RUST_FEATURES},localnet,custom-pcrs"
--bin tempo --features "${RUST_FEATURES},localnet,custom-pcrs" && \
profile_dir="${RUST_PROFILE}" && \
if [ "$profile_dir" = dev ]; then profile_dir=debug; fi && \
cp target/"$profile_dir"/tempo dist/tempo

FROM debian:bookworm-slim@sha256:4724b8cc51e33e398f0e2e15e18d5ec2851ff0c2280647e1310bc1642182655d AS base

Expand All @@ -39,36 +54,36 @@ RUN apt-get update && apt-get install -y --no-install-recommends \

WORKDIR /data

FROM base AS node-base
COPY --from=history-builder /app/dist/eras /usr/local/bin/eras
COPY --from=history-builder /app/dist/tempo-eras.json /usr/local/bin/tempo-eras.json
COPY --from=history-builder /app/dist/history /usr/local/bin/history

# tempo
FROM base AS tempo
ARG RUST_PROFILE=profiling
COPY --from=builder /app/target/${RUST_PROFILE}/tempo /usr/local/bin/tempo
FROM node-base AS tempo
COPY --from=builder /app/dist/tempo /usr/local/bin/tempo
ENTRYPOINT ["/usr/local/bin/tempo"]

# tempo-devnet
FROM base AS tempo-devnet
ARG RUST_PROFILE=profiling
COPY --from=devnet-builder /app/target/${RUST_PROFILE}/tempo /usr/local/bin/tempo
FROM node-base AS tempo-devnet
COPY --from=devnet-builder /app/dist/tempo /usr/local/bin/tempo
ENTRYPOINT ["/usr/local/bin/tempo"]

# tempo-localnet
FROM base AS tempo-localnet
ARG RUST_PROFILE=profiling
COPY --from=builder /app/target/${RUST_PROFILE}/tempo /usr/local/bin/tempo
COPY --from=builder /app/target/${RUST_PROFILE}/tempo-localnet /usr/local/bin/tempo-localnet
FROM node-base AS tempo-localnet
COPY --from=builder /app/dist/tempo /usr/local/bin/tempo
COPY --from=builder /app/dist/tempo-localnet /usr/local/bin/tempo-localnet
EXPOSE 8545
VOLUME ["/data"]
HEALTHCHECK --interval=2s --timeout=2s --start-period=120s --retries=5 CMD ["/usr/local/bin/tempo-localnet", "--health"]
ENTRYPOINT ["/usr/local/bin/tempo-localnet"]

# tempo-sidecar
FROM base AS tempo-sidecar
ARG RUST_PROFILE=profiling
COPY --from=builder /app/target/${RUST_PROFILE}/tempo-sidecar /usr/local/bin/tempo-sidecar
COPY --from=builder /app/dist/tempo-sidecar /usr/local/bin/tempo-sidecar
ENTRYPOINT ["/usr/local/bin/tempo-sidecar"]

# tempo-xtask
FROM base AS tempo-xtask
ARG RUST_PROFILE=profiling
COPY --from=builder /app/target/${RUST_PROFILE}/tempo-xtask /usr/local/bin/tempo-xtask
COPY --from=builder /app/dist/tempo-xtask /usr/local/bin/tempo-xtask
ENTRYPOINT ["/usr/local/bin/tempo-xtask"]
1 change: 1 addition & 0 deletions Dockerfile.chef
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ ARG EXTRA_RUSTFLAGS=""
RUN apt-get update \
&& apt-get install --no-install-recommends -y \
pkg-config \
python3 \
libssl-dev \
build-essential \
clang \
Expand Down
10 changes: 9 additions & 1 deletion Dockerfile.reproducible
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#
# Output (with `--target artifacts -o type=local,dest=./out`):
# ./out/tempo the reproducible binary
# ./out/{eras,tempo-eras.json,history} the frozen worker bundle
#
# Reproducibility-critical decisions:
# * Pinned base image digest (rust 1.96.0 on Debian Bookworm).
Expand Down Expand Up @@ -65,6 +66,7 @@ RUN apt-get -o Acquire::Check-Valid-Until=false update && \
clang \
libssl-dev \
pkg-config \
python3 \
mold && \
rm -rf /var/lib/apt/lists/*

Expand All @@ -82,7 +84,13 @@ RUN cargo build --locked --bin tempo \
--profile reproducible \
--target x86_64-unknown-linux-gnu

# `--target artifacts` extracts just the binary; `--target builder` (the
RUN python3 scripts/bundle-history.py \
--output target/x86_64-unknown-linux-gnu/reproducible --profile release

# `--target artifacts` extracts the bundle; `--target builder` (the
# default) keeps the full Rust environment for debugging.
FROM scratch AS artifacts
COPY --from=builder /app/target/x86_64-unknown-linux-gnu/reproducible/tempo /tempo
COPY --from=builder /app/target/x86_64-unknown-linux-gnu/reproducible/eras /eras
COPY --from=builder /app/target/x86_64-unknown-linux-gnu/reproducible/tempo-eras.json /tempo-eras.json
COPY --from=builder /app/target/x86_64-unknown-linux-gnu/reproducible/history /history
25 changes: 21 additions & 4 deletions bin/tempo-metabinary/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,24 @@ The release supplies `tempo-eras.json` beside the executable. There is no node `
```

These values are illustrative. Closed eras name frozen executables, resolved relative to the
catalog; the last era uses the running node. The built-in development catalog
(`bin/tempo/eras.json`) is empty. Unmatched chains and custom fork schedules use the native path.
catalog; the last era uses the running node. The built-in catalog (`bin/tempo/eras.json`) routes
mainnet and Moderato execution through Genesis–T10 to `eras/tempo-genesis-t10`, with T11+ running
in-process. Unmatched chains and custom fork schedules use the native path.

Docker images, release archives, and `tempoup` install the worker beside the live executable.
Source builds must bundle it explicitly; the pinned source and era metadata are recorded in
`bin/tempo/history.json`:

```sh
cargo build --locked --release --bin tempo
python3 scripts/bundle-history.py --output target/release --profile release
```

The helper fetches the pinned frozen source, builds with its frozen feature, and
verifies calls and traces before installation. Use `--source /path/to/tempo-history` for a clean
local checkout at the pin, or `--package /path/to/extracted-bundle` for a trusted, already verified
package matching the host and profile.
Installed checksums and build/smoke provenance live in `history/genesis-t10/`.

Workers inherit the selected genesis, datadir, custom static-file/RocksDB paths, and resolved SDK
`EthConfig` through private temporary files. Gas, simulation, memory, and tracing settings retain
Expand Down Expand Up @@ -100,8 +116,9 @@ verifies its epoch boundary header from the authenticated chain. No boundary cer
It persists the certified floor in the consensus archives. Cached executed blocks survive an
interrupted handoff and can be replayed without an upstream. Finality is persisted only through
the durable execution prefix. Custom fork schedules retain their native startup path, and
canonical-file bootstrap remains available for offline imports. The active binary still supports
old forks; configuring and packaging a frozen replacement is a separate release change.
canonical-file bootstrap remains available for offline imports.

The active binary executes T10+ only; mainnet and Moderato use the bundled Genesis–T10 writer.
Historical validator-config reads already use storage without constructing an EVM.

Frozen artifacts need this `rpc-only` discovery protocol and hidden `--rpc-config` interface,
Expand Down
41 changes: 40 additions & 1 deletion bin/tempo/eras.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,42 @@
{
"chains": []
"chains": [
{
"chain_id": "0x1079",
"genesis_hash": "0x49d7ec7085e77bf5a403d0fcb4cfc42a4084a89dfff60477579c5e09c9e03c54",
"eras": [
{
"name": "genesis-t10",
"start_timestamp": 0,
"binary": "eras/tempo-genesis-t10",
"checkpoint": {
"number": 38891820,
"hash": "0x420c51c9ce58d0775fff3039b1ca7d31b6c59815d5bc20a9e09ed1a6272d635e"
}
},
{
"name": "t11-live",
"start_timestamp": 1789048800
}
]
},
{
"chain_id": "0xa5bf",
"genesis_hash": "0xba49153e6f3996a340f3fe3d69e7c0826b717ae672cb115cf0527cada0b78e9e",
"eras": [
{
"name": "genesis-t10",
"start_timestamp": 0,
"binary": "eras/tempo-genesis-t10",
"checkpoint": {
"number": 34557122,
"hash": "0x9ae15322bb005ed9157a6ee3847d3a6b73a9d667af91ec4522df1ca838e8e9db"
}
},
{
"name": "t11-live",
"start_timestamp": 1788962400
}
]
}
]
}
9 changes: 9 additions & 0 deletions bin/tempo/history.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"repository": "https://github.com/tempoxyz/tempo-history.git",
"ref": "rus/tempo-era-handoff",
"revision": "6b016cf3bf5a51686ef0b55ab3bb4525c279d677",
"era": "genesis-t10",
"version": "0.1.2",
"upstream_revision": "4e14416db157271526e90d70a68b132c3b3a4123",
"era_sha256": "26c04a2c1a7c5c05531ba5d0e69fe5ea73d60f4ce79ffc64c87dd26af3914b2c"
}
28 changes: 27 additions & 1 deletion bin/tempo/src/eras.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use tempo_metabinary::{
workers::{DEFAULT_STARTUP_TIMEOUT_SECS, HistoricalWorkers, WorkerContext},
};

/// Load chain-bound metadata bundled with the release. Development builds have no frozen eras.
/// Load the bundled Genesis–T10 schedule, resolving workers beside the executable.
/// A packaged catalog next to the executable supersedes the built-in catalog; it contains only
/// release artifacts and activation times, never the operator's node configuration.
pub(crate) fn release_catalog() -> eyre::Result<Catalog> {
Expand Down Expand Up @@ -233,6 +233,32 @@ mod tests {
use super::*;
use serde_json::json;

#[test]
fn bundled_history_matches_builtin_chains() {
let mut catalog: Catalog = serde_json::from_str(include_str!("../eras.json")).unwrap();
catalog.validate().unwrap();
catalog.resolve_binaries(std::path::Path::new("/tempo"));
for spec in [
&**tempo_chainspec::spec::PRESTO,
&**tempo_chainspec::spec::MODERATO,
] {
let chain = catalog
.for_chain(spec.chain_id(), spec.genesis_hash())
.unwrap();
let boundary = spec
.info
.fork_time(tempo_chainspec::TempoHardfork::T11)
.unwrap();
assert_eq!(
chain.eras[0].binary.as_deref(),
Some(std::path::Path::new("/tempo/eras/tempo-genesis-t10"))
);
for (timestamp, era) in [(0, 0), (boundary - 1, 0), (boundary, 1)] {
assert_eq!(chain.era_for_timestamp(timestamp), era);
}
}
}

#[test]
fn custom_fork_schedule_does_not_inherit_builtin_eras() {
let known = &*tempo_chainspec::spec::PRESTO;
Expand Down
2 changes: 1 addition & 1 deletion crates/alloy/src/rpc/reth_compat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ impl TryIntoTxEnv<TempoTxEnv, TempoHardfork, TempoBlockEnv> for TempoTransaction
evm_env: &EvmEnv<TempoHardfork, TempoBlockEnv>,
) -> Result<TempoTxEnv, Self::Err> {
let inner = self.inner.clone().try_into_tx_env(evm_env)?;
self.try_into_tempo_tx_env(TempoTxEnv::from(inner), evm_env.spec_id().is_t1c())
self.try_into_tempo_tx_env(TempoTxEnv::from(inner))
.map_err(|err| EthApiError::InvalidParams(err.to_string()))
}
}
Expand Down
13 changes: 3 additions & 10 deletions crates/alloy/src/rpc/revm_compat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ impl TempoTransactionRequest {
pub fn try_into_tempo_tx_env(
self,
mut tx_env: TempoTxEnv,
is_t1c: bool,
) -> Result<TempoTxEnv, ValueError<Self>> {
let caller_addr = self.inner.from.unwrap_or_default();
let is_aa = self.has_aa_fields();
Expand Down Expand Up @@ -70,7 +69,7 @@ impl TempoTransactionRequest {
tx_env.tempo_tx_env = if is_aa {
let key_type = key_type.unwrap_or(SignatureType::Secp256k1);
let mock_signature =
create_mock_tempo_sig(&key_type, key_data.as_ref(), key_id, caller_addr, is_t1c);
create_mock_tempo_sig(&key_type, key_data.as_ref(), key_id, caller_addr);

let mut calls = calls;
if let Some(to) = &inner.to {
Expand Down Expand Up @@ -111,19 +110,13 @@ pub(super) fn create_mock_tempo_sig(
key_data: Option<&Bytes>,
key_id: Option<Address>,
caller_addr: Address,
is_t1c: bool,
) -> TempoSignature {
use tempo_primitives::transaction::tt_signature::{KeychainSignature, TempoSignature};

let inner_sig = create_mock_primitive_signature(key_type, key_data.cloned());

if key_id.is_some() {
let keychain_sig = if is_t1c {
KeychainSignature::new(caller_addr, inner_sig)
} else {
KeychainSignature::new_v1(caller_addr, inner_sig)
};
TempoSignature::Keychain(keychain_sig)
TempoSignature::Keychain(KeychainSignature::new(caller_addr, inner_sig))
} else {
TempoSignature::Primitive(inner_sig)
}
Expand Down Expand Up @@ -218,7 +211,7 @@ mod tests {
};

let env = request
.try_into_tempo_tx_env(TempoTxEnv::default(), true)
.try_into_tempo_tx_env(TempoTxEnv::default())
.expect("valid simulation request");
let aa = env.tempo_tx_env.as_ref().expect("AA simulation env");

Expand Down
Loading
Loading