Skip to content

Commit

Permalink
Merge pull request #16 from noislabs/query-is-incentivized
Browse files Browse the repository at this point in the history
Query and use IsIncentivized
  • Loading branch information
webmaster128 committed Jul 30, 2023
2 parents 06fa03b + 5cb419b commit 654830b
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 85 deletions.
13 changes: 10 additions & 3 deletions drand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,23 @@ export const drandUrls = [
const DRAND_GENESIS = 1677685200;
const DRAND_ROUND_LENGTH = 3;

// Time of round in seconds.
// Time of round in milliseconds.
//
// See TimeOfRound implementation: https://github.com/drand/drand/blob/eb36ba81e3f28c966f95bcd602f60e7ff8ef4c35/chain/time.go#L30-L33
export function timeOfRound(round: number): number {
return (DRAND_GENESIS + (round - 1) * DRAND_ROUND_LENGTH);
return (DRAND_GENESIS + (round - 1) * DRAND_ROUND_LENGTH) * 1000;
}

/**
* Time between publishing and now in milliseconds
*/
export function publishedSince(round: number): number {
return Date.now() - timeOfRound(round) * 1000;
return Date.now() - timeOfRound(round);
}

/**
* Time between now and publishing in milliseconds
*/
export function publishedIn(round: number): number {
return -publishedSince(round);
}
16 changes: 15 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 { CosmWasmClient, MsgExecuteContractEncodeObject, toUtf8 } from "./deps.ts";
import { assert, CosmWasmClient, MsgExecuteContractEncodeObject, toUtf8 } from "./deps.ts";

export function makeAddBeaconMessage(
senderAddress: string,
Expand Down Expand Up @@ -33,3 +33,17 @@ export async function queryIsAllowListed(
});
return listed;
}

export async function queryIsIncentivized(
client: CosmWasmClient,
contractAddress: string,
rounds: number[],
botAddress: string,
): Promise<boolean[]> {
const { incentivized } = await client.queryContractSmart(contractAddress, {
is_incentivized: { rounds, sender: botAddress },
});
// console.log(`#${rounds[0]} incentivized query returned at ${publishedSince(rounds[0])}ms`)
assert(Array.isArray(incentivized));
return incentivized;
}
17 changes: 0 additions & 17 deletions group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,3 @@ export function group(address: string): "A" | "B" {
if (hash % 2 == 0) return "A";
else return "B";
}

/**
* All rounds not ending on 0 are skipped. Rounds divisible by 20 to to group B and the others go to group A.
*/
export function eligibleGroup(round: number): "A" | "B" | null {
if (!round) throw new Error("Round is falsy");
if (!Number.isInteger(round)) throw new Error("Round value not an Integer");

if (round % 10 != 0) return null;

if (round % 20 == 0) return "B";
else return "A";
}

export function isMyGroup(address: string, round: number): boolean {
return eligibleGroup(round) == group(address);
}
54 changes: 1 addition & 53 deletions group_test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { assertEquals } from "https://deno.land/[email protected]/testing/asserts.ts";
import { eligibleGroup, group } from "./group.ts";
import { group } from "./group.ts";

Deno.test("group works", () => {
assertEquals(group("nois1ffy2rz96sjxzm2ezwkmvyeupktp7elt6w3xckt"), "B");
Expand All @@ -9,55 +9,3 @@ Deno.test("group works", () => {
assertEquals(group("nois1rw47dxvhw3ahdlcznvwpcz43cdq8l0832eg6re"), "A");
assertEquals(group("nois12a8yv4ndgnkygujj7cmmkfz2j9wjanezldwye0"), "B");
});

Deno.test("eligibleGroup works", () => {
assertEquals(eligibleGroup(1), null);
assertEquals(eligibleGroup(2), null);
assertEquals(eligibleGroup(3), null);
assertEquals(eligibleGroup(4), null);
assertEquals(eligibleGroup(5), null);
assertEquals(eligibleGroup(6), null);
assertEquals(eligibleGroup(7), null);
assertEquals(eligibleGroup(8), null);
assertEquals(eligibleGroup(9), null);
assertEquals(eligibleGroup(10), "A");
assertEquals(eligibleGroup(11), null);
assertEquals(eligibleGroup(12), null);
assertEquals(eligibleGroup(13), null);
assertEquals(eligibleGroup(14), null);
assertEquals(eligibleGroup(15), null);
assertEquals(eligibleGroup(16), null);
assertEquals(eligibleGroup(17), null);
assertEquals(eligibleGroup(18), null);
assertEquals(eligibleGroup(19), null);
assertEquals(eligibleGroup(20), "B");
assertEquals(eligibleGroup(21), null);
assertEquals(eligibleGroup(22), null);
assertEquals(eligibleGroup(23), null);
assertEquals(eligibleGroup(24), null);
assertEquals(eligibleGroup(25), null);
assertEquals(eligibleGroup(26), null);
assertEquals(eligibleGroup(27), null);
assertEquals(eligibleGroup(28), null);
assertEquals(eligibleGroup(29), null);
assertEquals(eligibleGroup(30), "A");
assertEquals(eligibleGroup(31), null);

assertEquals(eligibleGroup(111765), null);
assertEquals(eligibleGroup(111766), null);
assertEquals(eligibleGroup(111767), null);
assertEquals(eligibleGroup(111768), null);
assertEquals(eligibleGroup(111769), null);
assertEquals(eligibleGroup(111770), "A");
assertEquals(eligibleGroup(111771), null);
assertEquals(eligibleGroup(111772), null);
assertEquals(eligibleGroup(111773), null);
assertEquals(eligibleGroup(111774), null);
assertEquals(eligibleGroup(111775), null);
assertEquals(eligibleGroup(111776), null);
assertEquals(eligibleGroup(111777), null);
assertEquals(eligibleGroup(111778), null);
assertEquals(eligibleGroup(111779), null);
assertEquals(eligibleGroup(111780), "B");
assertEquals(eligibleGroup(111781), null);
});
23 changes: 16 additions & 7 deletions loop.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
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,
Expand All @@ -24,6 +23,7 @@ interface Capture {
gasPrice: string;
userAgent: string;
getNextSignData: () => SignerData;
incentivizedRounds: Map<number, Promise<boolean>>;
}

export async function loop(
Expand All @@ -37,23 +37,32 @@ export async function loop(
gasPrice,
userAgent,
getNextSignData,
incentivizedRounds,
}: Capture,
beacon: RandomnessBeacon,
): Promise<boolean> {
const baseText = `➘ #${beacon.round} received after ${publishedSince(beacon.round)}ms`;
if (!isMyGroup(botAddress, beacon.round)) {
console.log(`${baseText}. Skipping.`);
console.log(`➘ #${beacon.round} received after ${publishedSince(beacon.round)}ms`);

// We don't have evidence that this round is incentivized. This is no guarantee it did not
// get incentivized in the meantime, but we prefer to skip than risk the gas.
const isIncentivized = await incentivizedRounds.get(beacon.round);
if (!isIncentivized) {
console.log(`Skipping.`);
return false;
} else {
console.log(`${baseText}. Submitting.`);
}

// Use this log to ensure awaiting the isIncentivized query does not slow us down.
console.log(`♪ #${beacon.round} ready for signing after ${publishedSince(beacon.round)}ms`);

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);

// console.log(`♫ #${beacon.round} signed after ${publishedSince(beacon.round)}ms`);

const tx = Uint8Array.from(TxRaw.encode(signed).finish());

const p1 = client.broadcastTx(tx);
Expand Down Expand Up @@ -100,7 +109,7 @@ export async function loop(
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 publishTime = timeOfRound(beacon.round) / 1000;
const { block } = await client.forceGetTmClient().block(result.height);
const commitTime = block.header.time.getTime() / 1000; // seconds with fractional part
const diff = commitTime - publishTime;
Expand Down
31 changes: 27 additions & 4 deletions main.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { drandOptions, drandUrls } from "./drand.ts";
import { drandOptions, drandUrls, publishedIn } from "./drand.ts";
import { group } from "./group.ts";
import {
assert,
Expand All @@ -16,7 +16,7 @@ import {
} from "./deps.ts";
import { BeaconCache } from "./cache.ts";
import { loop } from "./loop.ts";
import { queryIsAllowListed } from "./drand_contract.ts";
import { queryIsAllowListed, queryIsIncentivized } from "./drand_contract.ts";

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

let nextSignData: SignerData = {
type Mutable<Type> = {
-readonly [Key in keyof Type]: Type[Key];
};

let nextSignData: Mutable<SignerData> = {
chainId: "",
accountNumber: NaN,
sequence: NaN,
Expand Down Expand Up @@ -119,12 +123,30 @@ if (import.meta.main) {
// Initialize local sign data
await resetSignData();

const incentivizedRounds = new Map<number, Promise<boolean>>();

const fastestNodeClient = new FastestNodeClient(drandUrls, drandOptions);
fastestNodeClient.start();
const cache = new BeaconCache(fastestNodeClient, 200 /* 10 min of beacons */);
const abortController = new AbortController();
for await (const beacon of watch(fastestNodeClient, abortController)) {
cache.add(beacon.round, beacon.signature);
const n = beacon.round; // n is the round we just received and process now
const m = n + 1; // m := n+1 refers to the next round in this current loop

cache.add(n, beacon.signature);

setTimeout(() => {
// This is called 100ms after publishing time (might be some ms later)
// From here we have ~300ms until the beacon comes in which should be
// enough for the query to finish. In case the query is not yet done,
// we can wait for the promise to be resolved.
// console.log(`Now : ${new Date().toISOString()}\nPublish time: ${new Date(timeOfRound(round)).toISOString()}`);
const promise = queryIsIncentivized(client, config.contract, [m], botAddress).then(
(incentivized) => !!incentivized[0],
(_err) => false,
);
incentivizedRounds.set(m, promise);
}, publishedIn(m) + 100);

const didSubmit = await loop({
client,
Expand All @@ -136,6 +158,7 @@ if (import.meta.main) {
botAddress,
drandAddress: config.contract,
userAgent,
incentivizedRounds,
}, beacon);

if (didSubmit) {
Expand Down

0 comments on commit 654830b

Please sign in to comment.