Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,42 @@ jobs:
- name: Run tests
working-directory: agent
run: npm test

# ── End-to-end integration test ───────────────────────────────────────────
e2e-test:
name: End-to-end integration test
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown,wasm32v1-none
components: rustfmt

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'

- name: Install system dependencies for stellar-cli
run: sudo apt-get update && sudo apt-get install -y libdbus-1-dev libssl-dev libudev-dev

- name: Install Stellar CLI
run: cargo install --locked stellar-cli

- name: Build contracts
run: |
cd contract
rustup target add wasm32v1-none
stellar contract build
cd agents
rustup target add wasm32v1-none
stellar contract build

- name: Run end-to-end integration test
run: ./scripts/e2e-test.sh
328 changes: 328 additions & 0 deletions scripts/e2e-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,328 @@
#!/bin/bash
set -e

# End-to-end integration test for Lodestar
# This script tests cross-contract integration between registry and agents contracts
# by deploying them to a local Stellar quickstart container and verifying that
# cross-contract calls work correctly. This catches contract field mismatches.

echo "=== Lodestar End-to-End Integration Test ==="

# Colors for output
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

# Cleanup function
cleanup() {
echo -e "${YELLOW}Cleaning up...${NC}"
if [ -n "$BACKEND_PID" ]; then
kill $BACKEND_PID 2>/dev/null || true
fi
if [ -n "$QUICKSTART_CONTAINER" ]; then
docker stop $QUICKSTART_CONTAINER 2>/dev/null || true
docker rm $QUICKSTART_CONTAINER 2>/dev/null || true
fi
echo "Cleanup complete"
}

trap cleanup EXIT

# Check required tools
command -v docker >/dev/null 2>&1 || { echo -e "${RED}Docker is required but not installed${NC}"; exit 1; }
command -v stellar >/dev/null 2>&1 || { echo -e "${RED}stellar-cli is required but not installed${NC}"; exit 1; }

# Start Stellar quickstart container
echo "Starting Stellar quickstart container..."
QUICKSTART_CONTAINER="lodestar-e2e-quickstart"
docker run -d \
--name $QUICKSTART_CONTAINER \
-p 8000:8000 \
-p 8001:8001 \
stellar/quickstart:latest \
--testnet \
--enable-rpc \
--enable-http \
--protocol-version 22

# Wait for quickstart to be ready
echo "Waiting for Stellar quickstart to be ready..."
for i in {1..60}; do
if curl -s http://localhost:8000/health >/dev/null 2>&1; then
echo -e "${GREEN}Stellar quickstart is ready${NC}"
break
fi
if [ $i -eq 60 ]; then
echo -e "${RED}Timeout waiting for Stellar quickstart${NC}"
exit 1
fi
sleep 2
done

# Generate test accounts
echo "Generating test accounts..."
SERVER_SECRET=$(stellar keys generate --no-public)
SERVER_ADDRESS=$(stellar keys address --secret $SERVER_SECRET)

AGENT_SECRET=$(stellar keys generate --no-public)
AGENT_ADDRESS=$(stellar keys address --secret $AGENT_SECRET)

PROVIDER_SECRET=$(stellar keys generate --no-public)
PROVIDER_ADDRESS=$(stellar keys address --secret $PROVIDER_SECRET)

echo "Server address: $SERVER_ADDRESS"
echo "Agent address: $AGENT_ADDRESS"
echo "Provider address: $PROVIDER_ADDRESS"

# Fund accounts using friendbot (testnet)
echo "Funding test accounts..."
curl -s "https://friendbot.stellar.org?addr=$SERVER_ADDRESS" >/dev/null
curl -s "https://friendbot.stellar.org?addr=$AGENT_ADDRESS" >/dev/null
curl -s "https://friendbot.stellar.org?addr=$PROVIDER_ADDRESS" >/dev/null

# Build contracts
echo "Building contracts..."
cd contract
rustup target add wasm32v1-none
stellar contract build
cd agents
rustup target add wasm32v1-none
stellar contract build
cd ../..

# Deploy registry contract
echo "Deploying registry contract..."
REGISTRY_WASM=$(ls contract/target/wasm32v1-none/release/*.wasm | head -1)
REGISTRY_ID=$(stellar contract deploy \
--wasm $REGISTRY_WASM \
--source $SERVER_SECRET \
--rpc-url http://localhost:8000/soroban/rpc \
--network-passphrase "Test SDF Network ; September 2015")

echo "Registry contract ID: $REGISTRY_ID"

# Deploy agents contract
echo "Deploying agents contract..."
AGENTS_WASM=$(ls contract/agents/target/wasm32v1-none/release/*.wasm | head -1)
AGENTS_ID=$(stellar contract deploy \
--wasm $AGENTS_WASM \
--source $SERVER_SECRET \
--rpc-url http://localhost:8000/soroban/rpc \
--network-passphrase "Test SDF Network ; September 2015")

echo "Agents contract ID: $AGENTS_ID"

# Initialize agents contract with registry address
echo "Initializing agents contract..."
stellar contract invoke \
--id $AGENTS_ID \
--source $SERVER_SECRET \
--rpc-url http://localhost:8000/soroban/rpc \
--network-passphrase "Test SDF Network ; September 2015" \
-- \
init \
--registry $REGISTRY_ID

# Initialize registry contract with agents address
echo "Initializing registry contract with agents contract address..."
stellar contract invoke \
--id $REGISTRY_ID \
--source $SERVER_SECRET \
--rpc-url http://localhost:8000/soroban/rpc \
--network-passphrase "Test SDF Network ; September 2015" \
-- \
__constructor \
--agents_contract $AGENTS_ID

# Test cross-contract call: registry -> agents (is_registered)
echo "Testing cross-contract call: registry -> agents (is_registered)..."
# First register the agent via the agents contract
stellar contract invoke \
--id $AGENTS_ID \
--source $SERVER_SECRET \
--rpc-url http://localhost:8000/soroban/rpc \
--network-passphrase "Test SDF Network ; September 2015" \
-- \
register_agent \
--agent_address $AGENT_ADDRESS \
--name "Test Agent" \
--description "Test Description" \
--owner $SERVER_ADDRESS

# Now try to vote on a service - this will trigger registry to call agents.is_registered
# First register a service
SERVICE_ID=$(stellar contract invoke \
--id $REGISTRY_ID \
--source $PROVIDER_SECRET \
--rpc-url http://localhost:8000/soroban/rpc \
--network-passphrase "Test SDF Network ; September 2015" \
-- \
register_service \
--provider $PROVIDER_ADDRESS \
--name "Test Service" \
--description "Test Description" \
--endpoint "http://test.com" \
--price_usdc "10" \
--pay_to $PROVIDER_ADDRESS \
--category "test")

echo "Service registered with ID: $SERVICE_ID"

# Try to vote - this triggers cross-contract call from registry to agents
echo "Testing reputation voting (triggers registry -> agents cross-contract call)..."
if stellar contract invoke \
--id $REGISTRY_ID \
--source $AGENT_SECRET \
--rpc-url http://localhost:8000/soroban/rpc \
--network-passphrase "Test SDF Network ; September 2015" \
-- \
update_reputation \
--id $SERVICE_ID \
--positive true \
--caller $AGENT_ADDRESS 2>&1; then
echo -e "${GREEN}Cross-contract call registry -> agents succeeded${NC}"
else
echo -e "${RED}Cross-contract call registry -> agents failed${NC}"
exit 1
fi

# Test cross-contract call: agents -> registry (get_service)
echo "Testing cross-contract call: agents -> registry (get_service)..."
# Record a payment - this triggers agents to call registry.get_service
if stellar contract invoke \
--id $AGENTS_ID \
--source $PROVIDER_SECRET \
--rpc-url http://localhost:8000/soroban/rpc \
--network-passphrase "Test SDF Network ; September 2015" \
-- \
record_payment \
--agent_address $AGENT_ADDRESS \
--service_id $SERVICE_ID \
--amount_stroops 10000000 \
--success true \
--caller $PROVIDER_ADDRESS 2>&1; then
echo -e "${GREEN}Cross-contract call agents -> registry succeeded${NC}"
else
echo -e "${RED}Cross-contract call agents -> registry failed${NC}"
exit 1
fi
Comment on lines +165 to +181

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Exercise the actual agent runtime.

This records a payment through stellar contract invoke; the script never installs or runs the agent component. It therefore cannot validate the required agent-to-backend discover/pay flow.

🧰 Tools
🪛 Shellcheck (0.11.0)

[info] 194-194: Double quote to prevent globbing and word splitting.

(SC2086)


[info] 195-195: Double quote to prevent globbing and word splitting.

(SC2086)


[info] 200-200: Double quote to prevent globbing and word splitting.

(SC2086)


[info] 201-201: Double quote to prevent globbing and word splitting.

(SC2086)


[info] 204-204: Double quote to prevent globbing and word splitting.

(SC2086)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/e2e-test.sh` around lines 193 - 209, Replace the direct `stellar
contract invoke` payment test with execution of the actual `agent` component and
its runtime flow, including installation/setup as needed. Ensure the test
exercises the agent-to-backend discover/pay path and preserves failure handling
through the surrounding success/failure branch.


# Configure backend environment
echo "Configuring backend..."
cat > backend/.env.e2e << EOF
CONTRACT_ID=$REGISTRY_ID
AGENTS_CONTRACT_ID=$AGENTS_ID
SERVER_STELLAR_ADDRESS=$SERVER_ADDRESS
SERVER_STELLAR_SECRET=$SERVER_SECRET
STELLAR_RPC_URL=http://localhost:8000/soroban/rpc
STELLAR_NETWORK_PASSPHRASE=Test SDF Network ; September 2015
FACILITATOR_URL=http://localhost:8000
USDC_CONTRACT_ID=CDLZFC3SYJYDZT7S71PSEEZKJQKJDZ4QDFAK3ZHZQWL47V2ZAHWVKX
NODE_ENV=test
PORT=3001
LOG_LEVEL=error
PAYMENT_ADDRESS=$PROVIDER_ADDRESS
EOF

# Install backend dependencies
echo "Installing backend dependencies..."
cd backend
npm ci --silent
cd ..

# Start backend
echo "Starting backend..."
cd backend
NODE_ENV=test LOG_LEVEL=error node src/index.js &
BACKEND_PID=$!
cd ..
Comment on lines +185 to +211

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n -C2 --glob '*.js' '(dotenv|\.env\.e2e|process\.env\.(CONTRACT_ID|AGENTS_CONTRACT_ID|STELLAR_RPC_URL))' backend

Repository: Stellar-Ecosystem/lodestar

Length of output: 4170


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== scripts/e2e-test.sh relevant section =="
sed -n '190,250p' scripts/e2e-test.sh 2>/dev/null || true

echo
echo "== backend/src/config.js =="
sed -n '1,220p' backend/src/config.js 2>/dev/null || true

echo
echo "== backend/src/index.js =="
sed -n '1,200p' backend/src/index.js 2>/dev/null || true

echo
echo "== backend package scripts/dependencies =="
sed -n '1,220p' backend/package.json 2>/dev/null || true

echo
echo "== dotenv/config reference in backend =="
rg -n "dotenv/config|\\.env(e2e)?|require\\('dotenv'|require\\(\"dotenv\"|NODE_OPTIONS|--env-file" backend scripts 2>/dev/null || true

echo
echo "== Node option availability =="
node -e "console.log(process.version)" 2>/dev/null || true
node --version 2>/dev/null || true
node -h 2>&1 | grep -E -- '--env-file|NODE_OPTIONS|dotenv' || true

Repository: Stellar-Ecosystem/lodestar

Length of output: 22569


🌐 Web query:

dotenv/config default .env file node dotenv/config module loading .env behavior

💡 Result:

The dotenv/config module is a entry point designed to automatically load environment variables from a.env file into process.env when imported [1][2]. Behavior and Loading When you import 'dotenv/config', it executes dotenv.config immediately [1]. By default, this looks for a file named.env located in the current working directory (process.cwd) [3][4]. It parses this file and merges the key-value pairs into process.env [2][5]. Key Characteristics 1. Precedence: By default, existing environment variables in the shell (OS environment) take precedence [5]. The.env file will not overwrite these existing variables unless the override option is set [1][3]. Note that when using the 'dotenv/config' module, you can pass configuration options via command-line arguments using the format dotenv_config_key=value (e.g., dotenv_config_override=true) [2][3]. 2. Initialization Order: Because it is a module, the import is executed as part of the module dependency graph traversal [2][6]. To ensure environment variables are available to your application code and other modules, it is critical to import 'dotenv/config' as the very first operation in your entry file, before any other modules that might rely on process.env [1][5][7]. 3. Alternative Loading: Instead of importing, you can preload the module using the Node.js command-line flag: node -r dotenv/config your_script.js [2][3]. This approach ensures the variables are loaded before any of your application code executes [2]. If the.env file is missing, dotenv will fail silently by default [5], and the config function will return an object with an error property [1][3]. Top Results: [1][2][3][5]

Citations:


Load .env.e2e before starting the backend.

import 'dotenv/config' defaults to .env, so the shell-created backend/.env.e2e is not loaded for src/index.js; the e2e backend can start with wrong contract IDs or abort during validateConfig.

Proposed fix
- NODE_ENV=test LOG_LEVEL=error node src/index.js &
+ NODE_ENV=test LOG_LEVEL=error node --env-file=.env.e2e src/index.js &
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
cat > backend/.env.e2e << EOF
CONTRACT_ID=$REGISTRY_ID
AGENTS_CONTRACT_ID=$AGENTS_ID
SERVER_STELLAR_ADDRESS=$SERVER_ADDRESS
SERVER_STELLAR_SECRET=$SERVER_SECRET
STELLAR_RPC_URL=http://localhost:8000/soroban/rpc
STELLAR_NETWORK_PASSPHRASE=Test SDF Network ; September 2015
FACILITATOR_URL=http://localhost:8000
USDC_CONTRACT_ID=CDLZFC3SYJYDZT7S71PSEEZKJQKJDZ4QDFAK3ZHZQWL47V2ZAHWVKX
NODE_ENV=test
PORT=3001
LOG_LEVEL=error
PAYMENT_ADDRESS=$PROVIDER_ADDRESS
EOF
# Install backend dependencies
echo "Installing backend dependencies..."
cd backend
npm ci --silent
cd ..
# Start backend
echo "Starting backend..."
cd backend
NODE_ENV=test LOG_LEVEL=error node src/index.js &
BACKEND_PID=$!
cd ..
cat > backend/.env.e2e << EOF
CONTRACT_ID=$REGISTRY_ID
AGENTS_CONTRACT_ID=$AGENTS_ID
SERVER_STELLAR_ADDRESS=$SERVER_ADDRESS
SERVER_STELLAR_SECRET=$SERVER_SECRET
STELLAR_RPC_URL=http://localhost:8000/soroban/rpc
STELLAR_NETWORK_PASSPHRASE=Test SDF Network ; September 2015
FACILITATOR_URL=http://localhost:8000
USDC_CONTRACT_ID=CDLZFC3SYJYDZT7S71PSEEZKJQKJDZ4QDFAK3ZHZQWL47V2ZAHWVKX
NODE_ENV=test
PORT=3001
LOG_LEVEL=error
PAYMENT_ADDRESS=$PROVIDER_ADDRESS
EOF
# Install backend dependencies
echo "Installing backend dependencies..."
cd backend
npm ci --silent
cd ..
# Start backend
echo "Starting backend..."
cd backend
NODE_ENV=test LOG_LEVEL=error node --env-file=.env.e2e src/index.js &
BACKEND_PID=$!
cd ..
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/e2e-test.sh` around lines 213 - 239, Update the backend startup
command in the e2e script to explicitly load backend/.env.e2e before launching
src/index.js, rather than relying on dotenv/config’s default .env lookup.
Preserve the existing NODE_ENV, LOG_LEVEL, background execution, and BACKEND_PID
assignment.


# Wait for backend to be ready
echo "Waiting for backend to be ready..."
for i in {1..30}; do
if curl -s http://localhost:3001/healthz >/dev/null 2>&1; then
echo -e "${GREEN}Backend is ready${NC}"
Comment on lines +215 to +217

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Require a healthy backend response.

curl exits successfully on HTTP 503, so this loop marks /healthz ready as soon as it is reachable. The backend deliberately returns 503 while unhealthy.

Proposed fix
-    if curl -s http://localhost:3001/healthz >/dev/null 2>&1; then
+    if curl -fsS http://localhost:3001/healthz >/dev/null 2>&1; then
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for i in {1..30}; do
if curl -s http://localhost:3001/healthz >/dev/null 2>&1; then
echo -e "${GREEN}Backend is ready${NC}"
for i in {1..30}; do
if curl -fsS http://localhost:3001/healthz >/dev/null 2>&1; then
echo -e "${GREEN}Backend is ready${NC}"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/e2e-test.sh` around lines 243 - 245, Update the readiness check in
the health-wait loop to require a successful HTTP status, not merely curl
connectivity; configure curl to fail for HTTP errors so `/healthz` responses
such as 503 continue polling, while a healthy response proceeds to the “Backend
is ready” branch.

break
fi
if [ $i -eq 30 ]; then
echo -e "${RED}Timeout waiting for backend${NC}"
exit 1
fi
sleep 1
done

# Test registration through backend
echo "Testing agent registration through backend..."
REG_RESPONSE=$(curl -s -X POST http://localhost:3001/api/agents/register \
-H "Content-Type: application/json" \
-d "{
\"agentAddress\": \"$AGENT_ADDRESS\",
\"name\": \"E2E Test Agent\",
\"description\": \"Agent for end-to-end testing\",
\"maxPerTxUsdc\": \"0.01\",
\"maxPerDayUsdc\": \"1.00\",
\"allowedCategories\": [\"weather\", \"search\"]
}")

if echo "$REG_RESPONSE" | grep -q "error\|Error"; then
echo -e "${YELLOW}Agent may already be registered or backend returned error${NC}"
else
echo -e "${GREEN}Agent registration through backend succeeded${NC}"
fi
Comment on lines +240 to +244

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Fail when a required backend flow fails. These checks allow the script to print PASSED without validating three acceptance-critical flows. Registration is guaranteed to return 409 because the same agent was registered directly at lines 141-151; voting also reuses the agent/service vote already cast at lines 174-183, which is subject to the documented cooldown.

  • scripts/e2e-test.sh#L268-L272: register a distinct, unregistered agent through the backend and require HTTP 201.
  • scripts/e2e-test.sh#L298-L302: require the expected discovered service instead of accepting an empty/error response.
  • scripts/e2e-test.sh#L310-L314: use a permitted agent/service combination that has not already voted, then require a successful reputation response.
📍 Affects 1 file
  • scripts/e2e-test.sh#L268-L272 (this comment)
  • scripts/e2e-test.sh#L298-L302
  • scripts/e2e-test.sh#L310-L314
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/e2e-test.sh` around lines 268 - 272, Update scripts/e2e-test.sh at
lines 268-272, 298-302, and 310-314: use a distinct unregistered agent in the
registration flow and require HTTP 201; require discovery to return the expected
service rather than accepting empty or error responses; and use an allowed
agent/service pair that has not voted previously, requiring the reputation
request to succeed.


# Test service registration through backend
echo "Testing service registration through backend..."
SERVICE_RESPONSE=$(curl -s -X POST http://localhost:3001/api/services \
-H "Content-Type: application/json" \
-d "{
\"name\": \"Test Weather Service\",
\"description\": \"A weather service for E2E testing\",
\"endpoint\": \"http://localhost:9999/weather\",
\"priceUsdc\": \"0.001\",
\"payTo\": \"$PROVIDER_ADDRESS\",
\"category\": \"weather\"
}")

if echo "$SERVICE_RESPONSE" | grep -q '"id":[0-9]'; then
echo -e "${GREEN}Service registration through backend succeeded${NC}"
else
echo -e "${RED}Service registration through backend failed${NC}"
echo "$SERVICE_RESPONSE"
exit 1
fi

# Test service discovery through backend
echo "Testing service discovery through backend..."
SERVICES=$(curl -s "http://localhost:3001/api/services?category=test")
if echo "$SERVICES" | grep -q "Test Service"; then
echo -e "${GREEN}Service discovery through backend succeeded${NC}"
else
echo -e "${YELLOW}Service discovery through backend (may not have test category services)${NC}"
fi

# Test reputation voting through backend
echo "Testing reputation voting through backend..."
VOTE_RESPONSE=$(curl -s -X POST "http://localhost:3001/api/reputation/$SERVICE_ID" \
-H "Content-Type: application/json" \
-d "{\"positive\": true, \"agent\": \"$AGENT_ADDRESS\"}")

if echo "$VOTE_RESPONSE" | grep -q "error\|Error"; then
echo -e "${YELLOW}Reputation voting may have cooldown or other error${NC}"
else
echo -e "${GREEN}Reputation voting through backend succeeded${NC}"
fi

echo -e "${GREEN}=== End-to-End Integration Test PASSED ===${NC}"
echo "All components integrated successfully:"
echo " ✓ Contract deployment"
echo " ✓ Cross-contract call: registry -> agents (is_registered)"
echo " ✓ Cross-contract call: agents -> registry (get_service)"
echo " ✓ Backend integration with both contracts"
echo " ✓ Agent registration through backend"
echo " ✓ Service registration through backend"
echo " ✓ Service discovery through backend"
echo " ✓ Reputation voting through backend"
echo ""
echo "This test would FAIL if there are contract field mismatches between"
echo "the registry and agents contracts (e.g., ServiceEntry structure)."
Loading