Skip to content

Commit

Permalink
Merge pull request #15 from noislabs/refactor-loop
Browse files Browse the repository at this point in the history
Refactor loop
  • Loading branch information
webmaster128 authored Jul 30, 2023
2 parents 5e12521 + 744eabe commit 06fa03b
Show file tree
Hide file tree
Showing 5 changed files with 173 additions and 108 deletions.
3 changes: 2 additions & 1 deletion deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ export { DirectSecp256k1HdWallet } from "npm:@cosmjs/proto-signing@^0.31.0";
export { assert, isDefined, sleep } from "npm:@cosmjs/utils@^0.31.0";
export type { Coin } from "npm:@cosmjs/amino@^0.31.0";
export type { MsgExecuteContractEncodeObject } from "npm:@cosmjs/cosmwasm-stargate@^0.31.0";
export type { SignerData } from "npm:@cosmjs/stargate@^0.31.0";

// drand
export type { ChainClient, ChainOptions } from "npm:drand-client@^1.2.0";
export type { ChainClient, ChainOptions, RandomnessBeacon } from "npm:drand-client@^1.2.0";
export { FastestNodeClient, watch } from "npm:drand-client@^1.2.0";
13 changes: 12 additions & 1 deletion drand_contract.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { MsgExecuteContract } from "npm:cosmjs-types/cosmwasm/wasm/v1/tx.js";

import { MsgExecuteContractEncodeObject, toUtf8 } from "./deps.ts";
import { CosmWasmClient, MsgExecuteContractEncodeObject, toUtf8 } from "./deps.ts";

export function makeAddBeaconMessage(
senderAddress: string,
Expand All @@ -22,3 +22,14 @@ export function makeAddBeaconMessage(
}),
};
}

export async function queryIsAllowListed(
client: CosmWasmClient,
contractAddress: string,
botAddress: string,
): Promise<boolean> {
const { listed } = await client.queryContractSmart(contractAddress, {
is_allow_listed: { bot: botAddress },
});
return listed;
}
12 changes: 12 additions & 0 deletions ibc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// deno-lint-ignore no-explicit-any
export function ibcPacketsSent(resultLogs: any) {
// deno-lint-ignore no-explicit-any
const allEvents = resultLogs.flatMap((log: any) => log.events);
// deno-lint-ignore no-explicit-any
const packetsEvents = allEvents.filter((e: any) => e.type === "send_packet");
// deno-lint-ignore no-explicit-any
const attributes = packetsEvents.flatMap((e: any) => e.attributes);
// deno-lint-ignore no-explicit-any
const packetsSentCount = attributes.filter((a: any) => a.key === "packet_sequence").length;
return packetsSentCount;
}
118 changes: 118 additions & 0 deletions loop.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { TxRaw } from "npm:cosmjs-types/cosmos/tx/v1beta1/tx.js";
import { publishedSince, timeOfRound } from "./drand.ts";
import { isMyGroup } from "./group.ts";
import {
assertIsDeliverTxSuccess,
calculateFee,
CosmWasmClient,
isDefined,
logs,
RandomnessBeacon,
SignerData,
SigningCosmWasmClient,
} from "./deps.ts";
import { makeAddBeaconMessage } from "./drand_contract.ts";
import { ibcPacketsSent } from "./ibc.ts";

interface Capture {
client: SigningCosmWasmClient;
broadcaster2: CosmWasmClient | null;
broadcaster3: CosmWasmClient | null;
botAddress: string;
drandAddress: string;
gasLimitAddBeacon: number;
gasPrice: string;
userAgent: string;
getNextSignData: () => SignerData;
}

export async function loop(
{
client,
broadcaster2,
broadcaster3,
botAddress,
drandAddress,
gasLimitAddBeacon,
gasPrice,
userAgent,
getNextSignData,
}: Capture,
beacon: RandomnessBeacon,
): Promise<boolean> {
const baseText = `➘ #${beacon.round} received after ${publishedSince(beacon.round)}ms`;
if (!isMyGroup(botAddress, beacon.round)) {
console.log(`${baseText}. Skipping.`);
return false;
} else {
console.log(`${baseText}. Submitting.`);
}

const broadcastTime = Date.now() / 1000;
const msg = makeAddBeaconMessage(botAddress, drandAddress, beacon);
const fee = calculateFee(gasLimitAddBeacon, gasPrice);
const memo = `Add round: ${beacon.round} (${userAgent})`;
const signData = getNextSignData(); // Do this the manual way to save one query
const signed = await client.sign(botAddress, [msg], fee, memo, signData);
const tx = Uint8Array.from(TxRaw.encode(signed).finish());

const p1 = client.broadcastTx(tx);
const p2 = broadcaster2?.broadcastTx(tx);
const p3 = broadcaster3?.broadcastTx(tx);

p1.then(
() => {
const t = publishedSince(beacon.round);
console.log(
`➚ #${beacon.round} broadcast 1 succeeded (${t}ms after publish time)`,
);
},
(err: unknown) => console.warn(`Broadcast 1 failed: ${err}`),
);
p2?.then(
() => {
const t = publishedSince(beacon.round);
console.log(
`➚ #${beacon.round} broadcast 2 succeeded (${t}ms after publish time)`,
);
},
(err: unknown) => console.warn(`Broadcast 2 failed: ${err}`),
);
p3?.then(
() => {
const t = publishedSince(beacon.round);
console.log(
`➚ #${beacon.round} broadcast 3 succeeded (${t}ms after publish time)`,
);
},
(err: unknown) => console.warn(`Broadcast 3 failed: ${err}`),
);

const result = await Promise.any([p1, p2, p3].filter(isDefined));
assertIsDeliverTxSuccess(result);
const parsedLogs = logs.parseRawLog(result.rawLog);
const jobs = ibcPacketsSent(parsedLogs);
const wasmEvent = result.events.find((event) => (event.type == "wasm"));
const points = wasmEvent?.attributes.find((attr) => attr.key.startsWith("reward_points"))
?.value;
const payout = wasmEvent?.attributes.find((attr) => attr.key.startsWith("reward_payout"))
?.value;
console.info(
`✔ #${beacon.round} committed (Points: ${points}; Payout: ${payout}; Gas: ${result.gasUsed}/${result.gasWanted}; Jobs processed: ${jobs}; Transaction: ${result.transactionHash})`,
);
const publishTime = timeOfRound(beacon.round);
const { block } = await client.forceGetTmClient().block(result.height);
const commitTime = block.header.time.getTime() / 1000; // seconds with fractional part
const diff = commitTime - publishTime;
console.info(
`Broadcast time (local): ${
broadcastTime.toFixed(2)
}; Drand publish time: ${publishTime}; Commit time: ${commitTime.toFixed(2)}; Diff: ${
diff.toFixed(
2,
)
}`,
);

return true;
}
135 changes: 29 additions & 106 deletions main.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
import { TxRaw } from "npm:cosmjs-types/cosmos/tx/v1beta1/tx.js";
import { drandOptions, drandUrls, publishedSince, timeOfRound } from "./drand.ts";
import { group, isMyGroup } from "./group.ts";
import { drandOptions, drandUrls } from "./drand.ts";
import { group } from "./group.ts";
import {
assert,
assertIsDeliverTxSuccess,
calculateFee,
Coin,
CosmWasmClient,
Decimal,
DirectSecp256k1HdWallet,
FastestNodeClient,
GasPrice,
isDefined,
logs,
SignerData,
SigningCosmWasmClient,
sleep,
watch,
} from "./deps.ts";
import { BeaconCache } from "./cache.ts";
import { makeAddBeaconMessage } from "./drand_contract.ts";
import { loop } from "./loop.ts";
import { queryIsAllowListed } from "./drand_contract.ts";

// Constants
const gasLimitRegister = 200_000;
Expand All @@ -34,31 +32,18 @@ function printableCoin(coin: Coin): string {
}
}

let nextSignData = {
let nextSignData: SignerData = {
chainId: "",
accountNumber: NaN,
sequence: NaN,
};

function getNextSignData() {
function getNextSignData(): SignerData {
const out = { ...nextSignData }; // copy values
nextSignData.sequence += 1;
return out;
}

// deno-lint-ignore no-explicit-any
export function ibcPacketsSent(resultLogs: any) {
// deno-lint-ignore no-explicit-any
const allEvents = resultLogs.flatMap((log: any) => log.events);
// deno-lint-ignore no-explicit-any
const packetsEvents = allEvents.filter((e: any) => e.type === "send_packet");
// deno-lint-ignore no-explicit-any
const attributes = packetsEvents.flatMap((e: any) => e.attributes);
// deno-lint-ignore no-explicit-any
const packetsSentCount = attributes.filter((a: any) => a.key === "packet_sequence").length;
return packetsSentCount;
}

if (import.meta.main) {
const { default: config } = await import("./config.json", {
assert: { type: "json" },
Expand Down Expand Up @@ -126,9 +111,7 @@ if (import.meta.main) {
await Promise.all([
sleep(500), // the min waiting time
(async function () {
const { listed } = await client.queryContractSmart(config.contract, {
is_allow_listed: { bot: botAddress },
});
const listed = await queryIsAllowListed(client, config.contract, botAddress);
console.info(`Bot allow listed for rewards: ${listed}`);
})(),
]);
Expand All @@ -143,87 +126,27 @@ if (import.meta.main) {
for await (const beacon of watch(fastestNodeClient, abortController)) {
cache.add(beacon.round, beacon.signature);

const baseText = `➘ #${beacon.round} received after ${publishedSince(beacon.round)}ms`;
if (!isMyGroup(botAddress, beacon.round)) {
console.log(`${baseText}. Skipping.`);
continue;
} else {
console.log(`${baseText}. Submitting.`);
}

const broadcastTime = Date.now() / 1000;
const msg = makeAddBeaconMessage(botAddress, config.contract, beacon);
const fee = calculateFee(gasLimitAddBeacon, config.gasPrice);
const memo = `Add round: ${beacon.round} (${userAgent})`;
const signData = getNextSignData(); // Do this the manual way to save one query
const signed = await client.sign(botAddress, [msg], fee, memo, signData);
const tx = Uint8Array.from(TxRaw.encode(signed).finish());

const p1 = client.broadcastTx(tx);
const p2 = broadcaster2?.broadcastTx(tx);
const p3 = broadcaster3?.broadcastTx(tx);

p1.then(
() => {
const t = publishedSince(beacon.round);
console.log(
`➚ #${beacon.round} broadcast 1 succeeded (${t}ms after publish time)`,
);
},
(err: unknown) => console.warn(`Broadcast 1 failed: ${err}`),
);
p2?.then(
() => {
const t = publishedSince(beacon.round);
console.log(
`➚ #${beacon.round} broadcast 2 succeeded (${t}ms after publish time)`,
);
},
(err: unknown) => console.warn(`Broadcast 2 failed: ${err}`),
);
p3?.then(
() => {
const t = publishedSince(beacon.round);
console.log(
`➚ #${beacon.round} broadcast 3 succeeded (${t}ms after publish time)`,
const didSubmit = await loop({
client,
broadcaster2,
broadcaster3,
getNextSignData,
gasLimitAddBeacon,
gasPrice: config.gasPrice,
botAddress,
drandAddress: config.contract,
userAgent,
}, beacon);

if (didSubmit) {
// Some seconds after the submission when things are idle, check and log
// the balance of the bot.
setTimeout(() => {
client.getBalance(botAddress, config.denom).then(
(balance) => console.log(`Balance: ${printableCoin(balance)}`),
(error: unknown) => console.warn(`Error getting bot balance: ${error}`),
);
},
(err: unknown) => console.warn(`Broadcast 3 failed: ${err}`),
);

const result = await Promise.any([p1, p2, p3].filter(isDefined));
assertIsDeliverTxSuccess(result);
const parsedLogs = logs.parseRawLog(result.rawLog);
const jobs = ibcPacketsSent(parsedLogs);
const wasmEvent = result.events.find((event) => (event.type == "wasm"));
const points = wasmEvent?.attributes.find((attr) => attr.key.startsWith("reward_points"))
?.value;
const payout = wasmEvent?.attributes.find((attr) => attr.key.startsWith("reward_payout"))
?.value;
console.info(
`✔ #${beacon.round} committed (Points: ${points}; Payout: ${payout}; Gas: ${result.gasUsed}/${result.gasWanted}; Jobs processed: ${jobs}; Transaction: ${result.transactionHash})`,
);
const publishTime = timeOfRound(beacon.round);
const { block } = await client.forceGetTmClient().block(result.height);
const commitTime = block.header.time.getTime() / 1000; // seconds with fractional part
const diff = commitTime - publishTime;
console.info(
`Broadcast time (local): ${
broadcastTime.toFixed(2)
}; Drand publish time: ${publishTime}; Commit time: ${commitTime.toFixed(2)}; Diff: ${
diff.toFixed(
2,
)
}`,
);

// Some seconds after the submission when things are idle, check and log
// the balance of the bot.
setTimeout(() => {
client.getBalance(botAddress, config.denom).then(
(balance) => console.log(`Balance: ${printableCoin(balance)}`),
(error: unknown) => console.warn(`Error getting bot balance: ${error}`),
);
}, 5_000);
});
}
}
}

0 comments on commit 06fa03b

Please sign in to comment.