Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add two new zombienet tests for bridges (manual run) #3072

Merged
merged 10 commits into from
Jan 29, 2024
11 changes: 11 additions & 0 deletions bridges/zombienet/helpers/chains/rococo-at-westend.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module.exports = {
grandpaPalletName: "bridgeRococoGrandpa",
parachainsPalletName: "bridgeRococoParachains",
messagesPalletName: "bridgeRococoMessages",
bestBridgedRelayChainGrandpaAuthoritySet: async function(api) {
return await api.query.bridgeRococoGrandpa.currentAuthoritySet();
svyatonik marked this conversation as resolved.
Show resolved Hide resolved
},
bestBridgedParachainInfo: async function(api) {
return await api.query.bridgeRococoParachains.parasInfo(1013);
},
}
11 changes: 11 additions & 0 deletions bridges/zombienet/helpers/chains/westend-at-rococo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module.exports = {
grandpaPalletName: "bridgeWestendGrandpa",
parachainsPalletName: "bridgeWestendParachains",
messagesPalletName: "bridgeWestendMessages",
bestBridgedRelayChainGrandpaAuthoritySet: async function(api) {
return await api.query.bridgeWestendGrandpa.currentAuthoritySet();
},
bestBridgedParachainInfo: async function(api) {
return await api.query.bridgeWestendParachains.parasInfo(1002);
},
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
const utils = require("./utils");

async function run(nodeName, networkInfo, args) {
const {wsUri, userDefinedTypes} = networkInfo.nodesByName[nodeName];
const api = await zombie.connect(wsUri, userDefinedTypes);

// parse arguments
const exitAfterSeconds = Number(args[0]);
const bridgedChain = require("./chains/" + args[1]);

// start listening to new blocks
let totalGrandpaHeaders = 0;
let totalParachainHeaders = 0;
api.rpc.chain.subscribeNewHeads(async function (header) {
const apiAtParent = await api.at(header.parentHash);
const apiAtCurrent = await api.at(header.hash);
const currentEvents = await apiAtCurrent.query.system.events();

totalGrandpaHeaders += await utils.ensureOnlyMandatoryGrandpaHeadersImported(
bridgedChain,
apiAtParent,
apiAtCurrent,
currentEvents,
);
totalParachainHeaders += await utils.ensureOnlyInitialParachainHeaderImported(
bridgedChain,
apiAtParent,
apiAtCurrent,
currentEvents,
);
});

// wait given time
await new Promise(resolve => setTimeout(resolve, exitAfterSeconds * 1000));
// if we haven't seen any new GRANDPA or parachain headers => fail
if (totalGrandpaHeaders == 0) {
throw new Error("No bridged relay chain headers imported");
}
if (totalParachainHeaders == 0) {
throw new Error("No bridged parachain headers imported");
}
}

module.exports = { run }
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
const utils = require("./utils");

async function run(nodeName, networkInfo, args) {
const {wsUri, userDefinedTypes} = networkInfo.nodesByName[nodeName];
const api = await zombie.connect(wsUri, userDefinedTypes);

// parse arguments
const exitAfterSeconds = Number(args[0]);
const bridgedChain = require("./chains/" + args[1]);

// start listening to new blocks
let atLeastOneMessageReceived = false;
let atLeastOneMessageDelivered = false;
const unsubscribe = await api.rpc.chain.subscribeNewHeads(async function (header) {
const apiAtParent = await api.at(header.parentHash);
serban300 marked this conversation as resolved.
Show resolved Hide resolved
const apiAtCurrent = await api.at(header.hash);
const currentEvents = await apiAtCurrent.query.system.events();

const messagesReceived = currentEvents.find((record) => {
return record.event.section == bridgedChain.messagesPalletName
&& record.event.method == "MessagesReceived";
}) != undefined;
const messagesDelivered = currentEvents.find((record) => {
return record.event.section == bridgedChain.messagesPalletName &&
record.event.method == "MessagesDelivered";
}) != undefined;
const hasMessageUpdates = messagesReceived || messagesDelivered;
atLeastOneMessageReceived = atLeastOneMessageReceived || messagesReceived;
atLeastOneMessageDelivered = atLeastOneMessageDelivered || messagesDelivered;

if (!hasMessageUpdates) {
// if there are no any message update transactions, we only expect mandatory GRANDPA
// headers and initial parachain headers
await utils.ensureOnlyMandatoryGrandpaHeadersImported(
bridgedChain,
apiAtParent,
apiAtCurrent,
currentEvents,
);
await utils.ensureOnlyInitialParachainHeaderImported(
bridgedChain,
apiAtParent,
apiAtCurrent,
currentEvents,
);
} else {
const messageTransactions = (messagesReceived ? 1 : 0) + (messagesDelivered ? 1 : 0);

// otherwise we only accept at most one GRANDPA header
const newGrandpaHeaders = utils.countGrandpaHeaderImports(bridgedChain, currentEvents);
if (newGrandpaHeaders > 1) {
utils.logEvents(currentEvents);
throw new Error("Unexpected relay chain header import: " + newGrandpaHeaders + " / " + messageTransactions);
}

// ...and at most one parachain header
const newParachainHeaders = utils.countParachainHeaderImports(bridgedChain, currentEvents);
if (newParachainHeaders > 1) {
utils.logEvents(currentEvents);
throw new Error("Unexpected parachain header import: " + newParachainHeaders + " / " + messageTransactions);
}
}
});

// wait until we have received + delivered messages OR until timeout
await utils.pollUntil(
exitAfterSeconds,
() => { return atLeastOneMessageReceived && atLeastOneMessageDelivered; },
() => { unsubscribe(); },
() => {
if (!atLeastOneMessageReceived) {
throw new Error("No messages received from bridged chain");
}
if (!atLeastOneMessageDelivered) {
throw new Error("No messages delivered to bridged chain");
}
},
);
}

module.exports = { run }
103 changes: 103 additions & 0 deletions bridges/zombienet/helpers/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
module.exports = {
logEvents: function(events) {
let sevents = "";
svyatonik marked this conversation as resolved.
Show resolved Hide resolved
events.forEach((record) => {
if (sevents != "") {
sevents += ", ";
}
sevents += record.event.section + "::" + record.event.method;
});
console.log("Block events: " + sevents);
},
countGrandpaHeaderImports: function(bridgedChain, events) {
return events.reduce(
(count, record) => {
const { event } = record;
if (event.section == bridgedChain.grandpaPalletName && event.method == "UpdatedBestFinalizedHeader") {
count += 1;
}
return count;
},
0,
);
},
countParachainHeaderImports: function(bridgedChain, events) {
return events.reduce(
(count, record) => {
const { event } = record;
if (event.section == bridgedChain.parachainsPalletName && event.method == "UpdatedParachainHead") {
count += 1;
}
return count;
},
0,
);
},
pollUntil: async function(
timeoutInSecs,
predicate,
cleanup,
onFailure,
) {
const begin = new Date().getTime();
const end = begin + timeoutInSecs * 1000;
while (new Date().getTime() < end) {
if (predicate()) {
cleanup();
return;
}
await new Promise(resolve => setTimeout(resolve, 100));
}

cleanup();
onFailure();
},
ensureOnlyMandatoryGrandpaHeadersImported: async function(
bridgedChain,
apiAtParent,
apiAtCurrent,
currentEvents,
) {
// remember id of bridged relay chain GRANDPA authorities set at parent block
const authoritySetAtParent = await bridgedChain.bestBridgedRelayChainGrandpaAuthoritySet(apiAtParent);
const authoritySetIdAtParent = authoritySetAtParent["setId"];

// now read the id of bridged relay chain GRANDPA authorities set at current block
const authoritySetAtCurrent = await bridgedChain.bestBridgedRelayChainGrandpaAuthoritySet(apiAtCurrent);
const authoritySetIdAtCurrent = authoritySetAtCurrent["setId"];

// we expect to see no more than `authoritySetIdAtCurrent - authoritySetIdAtParent` new GRANDPA headers
const maxNewGrandpaHeaders = authoritySetIdAtCurrent - authoritySetIdAtParent;
const newGrandpaHeaders = module.exports.countGrandpaHeaderImports(bridgedChain, currentEvents);

// check that our assumptions are correct
if (newGrandpaHeaders > maxNewGrandpaHeaders) {
module.exports.logEvents(currentEvents);
throw new Error("Unexpected relay chain header import: " + newGrandpaHeaders + " / " + maxNewGrandpaHeaders);
}

return newGrandpaHeaders;
},
ensureOnlyInitialParachainHeaderImported: async function(
bridgedChain,
apiAtParent,
apiAtCurrent,
currentEvents,
) {
// remember whether we already know bridged parachain header at a parent block
const bestBridgedParachainHeader = await bridgedChain.bestBridgedParachainInfo(apiAtParent);
const hasBestBridgedParachainHeader = bestBridgedParachainHeader.isSome;

// we expect to see: no more than `1` bridged parachain header if there were no parachain header before.
const maxNewParachainHeaders = hasBestBridgedParachainHeader ? 0 : 1;
const newParachainHeaders = module.exports.countParachainHeaderImports(bridgedChain, currentEvents);

// check that our assumptions are correct
if (newParachainHeaders > maxNewParachainHeaders) {
module.exports.logEvents(currentEvents);
throw new Error("Unexpected parachain header import: " + newParachainHeaders + " / " + maxNewParachainHeaders);
}

return newParachainHeaders;
},
}
40 changes: 21 additions & 19 deletions bridges/zombienet/run-tests.sh
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
#!/bin/bash
#set -eu
set -x
shopt -s nullglob

trap "trap - SIGINT SIGTERM EXIT && kill -- -$$" SIGINT SIGTERM EXIT
trap "trap - SIGINT SIGTERM EXIT && killall -q -9 substrate-relay && kill -- -$$" SIGINT SIGTERM EXIT

# run tests in range [TESTS_BEGIN; TESTS_END)
TESTS_BEGIN=1
TESTS_END=1000
# whether to use paths for zombienet+bridges tests container or for local testing
ZOMBIENET_DOCKER_PATHS=0
while [ $# -ne 0 ]
Expand All @@ -14,6 +16,11 @@ do
--docker)
ZOMBIENET_DOCKER_PATHS=1
;;
--test)
shift
TESTS_BEGIN="$1"
TESTS_END="$1"
;;
esac
shift
done
Expand Down Expand Up @@ -57,7 +64,8 @@ ALL_TESTS_FOLDER=`mktemp -d /tmp/bridges-zombienet-tests.XXXXX`
function start_coproc() {
local command=$1
local name=$2
local coproc_log=`mktemp -p $TEST_FOLDER`
local logname=`basename $name`
local coproc_log=`mktemp -p $TEST_FOLDER $logname.XXXXX`
coproc COPROC {
# otherwise zombienet uses some hardcoded paths
unset RUN_IN_CONTAINER
Expand All @@ -73,44 +81,36 @@ function start_coproc() {
}

# execute every test from tests folder
TEST_INDEX=1
TEST_INDEX=$TESTS_BEGIN
while true
do
declare -A TEST_COPROCS
TEST_COPROCS_COUNT=0
TEST_PREFIX=$(printf "%04d" $TEST_INDEX)

# it'll be used by the `sync-exit.sh` script
export TEST_FOLDER=`mktemp -d -p $ALL_TESTS_FOLDER`
export TEST_FOLDER=`mktemp -d -p $ALL_TESTS_FOLDER test-$TEST_PREFIX.XXXXX`

# check if there are no more tests
zndsl_files=($BRIDGE_TESTS_FOLDER/$TEST_PREFIX-*.zndsl)
if [ ${#zndsl_files[@]} -eq 0 ]; then
break
fi

# start relay
if [ -f $BRIDGE_TESTS_FOLDER/$TEST_PREFIX-start-relay.sh ]; then
start_coproc "${BRIDGE_TESTS_FOLDER}/${TEST_PREFIX}-start-relay.sh" "relay"
RELAY_COPROC=$COPROC_PID
((TEST_COPROCS_COUNT++))
fi
# start tests
for zndsl_file in "${zndsl_files[@]}"; do
start_coproc "$ZOMBIENET_BINARY_PATH --provider native test $zndsl_file" "$zndsl_file"
echo -n "1">>$TEST_FOLDER/exit-sync
((TEST_COPROCS_COUNT++))
done
# wait until all tests are completed
relay_exited=0
for n in `seq 1 $TEST_COPROCS_COUNT`; do
if [ "$IS_BASH_5_1" -eq 1 ]; then
wait -n -p COPROC_PID
exit_code=$?
coproc_name=${TEST_COPROCS[$COPROC_PID, 0]}
coproc_log=${TEST_COPROCS[$COPROC_PID, 1]}
coproc_stdout=$(cat $coproc_log)
relay_exited=$(expr "${coproc_name}" == "relay")
else
wait -n
exit_code=$?
Expand All @@ -128,14 +128,16 @@ do

serban300 marked this conversation as resolved.
Show resolved Hide resolved
exit 1
fi

# if last test has exited, exit relay too
if [ $n -eq $(($TEST_COPROCS_COUNT - 1)) ] && [ $relay_exited -eq 0 ]; then
kill $RELAY_COPROC
break
fi
done

# proceed to next index
((TEST_INDEX++))
if [ "$TEST_INDEX" -ge "$TESTS_END" ]; then
break
fi

# kill relay here - it is started manually by tests
killall substrate-relay
done

echo "====================================================================="
Expand Down
4 changes: 3 additions & 1 deletion bridges/zombienet/scripts/invoke-script.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#!/bin/bash

INVOKE_LOG=`mktemp -p $TEST_FOLDER invoke.XXXXX`

pushd $POLKADOT_SDK_FOLDER/cumulus/scripts
./bridges_rococo_westend.sh $1
./bridges_rococo_westend.sh $1 >$INVOKE_LOG 2>&1
popd
7 changes: 7 additions & 0 deletions bridges/zombienet/scripts/start-relayer.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/bash

RELAY_LOG=`mktemp -p $TEST_FOLDER relay.XXXXX`

pushd $POLKADOT_SDK_FOLDER/cumulus/scripts
./bridges_rococo_westend.sh run-relay >$RELAY_LOG 2>&1&
popd
Loading
Loading