Skip to content
Merged
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
31 changes: 31 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,34 @@ jobs:
! git ls-files | grep -E '(^|/)(private-spool|keystore)(/|$)'
uv run python scripts/check_publishable_tree.py
uv run pytest tests/unit/test_publication.py -q
go-runtime:
runs-on: ubuntu-latest
env:
XIR_LIVE_WRITES_DISABLED: "1"
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.23"
cache-dependency-path: go-runtime/go.sum
- uses: foundry-rs/foundry-toolchain@v1
with:
version: v1.5.1
- uses: actions/setup-node@v4
with:
node-version: "22"
- name: Build pinned real protocol fixtures
run: scripts/prepare_go_e2e.sh "$RUNNER_TEMP/go-e2e-protocols"
- run: go vet ./...
working-directory: go-runtime
- run: go test -count=1 ./...
working-directory: go-runtime
env:
XIR_REQUIRE_E2E: "1"
- name: Require real Anvil and recovery tests (no skips)
run: scripts/run_go_e2e.sh "$RUNNER_TEMP/go-e2e-results.json"
- uses: actions/upload-artifact@v4
if: always()
with:
name: go-e2e-results
path: ${{ runner.temp }}/go-e2e-results.json
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ data/blobs/
data/releases/
results/
artifacts/
# The Go runtime's Forge artifact loader is source, not evidence output.
!go-runtime/internal/artifacts/
release/
releases/

Expand Down
16 changes: 16 additions & 0 deletions CLAIM_BOUNDARIES.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,22 @@ XIR-minus-baseline overhead requires matched routes, inputs, environments,
and eligible samples from both arms. Planning a paired experiment does not
establish that its measurements were collected.

## Go runtime

`go-runtime/` is an independent implementation of the execution layer. Its
claims are limited to byte compatibility with the Solidity contracts and the
Python runtime, verified by the generated parity vectors, and to the execution
behaviour its integration tests exercise on disposable local chains.

A Go run is not a campaign result: it does not reproduce the review gate, phase
authority, validator-volume attestation, resource monitor, or preflight digests,
and it produces no published measurement. The integration tests use anvil with
the frozen chain ids and the same contracts because the five-chain Besu QBFT
topology does not fit on a development host; they are fixtures, not the
environment of record. The Go runtime has no mainnet path and no public-testnet
write path, and its self-hosted Hyperlane validator/relayer and LayerZero
DVN/Executor roles are research roles, not managed third-party services.

## Trust, costs, and reporting

The paper's theorem is conditional on sound protocol deliveries, correct XIR
Expand Down
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@ Adapters carry this record across hops so that the destination can check the
message and its verification history before execution.

This repository contains the XIR research implementation and experiment tools:
Solidity contracts, Python orchestration, Bash deployment scripts, versioned
configurations, and selected verification artifacts. The implemented EVM
protocol integrations are **Hyperlane and LayerZero V2**. The accompanying
observational dataset covers six protocols.
Solidity contracts, a Python orchestration runtime, an independent Go runtime
for the execution layer, Bash deployment scripts, versioned configurations, and
selected verification artifacts. The implemented EVM protocol integrations are
**Hyperlane and LayerZero V2**. The accompanying observational dataset covers
six protocols.

## How XIR works

Expand All @@ -52,6 +53,7 @@ protocol for both connections.
| [XIR Registry](contracts/src/XIRRegistry.sol) | Store verification profiles, approved components, and source authorization configuration |
| [Hyperlane Adapter](contracts/src/HyperlaneAdapter.sol) and [LayerZero Adapter](contracts/src/LayerZeroAdapter.sol) | Connect native protocol callbacks and sends to the common representation |
| [Python runtime](src/xir_lab/) | Plan and execute bounded experiments, collect evidence, reconcile outcomes, and build reports |
| [Go runtime](go-runtime/) | Independently implement the execution layer: byte-compatible encoding and signing, durable dispatch and confirmation, and restart recovery. It consumes frozen plans and deployment documents; planning, governance gates, analysis, and publication stay Python-side ([notes](docs/go-runtime.md)) |

The controlled experiments use Besu QBFT chains. Hyperlane's validator and
relayer are upstream Rust components; the local LayerZero deployment combines
Expand Down
31 changes: 31 additions & 0 deletions configs/go-runtime/local-hl-example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"schema_version": "xir-go-runtime-config-v1",
"chains": [
{"role": "a", "chain_id": 3133701, "rpc_url": "http://127.0.0.1:18545", "hyperlane_domain": 3133701, "layerzero_eid": 49001},
{"role": "b", "chain_id": 3133702, "rpc_url": "http://127.0.0.1:28545", "hyperlane_domain": 3133702, "layerzero_eid": 49002},
{"role": "c", "chain_id": 3133703, "rpc_url": "http://127.0.0.1:38545", "hyperlane_domain": 3133703, "layerzero_eid": 49003}
],
"attempts": [
{"attempt_id": "go-runtime-example-000001", "phase": "smoke", "route": "HL", "route_sequence": 0, "switch_count": 1}
],
"payload_schedule": {"minimum_bytes": 32, "size_bucket_count": 4, "size_step_bytes": 32},
"fixed_seed": "xir-go-runtime-example",
"policy_label": "XIR_NATIVE_MULTIHOP_POLICY_V1",
"deployment": "../xir-lab-runtime/deployment.json",
"artifacts_root": "contracts/out",
"protocol_root": "protocol-projects",
"state_path": "../xir-lab-runtime/runner-state.sqlite",
"raw_root": "../xir-lab-runtime/raw",
"timeout_seconds": 600,
"poll_millis": 500,
"gas_limit": 6000000,
"embedded_agents": true,
"finality": {"mode": "rpc-finalized", "timeout_seconds": 120},
"key_environment": {
"runner": "XIR_RUNNER_KEY",
"root_signer": "XIR_ROOT_SIGNER_KEY",
"layerzero_worker": "XIR_LZ_WORKER_KEY",
"hyperlane_validator": "XIR_HL_VALIDATOR_KEY",
"hyperlane_relayer": "XIR_HL_RELAYER_KEY"
}
}
97 changes: 97 additions & 0 deletions docs/go-runtime.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Go runtime

`go-runtime/` holds an independent implementation of the XIR execution layer.
It was added so the delivery, confirmation, and recovery paths can be reviewed,
tested, and operated without the Python runtime's analysis and publication
machinery, while staying byte compatible with the Solidity contracts and with
the frozen campaign artefacts.

## What was ported

| Python reference | Go implementation |
| --- | --- |
| `src/xir_lab/native/xir_trace.py` | `internal/xir` (typed ids, record/context/receipt hashing, `rid`/`mid`, prefix chain, bundle commitment, tuples) |
| `src/xir_lab/native/multihop_deployer.py` identity helpers | `internal/xir` (`ProfileHash`, `GatewayTypedID`, `AdapterKey`) |
| `src/xir_lab/native/multihop_runner.py` coordinator | `internal/runner` (attempt loop, carriers, transitions, delivery, effect check) |
| `src/xir_lab/native/layerzero.py` | `internal/layerzero` (packet codec, options, DVN instruction, stage encoders) |
| `src/xir_lab/native/layerzero_worker.py` | `internal/layerzero` `Plan` + `internal/runner` LayerZero carrier |
| `src/xir_lab/native/runner.py` and `multihop_runner.py` state | `internal/state` (SQLite ledger with the same tables) |
| `src/xir_lab/native/root_signer.py` | `internal/runner` root signing (`verifyRootCreation`) and `internal/evm` signer |
| `src/xir_lab/native/multihop_deployer.py` deployment | `internal/deploy` |

The Hyperlane delivery role has no Python counterpart: the native campaign used
the upstream Rust validator and relayer binaries, so `internal/hyperlane` and the
embedded relayer in `internal/runner` are new code. They follow the pinned
Hyperlane sources (`toolchain/native-protocol-stack.lock.json` commit
`5857ead81a8783d168d48d370be72de88d5fb230`) and are accepted by the real
`StaticMessageIdMultisigIsm` deployed in the integration test, which is the
binding check for the checkpoint digest, the metadata layout, and the validator
signature.

## What was deliberately not ported

The campaign governance chain stays Python-side: the review gate, phase
authority, validator-volume attestation, resource monitor, preflight digests,
plan materialisation, analysis, and publication. A Go run therefore produces
execution evidence, never a campaign result.

## Parity

`go-runtime/testdata/vectors.json` is generated from the Python modules by
`scripts/generate_go_parity_vectors.py` and pins:

- typed-id encodings and hashes;
- record, context, root id, message id, root prefix, transition hashes;
- receipt hashes, prefix chains, bundle start/step commitments;
- multihop profile hashes, gateway typed ids, adapter keys;
- application payloads and the `NativeMultihopPayload` encoding;
- the full `XIRTypes.Envelope` ABI encoding and the EIP-191 root signature;
- the LayerZero packet fields, DVN instruction (call data, hash, signature) and
the three stage calldatas;
- the Hyperlane evidence hash.

`internal/xir`, `internal/layerzero`, and `internal/hyperlane` assert those bytes
directly, and the runner's on-chain calls are accepted by the same contracts the
Python runner used, which checks the encoding a second time from the other side.

## Verification

```console
$ cd go-runtime
$ go vet ./...
$ go test ./...
```

Unit and parity tests run anywhere. Integration tests deploy the pinned real
Hyperlane and LayerZero contracts onto disposable local Anvil chains. Build the
fixtures from the protocol lock (Git, jq, Foundry, Node 22 and Corepack required):

```console
$ scripts/prepare_go_e2e.sh /tmp/xir-go-e2e-protocols
$ scripts/run_go_e2e.sh /tmp/xir-go-e2e-results.json
```

The preparation script fetches the two locked protocol commits, installs their
Soldeer/Yarn dependencies and builds Solidity artifacts. It does not build Rust
validators or send transactions to remote networks. The test script forces
`XIR_REQUIRE_E2E=1`, disables test caching, retains Go JSON output and rejects any
skip, failure, or missing required recovery test. CI runs both scripts; a skipped
integration test cannot count as success. Plain `go test ./...` still permits
missing-fixture skips for developers; use the script for release verification.

The integration tests assert the destination `NativeMultihopEffectApplied`
event, the receiver's `deliveryCount`, and that each carrier dispatched exactly
once. `TestAnvilRestartResumesWithoutDuplicateDispatch` interrupts an attempt
after its first dispatch with an injected fault, restarts the runtime over the
same ledger, and asserts that the attempt completes while the dispatch count
stays at one.

## Environment boundary

The published measurements come from the frozen five-chain Besu QBFT topology,
which requires at least eight logical CPUs and 20 GB of memory
(`configs/local/topology-multihop-remote-v1.json`). The integration tests use
anvil with the same chain ids and the same contracts because that topology does
not fit on a development host. Anvil chains are test fixtures: they produce no
campaign evidence, and a Go run is never a substitute for a frozen campaign
result.
89 changes: 89 additions & 0 deletions go-runtime/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# XIR Go runtime

`xirlab-go` is an independent implementation of the XIR execution layer. It
creates XIR roots, dispatches every hop through the Hyperlane or LayerZero
carrier adapters, confirms the evidence each adapter recorded, records carrier
switches, delivers the envelope to the destination gateway, and survives a
mid-flight process restart without duplicating a transaction.

The Python runtime in `src/xir_lab/` remains the authority for frozen plans,
preflight gates, analysis, and publication. The Go runtime consumes those frozen
inputs (a deployment document and a list of attempts) and writes the same
durable rows, so the Python analysis tooling can still read a Go-produced run.

## Layout

| Package | Contents |
| --- | --- |
| `internal/xir` | Wire encoding: typed ids, record/context/receipt hashing, `rid`/`mid`/prefix chain, bundle commitments, profile hashes, application payloads, ABI tuple shapes |
| `internal/abiutil` | ABI document parsing, calldata packing, log decoding |
| `internal/state` | Durable SQLite ledger: actions, attempts, stages, stage history, events, errors |
| `internal/evm` | JSON-RPC client, signer, transactor with intent→signed→submitted→receipt durability, finality rule |
| `internal/artifacts` | Forge artifact loading (`out/<Name>.sol/<Name>.json`) |
| `internal/deploy` | Protocol-stack and XIR application deployment, registry and adapter configuration |
| `internal/layerzero` | PacketV1Codec, DVN instruction, DVN/ReceiveUln302/Executor submission plan |
| `internal/hyperlane` | Message format, checkpoint digest, ISM metadata, validator signing, mailbox `process` plan |
| `internal/runner` | Multihop orchestration, carriers, delivery, destination effect checks |
| `internal/lab` | Disposable anvil chains for integration tests |
| `cmd/xirlab-go` | Command line entry point |

## Parity

Every derivation is byte exact with the Solidity contracts and with the Python
runtime. The parity suite is generated from the Python modules:

```console
$ uv run python scripts/generate_go_parity_vectors.py --output go-runtime/testdata/vectors.json
$ cd go-runtime && go test ./...
```

`go-runtime/testdata/vectors.json` pins typed-id encodings, record/context/root
hashes, message ids, prefix chains, bundle commitments, profile hashes, gateway
ids, adapter keys, application payloads, the full envelope ABI encoding, the
EIP-191 root signature, the LayerZero packet and DVN instruction, and the
Hyperlane evidence hash. `internal/xir` and `internal/layerzero` compare against
those bytes; a mismatch is a defect, never an approximation.

## Running

The runtime reads one JSON configuration document
(`schemas/xir-go-runtime-config-v1.schema.json`). Private keys never appear in
the document: each entry of `key_environment` names an environment variable that
holds one signing key.

```console
$ export XIR_RUNNER_KEY=0x... # runner: createRecord, recorder.record, deliver
$ export XIR_ROOT_SIGNER_KEY=0x... # XIR root certificate signer
$ export XIR_LZ_WORKER_KEY=0x... # LayerZero DVN signer and Executor submitter
$ export XIR_HL_VALIDATOR_KEY=0x... # Hyperlane validator
$ export XIR_HL_RELAYER_KEY=0x... # Hyperlane process() submitter
$ cd go-runtime && go run ./cmd/xirlab-go run --config ../configs/go-runtime/local-hl.json
```

`--config` names the chains, the deployment document, the attempts, and the
private state paths. `state_path` and `raw_root` must live outside the
repository: they hold the durable ledger and the raw signed transactions.

Set `embedded_agents` to `false` when an external LayerZero DVN/Executor or
Hyperlane validator/relayer should perform the delivery; the runtime then only
dispatches and waits for the destination adapter to report the evidence.

`finality.mode` decides when a created root may be signed. `rpc-finalized` (the
default) requires the creation block to reach the finalized height the node
reports, and falls back to the private-QBFT rule when the node does not
implement the tag, which is what the frozen Besu lab does. `confirmations`
requires N successor blocks and a canonical creation block; it exists because
development chains such as anvil pin the finalized tag at genesis. `none`
accepts the mined receipt and is recorded in the ledger as test-only.

## What this runtime is not

- It is not a production bridge, wallet, or relayer service.
- It does not reproduce the campaign governance gates (review gate, phase
authority, validator-volume attestation, resource monitor, preflight digests);
those remain Python-side and a Go run is not a campaign result.
- It has no mainnet path and no public-testnet write path.
- Its integration tests run on disposable anvil chains with the same chain ids
and one-second block period as the frozen five-chain Besu topology, because
that topology needs more CPU and memory than a development host provides. The
frozen lab remains the environment of record for published measurements.
Loading
Loading