Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
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
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,29 @@ cargo build --release -p polkadot-bulletin-chain
POLKADOT_BULLETIN_BINARY_PATH=./target/release/polkadot-bulletin-chain zombienet -p native spawn ./zombienet/bulletin-polkadot-local.toml
```

### Run Bulletin Westend local chain

```bash
# 1. Build and create the chain spec
cargo build --release -p bulletin-westend-runtime
./scripts/create_bulletin_westend_spec.sh

# 2. Spawn the zombienet
POLKADOT_BINARY_PATH=~/local_bridge_testing/bin/polkadot \
POLKADOT_PARACHAIN_BINARY_PATH=~/local_bridge_testing/bin/polkadot-parachain \
zombienet -p native spawn ./zombienet/bulletin-westend-local.toml
```

#### Assigning Cores (Multi-Core Block Production)

After the zombienet is running, you need to assign cores to the bulletin parachain for block production. The relay chain is configured with 3 cores (`num_cores = 3`).

```bash
./scripts/assign_cores.sh ws://localhost:9942 1006 0 1
```

This uses sudo to submit a batch of `coretime.assignCore` extrinsics on the relay chain.

### Run a production chain (but only with Alice validator)
You can override the Alice validator keys here: [adjust\_bp\_spec.sh](./zombienet/adjust_bp_spec.sh) (you should see finalized blocks in the logs).

Expand Down
34 changes: 34 additions & 0 deletions integration-tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Bulletin Chain Integration Tests

Integration tests for the Polkadot Bulletin Chain (Westend Parachain).

## Prerequisites

```bash
mkdir -p ~/local_bridge_testing/bin

# Build polkadot and polkadot-parachain from polkadot-sdk
# Copy to ~/local_bridge_testing/bin/

# Download zombienet
wget "https://github.com/paritytech/zombienet/releases/download/v1.3.133/zombienet-linux-x64" \
-O ~/local_bridge_testing/bin/zombienet
chmod +x ~/local_bridge_testing/bin/zombienet

# Generate chain spec
cd polkadot-bulletin-chain
./scripts/create_bulletin_westend_spec.sh

# Install and start Kubo
wget https://dist.ipfs.tech/kubo/v0.38.1/kubo_v0.38.1_darwin-arm64.tar.gz
tar -xvzf kubo_v0.38.1_darwin-arm64.tar.gz
./kubo/ipfs version
./kubo/ipfs init
./kubo/ipfs daemon &
```

## Running Tests

```bash
./integration-tests/run-test.sh 01-store-data
```
107 changes: 107 additions & 0 deletions integration-tests/run-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#!/bin/bash
#
# Integration test runner for Polkadot Bulletin Chain (Westend Parachain).
#
# Usage:
# ./run-test.sh <test_name>
#
# Example:
# ./run-test.sh 01-store-data
# ./run-test.sh 02-elastic-scaling
#
# Prerequisites:
# - polkadot binary at ~/local_bridge_testing/bin/polkadot
# - polkadot-parachain binary at ~/local_bridge_testing/bin/polkadot-parachain
# - zombienet binary at ~/local_bridge_testing/bin/zombienet
# - Node.js installed

set -e

# Kill any leftover processes from previous runs (kills all zombienet processes
# maybe too strict? Solution to problems when killing and restarting tests)
cleanup_old_processes() {
echo "Killing any running zombienet processes..."
pkill -9 -f "zombienet.*spawn.*bulletin-westend" 2>/dev/null || true
pkill -9 -f "polkadot.*westend-local" 2>/dev/null || true
pkill -9 -f "polkadot-parachain.*bulletin-westend" 2>/dev/null || true
pkill -9 -f "ipfs-reconnect-westend" 2>/dev/null || true
sleep 1
}

# Cleanup function for this test run
cleanup() {
echo ""
echo "Cleaning up test environment..."

# Kill all child processes
pkill -9 -P $$ 2>/dev/null || true

# Also kill by pattern in case they escaped the process tree
pkill -9 -f "zombienet.*spawn.*bulletin-westend" 2>/dev/null || true
pkill -9 -f "polkadot.*westend-local" 2>/dev/null || true
pkill -9 -f "polkadot-parachain.*bulletin-westend" 2>/dev/null || true
pkill -9 -f "ipfs-reconnect-westend" 2>/dev/null || true

echo "Cleanup complete."
}

# Set trap for cleanup on exit
trap cleanup SIGINT SIGTERM EXIT

test=$1

if [ -z "$test" ]; then
echo "Usage: $0 <test_name>"
echo ""
echo "Available tests:"
for dir in "${BASH_SOURCE%/*}"/tests/*/; do
if [ -d "$dir" ]; then
basename "$dir"
fi
done
exit 1
fi

# Cleanup old processes first
cleanup_old_processes

# Setup paths
export LOCAL_BRIDGE_TESTING_PATH=${LOCAL_BRIDGE_TESTING_PATH:-~/local_bridge_testing}
export BULLETIN_REPO_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"

# Binary paths (default paths can be overridden by env vars)
export ZOMBIENET_BINARY=${ZOMBIENET_BINARY:-$LOCAL_BRIDGE_TESTING_PATH/bin/zombienet}
export POLKADOT_BINARY_PATH=${POLKADOT_BINARY_PATH:-$LOCAL_BRIDGE_TESTING_PATH/bin/polkadot}
export POLKADOT_PARACHAIN_BINARY_PATH=${POLKADOT_PARACHAIN_BINARY_PATH:-$LOCAL_BRIDGE_TESTING_PATH/bin/polkadot-parachain}

# Verify binaries exist
for bin in "$ZOMBIENET_BINARY" "$POLKADOT_BINARY_PATH" "$POLKADOT_PARACHAIN_BINARY_PATH"; do
if [ ! -f "$bin" ]; then
echo "Error: Required binary not found: $bin"
echo ""
echo "Please ensure the following binaries are installed:"
echo " - $ZOMBIENET_BINARY"
echo " - $POLKADOT_BINARY_PATH"
echo " - $POLKADOT_PARACHAIN_BINARY_PATH"
exit 1
fi
done

# Create test directory
export TEST_DIR=$(mktemp -d /tmp/bulletin-tests-XXXXX)
mkdir -p "$TEST_DIR/logs"
echo "Test directory: $TEST_DIR"
echo ""

# Run the test
test_script="${BASH_SOURCE%/*}/tests/$test/run.sh"
if [ ! -f "$test_script" ]; then
echo "Error: Test '$test' not found at $test_script"
exit 1
fi

echo "Running test: $test"
echo "================================================"
echo ""

bash "$test_script"
24 changes: 24 additions & 0 deletions integration-tests/tests/01-store-data/authorize-and-store.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { ApiPromise, WsProvider, Keyring } from '@polkadot/api';
import { cryptoWaitReady, blake2AsU8a } from '@polkadot/util-crypto';
import { CID } from 'multiformats/cid';
import { create } from 'multiformats/hashes/digest';

const [,, endpoint, seed, data] = process.argv;

await cryptoWaitReady();
const api = await ApiPromise.create({ provider: new WsProvider(endpoint), noInitWarn: true });
const pair = new Keyring({ type: 'sr25519' }).addFromUri(seed);

// Authorize
console.error('Authorizing...');
await api.tx.sudo.sudo(api.tx.transactionStorage.authorizeAccount(pair.address, 2, 65536)).signAndSend(pair);
await new Promise(r => setTimeout(r, 7000));

// Store
console.error('Storing...');
const cid = CID.createV1(0x55, create(0xb220, blake2AsU8a(data)));
await api.tx.transactionStorage.store(data).signAndSend(pair);
await new Promise(r => setTimeout(r, 7000));

console.log(`OUTPUT_CID=${cid.toString()}`);
await api.disconnect();
20 changes: 20 additions & 0 deletions integration-tests/tests/01-store-data/check-authorization.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { ApiPromise, WsProvider } from '@polkadot/api';

const [,, endpoint, account, expected] = process.argv;
const expectAuth = expected === 'true';

const api = await ApiPromise.create({ provider: new WsProvider(endpoint), noInitWarn: true });

for (let i = 0; i < 10; i++) {
const auth = await api.query.transactionStorage.authorizations({ Account: account });
if (auth.isSome === expectAuth) {
console.log(expectAuth ? '✅ Authorized' : '✅ Not authorized (expected)');
await api.disconnect();
process.exit(0);
}
await new Promise(r => setTimeout(r, 3000));
}

console.error(`❌ Authorization mismatch: expected ${expectAuth}`);
await api.disconnect();
process.exit(1);
10 changes: 10 additions & 0 deletions integration-tests/tests/01-store-data/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"type": "module",
"dependencies": {
"@polkadot/api": "^16.5.2",
"@polkadot/keyring": "^13.5.8",
"@polkadot/util-crypto": "^13.5.8",
"ipfs-http-client": "^60.0.1",
"multiformats": "^13.4.1"
}
}
56 changes: 56 additions & 0 deletions integration-tests/tests/01-store-data/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/bin/bash
set -e

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BULLETIN_REPO_PATH="${BULLETIN_REPO_PATH:-$(cd "$SCRIPT_DIR/../../.." && pwd)}"

ZOMBIENET_CONFIG="$BULLETIN_REPO_PATH/zombienet/bulletin-westend-local.toml"
RELAY_ENDPOINT="ws://127.0.0.1:9942"
PARACHAIN_ENDPOINT="ws://127.0.0.1:10000"
IPFS_URL="http://127.0.0.1:5001"
TEST_DATA="0x48656c6c6f20576f726c64"
ALICE="5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"

SPAWNED_PIDS=()
cleanup() {
for pid in "${SPAWNED_PIDS[@]}"; do kill -9 "$pid" 2>/dev/null || true; done
[ -n "$zombienet_pid" ] && pkill -9 -P "$zombienet_pid" 2>/dev/null || true
}
trap cleanup EXIT

echo "=== Bulletin Westend Data Storage Test ==="

[ -f "$BULLETIN_REPO_PATH/zombienet/bulletin-westend-spec.json" ] || bash "$BULLETIN_REPO_PATH/scripts/create_bulletin_westend_spec.sh"
[ -d "$SCRIPT_DIR/node_modules" ] || (cd "$SCRIPT_DIR" && npm install)

echo "Starting network..."
pushd "$BULLETIN_REPO_PATH" > /dev/null
"$ZOMBIENET_BINARY" -p native spawn "$ZOMBIENET_CONFIG" > "$TEST_DIR/logs/zombienet.log" 2>&1 &
zombienet_pid=$!
SPAWNED_PIDS+=($zombienet_pid)
popd > /dev/null

echo "Waiting for relay chain..."
node "$SCRIPT_DIR/wait-for-chain.js" "$RELAY_ENDPOINT" 2>/dev/null
echo "Waiting for parachain..."
node "$SCRIPT_DIR/wait-for-chain.js" "$PARACHAIN_ENDPOINT" 2>/dev/null

"$BULLETIN_REPO_PATH/scripts/ipfs-reconnect-westend.sh" > "$TEST_DIR/logs/ipfs.log" 2>&1 &
disown

echo "Checking no initial authorization..."
node "$SCRIPT_DIR/check-authorization.js" "$PARACHAIN_ENDPOINT" "$ALICE" false 2>/dev/null

echo "Authorizing and storing data..."
output=$(node "$SCRIPT_DIR/authorize-and-store.js" "$PARACHAIN_ENDPOINT" "//Alice" "$TEST_DATA" 2>/dev/null)
echo "$output"
CID=$(echo "$output" | grep "OUTPUT_CID=" | cut -d= -f2)

echo "Verifying authorization..."
node "$SCRIPT_DIR/check-authorization.js" "$PARACHAIN_ENDPOINT" "$ALICE" true 2>/dev/null

echo "Verifying data via IPFS..."
sleep 10
node "$SCRIPT_DIR/verify-ipfs.js" "$CID" "$TEST_DATA" "$IPFS_URL" 2>/dev/null

echo "=== All tests passed! ==="
25 changes: 25 additions & 0 deletions integration-tests/tests/01-store-data/verify-ipfs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
const [,, cid, expectedData, ipfsUrl] = process.argv;

const { create } = await import('ipfs-http-client');
const client = create({ url: ipfsUrl });

for (let i = 0; i < 10; i++) {
try {
const chunks = [];
for await (const chunk of client.cat(cid)) chunks.push(chunk);
const data = '0x' + Buffer.concat(chunks).toString('hex');

if (data === expectedData) {
console.log('✅ Data verified via IPFS');
process.exit(0);
}
console.error(`❌ Data mismatch: got ${data}, expected ${expectedData}`);
process.exit(1);
} catch (e) {
console.log(`Waiting for IPFS... (${e.message})`);
await new Promise(r => setTimeout(r, 3000));
}
}

console.error('❌ IPFS verification timeout');
process.exit(1);
19 changes: 19 additions & 0 deletions integration-tests/tests/01-store-data/wait-for-chain.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { ApiPromise, WsProvider } from '@polkadot/api';

const [,, endpoint, minBlocks = "1"] = process.argv;
const required = parseInt(minBlocks);

const provider = new WsProvider(endpoint);
const api = await ApiPromise.create({ provider, noInitWarn: true });

while (true) {
const header = await api.rpc.chain.getHeader();
const height = header.number.toNumber();
if (height >= required) {
console.log(`✅ Chain ready at ${endpoint} (height: ${height})`);
await api.disconnect();
process.exit(0);
}
await new Promise(r => setTimeout(r, 1000));
}

8 changes: 8 additions & 0 deletions integration-tests/tests/02-elastic-scaling/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"type": "module",
"dependencies": {
"@polkadot/api": "^16.5.2",
"@polkadot/keyring": "^13.5.8",
"@polkadot/util-crypto": "^13.5.8"
}
}
44 changes: 44 additions & 0 deletions integration-tests/tests/02-elastic-scaling/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/bin/bash
set -e

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BULLETIN_REPO_PATH="${BULLETIN_REPO_PATH:-$(cd "$SCRIPT_DIR/../../.." && pwd)}"

ZOMBIENET_CONFIG="$BULLETIN_REPO_PATH/zombienet/bulletin-westend-local.toml"
RELAY_ENDPOINT="ws://127.0.0.1:9942"
PARACHAIN_ENDPOINT="ws://127.0.0.1:10000"

SPAWNED_PIDS=()
cleanup() {
for pid in "${SPAWNED_PIDS[@]}"; do kill -9 "$pid" 2>/dev/null || true; done
[ -n "$zombienet_pid" ] && pkill -9 -P "$zombienet_pid" 2>/dev/null || true
}
trap cleanup EXIT

echo "=== Bulletin Westend Elastic Scaling Test ==="

[ -f "$BULLETIN_REPO_PATH/zombienet/bulletin-westend-spec.json" ] || bash "$BULLETIN_REPO_PATH/scripts/create_bulletin_westend_spec.sh"
[ -d "$SCRIPT_DIR/node_modules" ] || (cd "$SCRIPT_DIR" && npm install)

echo "Starting network..."
pushd "$BULLETIN_REPO_PATH" > /dev/null
"$ZOMBIENET_BINARY" -p native spawn "$ZOMBIENET_CONFIG" > "$TEST_DIR/logs/zombienet.log" 2>&1 &
zombienet_pid=$!
SPAWNED_PIDS+=($zombienet_pid)
popd > /dev/null

echo "Waiting for relay chain..."
node "$SCRIPT_DIR/wait-for-chain.js" "$RELAY_ENDPOINT" 2>/dev/null
echo "Waiting for parachain..."
node "$SCRIPT_DIR/wait-for-chain.js" "$PARACHAIN_ENDPOINT" 2>/dev/null

echo "Assigning extra cores for elastic scaling..."
bash "$BULLETIN_REPO_PATH/scripts/assign_cores.sh" "$RELAY_ENDPOINT" 1006 0 1 2>/dev/null

echo "Waiting for cores to take effect..."
node "$SCRIPT_DIR/wait-for-chain.js" "$PARACHAIN_ENDPOINT" 3 2>/dev/null

echo "Testing elastic scaling with data storage..."
node "$SCRIPT_DIR/store-and-check-blocktime.js" "$PARACHAIN_ENDPOINT" "//Alice"

echo "=== Elastic scaling test passed! ==="
Loading
Loading