Continuously send messages to create load (#118)

* save

* Continuously send messages to create load

* add metrics
This commit is contained in:
Joe Howarth 2023-03-14 10:45:03 -07:00 committed by GitHub
parent 92dbc20c8b
commit eff19d28aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 1266 additions and 156 deletions

View File

@ -10,6 +10,13 @@ import "../interfaces/IWormholeReceiver.sol";
import "forge-std/console.sol";
interface Structs {
struct XAddress {
uint16 chainId;
bytes32 addr;
}
}
contract MockRelayerIntegration is IWormholeReceiver {
using BytesLib for bytes;
@ -28,7 +35,8 @@ contract MockRelayerIntegration is IWormholeReceiver {
// mapping of other MockRelayerIntegration contracts
mapping(uint16 => bytes32) registeredContracts;
bytes[] messages;
// bytes[] messages;
bytes[][] messageHistory;
struct FurtherInstructions {
bool keepSending;
@ -139,14 +147,17 @@ contract MockRelayerIntegration is IWormholeReceiver {
function receiveWormholeMessages(bytes[] memory wormholeObservations, bytes[] memory) public payable override {
// loop through the array of wormhole observations from the batch and store each payload
uint256 numObservations = wormholeObservations.length;
messages = new bytes[](wormholeObservations.length - 2);
bytes[] memory messages = new bytes[](wormholeObservations.length - 2);
uint16 emitterChainId;
for (uint256 i = 0; i < numObservations - 2; i++) {
(IWormhole.VM memory parsed, bool valid, string memory reason) =
wormhole.parseAndVerifyVM(wormholeObservations[i]);
require(valid, reason);
require(registeredContracts[parsed.emitterChainId] == parsed.emitterAddress);
emitterChainId = parsed.emitterChainId;
messages[i] = parsed.payload;
}
messageHistory.push(messages);
(IWormhole.VM memory parsed, bool valid, string memory reason) =
wormhole.parseAndVerifyVM(wormholeObservations[wormholeObservations.length - 2]);
@ -183,14 +194,21 @@ contract MockRelayerIntegration is IWormholeReceiver {
}
function getMessage() public view returns (bytes memory) {
if (messages.length == 0) {
if (messageHistory.length == 0 || messageHistory[messageHistory.length - 1].length == 0) {
return new bytes(0);
}
return messages[0];
return messageHistory[messageHistory.length - 1][0];
}
function getMessages() public view returns (bytes[] memory) {
return messages;
if (messageHistory.length == 0 || messageHistory[messageHistory.length - 1].length == 0) {
return new bytes[](0);
}
return messageHistory[messageHistory.length - 1];
}
function getMessageHistory() public view returns (bytes[][] memory) {
return messageHistory;
}
function clearPayload(bytes32 hash) public {
@ -210,6 +228,13 @@ contract MockRelayerIntegration is IWormholeReceiver {
registeredContracts[chainId] = emitterAddress;
}
function registerEmitters(Structs.XAddress[] calldata emitters) public {
require(msg.sender == owner);
for (uint256 i = 0; i < emitters.length; i++) {
registeredContracts[emitters[i].chainId] = emitters[i].addr;
}
}
function encodeFurtherInstructions(FurtherInstructions memory furtherInstructions)
public
view

View File

@ -23,7 +23,7 @@ import {IWormhole} from "../contracts/interfaces/IWormhole.sol";
import {WormholeSimulator, FakeWormholeSimulator} from "./WormholeSimulator.sol";
import {IWormholeReceiver} from "../contracts/interfaces/IWormholeReceiver.sol";
import {AttackForwardIntegration} from "../contracts/mock/AttackForwardIntegration.sol";
import {MockRelayerIntegration} from "../contracts/mock/MockRelayerIntegration.sol";
import {MockRelayerIntegration, Structs} from "../contracts/mock/MockRelayerIntegration.sol";
import "../contracts/libraries/external/BytesLib.sol";
import "forge-std/Test.sol";
@ -255,6 +255,9 @@ contract TestCoreRelayer is Test {
);
map[i].relayProvider.updateMaximumBudget(j, maxBudget);
map[i].integration.registerEmitter(j, bytes32(uint256(uint160(address(map[j].integration)))));
Structs.XAddress[] memory addresses = new Structs.XAddress[](1);
addresses[0] = Structs.XAddress(j, bytes32(uint256(uint160(address(map[j].integration)))));
map[i].integration.registerEmitters(addresses);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -7,6 +7,8 @@
"@poanet/solidity-flattener": "^3.0.8",
"@typechain/ethers-v5": "^10.1.1",
"@types/chai": "^4.3.3",
"@types/koa": "^2.13.5",
"@types/koa-router": "^7.4.4",
"@types/mocha": "^9.1.1",
"chai": "^4.3.6",
"dotenv": "^16.0.3",
@ -19,6 +21,7 @@
"build": "forge build -o build --via-ir",
"forge-test": "forge test -vvv --via-ir",
"integration-test": "bash shell-scripts/run_integration_tests.sh",
"load-test": "ENV=testnet ts-node ts-scripts/mockIntegrations/loadGeneration.ts",
"typechain": "bash ../sdk/scripts/make_ethers_types.sh",
"flatten": "mkdir -p node_modules/@poanet/solidity-flattener/contracts && cp -r contracts/* node_modules/@poanet/solidity-flattener/contracts/ && poa-solidity-flattener",
"deployAndConfigureTilt": "ENV=tilt bash ./ts-scripts/shell/deployConfigureTest.sh",
@ -32,6 +35,9 @@
"@improbable-eng/grpc-web-node-http-transport": "^0.15.0",
"elliptic": "^6.5.4",
"jsonfile": "^6.1.0",
"koa": "^2.14.1",
"koa-router": "^12.0.0",
"prom-client": "^14.2.0",
"typescript": "^4.8.3"
}
}

View File

@ -48,23 +48,23 @@
"mockIntegrations": [
{
"chainId": 6,
"address": "0x0819CaB27473e585DD28c0D908DCc218dE4cB05C"
"address": "0xaC19a80C3409F8296043FC24F955790164Ccb15E"
},
{
"chainId": 14,
"address": "0xB7c205B9E147e4371E28fAe3F9530566de374Dcc"
"address": "0xC02d04f9067Ede6fF4CaDC31233c795ec982eFd7"
},
{
"chainId": 4,
"address": "0xD1d6Ae0149A460204967F91Cb358C3581D4F1692"
"address": "0x3C1cd47E8D9aa563B238841B8D95c80d11E6B3A6"
},
{
"chainId": 5,
"address": "0xE8cfD947Eb14b0F08DEE7bf0E352480164ED2e6e"
"address": "0xf4092f14f74E119fae5343d08C3A253FB9D2e8d6"
},
{
"chainId": 16,
"address": "0x87c26Ad201cfC0A969911143985B39855C9CB704"
"address": "0x0896fB74CaBeDb603E3A6EdA36f40af00010Bf4c"
}
]
}
}

View File

@ -1,7 +1,13 @@
import { init, loadChains, writeOutputFiles, getMockIntegration } from "../helpers/env"
import {
init,
loadChains,
writeOutputFiles,
getMockIntegration,
Deployment,
} from "../helpers/env"
import { deployMockIntegration } from "../helpers/deployments"
import { BigNumber } from "ethers"
import { tryNativeToHexString } from "@certusone/wormhole-sdk"
import { BigNumber, BigNumberish, BytesLike } from "ethers"
import { tryNativeToHexString, tryNativeToUint8Array } from "@certusone/wormhole-sdk"
import { MockRelayerIntegration__factory } from "../../../sdk/src"
import { wait } from "../helpers/utils"
@ -11,13 +17,12 @@ const chains = loadChains()
async function run() {
console.log("Start!")
const output: any = {
mockIntegrations: [],
const output = {
mockIntegrations: [] as Deployment[],
}
for (let i = 0; i < chains.length; i++) {
const mockIntegration = await deployMockIntegration(chains[i])
output.mockIntegrations.push(mockIntegration)
}
@ -25,18 +30,17 @@ async function run() {
for (let i = 0; i < chains.length; i++) {
console.log(`Registering emitters for chainId ${chains[i].chainId}`)
// note: must use useLastRun = true
const mockIntegration = getMockIntegration(chains[i])
for (let j = 0; j < chains.length; j++) {
console.log(`Registering emitter ${chains[j].chainId}`)
const secondMockIntegration = output.mockIntegrations[j]
await mockIntegration
.registerEmitter(
secondMockIntegration.chainId,
"0x" + tryNativeToHexString(secondMockIntegration.address, "ethereum"),
{ gasLimit: 500000 }
)
.then(wait)
}
const arg: {
chainId: BigNumberish
addr: BytesLike
}[] = chains.map((c, j) => ({
chainId: c.chainId,
addr: "0x" + tryNativeToHexString(output.mockIntegrations[j].address, "ethereum"),
}))
await mockIntegration.registerEmitters(arg, { gasLimit: 500000 }).then(wait)
}
}

View File

@ -0,0 +1,110 @@
import { ChainInfo, init, loadChains } from "../helpers/env"
import { sendMessage, sleep } from "./messageUtils"
import { Counter, register } from "prom-client"
import Koa from "koa"
import Router from "koa-router"
const promPort = 3000
init()
const chains = loadChains()
export const undeliveredMessages = new Counter({
name: "undelivered_messages",
help: "Counter for number of messages that were not delivered",
labelNames: ["sourceChain", "targetChain"],
})
export const deliveredMessages = new Counter({
name: "delivered_messages",
help: "Counter for number of messages that were successfully delivered",
labelNames: ["sourceChain", "targetChain"],
})
async function run() {
const chainIntervalIdx = process.argv.findIndex((arg) => arg === "--chainInterval")
const salvoIntervalIdx = process.argv.findIndex((arg) => arg === "--salvoInterval")
const chainInterval =
chainIntervalIdx !== -1 ? Number(process.argv[chainIntervalIdx + 1]) : 5_000
const salvoInterval =
salvoIntervalIdx !== -1 ? Number(process.argv[salvoIntervalIdx + 1]) : 60_000
console.log(`chainInterval: ${chainInterval}`)
console.log(`salvoInterval: ${salvoInterval}`)
if (process.argv.find((arg) => arg === "--per-chain")) {
await perChain(chainInterval, salvoInterval)
} else {
await matrix(chainInterval, salvoInterval)
}
}
async function perChain(chainIntervalMS: number, salvoIntervalMS: number) {
console.log(`Sending test messages to and from each chain...`)
for (let salvo = 0; true; salvo++) {
console.log("")
console.log(`Sending salvo ${salvo}`)
for (let i = 0; i < chains.length; ++i) {
const j = i === 0 ? chains.length - 1 : 0
await sendMessageAndReportMetric(chains[i], chains[j], chainIntervalMS)
}
await sleep(salvoIntervalMS)
}
}
async function matrix(chainIntervalMS: number, salvoIntervalMS: number) {
console.log(`Sending test messages to and from every combination of chains...`)
for (let salvo = 0; true; salvo++) {
console.log("")
console.log(`Sending salvo ${salvo}`)
for (let i = 0; i < chains.length; ++i) {
for (let j = 0; i < chains.length; ++i) {
await sendMessageAndReportMetric(chains[i], chains[j], chainIntervalMS)
}
}
await sleep(salvoIntervalMS)
}
}
async function sendMessageAndReportMetric(
sourceChain: ChainInfo,
targetChain: ChainInfo,
chainInterval: number
) {
try {
const notFound = await sendMessage(sourceChain, targetChain, false, true)
const counter = notFound ? undeliveredMessages : deliveredMessages
counter
.labels({
sourceChain: sourceChain.chainId,
targetChain: targetChain.chainId,
})
.inc()
} catch (e) {
console.error(e)
}
await sleep(chainInterval)
}
async function launchMetricsServer() {
const app = new Koa()
const router = new Router()
router.get("/metrics", async (ctx, next) => {
let metrics = await register.metrics()
ctx.body = metrics
})
app.use(router.allowedMethods())
app.use(router.routes())
app.listen(promPort, () =>
console.log(`Prometheus metrics running on port ${promPort}`)
)
}
console.log("Start!")
run().then(() => console.log("Done!"))

View File

@ -1,107 +1,18 @@
import * as wh from "@certusone/wormhole-sdk"
import { Implementation__factory } from "@certusone/wormhole-sdk/lib/cjs/ethers-contracts"
import { LogMessagePublishedEvent } from "../../../sdk/src"
import {
ChainInfo,
getCoreRelayer,
getCoreRelayerAddress,
getMockIntegration,
getMockIntegrationAddress,
getRelayProviderAddress,
init,
loadChains,
} from "../helpers/env"
import * as grpcWebNodeHttpTransport from "@improbable-eng/grpc-web-node-http-transport"
import { sendMessage } from "./messageUtils"
init()
const chains = loadChains()
async function sendMessage(
sourceChain: ChainInfo,
targetChain: ChainInfo,
fetchSignedVaa: boolean = false,
queryMessageOnTarget: boolean = false
) {
console.log(
`Sending message from chain ${sourceChain.chainId} to ${targetChain.chainId}...`
)
const sourceRelayer = getCoreRelayer(sourceChain)
// todo: remove
const registeredChain = await sourceRelayer.registeredCoreRelayerContract(
sourceChain.chainId
)
console.log("The source chain should be registered to itself")
console.log(registeredChain)
console.log(getCoreRelayerAddress(sourceChain))
console.log("")
const defaultRelayerProvider = await sourceRelayer.getDefaultRelayProvider()
console.log("Default relay provider should be this chains relayProvider ")
console.log(defaultRelayerProvider)
console.log(getRelayProviderAddress(sourceChain))
console.log("")
const relayQuote = await (
await sourceRelayer.quoteGas(
targetChain.chainId,
2000000,
await sourceRelayer.getDefaultRelayProvider()
)
).add(10000000000)
console.log("relay quote: " + relayQuote)
const mockIntegration = getMockIntegration(sourceChain)
const targetAddress = getMockIntegrationAddress(targetChain)
const sentMessage = Buffer.from("Hello World: " + String(Math.ceil(Math.random() * 100)))
const tx = await mockIntegration.sendMessage(
sentMessage,
targetChain.chainId,
targetAddress,
{
gasLimit: 1000000,
value: relayQuote,
}
)
const rx = await tx.wait()
const sequences = wh.parseSequencesFromLogEth(rx, sourceChain.wormholeAddress)
console.log("Tx hash: ", rx.transactionHash)
console.log(`Sequences: ${sequences}`)
if (fetchSignedVaa) {
for (let i = 0; i < 120; i++) {
try {
const vaa1 = await fetchVaaFromLog(rx.logs[0], sourceChain.chainId)
console.log(vaa1)
const vaa2 = await fetchVaaFromLog(rx.logs[1], sourceChain.chainId)
console.log(vaa2)
break
} catch (e) {
console.error(`${i} seconds`)
if (i === 0) {
console.error(e)
}
}
await new Promise((resolve) => setTimeout(resolve, 1_000))
}
}
if (queryMessageOnTarget) {
await new Promise<void>((resolve) => setTimeout(() => resolve(), 5000))
const targetIntegration = getMockIntegration(targetChain)
const message = await targetIntegration.getMessage()
const messageParsed = Buffer.from(message, "hex").toString("utf-8")
console.log(`Sent message: ${sentMessage}`)
console.log(`Received message: ${message}`)
}
console.log("")
}
async function run() {
console.log(process.argv)
const fetchSignedVaa = !!process.argv.find((arg) => arg === "--fetchSignedVaa")
const queryMessageOnTarget = !!process.argv.find(
(arg) => arg === "--queryMessageOnTarget"
const queryMessageOnTarget = !process.argv.find(
(arg) => arg === "--noQueryMessageOnTarget"
)
if (process.argv[2] === "--from" && process.argv[4] === "--to") {
await sendMessage(
@ -148,35 +59,3 @@ function getChainById(id: number | string): ChainInfo {
console.log("Start!")
run().then(() => console.log("Done!"))
export async function encodeEmitterAddress(
myChainId: wh.ChainId,
emitterAddressStr: string
): Promise<string> {
if (myChainId === wh.CHAIN_ID_SOLANA || myChainId === wh.CHAIN_ID_PYTHNET) {
return wh.getEmitterAddressSolana(emitterAddressStr)
}
if (wh.isTerraChain(myChainId)) {
return wh.getEmitterAddressTerra(emitterAddressStr)
}
if (wh.isEVMChain(myChainId)) {
return wh.getEmitterAddressEth(emitterAddressStr)
}
throw new Error(`Unrecognized wormhole chainId ${myChainId}`)
}
function fetchVaaFromLog(bridgeLog: any, chainId: wh.ChainId): Promise<wh.SignedVaa> {
const iface = Implementation__factory.createInterface()
const log = iface.parseLog(bridgeLog) as unknown as LogMessagePublishedEvent
const sequence = log.args.sequence.toString()
const emitter = wh.tryNativeToHexString(log.args.sender, "ethereum")
return wh
.getSignedVAA(
"https://wormhole-v2-testnet-api.certus.one",
chainId,
emitter,
sequence,
{ transport: grpcWebNodeHttpTransport.NodeHttpTransport() }
)
.then((r) => r.vaaBytes)
}

View File

@ -0,0 +1,141 @@
import * as wh from "@certusone/wormhole-sdk"
import { Implementation__factory } from "@certusone/wormhole-sdk/lib/cjs/ethers-contracts"
import { LogMessagePublishedEvent } from "../../../sdk/src"
import {
ChainInfo,
getCoreRelayer,
getMockIntegration,
getMockIntegrationAddress,
} from "../helpers/env"
import * as grpcWebNodeHttpTransport from "@improbable-eng/grpc-web-node-http-transport"
import { ethers } from "ethers"
export async function sendMessage(
sourceChain: ChainInfo,
targetChain: ChainInfo,
fetchSignedVaa: boolean = false,
queryMessageOnTargetFlag: boolean = true
): Promise<boolean | undefined> {
console.log(
`Sending message from chain ${sourceChain.chainId} to ${targetChain.chainId}...`
)
const sourceRelayer = getCoreRelayer(sourceChain)
const relayQuote = await (
await sourceRelayer.quoteGas(
targetChain.chainId,
2000000,
await sourceRelayer.getDefaultRelayProvider()
)
).add(10000000000)
console.log("relay quote: " + relayQuote)
const mockIntegration = getMockIntegration(sourceChain)
const targetAddress = getMockIntegrationAddress(targetChain)
const sentMessage = "ID: " + String(Math.ceil(Math.random() * 10000))
console.log(`Sent message: ${sentMessage}`)
const tx = await mockIntegration.sendMessage(
Buffer.from(sentMessage),
targetChain.chainId,
targetAddress,
{
gasLimit: 1000000,
value: relayQuote,
}
)
const rx = await tx.wait()
const sequences = wh.parseSequencesFromLogEth(rx, sourceChain.wormholeAddress)
console.log("Tx hash: ", rx.transactionHash)
console.log(`Sequences: ${sequences}`)
if (fetchSignedVaa) {
for (let i = 0; i < 120; i++) {
try {
const vaa1 = await fetchVaaFromLog(rx.logs[0], sourceChain.chainId)
console.log(vaa1)
const vaa2 = await fetchVaaFromLog(rx.logs[1], sourceChain.chainId)
console.log(vaa2)
break
} catch (e) {
console.error(`${i} seconds`)
if (i === 0) {
console.error(e)
}
}
await new Promise((resolve) => setTimeout(resolve, 1_000))
}
}
if (queryMessageOnTargetFlag) {
return await queryMessageOnTarget(sentMessage, targetChain)
}
console.log("")
}
async function queryMessageOnTarget(
sentMessage: string,
targetChain: ChainInfo
): Promise<boolean> {
let messageHistory: string[][] = []
const targetIntegration = getMockIntegration(targetChain)
let notFound = true
for (let i = 0; i < 20 && notFound; i++) {
await new Promise<void>((resolve) => setTimeout(() => resolve(), 2000))
const messageHistoryResp = await targetIntegration.getMessageHistory()
messageHistory = messageHistoryResp.map((messages) =>
messages.map((message) => ethers.utils.toUtf8String(message))
)
notFound = !messageHistory
.slice(messageHistory.length - 20)
.find((msgs) => msgs.find((m) => m === sentMessage))
process.stdout.write("..")
}
console.log("")
if (notFound) {
console.log(`ERROR: Did not receive message!`)
return false
}
console.log(`Received message: ${messageHistory[messageHistory.length - 1][0]}`)
console.log(`Received messageHistory: ${messageHistory.join(", ")}`)
return true
}
export async function encodeEmitterAddress(
myChainId: wh.ChainId,
emitterAddressStr: string
): Promise<string> {
if (myChainId === wh.CHAIN_ID_SOLANA || myChainId === wh.CHAIN_ID_PYTHNET) {
return wh.getEmitterAddressSolana(emitterAddressStr)
}
if (wh.isTerraChain(myChainId)) {
return wh.getEmitterAddressTerra(emitterAddressStr)
}
if (wh.isEVMChain(myChainId)) {
return wh.getEmitterAddressEth(emitterAddressStr)
}
throw new Error(`Unrecognized wormhole chainId ${myChainId}`)
}
function fetchVaaFromLog(bridgeLog: any, chainId: wh.ChainId): Promise<wh.SignedVaa> {
const iface = Implementation__factory.createInterface()
const log = iface.parseLog(bridgeLog) as unknown as LogMessagePublishedEvent
const sequence = log.args.sequence.toString()
const emitter = wh.tryNativeToHexString(log.args.sender, "ethereum")
return wh
.getSignedVAA(
"https://wormhole-v2-testnet-api.certus.one",
chainId,
emitter,
sequence,
{ transport: grpcWebNodeHttpTransport.NodeHttpTransport() }
)
.then((r) => r.vaaBytes)
}
export async function sleep(ms: number) {
return new Promise((r) => setTimeout(r, ms))
}

View File

@ -4,7 +4,6 @@ import {
assertInt,
CommonPluginEnv,
ContractFilter,
dbg,
getScopedLogger,
ParsedVaaWithBytes,
parseVaaWithBytes,
@ -20,7 +19,6 @@ import { Logger } from "winston"
import { PluginError } from "./utils"
import { SignedVaa } from "@certusone/wormhole-sdk"
import {
IWormhole,
IWormhole__factory,
RelayProvider__factory,
LogMessagePublishedEvent,
@ -36,7 +34,6 @@ import * as ethers from "ethers"
import { Implementation__factory } from "@certusone/wormhole-sdk/lib/cjs/ethers-contracts"
import * as grpcWebNodeHttpTransport from "@improbable-eng/grpc-web-node-http-transport"
import { retryAsyncUntilDefined } from "ts-retry/lib/cjs/retry"
import { hexToNativeStringAlgorand } from "@certusone/wormhole-sdk/lib/cjs/algorand"
let PLUGIN_NAME: string = "GenericRelayerPlugin"