8 Commits

Author SHA1 Message Date
Luke Parker
f9453b1ef4 Attempt downgrading docker from .28 to .27 2025-08-09 14:32:14 -04:00
Luke Parker
ed9c1a2d84 Tweak how subtrate-client tests waits to connect to the Monero node 2025-08-09 14:15:53 -04:00
Luke Parker
a6ffeabafc Add dedicated error for when amounts aren't representable within a u64
Fixes the issue where _inputs_ could still overflow u64::MAX and cause a panic.
2025-08-09 14:06:27 -04:00
Luke Parker
64e7fa112b Have docker install set host 2025-08-09 13:34:25 -04:00
Luke Parker
48a65285a1 Add uidmap dependency for rootless Docker 2025-08-09 12:49:56 -04:00
Luke Parker
f390426cae if: runner.os == 'Linux', with single quotes 2025-08-09 12:38:58 -04:00
Luke Parker
5dcc500b44 Attempt using rootless Docker in CI via the setup-docker-action
Restores using ubuntu-latest.

Basically, at some point in the last year the existing Docker e2e tests started
failing. I'm unclear if this is an issue with the OS, the docker packages, or
what. This just tries to find a solution.
2025-08-09 12:30:37 -04:00
Luke Parker
8df56dca83 Normalize FROM AS casing in Dockerfiles 2025-08-09 12:20:58 -04:00
19 changed files with 102 additions and 47 deletions

View File

@@ -13,9 +13,6 @@ runs:
sudo apt remove -y --allow-remove-essential -f shim-signed sudo apt remove -y --allow-remove-essential -f shim-signed
# This command would fail, due to shim-signed having unmet dependencies, hence its removal # This command would fail, due to shim-signed having unmet dependencies, hence its removal
sudo apt remove -y "*qemu*" "*sql*" "*texinfo*" "*imagemagick*" sudo apt remove -y "*qemu*" "*sql*" "*texinfo*" "*imagemagick*"
sudo apt autoremove -y
sudo apt clean
docker system prune -a --volumes
if: runner.os == 'Linux' if: runner.os == 'Linux'
- name: Remove unused packages - name: Remove unused packages
@@ -47,5 +44,31 @@ runs:
svm install 0.8.25 svm install 0.8.25
svm use 0.8.25 svm use 0.8.25
- name: Remove preinstalled Docker
shell: bash
run: |
docker system prune -a --volumes
sudo apt remove -y *docker*
# Install uidmap which will be required for the explicitly installed Docker
sudo apt install uidmap
if: runner.os == 'Linux'
- name: Update system dependencies
shell: bash
run: |
sudo apt update -y
sudo apt upgrade -y
sudo apt autoremove -y
sudo apt clean
if: runner.os == 'Linux'
- name: Install rootless Docker
uses: docker/setup-docker-action@b60f85385d03ac8acfca6d9996982511d8620a19
with:
rootless: true
set-host: true
version: type=image,tag=27.5.1
if: runner.os == 'Linux'
# - name: Cache Rust # - name: Cache Rust
# uses: Swatinem/rust-cache@a95ba195448af2da9b00fb742d14ffaaf3c21f43 # uses: Swatinem/rust-cache@a95ba195448af2da9b00fb742d14ffaaf3c21f43

View File

@@ -29,7 +29,7 @@ on:
jobs: jobs:
build: build:
runs-on: ubuntu-22.04 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac

View File

@@ -11,7 +11,7 @@ on:
jobs: jobs:
build: build:
runs-on: ubuntu-22.04 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac

View File

@@ -25,7 +25,7 @@ on:
jobs: jobs:
build: build:
runs-on: ubuntu-22.04 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac

View File

@@ -18,7 +18,7 @@ on:
jobs: jobs:
# Only run these once since they will be consistent regardless of any node # Only run these once since they will be consistent regardless of any node
unit-tests: unit-tests:
runs-on: ubuntu-22.04 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
@@ -43,7 +43,7 @@ jobs:
# Doesn't run unit tests with features as the tests workflow will # Doesn't run unit tests with features as the tests workflow will
integration-tests: integration-tests:
runs-on: ubuntu-22.04 runs-on: ubuntu-latest
# Test against all supported protocol versions # Test against all supported protocol versions
strategy: strategy:
matrix: matrix:

View File

@@ -19,7 +19,7 @@ on:
jobs: jobs:
test-networks: test-networks:
runs-on: ubuntu-22.04 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac

View File

@@ -29,7 +29,7 @@ on:
jobs: jobs:
build: build:
runs-on: ubuntu-22.04 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac

View File

@@ -25,7 +25,7 @@ on:
jobs: jobs:
build: build:
runs-on: ubuntu-22.04 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac

View File

@@ -27,7 +27,7 @@ on:
jobs: jobs:
test-infra: test-infra:
runs-on: ubuntu-22.04 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
@@ -47,7 +47,7 @@ jobs:
-p serai-docker-tests -p serai-docker-tests
test-substrate: test-substrate:
runs-on: ubuntu-22.04 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac
@@ -77,7 +77,7 @@ jobs:
-p serai-node -p serai-node
test-serai-client: test-serai-client:
runs-on: ubuntu-22.04 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac

View File

@@ -177,6 +177,17 @@ pub enum SendError {
/// The created transaction was too large. /// The created transaction was too large.
#[cfg_attr(feature = "std", error("too large of a transaction"))] #[cfg_attr(feature = "std", error("too large of a transaction"))]
TooLargeTransaction, TooLargeTransaction,
/// The transactions' amounts could not be represented within a `u64`.
#[cfg_attr(
feature = "std",
error("transaction amounts exceed u64::MAX (in {in_amount}, out {out_amount})")
)]
AmountsUnrepresentable {
/// The amount in (via inputs).
in_amount: u128,
/// The amount which would be out (between outputs and the fee).
out_amount: u128,
},
/// This transaction could not pay for itself. /// This transaction could not pay for itself.
#[cfg_attr( #[cfg_attr(
feature = "std", feature = "std",
@@ -300,23 +311,34 @@ impl SignableTransaction {
} }
// Make sure we have enough funds // Make sure we have enough funds
let in_amount = self.inputs.iter().map(|input| input.commitment().amount).sum::<u64>(); let weight;
let payments_amount = self {
.payments let in_amount: u128 =
.iter() self.inputs.iter().map(|input| u128::from(input.commitment().amount)).sum();
.filter_map(|payment| match payment { let payments_amount: u128 = self
InternalPayment::Payment(_, amount) => Some(*amount), .payments
InternalPayment::Change(_) => None, .iter()
}) .filter_map(|payment| match payment {
.try_fold(0, u64::checked_add); InternalPayment::Payment(_, amount) => Some(u128::from(*amount)),
let payments_amount = payments_amount.ok_or(SendError::TooManyOutputs)?; InternalPayment::Change(_) => None,
let (weight, necessary_fee) = self.weight_and_necessary_fee(); })
if payments_amount.checked_add(necessary_fee).is_none_or(|total_out| in_amount < total_out) { .sum();
Err(SendError::NotEnoughFunds { let necessary_fee;
inputs: in_amount, (weight, necessary_fee) = self.weight_and_necessary_fee();
outputs: payments_amount, let out_amount = payments_amount + u128::from(necessary_fee);
necessary_fee: Some(necessary_fee), let in_out_amount = u64::try_from(in_amount)
})?; .and_then(|in_amount| u64::try_from(out_amount).map(|out_amount| (in_amount, out_amount)));
let Ok((in_amount, out_amount)) = in_out_amount else {
Err(SendError::AmountsUnrepresentable { in_amount, out_amount })?
};
if in_amount < out_amount {
Err(SendError::NotEnoughFunds {
inputs: in_amount,
outputs: u64::try_from(payments_amount)
.expect("total out fit within u64 but not part of total out"),
necessary_fee: Some(necessary_fee),
})?;
}
} }
// The limit is half the no-penalty block size // The limit is half the no-penalty block size

View File

@@ -92,7 +92,7 @@ fn os(os: Os, additional_root: &str, user: &str) -> String {
match os { match os {
Os::Alpine => format!( Os::Alpine => format!(
r#" r#"
FROM alpine:latest as image FROM alpine:latest AS image
COPY --from=mimalloc-alpine libmimalloc.so /usr/lib COPY --from=mimalloc-alpine libmimalloc.so /usr/lib
ENV LD_PRELOAD=libmimalloc.so ENV LD_PRELOAD=libmimalloc.so
@@ -117,7 +117,7 @@ WORKDIR /home/{user}
Os::Debian => format!( Os::Debian => format!(
r#" r#"
FROM debian:bookworm-slim as image FROM debian:bookworm-slim AS image
COPY --from=mimalloc-debian libmimalloc.so /usr/lib COPY --from=mimalloc-debian libmimalloc.so /usr/lib
RUN echo "/usr/lib/libmimalloc.so" >> /etc/ld.so.preload RUN echo "/usr/lib/libmimalloc.so" >> /etc/ld.so.preload
@@ -146,7 +146,7 @@ fn build_serai_service(prelude: &str, release: bool, features: &str, package: &s
format!( format!(
r#" r#"
FROM rust:1.89-slim-bookworm as builder FROM rust:1.89-slim-bookworm AS builder
COPY --from=mimalloc-debian libmimalloc.so /usr/lib COPY --from=mimalloc-debian libmimalloc.so /usr/lib
RUN echo "/usr/lib/libmimalloc.so" >> /etc/ld.so.preload RUN echo "/usr/lib/libmimalloc.so" >> /etc/ld.so.preload

View File

@@ -2,7 +2,7 @@ use crate::Os;
pub fn mimalloc(os: Os) -> &'static str { pub fn mimalloc(os: Os) -> &'static str {
const ALPINE_MIMALLOC: &str = r#" const ALPINE_MIMALLOC: &str = r#"
FROM alpine:latest as mimalloc-alpine FROM alpine:latest AS mimalloc-alpine
RUN apk update && apk upgrade && apk --no-cache add gcc g++ libc-dev make cmake git RUN apk update && apk upgrade && apk --no-cache add gcc g++ libc-dev make cmake git
RUN git clone https://github.com/microsoft/mimalloc && \ RUN git clone https://github.com/microsoft/mimalloc && \
@@ -16,7 +16,7 @@ RUN git clone https://github.com/microsoft/mimalloc && \
"#; "#;
const DEBIAN_MIMALLOC: &str = r#" const DEBIAN_MIMALLOC: &str = r#"
FROM debian:bookworm-slim as mimalloc-debian FROM debian:bookworm-slim AS mimalloc-debian
RUN apt update && apt upgrade -y && apt install -y gcc g++ make cmake git RUN apt update && apt upgrade -y && apt install -y gcc g++ make cmake git
RUN git clone https://github.com/microsoft/mimalloc && \ RUN git clone https://github.com/microsoft/mimalloc && \

View File

@@ -5,7 +5,7 @@ use crate::{Network, Os, mimalloc, os, write_dockerfile};
pub fn bitcoin(orchestration_path: &Path, network: Network) { pub fn bitcoin(orchestration_path: &Path, network: Network) {
#[rustfmt::skip] #[rustfmt::skip]
const DOWNLOAD_BITCOIN: &str = r#" const DOWNLOAD_BITCOIN: &str = r#"
FROM alpine:latest as bitcoin FROM alpine:latest AS bitcoin
ENV BITCOIN_VERSION=27.1 ENV BITCOIN_VERSION=27.1

View File

@@ -5,7 +5,7 @@ pub fn lighthouse(network: Network) -> (String, String, String) {
#[rustfmt::skip] #[rustfmt::skip]
const DOWNLOAD_LIGHTHOUSE: &str = r#" const DOWNLOAD_LIGHTHOUSE: &str = r#"
FROM alpine:latest as lighthouse FROM alpine:latest AS lighthouse
ENV LIGHTHOUSE_VERSION=5.1.3 ENV LIGHTHOUSE_VERSION=5.1.3

View File

@@ -20,7 +20,7 @@ pub fn nimbus(network: Network) -> (String, String, String) {
#[rustfmt::skip] #[rustfmt::skip]
let download_nimbus = format!(r#" let download_nimbus = format!(r#"
FROM alpine:latest as nimbus FROM alpine:latest AS nimbus
ENV NIMBUS_VERSION=24.3.0 ENV NIMBUS_VERSION=24.3.0
ENV NIMBUS_COMMIT=dc19b082 ENV NIMBUS_COMMIT=dc19b082

View File

@@ -5,7 +5,7 @@ pub fn reth(network: Network) -> (String, String, String) {
#[rustfmt::skip] #[rustfmt::skip]
const DOWNLOAD_RETH: &str = r#" const DOWNLOAD_RETH: &str = r#"
FROM alpine:latest as reth FROM alpine:latest AS reth
ENV RETH_VERSION=0.2.0-beta.6 ENV RETH_VERSION=0.2.0-beta.6

View File

@@ -22,7 +22,7 @@ fn monero_internal(
#[rustfmt::skip] #[rustfmt::skip]
let download_monero = format!(r#" let download_monero = format!(r#"
FROM alpine:latest as monero FROM alpine:latest AS monero
RUN apk --no-cache add gnupg RUN apk --no-cache add gnupg

View File

@@ -390,6 +390,8 @@ impl Monero {
MakeSignableTransactionResult::SignableTransaction(signable) MakeSignableTransactionResult::SignableTransaction(signable)
} }
})), })),
// AmountsUnrepresentable is unreachable on Monero without 100% of the supply before tail
// emission or fundamental corruption
Err(e) => match e { Err(e) => match e {
SendError::UnsupportedRctType => { SendError::UnsupportedRctType => {
panic!("trying to use an RctType unsupported by monero-wallet") panic!("trying to use an RctType unsupported by monero-wallet")
@@ -398,6 +400,7 @@ impl Monero {
SendError::InvalidDecoyQuantity | SendError::InvalidDecoyQuantity |
SendError::NoOutputs | SendError::NoOutputs |
SendError::TooManyOutputs | SendError::TooManyOutputs |
SendError::AmountsUnrepresentable { .. } |
SendError::NoChange | SendError::NoChange |
SendError::TooMuchArbitraryData | SendError::TooMuchArbitraryData |
SendError::TooLargeTransaction | SendError::TooLargeTransaction |

View File

@@ -49,17 +49,24 @@ macro_rules! serai_test {
test.provide_container(composition); test.provide_container(composition);
test.run_async(|ops| async move { test.run_async(|ops| async move {
// Sleep until the Substrate RPC starts // Sleep until the Substrate RPC starts
let serai_rpc = ops.handle(handle).host_port(9944).unwrap(); let mut ticks = 0;
let serai_rpc = format!("http://{}:{}", serai_rpc.0, serai_rpc.1); let serai_rpc = loop {
// Bound execution to 60 seconds // Bound execution to 60 seconds
for _ in 0 .. 60 { if ticks > 60 {
panic!("Serai node didn't start within 60 seconds");
}
tokio::time::sleep(core::time::Duration::from_secs(1)).await; tokio::time::sleep(core::time::Duration::from_secs(1)).await;
ticks += 1;
let Ok(serai_rpc) = ops.handle(handle).host_port(9944) else { continue };
let serai_rpc = format!("http://{}:{}", serai_rpc.0, serai_rpc.1);
let Ok(client) = Serai::new(serai_rpc.clone()).await else { continue }; let Ok(client) = Serai::new(serai_rpc.clone()).await else { continue };
if client.latest_finalized_block_hash().await.is_err() { if client.latest_finalized_block_hash().await.is_err() {
continue; continue;
} }
break; break serai_rpc;
} };
#[allow(clippy::redundant_closure_call)] #[allow(clippy::redundant_closure_call)]
$test(Serai::new(serai_rpc).await.unwrap()).await; $test(Serai::new(serai_rpc).await.unwrap()).await;
}).await; }).await;