Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
717a90c
feat(p2p): detect and track announce IP changes at runtime
spypsy Apr 16, 2026
7453562
public IP services as config
spypsy Apr 20, 2026
79e59cd
Merge branch 'merge-train/spartan' into spy/p2p-ip-refresh-2
spypsy Apr 29, 2026
ed3d577
fix deploy-next-net
spypsy Apr 29, 2026
9930ab1
fix(test): warp L1 forward when proposer scan hits EpochNotStable (#2…
spalladino May 6, 2026
355db43
test(e2e): fail epochs tests on proposer-rollup-check-failed (#22965)
spalladino May 6, 2026
4a99638
fix: grafana switch to aztec_status="proposed" (#22978)
AztecBot May 6, 2026
3601722
chore: update benchmark scraper (#22984)
alexghr May 6, 2026
5c006cf
Merge branch 'next' into merge-train/spartan
May 6, 2026
4b563a6
test(e2e): migrate simple epoch tests to pipelining (#22973)
spalladino May 6, 2026
5f8441e
chore: remove top-level yarn.lock (#22987)
spalladino May 6, 2026
7f38437
refactor(archiver)!: unify L2BlockSource checkpoint lookups via query…
spalladino May 6, 2026
8d06bdc
Merge branch 'next' into merge-train/spartan
May 6, 2026
3b04a40
fix(sequencer): bounded sweep instead of event scan for governance pr…
spalladino May 6, 2026
4481bad
Merge branch 'next' into merge-train/spartan
May 6, 2026
4d36901
Merge branch 'next' into merge-train/spartan
May 6, 2026
fbcd19f
Merge branch 'next' into merge-train/spartan
May 6, 2026
1d01c59
fix(docs): allow webapp-tutorial yarn install to populate empty lockf…
AztecBot May 6, 2026
b9a3eb5
Merge branch 'next' into merge-train/spartan
May 7, 2026
c4e1914
test(e2e): enable pipelining in l1-reorgs and mbps redistribution tes…
spalladino May 7, 2026
c763be7
fix(archiver): restore pending block height metric under pipelining (…
spalladino May 7, 2026
9ac1c29
Merge branch 'next' into merge-train/spartan
May 7, 2026
81e310c
Merge branch 'next' into merge-train/spartan
May 7, 2026
edd4560
chore(p2p): remove skipped validation result option (#23034)
fcarreiro May 7, 2026
47c0755
refactor(p2p)!: remove slow tx collection flow (#22878)
fcarreiro May 7, 2026
2c38f05
Merge branch 'next' into merge-train/spartan
May 7, 2026
9fa020f
Merge branch 'next' into merge-train/spartan
May 7, 2026
22b578d
chore(spartan): add next-net-clone environment config (#22995)
PhilWindle May 7, 2026
7a24d99
Merge branch 'next' into merge-train/spartan
May 7, 2026
35e1c62
Merge branch 'next' into merge-train/spartan
May 7, 2026
4cb448a
Merge branch 'next' into merge-train/spartan
May 7, 2026
f9fc2d2
Merge branch 'next' into merge-train/spartan
May 7, 2026
d91c7a3
Merge branch 'next' into merge-train/spartan
May 7, 2026
0b96c13
Merge branch 'next' into merge-train/spartan
May 7, 2026
1e94ab8
Merge branch 'next' into merge-train/spartan
May 8, 2026
7dee5cd
Merge branch 'next' into merge-train/spartan
May 8, 2026
e62bd70
chore(sequencer): add context to proposer-rollup-check-failed logs (#…
spalladino May 8, 2026
9d2cf9f
test(e2e): wait for archiver sync before asserting pipelining (#22997)
spalladino May 8, 2026
e0899d6
refactor(node-rpc)!: remove deprecated AztecNode methods and L2BlockS…
spalladino May 8, 2026
b6bce5c
feat(p2p): detect and track announce IP changes at runtime (#22405)
AztecBot May 8, 2026
7513e78
test: mark tx_stats_bench 10 TPS as flake-retryable on merge-train/sp…
AztecBot May 8, 2026
5dc3270
fix(sequencer): bind vote-only multicalls to target slot under pipeli…
spalladino May 8, 2026
d14b8a0
feat(sequencer): build optimistically across pruning epoch boundary (…
spalladino May 8, 2026
4284d0b
Merge branch 'next' into merge-train/spartan
May 8, 2026
a358573
fix(sequencer): use chainTipsOverride.pending for log context (#23098)
spalladino May 8, 2026
354bf11
test(e2e): relax post-boundary slot assertion in epochs_proof_at_boun…
AztecBot May 8, 2026
4735e42
fix(archiver): move L2 tips cache refresh out of write transactions
spalladino May 8, 2026
6716d66
Revert "fix(archiver): move L2 tips cache refresh out of write transa…
spalladino May 8, 2026
6338c3f
fix(bb-prover): pool long-lived bb verifier processes instead of spaw…
AztecBot May 11, 2026
1db868c
fix(sequencer): anchor fee asset price modifier to predicted parent (…
spalladino May 11, 2026
7ece286
chore: error log when L1 head timestamp drifts (#22947)
spypsy May 11, 2026
8447782
fix(sequencer): override full parent checkpoint cell in pipelined sim…
spalladino May 11, 2026
cc2e612
test(e2e): enable pipelining on missed l1 slot test (#23068)
spalladino May 11, 2026
7c9e3d5
fix: more robust metrics reporting in IRM monitor (#23038)
spypsy May 11, 2026
7e6f438
fix: preserve LMDB slashing protection (#23145)
spypsy May 11, 2026
61a23c9
test(e2e): enable pipelining on p2p tests (#23070)
spalladino May 11, 2026
4d8791a
fix(archiver): move L2 tips cache refresh out of write transactions (…
spalladino May 11, 2026
30640df
test(e2e): fix data_withholding_slash flake by freezing L1 across res…
spalladino May 11, 2026
02265b1
fix(validator): include proposed checkpoint out-hashes when validatin…
spalladino May 11, 2026
cf63324
refactor(config): drop nested config option, flatten l1Contracts (#23…
spypsy May 12, 2026
371e11d
test(e2e): bump bash TIMEOUT for e2e_p2p/add_rollup to match jest 20m…
AztecBot May 12, 2026
b9f7b33
fix(p2p): chunk archive of mined txs on block finalization (A-969) (#…
PhilWindle May 12, 2026
26d9543
fix(p2p): stream tx pool hydration to bound startup memory (A-968) (#…
PhilWindle May 12, 2026
f0b537d
Merge remote-tracking branch 'origin/next' into merge-train/spartan
AztecBot May 12, 2026
dd65b1a
ci: daily merge-train/spartan stale-PR notifier (proposal)
AztecBot May 12, 2026
15dfe0d
chore: remove orphan --archiver flag usages from start invocations (#…
spalladino May 12, 2026
d34256d
move to github
spalladino May 12, 2026
c68c119
feat(ci): daily merge-train/spartan stale-PR notifier (#23189)
spalladino May 12, 2026
2a4793f
fix: preserve contract artifact permissions (#23174)
spypsy May 12, 2026
a4165bc
Merge branch 'next' into merge-train/spartan
May 12, 2026
01f196a
Merge branch 'next' into merge-train/spartan
May 12, 2026
a4433c2
fix(ci3): accept slashes in /list/<path:key> for merge-train hi…
AztecBot May 12, 2026
73824c5
feat(ci): route merge-train/spartan flake notifications to #team-alph…
AztecBot May 12, 2026
6b7d279
fix(cheat-codes): wait for post-warp L2 block in warpL2TimeAtLeastTo …
AztecBot May 13, 2026
cef95ec
feat: slash attesters signing over bad checkpoints (#23180)
alexghr May 13, 2026
19ec16d
refactor(prover-client): split orchestrator into sub-tree + top-tree …
PhilWindle May 13, 2026
6e4f894
Merge remote-tracking branch 'origin/next' into merge-train/spartan
PhilWindle May 13, 2026
54ef735
Merge branch 'next' into merge-train/spartan
May 13, 2026
ca33e3c
fix(srs): retry transient CRS HTTP downloads with exponential backoff…
AztecBot May 13, 2026
7517999
refactor(p2p): remove old reqresp mode (#23158)
fcarreiro May 13, 2026
69bbcde
Merge branch 'next' into merge-train/spartan
May 13, 2026
99e99fd
docs(sequencer-client): rewrite top-level and timing READMEs (#23149)
spalladino May 13, 2026
1e7eb72
fix(aztec-node): include upcoming checkpoint's L1 to L2 messages in s…
spalladino May 13, 2026
ae3bfc4
Merge branch 'next' into merge-train/spartan
May 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 2 additions & 2 deletions .github/workflows/deploy-irm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ jobs:
CLUSTER_NAME: ${{ inputs.cluster }}
GKE_CLUSTER_CONTEXT: "gke_testnet-440309_us-west1-a_${{ inputs.cluster }}"
REGION: us-west1-a
INFURA_SECRET_NAME: infura-${{ inputs.l1_network }}-url
ETHEREUM_HOSTS_SECRET_NAME: irm-ethereum-hosts-${{ inputs.l1_network }}

runs-on: ubuntu-latest
steps:
Expand Down Expand Up @@ -150,4 +150,4 @@ jobs:
echo "L1 network: ${{ inputs.l1_network }}"
echo "Image tag: ${IMAGE_TAG}"

./spartan/metrics/irm-monitor/scripts/update-monitoring.sh $NAMESPACE $MONITORING_NAMESPACE ${{ inputs.network }} $INFURA_SECRET_NAME
./spartan/metrics/irm-monitor/scripts/update-monitoring.sh $NAMESPACE $MONITORING_NAMESPACE ${{ inputs.network }} $ETHEREUM_HOSTS_SECRET_NAME
65 changes: 41 additions & 24 deletions .github/workflows/deploy-network.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ on:
required: true
type: string
semver:
description: "Semver version (e.g., 2.3.4)"
required: true
description: "Semver version (e.g., 2.3.4). Used to construct docker image if aztec_docker_image is not set."
required: false
type: string
docker_image_tag:
description: "Full docker image tag (optional, defaults to semver)"
aztec_docker_image:
description: "Full Aztec docker image (e.g., aztecprotocol/aztec:2.3.4). If not set, constructed from semver."
required: false
type: string
ref:
Expand Down Expand Up @@ -50,11 +50,11 @@ on:
- testnet
- mainnet
semver:
description: "Semver version (e.g., 2.3.4)"
required: true
description: "Semver version (e.g., 2.3.4). Used to construct docker image if aztec_docker_image is not set."
required: false
type: string
docker_image_tag:
description: "Full docker image tag (optional, defaults to semver)"
aztec_docker_image:
description: "Full Aztec docker image (e.g., aztecprotocol/aztec:2.3.4). If not set, constructed from semver."
required: false
type: string
namespace:
Expand All @@ -76,7 +76,7 @@ on:
type: string

concurrency:
group: deploy-network-${{ inputs.network }}-${{ inputs.namespace || inputs.network }}-${{ inputs.semver }}-${{ github.ref || github.ref_name }}
group: deploy-network-${{ inputs.network }}-${{ inputs.namespace || inputs.network }}-${{ inputs.aztec_docker_image || inputs.semver }}-${{ github.ref || github.ref_name }}
cancel-in-progress: true

jobs:
Expand Down Expand Up @@ -120,16 +120,33 @@ jobs:
exit 1
fi

# Validate semver format
if ! echo "${{ inputs.semver }}" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+(-.*)?$'; then
echo "Error: Invalid semver format '${{ inputs.semver }}'. Expected format: X.Y.Z or X.Y.Z-suffix"
# Require at least one of aztec_docker_image or semver
if [[ -z "${{ inputs.aztec_docker_image }}" && -z "${{ inputs.semver }}" ]]; then
echo "Error: Either 'aztec_docker_image' or 'semver' must be provided"
exit 1
fi

# Extract major version for v2 check
major_version="${{ inputs.semver }}"
major_version="${major_version%%.*}"
echo "MAJOR_VERSION=$major_version" >> $GITHUB_ENV
# Validate semver format if provided
if [[ -n "${{ inputs.semver }}" ]]; then
if ! echo "${{ inputs.semver }}" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+(-.*)?$'; then
echo "Error: Invalid semver format '${{ inputs.semver }}'. Expected format: X.Y.Z or X.Y.Z-suffix"
exit 1
fi
fi

# Resolve the docker image
if [[ -n "${{ inputs.aztec_docker_image }}" ]]; then
AZTEC_DOCKER_IMAGE="${{ inputs.aztec_docker_image }}"
else
AZTEC_DOCKER_IMAGE="aztecprotocol/aztec:${{ inputs.semver }}"
fi
echo "AZTEC_DOCKER_IMAGE=$AZTEC_DOCKER_IMAGE" >> $GITHUB_ENV

# Only use the separate prover-agent image for official semver builds;
# for custom images, let the deploy script fall back to AZTEC_DOCKER_IMAGE
if [[ -n "${{ inputs.semver }}" ]]; then
echo "PROVER_AGENT_DOCKER_IMAGE=aztecprotocol/aztec-prover-agent:${{ inputs.semver }}" >> $GITHUB_ENV
fi

- name: Store the GCP key in a file
env:
Expand Down Expand Up @@ -174,12 +191,12 @@ jobs:
RUN_ID: ${{ github.run_id }}
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
GOOGLE_APPLICATION_CREDENTIALS: ${{ env.GOOGLE_APPLICATION_CREDENTIALS }}
REF_NAME: "v${{ inputs.semver }}"
REF_NAME: ${{ inputs.semver && format('v{0}', inputs.semver) || '' }}
GCP_PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }}
NAMESPACE: ${{ inputs.namespace }}
AZTEC_DOCKER_IMAGE: "aztecprotocol/aztec:${{ inputs.docker_image_tag || inputs.semver }}"
AZTEC_DOCKER_IMAGE: ${{ env.AZTEC_DOCKER_IMAGE }}
CREATE_ROLLUP_CONTRACTS: ${{ inputs.deploy_contracts == true && 'true' || '' }}
PROVER_AGENT_DOCKER_IMAGE: "aztecprotocol/aztec-prover-agent:${{ inputs.docker_image_tag || inputs.semver }}"
PROVER_AGENT_DOCKER_IMAGE: ${{ env.PROVER_AGENT_DOCKER_IMAGE || env.AZTEC_DOCKER_IMAGE }}
VALIDATOR_HA_DOCKER_IMAGE: ${{ inputs.ha_docker_image || '' }}
run: |
echo "Deploying network: ${{ inputs.network }}"
Expand Down Expand Up @@ -209,7 +226,7 @@ jobs:
echo "| Item | Value |"
echo "|------|-------|"
echo "| Network | \`${{ inputs.network }}\` |"
echo "| Semver | \`${{ inputs.semver }}\` |"
echo "| Docker Image | \`${{ env.AZTEC_DOCKER_IMAGE }}\` |"
echo "| Ref | \`${{ steps.checkout-ref.outputs.ref }}\` |"
if [[ -n "${{ inputs.source_tag }}" ]]; then
echo "| Source Tag | [\`${{ inputs.source_tag }}\`](https://github.com/${{ github.repository }}/releases/tag/${{ inputs.source_tag }}) |"
Expand All @@ -229,7 +246,7 @@ jobs:

CHANNEL="#alerts-${{ inputs.network }}"
RUN_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
TEXT="Deploy Network workflow FAILED for *${{ inputs.network }}* (version ${{ inputs.semver }}): <${RUN_URL}|View Run> (🤖)"
TEXT="Deploy Network workflow FAILED for *${{ inputs.network }}* (image ${{ env.AZTEC_DOCKER_IMAGE }}): <${RUN_URL}|View Run> (🤖)"

# Post to Slack and capture timestamp for permalink
RESP=$(curl -sS -X POST https://slack.com/api/chat.postMessage \
Expand All @@ -247,11 +264,11 @@ jobs:
fi

# Dispatch ClaudeBox to investigate the failure
PROMPT="Deployment of ${{ inputs.network }} (version ${{ inputs.semver }}) failed. \
PROMPT="Deployment of ${{ inputs.network }} (image ${{ env.AZTEC_DOCKER_IMAGE }}) failed. \
Follow .claude/claudebox/deploy-investigation.md to investigate. \
GitHub Actions run: ${RUN_URL}. \
Network: ${{ inputs.network }}. Version: ${{ inputs.semver }}. \
Docker image: ${{ inputs.docker_image_tag || inputs.semver }}. \
Network: ${{ inputs.network }}. \
Docker image: ${{ env.AZTEC_DOCKER_IMAGE }}. \
Git ref: ${{ steps.checkout-ref.outputs.ref }}. \
Namespace: ${{ inputs.namespace || inputs.network }}. \
Deploy contracts: ${{ inputs.deploy_contracts }}."
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/deploy-next-net.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ on:
workflow_dispatch:
inputs:
image_tag:
description: 'Docker image tag (e.g., 2.3.4, 3.0.0-nightly.20251004-amd64, or leave empty for latest nightly)'
description: "Docker image tag (e.g., 2.3.4, 3.0.0-nightly.20251004-amd64, or leave empty for latest nightly)"
required: false
type: string

Expand Down Expand Up @@ -67,6 +67,6 @@ jobs:
with:
network: next-net
semver: ${{ needs.get-image-tag.outputs.semver }}
docker_image_tag: ${{ needs.get-image-tag.outputs.tag }}
aztec_docker_image: "aztecprotocol/aztec:${{ needs.get-image-tag.outputs.tag }}"
ref: ${{ github.ref }}
secrets: inherit
22 changes: 22 additions & 0 deletions .github/workflows/merge-train-stale-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: Merge-Train Stale Check

on:
schedule:
# Daily at 09:15 UTC — once per day, off the round-minute mark.
- cron: "15 9 * * *"
workflow_dispatch:

jobs:
spartan:
name: Check merge-train/spartan
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- name: Run stale check
env:
GH_TOKEN: ${{ secrets.AZTEC_BOT_GITHUB_TOKEN }}
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
run: ./ci3/merge_train_stale_check merge-train/spartan '#team-alpha'
8 changes: 8 additions & 0 deletions .test_patterns.yml
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,14 @@ tests:
owners:
- *palla

# tx_stats_bench's 10 TPS sub-test occasionally returns valid:false from a single IVC
# verification under heavy concurrency (8 parallel verifiers, each spawning a fresh bb subprocess
# via the bb.js NativeUnixSocket backend introduced in #21564). The serial sub-tests pass.
- regex: "tx_stats_bench"
error_regex: "tx_stats_bench\\.test\\.ts:[0-9]+:[0-9]+"
owners:
- *charlie

- regex: "src/e2e_token_bridge_tutorial.test.ts"
error_regex: "Error: Unable to find low leaf for block"
owners:
Expand Down
27 changes: 27 additions & 0 deletions barretenberg/cpp/src/barretenberg/bbapi/bbapi_chonk.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,33 @@ ChonkVerify::Response ChonkVerify::execute(const BBApiRequest& /*request*/) &&
return { .valid = verified };
}

ChonkVerifyFromFields::Response ChonkVerifyFromFields::execute(const BBApiRequest& /*request*/) &&
{
BB_BENCH_NAME(MSGPACK_SCHEMA_NAME);

using VerificationKey = Chonk::MegaVerificationKey;
validate_vk_size<VerificationKey>(vk);

auto hiding_kernel_vk = std::make_shared<VerificationKey>(from_buffer<VerificationKey>(vk));

// Validate total field count: must match num_public_inputs + fixed overhead.
const size_t expected_field_count =
static_cast<size_t>(hiding_kernel_vk->num_public_inputs) + ChonkProof::PROOF_LENGTH_WITHOUT_PUB_INPUTS;
if (proof.size() != expected_field_count) {
throw_or_abort("ChonkVerifyFromFields: proof has wrong field count: expected " +
std::to_string(expected_field_count) + ", got " + std::to_string(proof.size()));
}

// Split the flat field array into the structured ChonkProof. Layout knowledge stays here.
auto structured = ChonkProof::from_field_elements(proof);

auto vk_and_hash = std::make_shared<ChonkNativeVerifier::VKAndHash>(hiding_kernel_vk);
ChonkNativeVerifier verifier(vk_and_hash);
const bool verified = verifier.verify(structured);

return { .valid = verified };
}

ChonkBatchVerify::Response ChonkBatchVerify::execute(const BBApiRequest& /*request*/) &&
{
BB_BENCH_NAME(MSGPACK_SCHEMA_NAME);
Expand Down
30 changes: 30 additions & 0 deletions barretenberg/cpp/src/barretenberg/bbapi/bbapi_chonk.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,36 @@ struct ChonkVerify {
bool operator==(const ChonkVerify&) const = default;
};

/**
* @struct ChonkVerifyFromFields
* @brief Verify a Chonk proof passed as a flat field-element array (with public inputs prepended).
*
* The split into structured ChonkProof sub-proofs is done server-side via
* ChonkProof::from_field_elements, so callers do not need to know the per-component sub-proof
* sizes. This is the recommended entry point for TypeScript callers that hold the proof as a
* flat Fr[] (e.g. from tx.chonkProof.attachPublicInputs).
*/
struct ChonkVerifyFromFields {
static constexpr const char MSGPACK_SCHEMA_NAME[] = "ChonkVerifyFromFields";

struct Response {
static constexpr const char MSGPACK_SCHEMA_NAME[] = "ChonkVerifyFromFieldsResponse";

/** @brief True if the proof is valid */
bool valid;
SERIALIZATION_FIELDS(valid);
bool operator==(const Response&) const = default;
};

/** @brief Flat proof field elements with public inputs prepended */
std::vector<bb::fr> proof;
/** @brief The verification key */
std::vector<uint8_t> vk;
Response execute(const BBApiRequest& request = {}) &&;
SERIALIZATION_FIELDS(proof, vk);
bool operator==(const ChonkVerifyFromFields&) const = default;
};

/**
* @struct ChonkComputeVk
* @brief Compute MegaHonk verification key for a circuit to be accumulated in Chonk
Expand Down
2 changes: 2 additions & 0 deletions barretenberg/cpp/src/barretenberg/bbapi/bbapi_execute.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ using Command = NamedUnion<AvmProve,
ChonkAccumulate,
ChonkProve,
ChonkVerify,
ChonkVerifyFromFields,
ChonkBatchVerify,
VkAsFields,
MegaVkAsFields,
Expand Down Expand Up @@ -90,6 +91,7 @@ using CommandResponse = NamedUnion<ErrorResponse,
ChonkAccumulate::Response,
ChonkProve::Response,
ChonkVerify::Response,
ChonkVerifyFromFields::Response,
ChonkBatchVerify::Response,
VkAsFields::Response,
MegaVkAsFields::Response,
Expand Down
57 changes: 46 additions & 11 deletions barretenberg/cpp/src/barretenberg/srs/factories/http_download.hpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#pragma once
#include "barretenberg/common/log.hpp"
#include "barretenberg/common/throw_or_abort.hpp"
#include <cstdint>

Expand All @@ -24,13 +25,19 @@
#pragma clang diagnostic pop
#endif

#include <chrono>
#include <string>
#include <thread>
#include <vector>

namespace bb::srs {

/**
* @brief Download data from a URL with optional Range header support
*
* Retries on transient failures (connection errors, 5xx, 429) with exponential
* backoff so a momentary CDN/network blip doesn't propagate as a hard failure.
*
* @param url Full URL (e.g., "http://crs.aztec-cdn.foundation/g1.dat")
* @param start_byte Starting byte for range request (0 for no range)
* @param end_byte Ending byte for range request (0 for no range)
Expand Down Expand Up @@ -70,20 +77,48 @@ inline std::vector<uint8_t> http_download([[maybe_unused]] const std::string& ur
headers.emplace("Range", "bytes=" + std::to_string(start_byte) + "-" + std::to_string(end_byte));
}

// Download
auto res = cli.Get(path.c_str(), headers);
constexpr int max_attempts = 3;
// Bound retry-induced latency: each retry attempt uses tighter timeouts than the first try
// so the worst-case extra time (backoffs + retry attempts) stays under ~15s.
// Math: backoff 1s + 2s + 2 retries * 5s timeout = 13s.
constexpr int retry_timeout_seconds = 5;

if (!res) {
throw_or_abort("HTTP request failed for " + url + ": " + httplib::to_string(res.error()));
}
std::chrono::milliseconds backoff{ 1000 };
std::string last_error;
for (int attempt = 1; attempt <= max_attempts; ++attempt) {
if (attempt == 2) {
cli.set_connection_timeout(retry_timeout_seconds);
cli.set_read_timeout(retry_timeout_seconds);
}

if (res->status != 200 && res->status != 206) {
throw_or_abort("HTTP request failed for " + url + " with status " + std::to_string(res->status));
}
auto res = cli.Get(path.c_str(), headers);

if (res && (res->status == 200 || res->status == 206)) {
const std::string& body = res->body;
return std::vector<uint8_t>(body.begin(), body.end());
}

// Convert string body to vector<uint8_t>
const std::string& body = res->body;
return std::vector<uint8_t>(body.begin(), body.end());
bool retryable = false;
if (!res) {
last_error = httplib::to_string(res.error());
retryable = true;
} else {
last_error = "status " + std::to_string(res->status);
// Retry on 5xx and 429 (rate limit); other 4xx are client errors and won't change with retry.
retryable = res->status >= 500 || res->status == 429;
}

if (!retryable || attempt == max_attempts) {
throw_or_abort("HTTP request failed for " + url + ": " + last_error + " (after " + std::to_string(attempt) +
" attempt" + (attempt == 1 ? "" : "s") + ")");
}

vinfo("HTTP download of ", url, " failed (", last_error, "), retrying in ", backoff.count(), "ms");
std::this_thread::sleep_for(backoff);
backoff *= 2;
}
// Unreachable: loop above either returns on success or throws on the final attempt.
throw_or_abort("HTTP request failed for " + url + ": " + last_error);
#endif
}
} // namespace bb::srs
Loading
Loading