Skip to content

Commit 6356d37

Browse files
alenmestrovmiraclx
andauthored
Fix General e2e improvements (#1168)
Co-authored-by: Miraculous Owonubi <omiraculous@gmail.com>
1 parent e68ddb3 commit 6356d37

File tree

10 files changed

+243
-135
lines changed

10 files changed

+243
-135
lines changed

.github/workflows/e2e-tests.yml

Lines changed: 4 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ on:
1212
- '.github/workflows/e2e_tests.yml'
1313

1414
env:
15+
# Or you can use 'prerelease-{PR-NUMBER}'
1516
CALIMERO_CONTRACTS_VERSION: 'latest'
1617

1718
jobs:
@@ -71,41 +72,6 @@ jobs:
7172
run: |
7273
chmod +x target/debug/e2e-tests target/debug/merod target/debug/meroctl
7374
74-
- name: Generate ports
75-
id: generate_ports
76-
run: |
77-
get_random_port() {
78-
python3 -c 'import socket; s = socket.socket(); s.bind(("", 0)); port = s.getsockname()[1]; s.close(); print(port)'
79-
}
80-
81-
SWARM_PORT=$(get_random_port)
82-
SERVER_PORT=$(get_random_port)
83-
ICP_PORT=$(get_random_port)
84-
SWARM_HOST=$(ifconfig | grep 'inet ' | grep -v '127.0.0.1' | awk '{print $2}' | head -n 1)
85-
86-
# Set outputs for other steps
87-
echo "SWARM_PORT=$SWARM_PORT" >> $GITHUB_OUTPUT
88-
echo "SERVER_PORT=$SERVER_PORT" >> $GITHUB_OUTPUT
89-
echo "ICP_PORT=$ICP_PORT" >> $GITHUB_OUTPUT
90-
echo "SWARM_HOST=$SWARM_HOST" >> $GITHUB_OUTPUT
91-
92-
# Update config.json
93-
jq --arg swarmPort "$SWARM_PORT" \
94-
--arg serverPort "$SERVER_PORT" \
95-
--arg swarmHost "$SWARM_HOST" \
96-
'.network.swarmHost = ($swarmHost) |
97-
.network.startSwarmPort = ($swarmPort | tonumber) |
98-
.network.startServerPort = ($serverPort | tonumber)' \
99-
e2e-tests/config/config.json > updated_config.json
100-
101-
mv updated_config.json e2e-tests/config/config.json
102-
103-
# Print ports for debugging
104-
echo "Generated ports:"
105-
echo "SWARM_PORT: $SWARM_PORT"
106-
echo "SERVER_PORT: $SERVER_PORT"
107-
echo "ICP_PORT: $ICP_PORT"
108-
10975
- name: Download Contracts
11076
run: |
11177
echo "Downloading contracts, target version: $CALIMERO_CONTRACTS_VERSION"
@@ -121,7 +87,7 @@ jobs:
12187
- name: Deploy ICP Devnet
12288
if: matrix.protocol == 'icp'
12389
env:
124-
ICP_PORT: ${{ steps.generate_ports.outputs.ICP_PORT }}
90+
ICP_PORT: 4943
12591
run: |
12692
echo "ICP_PORT=$ICP_PORT"
12793
./scripts/icp/deploy-devnet.sh
@@ -133,7 +99,7 @@ jobs:
13399
- name: Deploy Stellar Devnet
134100
if: matrix.protocol == 'stellar'
135101
env:
136-
ICP_PORT: ${{ steps.generate_ports.outputs.ICP_PORT }}
102+
ICP_PORT: 4943
137103
run: |
138104
echo "Installing stellar CLI..."
139105
@@ -196,20 +162,16 @@ jobs:
196162
id: e2e-tests
197163
env:
198164
NO_COLOR: '1'
199-
SWARM_PORT: ${{ steps.generate_ports.outputs.SWARM_PORT }}
200-
SERVER_PORT: ${{ steps.generate_ports.outputs.SERVER_PORT }}
201-
SWARM_HOST: ${{ steps.generate_ports.outputs.SWARM_HOST }}
202165
continue-on-error: true
203166
run: |
204167
echo "Running e2e tests for ${{ matrix.protocol }}"
205-
echo "Using ports: SWARM_PORT=$SWARM_PORT, SERVER_PORT=$SERVER_PORT"
206168
207169
./target/debug/e2e-tests \
208170
--input-dir ./e2e-tests/config \
209171
--output-dir ./e2e-tests/corpus \
210172
--merod-binary ./target/debug/merod \
211173
--meroctl-binary ./target/debug/meroctl \
212-
--scenario ${{ matrix.protocol }}
174+
--scenarios ${{ matrix.protocol }}
213175
214176
- name: Upload Test Artifacts
215177
uses: actions/upload-artifact@v4

Cargo.lock

Lines changed: 16 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/context/config/src/client/env/proxy/query/context_storage_entries.rs

Lines changed: 43 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use std::io::Cursor;
22

3+
use alloy::dyn_abi::{DynSolType, DynSolValue};
34
use alloy_sol_types::SolValue;
45
use candid::{Decode, Encode};
5-
use eyre::OptionExt;
6+
use eyre::eyre;
67
use serde::Serialize;
78
use soroban_sdk::xdr::{Limited, Limits, ReadXdr, ScVal, ToXdr};
89
use soroban_sdk::{Bytes, Env, TryIntoVal};
@@ -38,7 +39,7 @@ impl Method<Near> for ContextStorageEntriesRequest {
3839
fn decode(response: Vec<u8>) -> eyre::Result<Self::Returns> {
3940
// Decode the response as Vec of tuples with boxed slices
4041
let entries: Vec<(Box<[u8]>, Box<[u8]>)> = serde_json::from_slice(&response)
41-
.map_err(|e| eyre::eyre!("Failed to decode response: {}", e))?;
42+
.map_err(|e| eyre!("Failed to decode response: {}", e))?;
4243

4344
// Convert to ContextStorageEntry
4445
Ok(entries
@@ -77,7 +78,7 @@ impl Method<Starknet> for ContextStorageEntriesRequest {
7778
.map(|chunk| {
7879
let chunk_array: [u8; 32] = chunk
7980
.try_into()
80-
.map_err(|e| eyre::eyre!("Failed to convert chunk to array: {}", e))?;
81+
.map_err(|e| eyre!("Failed to convert chunk to array: {}", e))?;
8182
Ok(Felt::from_bytes_be(&chunk_array))
8283
})
8384
.collect::<eyre::Result<Vec<Felt>>>()?;
@@ -95,14 +96,13 @@ impl Method<Icp> for ContextStorageEntriesRequest {
9596

9697
fn encode(self) -> eyre::Result<Vec<u8>> {
9798
// Encode offset and limit using Candid
98-
Encode!(&self.offset, &self.limit)
99-
.map_err(|e| eyre::eyre!("Failed to encode request: {}", e))
99+
Encode!(&self.offset, &self.limit).map_err(|e| eyre!("Failed to encode request: {}", e))
100100
}
101101

102102
fn decode(response: Vec<u8>) -> eyre::Result<Self::Returns> {
103103
// Decode the response as Vec of tuples
104104
let entries: Vec<(Vec<u8>, Vec<u8>)> = Decode!(&response, Vec<(Vec<u8>, Vec<u8>)>)
105-
.map_err(|e| eyre::eyre!("Failed to decode response: {}", e))?;
105+
.map_err(|e| eyre!("Failed to decode response: {}", e))?;
106106

107107
// Convert to ContextStorageEntry
108108
Ok(entries
@@ -113,10 +113,10 @@ impl Method<Icp> for ContextStorageEntriesRequest {
113113
}
114114

115115
impl Method<Stellar> for ContextStorageEntriesRequest {
116-
type Returns = Vec<ContextStorageEntry>;
117-
118116
const METHOD: &'static str = "context_storage_entries";
119117

118+
type Returns = Vec<ContextStorageEntry>;
119+
120120
fn encode(self) -> eyre::Result<Vec<u8>> {
121121
let env = Env::default();
122122
let offset_val: u32 = self.offset as u32;
@@ -154,33 +154,51 @@ impl Method<Stellar> for ContextStorageEntriesRequest {
154154
}
155155

156156
impl Method<Ethereum> for ContextStorageEntriesRequest {
157-
type Returns = Vec<ContextStorageEntry>;
158-
159157
const METHOD: &'static str = "contextStorageEntries(uint32,uint32)";
160158

159+
type Returns = Vec<ContextStorageEntry>;
160+
161161
fn encode(self) -> eyre::Result<Vec<u8>> {
162162
let offset = u32::try_from(self.offset)
163163
.map_err(|e| eyre::eyre!("Offset too large for u32: {}", e))?;
164164
let limit =
165165
u32::try_from(self.limit).map_err(|e| eyre::eyre!("Limit too large for u32: {}", e))?;
166-
167166
Ok((offset, limit).abi_encode())
168167
}
168+
169169
fn decode(response: Vec<u8>) -> eyre::Result<Self::Returns> {
170-
let decoded: Vec<alloy::primitives::Bytes> = SolValue::abi_decode(&response, false)?;
171-
let mut decoded = decoded.into_iter();
172-
let mut entries = Vec::with_capacity(decoded.len() >> 1);
173-
while let Some(key) = decoded.next() {
174-
let value = decoded
175-
.next()
176-
.ok_or_eyre("missing value for storage entry")?;
177-
178-
entries.push(ContextStorageEntry {
179-
key: key.into(),
180-
value: value.into(),
181-
});
182-
}
170+
// Define the struct type as a tuple
171+
let struct_type = "tuple(bytes,bytes)[]".parse::<DynSolType>()?;
172+
// Decode using dynamic ABI decoder
173+
let decoded = struct_type.abi_decode(&response)?;
174+
// Convert the decoded value to our type
175+
let DynSolValue::Array(entries) = decoded else {
176+
return Err(eyre!("Expected array"));
177+
};
183178

184-
Ok(entries)
179+
Ok(entries
180+
.into_iter()
181+
.map(|entry| {
182+
let DynSolValue::Tuple(fields) = entry else {
183+
return Err(eyre!("Expected tuple"));
184+
};
185+
186+
let all_bytes = fields[1]
187+
.as_bytes()
188+
.ok_or_else(|| eyre!("Failed to get bytes from field"))?;
189+
190+
// Get key
191+
let key_len = all_bytes[31] as usize;
192+
let key = all_bytes[32..32 + key_len].to_vec();
193+
194+
// Get value
195+
#[allow(clippy::integer_division, reason = "Need this for 32-byte alignment")]
196+
let value_offset = 32 + ((key_len + 31) / 32) * 32;
197+
let value_len = all_bytes[value_offset + 31] as usize;
198+
let value = all_bytes[value_offset + 32..value_offset + 32 + value_len].to_vec();
199+
200+
Ok(ContextStorageEntry { key, value })
201+
})
202+
.collect::<Result<Vec<_>, _>>()?)
185203
}
186204
}

e2e-tests/Cargo.toml

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,31 +9,33 @@ license.workspace = true
99
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
1010

1111
[dependencies]
12+
async-compression = { version = "0.4.22", features = ["tokio", "gzip"] }
1213
camino = { workspace = true, features = ["serde1"] }
1314
clap = { workspace = true, features = ["env", "derive"] }
1415
const_format.workspace = true
1516
eyre.workspace = true
17+
futures-util.workspace = true
1618
near-workspaces.workspace = true
1719
nix = { version = "0.29.0", features = ["signal"] }
1820
rand.workspace = true
1921
serde = { workspace = true, features = ["derive"] }
2022
serde_json.workspace = true
2123
tokio = { workspace = true, features = ["fs", "io-util", "macros", "process", "rt", "rt-multi-thread", "time"] }
22-
url = { workspace = true }
23-
reqwest = { workspace = true }
24+
reqwest.workspace = true
25+
tokio-util.workspace = true
26+
url.workspace = true
2427

2528
# Add ICP dependencies from workspace
26-
ic-agent = { workspace = true }
27-
candid = { workspace = true }
29+
ic-agent.workspace = true
30+
candid.workspace = true
2831

2932
# Add Ethereum dependencies from workspace
30-
alloy = { workspace = true }
33+
alloy.workspace = true
3134

3235
# Add Stellar dependencies from workspace
33-
soroban-client = { workspace = true }
34-
soroban-sdk = { workspace = true }
35-
base64 = { workspace = true }
36-
flate2 = "1.1.0"
36+
soroban-client.workspace = true
37+
soroban-sdk.workspace = true
38+
base64.workspace = true
3739

3840
[lints]
3941
workspace = true
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
{
2+
"protocol": "ethereum",
3+
"steps": [
4+
{
5+
"applicationInstall": {
6+
"application": { "localFile": "./apps/kv-store/res/kv_store.wasm" },
7+
"target": "allMembers"
8+
}
9+
},
10+
{
11+
"contextCreate": null
12+
},
13+
{
14+
"contextCreateAlias": {
15+
"aliasName": "my_context"
16+
}
17+
},
18+
{
19+
"call": {
20+
"methodName": "set",
21+
"argsJson": { "key": "foo", "value": "bar" },
22+
"expectedResultJson": null,
23+
"target": "inviter"
24+
}
25+
},
26+
{
27+
"call": {
28+
"methodName": "get",
29+
"argsJson": { "key": "foo" },
30+
"expectedResultJson": "bar",
31+
"target": "inviter"
32+
}
33+
},
34+
{
35+
"contextInviteJoin": null
36+
},
37+
{
38+
"wait": {
39+
"for": "consensus",
40+
"durationMs": 5000,
41+
"description": [
42+
"assuming it takes 5s to propagate we should",
43+
"only need to wait 5 * ceil(log2(nodes)) seconds"
44+
]
45+
}
46+
},
47+
{
48+
"call": {
49+
"methodName": "get",
50+
"argsJson": { "key": "foo" },
51+
"expectedResultJson": "bar",
52+
"target": "allMembers",
53+
"retries": 20,
54+
"intervalMs": 5000,
55+
"description": [
56+
"if we don't reach consensus in the ideal case",
57+
"wait 5 seconds for nodes that have not yet synced",
58+
"in the worst case, wait 20 * 5s for nodes that uselessly",
59+
"keep syncing with themselves without having the state"
60+
]
61+
}
62+
},
63+
{
64+
"call": {
65+
"methodName": "set",
66+
"argsJson": { "key": "foo", "value": "baz" },
67+
"expectedResultJson": null,
68+
"target": "inviter"
69+
}
70+
},
71+
{
72+
"wait": {
73+
"for": "broadcast",
74+
"durationMs": 5000,
75+
"description": ["wait exactly 5s for the broadcast to propagate"]
76+
}
77+
},
78+
{
79+
"call": {
80+
"methodName": "get",
81+
"argsJson": { "key": "foo" },
82+
"expectedResultJson": "baz",
83+
"target": "allMembers",
84+
"retries": 5,
85+
"intervalMs": 5000,
86+
"description": [
87+
"if a node still hasn't received the broadcast",
88+
"try 5 more times in 5 seconds, but no more"
89+
]
90+
}
91+
}
92+
]
93+
}

0 commit comments

Comments
 (0)