Skip to content

Commit 29fe1b7

Browse files
authored
Manual Docker Image Build Workflow (#839)
1 parent 66ebe4f commit 29fe1b7

File tree

3 files changed

+265
-1
lines changed

3 files changed

+265
-1
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
---
2+
# yaml-language-server: $schema=https://raw.githubusercontent.com/SchemaStore/schemastore/refs/heads/master/src/schemas/json/github-workflow.json
3+
name: Manual Docker Image Build
4+
5+
on:
6+
workflow_dispatch:
7+
inputs:
8+
build-madara:
9+
description: "Build Madara image"
10+
required: true
11+
type: boolean
12+
default: true
13+
build-orchestrator:
14+
description: "Build Orchestrator image"
15+
required: true
16+
type: boolean
17+
default: true
18+
build-bootstrapper:
19+
description: "Build Bootstrapper image"
20+
required: true
21+
type: boolean
22+
default: true
23+
24+
jobs:
25+
build-and-publish-madara:
26+
if: ${{ inputs.build-madara }}
27+
uses: ./.github/workflows/task-build-manual-and-publish.yml
28+
with:
29+
image-name: madara
30+
image-file: ./madara/Dockerfile
31+
permissions:
32+
contents: read
33+
packages: write
34+
attestations: write
35+
id-token: write
36+
secrets: inherit
37+
38+
build-and-publish-orchestrator:
39+
if: ${{ inputs.build-orchestrator }}
40+
uses: ./.github/workflows/task-build-manual-and-publish.yml
41+
with:
42+
image-name: orchestrator
43+
image-file: ./orchestrator/Dockerfile
44+
permissions:
45+
contents: read
46+
packages: write
47+
attestations: write
48+
id-token: write
49+
secrets: inherit
50+
51+
build-and-publish-bootstrapper:
52+
if: ${{ inputs.build-bootstrapper }}
53+
uses: ./.github/workflows/task-build-manual-and-publish.yml
54+
with:
55+
image-name: bootstrapper
56+
image-file: ./bootstrapper/Dockerfile
57+
permissions:
58+
contents: read
59+
packages: write
60+
attestations: write
61+
id-token: write
62+
secrets: inherit
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
---
2+
# yaml-language-server: $schema=https://raw.githubusercontent.com/SchemaStore/schemastore/refs/heads/master/src/schemas/json/github-workflow.json
3+
name: Task - Build And Publish Manual Docker Image
4+
5+
on:
6+
workflow_call:
7+
inputs:
8+
image-name:
9+
description: Name of the Docker image
10+
required: true
11+
type: string
12+
image-file:
13+
description: Dockerfile used to build the image
14+
required: true
15+
type: string
16+
registry:
17+
description: Container registry domain
18+
required: false
19+
default: ghcr.io
20+
type: string
21+
outputs:
22+
manual-sha:
23+
description: Manual image tag (with commit sha)
24+
value: ${{ jobs.build-manual-and-publish.outputs.manual-sha }}
25+
26+
permissions:
27+
contents: read
28+
packages: write
29+
attestations: write
30+
id-token: write
31+
32+
jobs:
33+
build-manual-and-publish:
34+
runs-on: blacksmith-16vcpu-ubuntu-2204
35+
outputs:
36+
manual-sha: ${{ steps.tag.outputs.manual-sha }}
37+
38+
steps:
39+
- name: Checkout repository
40+
uses: actions/checkout@v4
41+
with:
42+
submodules: recursive
43+
44+
- name: Load env
45+
uses: ./.github/actions/load-env
46+
47+
- name: Rust setup
48+
uses: ./.github/actions/setup-rust
49+
with:
50+
rust-version: ${{ env.BUILD_RUST_VERSION }}
51+
cache-key: madara-${{ runner.os }}-rust-1.89
52+
53+
- name: Python Cairo setup (for apollo_starknet_os_program dependencies)
54+
if: inputs.is-standalone == false
55+
uses: ./.github/actions/setup-python-cairo
56+
with:
57+
python-version: ${{ env.BUILD_PYTHON_VERSION }}
58+
59+
- name: Run make artifacts
60+
run: yes | make artifacts
61+
62+
- name: Run cargo check
63+
run: cargo check
64+
65+
- name: Tags
66+
id: tag
67+
run: |
68+
IMAGE="${{ inputs.registry }}/${{ github.repository_owner }}/${{ inputs.image-name }}"
69+
SHA=$(git rev-parse --short=7 "$GITHUB_SHA")
70+
MANUAL_SHA="$IMAGE:manual-$SHA"
71+
72+
echo "manual-sha=$MANUAL_SHA" >> $GITHUB_OUTPUT
73+
74+
- name: Log in to the Container registry
75+
uses: docker/login-action@v3
76+
with:
77+
registry: ${{ inputs.registry }}
78+
username: ${{ github.actor }}
79+
password: ${{ secrets.GITHUB_TOKEN }}
80+
81+
- name: Build Docker Image and Publish
82+
id: push
83+
uses: docker/build-push-action@v6
84+
with:
85+
context: .
86+
push: true
87+
file: ${{ inputs.image-file }}
88+
tags: ${{ steps.tag.outputs.manual-sha }}

madara/crates/primitives/receipt/src/from_blockifier.rs

Lines changed: 115 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,119 @@ fn recursive_call_info_iter(res: &TransactionExecutionInfo) -> impl Iterator<Ite
7979
.flat_map(|call_info| call_info.iter()) // flatmap over the roots' recursive inner call infos
8080
}
8181

82+
/// Formats the revert error string by removing redundant VM tracebacks.
83+
///
84+
/// The blockifier generates verbose error messages with VM tracebacks at every level
85+
/// of the call stack. This function filters them to show the traceback only once,
86+
/// positioned after the last regular contract call (CallContract) entry point frame
87+
/// and before any library call (LibraryCall) frames or the final error.
88+
///
89+
/// This makes error messages more concise while preserving the most relevant debugging information.
90+
fn format_revert_error(error_string: &str) -> String {
91+
// Check if the original string ends with a newline
92+
let ends_with_newline = error_string.ends_with('\n');
93+
94+
let lines: Vec<&str> = error_string.lines().collect();
95+
let mut output_lines = Vec::new();
96+
let mut i = 0;
97+
98+
while i < lines.len() {
99+
let line = lines[i];
100+
101+
// Check if this is the start of a VM exception frame
102+
// VM exception frames start with "Error at pc=..."
103+
if line.trim().starts_with("Error at pc=") {
104+
// Look ahead to see if there's another "Error in the called contract" entry
105+
// or "Error in a library call" coming up
106+
let mut has_nested_call_contract = false;
107+
let mut j = i + 1;
108+
109+
// Scan forward to find the next entry point frame
110+
while j < lines.len() {
111+
let next_line = lines[j].trim();
112+
113+
// Found the next entry point - check if it's a CallContract or LibraryCall
114+
if next_line.starts_with("Error in the called contract") {
115+
has_nested_call_contract = true;
116+
break;
117+
} else if next_line.starts_with("Error in a library call")
118+
|| next_line.starts_with("Execution failed")
119+
|| next_line.starts_with("Entry point") {
120+
// Next entry is a library call or final error - don't skip this traceback
121+
has_nested_call_contract = false;
122+
break;
123+
}
124+
125+
// Keep scanning through the traceback lines
126+
if next_line.starts_with("Unknown location")
127+
|| next_line.starts_with("Cairo traceback")
128+
|| next_line.is_empty() {
129+
j += 1;
130+
} else {
131+
// Reached a numbered entry like "1:" - check if it contains the patterns
132+
if let Some(colon_pos) = next_line.find(':') {
133+
let after_colon = &next_line[colon_pos + 1..].trim();
134+
if after_colon.starts_with("Error in the called contract") {
135+
has_nested_call_contract = true;
136+
break;
137+
} else if after_colon.starts_with("Error in a library call") {
138+
has_nested_call_contract = false;
139+
break;
140+
}
141+
}
142+
j += 1;
143+
}
144+
}
145+
146+
// Skip the VM traceback if there's a nested CallContract
147+
if has_nested_call_contract {
148+
// Skip this "Error at pc=..." line and all traceback lines until the next entry
149+
while i < lines.len() {
150+
let current_line = lines[i].trim();
151+
i += 1;
152+
153+
// Stop when we hit the next numbered entry or end
154+
if i < lines.len() {
155+
let next_line = lines[i].trim();
156+
if let Some(first_char) = next_line.chars().next() {
157+
if first_char.is_ascii_digit() && next_line.contains(':') {
158+
break;
159+
}
160+
}
161+
}
162+
163+
// Also stop at the end or at lines that look like new entries
164+
if current_line.is_empty() && i < lines.len() {
165+
let peek = lines[i].trim();
166+
if let Some(first_char) = peek.chars().next() {
167+
if first_char.is_ascii_digit() || peek.starts_with("Execution failed") {
168+
break;
169+
}
170+
}
171+
}
172+
}
173+
} else {
174+
// Keep this traceback - it's before a library call or final error
175+
output_lines.push(line);
176+
i += 1;
177+
}
178+
} else {
179+
// Not a VM traceback line - keep it
180+
output_lines.push(line);
181+
i += 1;
182+
}
183+
}
184+
185+
let mut result = output_lines.join("\n");
186+
187+
// Preserve the trailing newline if the original had one
188+
if ends_with_newline {
189+
result.push('\n');
190+
}
191+
192+
result
193+
}
194+
82195
pub fn from_blockifier_execution_info(res: &TransactionExecutionInfo, tx: &Transaction) -> TransactionReceipt {
83196
let price_unit = match blockifier_tx_fee_type(tx) {
84197
FeeType::Eth => PriceUnit::Wei,
@@ -196,7 +309,8 @@ pub fn from_blockifier_execution_info(res: &TransactionExecutionInfo, tx: &Trans
196309
};
197310

198311
let execution_result = if let Some(reason) = &res.revert_error {
199-
ExecutionResult::Reverted { reason: reason.to_string() }
312+
let formatted_reason = format_revert_error(&reason.to_string());
313+
ExecutionResult::Reverted { reason: formatted_reason }
200314
} else {
201315
ExecutionResult::Succeeded
202316
};

0 commit comments

Comments
 (0)