mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +00:00
Add a crate to test the runtime can be reproducibly built
This commit is contained in:
2
.github/workflows/message-queue-tests.yml
vendored
2
.github/workflows/message-queue-tests.yml
vendored
@@ -8,6 +8,7 @@ on:
|
|||||||
- "common/**"
|
- "common/**"
|
||||||
- "crypto/**"
|
- "crypto/**"
|
||||||
- "message-queue/**"
|
- "message-queue/**"
|
||||||
|
- "orchestration/message-queue/**"
|
||||||
- "tests/docker/**"
|
- "tests/docker/**"
|
||||||
- "tests/message-queue/**"
|
- "tests/message-queue/**"
|
||||||
|
|
||||||
@@ -16,6 +17,7 @@ on:
|
|||||||
- "common/**"
|
- "common/**"
|
||||||
- "crypto/**"
|
- "crypto/**"
|
||||||
- "message-queue/**"
|
- "message-queue/**"
|
||||||
|
- "orchestration/message-queue/**"
|
||||||
- "tests/docker/**"
|
- "tests/docker/**"
|
||||||
- "tests/message-queue/**"
|
- "tests/message-queue/**"
|
||||||
|
|
||||||
|
|||||||
4
.github/workflows/processor-tests.yml
vendored
4
.github/workflows/processor-tests.yml
vendored
@@ -9,7 +9,9 @@ on:
|
|||||||
- "crypto/**"
|
- "crypto/**"
|
||||||
- "coins/**"
|
- "coins/**"
|
||||||
- "message-queue/**"
|
- "message-queue/**"
|
||||||
|
- "orchestration/message-queue/**"
|
||||||
- "processor/**"
|
- "processor/**"
|
||||||
|
- "orchestration/processor/**"
|
||||||
- "tests/docker/**"
|
- "tests/docker/**"
|
||||||
- "tests/processor/**"
|
- "tests/processor/**"
|
||||||
|
|
||||||
@@ -19,7 +21,9 @@ on:
|
|||||||
- "crypto/**"
|
- "crypto/**"
|
||||||
- "coins/**"
|
- "coins/**"
|
||||||
- "message-queue/**"
|
- "message-queue/**"
|
||||||
|
- "orchestration/message-queue/**"
|
||||||
- "processor/**"
|
- "processor/**"
|
||||||
|
- "orchestration/processor/**"
|
||||||
- "tests/docker/**"
|
- "tests/docker/**"
|
||||||
- "tests/processor/**"
|
- "tests/processor/**"
|
||||||
|
|
||||||
|
|||||||
38
.github/workflows/reproducible-runtime.yml
vendored
Normal file
38
.github/workflows/reproducible-runtime.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
name: Reproducible Runtime
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- develop
|
||||||
|
paths:
|
||||||
|
- "Cargo.lock"
|
||||||
|
- "common/**"
|
||||||
|
- "crypto/**"
|
||||||
|
- "substrate/**"
|
||||||
|
- "orchestration/runtime/**"
|
||||||
|
- "tests/reproducible-runtime/**"
|
||||||
|
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- "Cargo.lock"
|
||||||
|
- "common/**"
|
||||||
|
- "crypto/**"
|
||||||
|
- "substrate/**"
|
||||||
|
- "orchestration/runtime/**"
|
||||||
|
- "tests/reproducible-runtime/**"
|
||||||
|
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Install Build Dependencies
|
||||||
|
uses: ./.github/actions/build-dependencies
|
||||||
|
with:
|
||||||
|
github-token: ${{ inputs.github-token }}
|
||||||
|
|
||||||
|
- name: Run Reproducible Runtime tests
|
||||||
|
run: cd tests/reproducible-runtime && GITHUB_CI=true cargo test
|
||||||
11
Cargo.lock
generated
11
Cargo.lock
generated
@@ -8865,6 +8865,17 @@ dependencies = [
|
|||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serai-reproducible-runtime"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"dockertest",
|
||||||
|
"hex",
|
||||||
|
"rand_core 0.6.4",
|
||||||
|
"serai-docker-tests",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serai-runtime"
|
name = "serai-runtime"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ members = [
|
|||||||
"tests/docker",
|
"tests/docker",
|
||||||
"tests/message-queue",
|
"tests/message-queue",
|
||||||
"tests/processor",
|
"tests/processor",
|
||||||
|
"tests/reproducible-runtime",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Always compile Monero (and a variety of dependencies) with optimizations due
|
# Always compile Monero (and a variety of dependencies) with optimizations due
|
||||||
|
|||||||
@@ -52,15 +52,10 @@ and signatures.
|
|||||||
* Expose necessary ports.
|
* Expose necessary ports.
|
||||||
* Map necessary volumes.
|
* Map necessary volumes.
|
||||||
|
|
||||||
The best way is to build using `docker compose`. If you'd prefer to build using
|
|
||||||
`docker` directly, each image can be built independently.
|
|
||||||
|
|
||||||
**Example:** `docker build ./coins/bitcoin`
|
|
||||||
|
|
||||||
### Entrypoint
|
### Entrypoint
|
||||||
|
|
||||||
The Serai node and external networks' nodes are each started from an entrypoint
|
The Serai node and external networks' nodes are each started from an entrypoint
|
||||||
script inside the /scripts folder.
|
script inside the `/scripts `folder.
|
||||||
|
|
||||||
To update the scripts on the image you must rebuild the updated images using the
|
To update the scripts on the image you must rebuild the updated images using the
|
||||||
`--build` flag after `up` in `docker compose`.
|
`--build` flag after `up` in `docker compose`.
|
||||||
|
|||||||
@@ -76,7 +76,19 @@ services:
|
|||||||
- "./processor/scripts:/scripts"
|
- "./processor/scripts:/scripts"
|
||||||
entrypoint: /scripts/entry-dev.sh
|
entrypoint: /scripts/entry-dev.sh
|
||||||
|
|
||||||
# Serai services
|
# Serai runtime
|
||||||
|
|
||||||
|
runtime:
|
||||||
|
profiles:
|
||||||
|
- runtime
|
||||||
|
build:
|
||||||
|
context: ../
|
||||||
|
dockerfile: ./orchestration/runtime/Dockerfile
|
||||||
|
entrypoint: |
|
||||||
|
sh -c "cd /serai/substrate/runtime && cargo clean && cargo build --release && \
|
||||||
|
sha256sum /serai/target/release/wbuild/serai-runtime/serai_runtime.wasm"
|
||||||
|
|
||||||
|
# Serai nodes
|
||||||
|
|
||||||
_serai:
|
_serai:
|
||||||
&serai_defaults
|
&serai_defaults
|
||||||
|
|||||||
28
orchestration/runtime/Dockerfile
Normal file
28
orchestration/runtime/Dockerfile
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
FROM rust:1.71.0-slim-bookworm as builder
|
||||||
|
|
||||||
|
# Add files for build
|
||||||
|
ADD common /serai/common
|
||||||
|
ADD crypto /serai/crypto
|
||||||
|
ADD coins /serai/coins
|
||||||
|
ADD message-queue /serai/message-queue
|
||||||
|
ADD processor /serai/processor
|
||||||
|
ADD coordinator /serai/coordinator
|
||||||
|
ADD substrate /serai/substrate
|
||||||
|
ADD tests /serai/tests
|
||||||
|
ADD Cargo.toml /serai
|
||||||
|
ADD Cargo.lock /serai
|
||||||
|
ADD AGPL-3.0 /serai
|
||||||
|
|
||||||
|
WORKDIR /serai
|
||||||
|
|
||||||
|
# Move to a Debian package snapshot
|
||||||
|
RUN rm -rf /etc/apt/sources.list.d/debian.sources && \
|
||||||
|
rm -rf /var/lib/apt/lists/* && \
|
||||||
|
echo "deb [arch=amd64] http://snapshot.debian.org/archive/debian/20230703T000000Z bookworm main" > /etc/apt/sources.list && \
|
||||||
|
apt update
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
RUN apt install clang -y
|
||||||
|
|
||||||
|
# Add the wasm toolchain
|
||||||
|
RUN rustup target add wasm32-unknown-unknown
|
||||||
@@ -26,5 +26,5 @@ sp-core = { git = "https://github.com/serai-dex/substrate", default-features = f
|
|||||||
sp-runtime = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
sp-runtime = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
std = ["lazy_static", "zeroize", "scale/std", "scale-info/std", "serde/std", "sp-core/std", "sp-runtime/std"]
|
std = ["lazy_static", "zeroize", "scale/std", "serde/std", "scale-info/std", "sp-core/std", "sp-runtime/std"]
|
||||||
default = ["std"]
|
default = ["std"]
|
||||||
|
|||||||
@@ -79,6 +79,11 @@ pub fn build(name: String) {
|
|||||||
meta(repo_path.join("message-queue")),
|
meta(repo_path.join("message-queue")),
|
||||||
meta(repo_path.join("processor")),
|
meta(repo_path.join("processor")),
|
||||||
],
|
],
|
||||||
|
"runtime" => vec![
|
||||||
|
meta(repo_path.join("common")),
|
||||||
|
meta(repo_path.join("crypto")),
|
||||||
|
meta(repo_path.join("substrate")),
|
||||||
|
],
|
||||||
_ => panic!("building unrecognized docker image"),
|
_ => panic!("building unrecognized docker image"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
23
tests/reproducible-runtime/Cargo.toml
Normal file
23
tests/reproducible-runtime/Cargo.toml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
[package]
|
||||||
|
name = "serai-reproducible-runtime"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Tests the Serai runtimee can be reproducibly built"
|
||||||
|
license = "AGPL-3.0-only"
|
||||||
|
repository = "https://github.com/serai-dex/serai/tree/develop/tests/reproducible-runtime"
|
||||||
|
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||||
|
keywords = []
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
all-features = true
|
||||||
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
rand_core = "0.6"
|
||||||
|
hex = "0.4"
|
||||||
|
|
||||||
|
dockertest = "0.3"
|
||||||
|
serai-docker-tests = { path = "../docker" }
|
||||||
|
|
||||||
|
tokio = { version = "1", features = ["time"] }
|
||||||
15
tests/reproducible-runtime/LICENSE
Normal file
15
tests/reproducible-runtime/LICENSE
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
AGPL-3.0-only license
|
||||||
|
|
||||||
|
Copyright (c) 2023 Luke Parker
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License Version 3 as
|
||||||
|
published by the Free Software Foundation.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
100
tests/reproducible-runtime/src/lib.rs
Normal file
100
tests/reproducible-runtime/src/lib.rs
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
#[test]
|
||||||
|
pub fn reproducibly_builds() {
|
||||||
|
use std::{collections::HashSet, process::Command};
|
||||||
|
|
||||||
|
use rand_core::{RngCore, OsRng};
|
||||||
|
|
||||||
|
use dockertest::{PullPolicy, Image, Composition, DockerTest};
|
||||||
|
|
||||||
|
const RUNS: usize = 3;
|
||||||
|
const TIMEOUT: u16 = 60 * 60; // 60 minutes
|
||||||
|
|
||||||
|
serai_docker_tests::build("runtime".to_string());
|
||||||
|
|
||||||
|
let mut ids = vec![[0; 8]; RUNS];
|
||||||
|
for id in &mut ids {
|
||||||
|
OsRng.fill_bytes(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut test = DockerTest::new();
|
||||||
|
for id in &ids {
|
||||||
|
test.add_composition(
|
||||||
|
Composition::with_image(
|
||||||
|
Image::with_repository("serai-dev-runtime").pull_policy(PullPolicy::Never),
|
||||||
|
)
|
||||||
|
.with_container_name(format!("runtime-build-{}", hex::encode(id)))
|
||||||
|
.with_cmd(vec![
|
||||||
|
"sh".to_string(),
|
||||||
|
"-c".to_string(),
|
||||||
|
// Sleep for a minute after building to prevent the container from closing before we
|
||||||
|
// retrieve the hash
|
||||||
|
"cd /serai/substrate/runtime && cargo clean && cargo build --release &&
|
||||||
|
printf \"Runtime hash: \" > hash &&
|
||||||
|
sha256sum /serai/target/release/wbuild/serai-runtime/serai_runtime.wasm >> hash &&
|
||||||
|
cat hash &&
|
||||||
|
sleep 60"
|
||||||
|
.to_string(),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
test.run(|_| async {
|
||||||
|
let ids = ids;
|
||||||
|
let mut containers = vec![];
|
||||||
|
for container in String::from_utf8(
|
||||||
|
Command::new("docker").arg("ps").arg("--format").arg("{{.Names}}").output().unwrap().stdout,
|
||||||
|
)
|
||||||
|
.expect("output wasn't utf-8")
|
||||||
|
.lines()
|
||||||
|
{
|
||||||
|
for id in &ids {
|
||||||
|
if container.contains(&hex::encode(id)) {
|
||||||
|
containers.push(container.trim().to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert_eq!(containers.len(), RUNS, "couldn't find all containers");
|
||||||
|
|
||||||
|
let mut res = vec![None; RUNS];
|
||||||
|
'attempt: for _ in 0 .. (TIMEOUT / 10) {
|
||||||
|
tokio::time::sleep(core::time::Duration::from_secs(10)).await;
|
||||||
|
|
||||||
|
'runner: for (i, container) in containers.iter().enumerate() {
|
||||||
|
if res[i].is_some() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let logs = Command::new("docker").arg("logs").arg(container).output().unwrap();
|
||||||
|
let Some(last_log) =
|
||||||
|
std::str::from_utf8(&logs.stdout).expect("output wasn't utf-8").lines().last() else {
|
||||||
|
continue 'runner;
|
||||||
|
};
|
||||||
|
|
||||||
|
let split = last_log.split("Runtime hash: ").collect::<Vec<_>>();
|
||||||
|
if split.len() == 2 {
|
||||||
|
res[i] = Some(split[1].to_string());
|
||||||
|
continue 'runner;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for item in &res {
|
||||||
|
if item.is_none() {
|
||||||
|
continue 'attempt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we didn't get resuts from all runners, panic
|
||||||
|
for item in &res {
|
||||||
|
if item.is_none() {
|
||||||
|
panic!("couldn't get runtime hashes within allowed time");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut identical = HashSet::new();
|
||||||
|
for res in res.clone() {
|
||||||
|
identical.insert(res.unwrap());
|
||||||
|
}
|
||||||
|
assert_eq!(identical.len(), 1, "got different runtime hashes {:?}", res);
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user