feat: initialize Kurdistan SDK - independent fork of Polkadot SDK

This commit is contained in:
2025-12-13 15:44:15 +03:00
commit e4778b4576
6838 changed files with 1847450 additions and 0 deletions
@@ -0,0 +1,25 @@
async function run(nodeName, networkInfo, args) {
const {wsUri, userDefinedTypes} = networkInfo.nodesByName[nodeName];
const api = await zombie.connect(wsUri, userDefinedTypes);
// TODO: could be replaced with https://github.com/polkadot-js/api/issues/4930 (depends on metadata v15) later
const bridgedChainName = args[0];
const expectedBridgedChainHeaderNumber = Number(args[1]);
const runtimeApiMethod = bridgedChainName + "FinalityApi_best_finalized";
while (true) {
const encodedBestFinalizedHeaderId = await api.rpc.state.call(runtimeApiMethod, []);
const bestFinalizedHeaderId = api.createType("Option<BpRuntimeHeaderId>", encodedBestFinalizedHeaderId);
if (bestFinalizedHeaderId.isSome) {
const bestFinalizedHeaderNumber = Number(bestFinalizedHeaderId.unwrap().toHuman()[0]);
if (bestFinalizedHeaderNumber > expectedBridgedChainHeaderNumber) {
return bestFinalizedHeaderNumber;
}
}
// else sleep and retry
await new Promise((resolve) => setTimeout(resolve, 6000));
}
}
module.exports = { run }
@@ -0,0 +1,6 @@
module.exports = {
grandpaPalletName: "bridgePezkuwichainGrandpa",
teyrchainsPalletName: "bridgePezkuwichainTeyrchains",
messagesPalletName: "bridgePezkuwichainMessages",
bridgedBridgeHubParaId: 1013,
}
@@ -0,0 +1,6 @@
module.exports = {
grandpaPalletName: "bridgeZagrosGrandpa",
teyrchainsPalletName: "bridgeZagrosTeyrchains",
messagesPalletName: "bridgeZagrosMessages",
bridgedBridgeHubParaId: 1002,
}
@@ -0,0 +1,34 @@
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 totalTeyrchainHeaders = 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.countGrandpaHeaderImports(bridgedChain, currentEvents);
totalTeyrchainHeaders += await utils.countTeyrchainHeaderImports(bridgedChain, currentEvents);
});
// wait given time
await new Promise(resolve => setTimeout(resolve, exitAfterSeconds * 1000));
// if we haven't seen many (>1) new GRANDPA or teyrchain headers => fail
if (totalGrandpaHeaders <= 1) {
throw new Error("No bridged relay chain headers imported");
}
if (totalTeyrchainHeaders <= 1) {
throw new Error("No bridged teyrchain headers imported");
}
}
module.exports = { run }
@@ -0,0 +1,12 @@
async function run(nodeName, networkInfo, args) {
const {wsUri, userDefinedTypes} = networkInfo.nodesByName[nodeName];
const api = await zombie.connect(wsUri, userDefinedTypes);
const accountAddress = args[0];
const accountData = await api.query.system.account(accountAddress);
const accountBalance = accountData.data['free'];
console.log("Balance of " + accountAddress + ": " + accountBalance);
return accountBalance;
}
module.exports = {run}
@@ -0,0 +1,21 @@
async function run(nodeName, networkInfo, args) {
const {wsUri, userDefinedTypes} = networkInfo.nodesByName[nodeName];
const api = await zombie.connect(wsUri, userDefinedTypes);
const accountAddress = args[0];
const expectedIncrease = BigInt(args[1]);
const initialAccountData = await api.query.system.account(accountAddress);
const initialAccountBalance = initialAccountData.data['free'];
while (true) {
const accountData = await api.query.system.account(accountAddress);
const accountBalance = accountData.data['free'];
if (accountBalance > initialAccountBalance + expectedIncrease) {
return accountBalance;
}
// else sleep and retry
await new Promise((resolve) => setTimeout(resolve, 6000));
}
}
module.exports = {run}
@@ -0,0 +1,85 @@
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);
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 teyrchain headers
await utils.ensureOnlyMandatoryGrandpaHeadersImported(
bridgedChain,
apiAtParent,
apiAtCurrent,
currentEvents,
);
await utils.ensureOnlyInitialTeyrchainHeaderImported(
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 teyrchain header
const newTeyrchainHeaders = utils.countTeyrchainHeaderImports(bridgedChain, currentEvents);
if (newTeyrchainHeaders > 1) {
utils.logEvents(currentEvents);
throw new Error("Unexpected teyrchain header import: " + newTeyrchainHeaders + " / " + 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}
@@ -0,0 +1,24 @@
async function run(nodeName, networkInfo, args) {
const {wsUri, userDefinedTypes} = networkInfo.nodesByName[nodeName];
const api = await zombie.connect(wsUri, userDefinedTypes);
// TODO: could be replaced with https://github.com/polkadot-js/api/issues/4930 (depends on metadata v15) later
const relayerAccountAddress = args.relayerAccountAddress;
const reward = args.reward;
const expectedRelayerReward = BigInt(args.expectedRelayerReward);
console.log("Waiting rewards for relayerAccountAddress: " + relayerAccountAddress + " expecting minimal rewards at least: " + expectedRelayerReward + " for " + JSON.stringify(reward));
while (true) {
const relayerReward = await api.query.bridgeRelayers.relayerRewards(relayerAccountAddress, reward);
if (relayerReward.isSome) {
const relayerRewardBalance = relayerReward.unwrap().toBigInt();
if (relayerRewardBalance > expectedRelayerReward) {
return relayerRewardBalance;
}
}
// else sleep and retry
await new Promise((resolve) => setTimeout(resolve, 6000));
}
}
module.exports = { run }
@@ -0,0 +1,103 @@
module.exports = {
logEvents: function(events) {
let stringifiedEvents = "";
events.forEach((record) => {
if (stringifiedEvents != "") {
stringifiedEvents += ", ";
}
stringifiedEvents += record.event.section + "::" + record.event.method;
});
console.log("Block events: " + stringifiedEvents);
},
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,
);
},
countTeyrchainHeaderImports: function(bridgedChain, events) {
return events.reduce(
(count, record) => {
const { event } = record;
if (event.section == bridgedChain.teyrchainsPalletName && event.method == "UpdatedTeyrchainHead") {
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 apiAtParent.query[bridgedChain.grandpaPalletName].currentAuthoritySet();
const authoritySetIdAtParent = authoritySetAtParent["setId"];
// now read the id of bridged relay chain GRANDPA authorities set at current block
const authoritySetAtCurrent = await apiAtCurrent.query[bridgedChain.grandpaPalletName].currentAuthoritySet();
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;
},
ensureOnlyInitialTeyrchainHeaderImported: async function(
bridgedChain,
apiAtParent,
apiAtCurrent,
currentEvents,
) {
// remember whether we already know bridged teyrchain header at a parent block
const bestBridgedTeyrchainHeader = await apiAtParent.query[bridgedChain.teyrchainsPalletName].parasInfo(bridgedChain.bridgedBridgeHubParaId);;
const hasBestBridgedTeyrchainHeader = bestBridgedTeyrchainHeader.isSome;
// we expect to see: no more than `1` bridged teyrchain header if there were no teyrchain header before.
const maxNewTeyrchainHeaders = hasBestBridgedTeyrchainHeader ? 0 : 1;
const newTeyrchainHeaders = module.exports.countTeyrchainHeaderImports(bridgedChain, currentEvents);
// check that our assumptions are correct
if (newTeyrchainHeaders > maxNewTeyrchainHeaders) {
module.exports.logEvents(currentEvents);
throw new Error("Unexpected teyrchain header import: " + newTeyrchainHeaders + " / " + maxNewTeyrchainHeaders);
}
return hasBestBridgedTeyrchainHeader;
},
}
@@ -0,0 +1,22 @@
async function run(nodeName, networkInfo, args) {
const {wsUri, userDefinedTypes} = networkInfo.nodesByName[nodeName];
const api = await zombie.connect(wsUri, userDefinedTypes);
const sibling = args[0];
while (true) {
const messagingStateAsObj = await api.query.teyrchainSystem.relevantMessagingState();
const messagingState = api.createType("Option<CumulusPalletTeyrchainSystemRelayStateSnapshotMessagingStateSnapshot>", messagingStateAsObj);
if (messagingState.isSome) {
const egressChannels = messagingState.unwrap().egressChannels;
if (egressChannels.find(x => x[0] == sibling)) {
return;
}
}
// else sleep and retry
await new Promise((resolve) => setTimeout(resolve, 6000));
}
}
module.exports = { run }
@@ -0,0 +1,24 @@
async function run(nodeName, networkInfo, args) {
const {wsUri, userDefinedTypes} = networkInfo.nodesByName[nodeName];
const api = await zombie.connect(wsUri, userDefinedTypes);
// TODO: could be replaced with https://github.com/polkadot-js/api/issues/4930 (depends on metadata v15) later
const accountAddress = args.accountAddress;
const expectedAssetId = args.expectedAssetId;
const expectedAssetBalance = BigInt(args.expectedAssetBalance);
while (true) {
const foreignAssetAccount = await api.query.foreignAssets.account(expectedAssetId, accountAddress);
if (foreignAssetAccount.isSome) {
const foreignAssetAccountBalance = foreignAssetAccount.unwrap().balance.toBigInt();
if (foreignAssetAccountBalance > expectedAssetBalance) {
return foreignAssetAccountBalance;
}
}
// else sleep and retry
await new Promise((resolve) => setTimeout(resolve, 6000));
}
}
module.exports = { run }