diff --git a/config.example.json b/config.example.json index 1962cc3..e062c89 100644 --- a/config.example.json +++ b/config.example.json @@ -2,7 +2,8 @@ "rpcEndpoint": "https://nois-testnet-rpc.itrocket.net:443", "rpcEndpoint2": "", "rpcEndpoint3": "", - "contract": "nois14xef285hz5cx5q9hh32p9nztu3cct4g44sxjgx3dmftt2tj2rweqkjextk", + "drandAddress": "nois14xef285hz5cx5q9hh32p9nztu3cct4g44sxjgx3dmftt2tj2rweqkjextk", + "gatewayAddress": "", "mnemonic": "", "denom": "unois", "gasPrice": "0.05unois", diff --git a/deps.ts b/deps.ts index 5613e84..480a099 100644 --- a/deps.ts +++ b/deps.ts @@ -12,7 +12,7 @@ export { } from "npm:@cosmjs/stargate@^0.31.0"; export { sha256 } from "npm:@cosmjs/crypto@^0.31.0"; export { toUtf8 } from "npm:@cosmjs/encoding@^0.31.0"; -export { Decimal } from "npm:@cosmjs/math@^0.31.0"; +export { Decimal, Uint53 } from "npm:@cosmjs/math@^0.31.0"; export { DirectSecp256k1HdWallet } from "npm:@cosmjs/proto-signing@^0.31.0"; export { Tendermint34Client, Tendermint37Client } from "npm:@cosmjs/tendermint-rpc@^0.31.0"; export { assert, isDefined, sleep } from "npm:@cosmjs/utils@^0.31.0"; diff --git a/drand.ts b/drand.ts index 791aad7..db2471c 100644 --- a/drand.ts +++ b/drand.ts @@ -1,6 +1,6 @@ import { ChainOptions } from "./deps.ts"; -const chainHash = "dbd506d6ef76e5f386f41c651dcb808c5bcbd75471cc4eafa3f4df7ad4e4c493"; +export const chainHash = "dbd506d6ef76e5f386f41c651dcb808c5bcbd75471cc4eafa3f4df7ad4e4c493"; const publicKey = "a0b862a7527fee3a731bcb59280ab6abd62d5c0b6ea03dc4ddf6612fdfc9d01f01c31542541771903475eb1ec6615f8d0df0b8b6dce385811d6dcf8cbefb8759e5e616a3dfd054c928940766d9a5b9db91e3b697e5d70a975181e007f87fca5e"; diff --git a/jobs.ts b/jobs.ts new file mode 100644 index 0000000..352f2f4 --- /dev/null +++ b/jobs.ts @@ -0,0 +1,57 @@ +import { assert, CosmWasmClient, Uint53 } from "./deps.ts"; +import { chainHash, timeOfRound } from "./drand.ts"; + +interface Job { + /// A RNG specific randomness source identifier, e.g. `drand::` + source_id: string; + // The channel the job came from and we have to send the response to + channel: string; + origin: string; +} + +interface JobsResponse { + jobs: Job[]; +} + +function parseRound(job: Job): number { + const [sourceType, networkId, round] = job.source_id.split(":"); + assert(sourceType == "drand", "Source type must be 'drand'"); + assert(networkId == chainHash, "Got wrong chain hash in job"); + assert(round, "Round must be set"); + return Uint53.fromString(round).toNumber(); +} + +export class JobsObserver { + private readonly client: CosmWasmClient; + private readonly gateway: string; + private readonly abort: AbortController; + private readonly intervalId: number; + + public constructor( + noisClient: CosmWasmClient, + gatewayAddress: string, + abortController: AbortController, + pollInterval = 1000, + ) { + this.client = noisClient; + this.gateway = gatewayAddress; + this.abort = abortController; + + this.intervalId = setInterval(() => { + const query = { jobs_desc: { offset: null, limit: 3 } }; + this.client.queryContractSmart(this.gateway, query).then( + ({ jobs }: JobsResponse) => { + if (jobs.length === 0) return; // Nothing to do for us + + const rounds = jobs.map(parseRound); + const roundInfos = rounds.map((round) => { + const due = timeOfRound(round) - Date.now() / 1000; + return `#${round} (due ${due.toFixed(1)}s)`; + }); + console.log(`Jobs pending for rounds: %c${roundInfos.join(", ")}`, "color: orange"); + }, + (err) => console.error(err), + ); + }, pollInterval); + } +} diff --git a/main.ts b/main.ts index 2e847cb..edc4350 100644 --- a/main.ts +++ b/main.ts @@ -15,6 +15,7 @@ import { watch, } from "./deps.ts"; import { BeaconCache } from "./cache.ts"; +import { JobsObserver } from "./jobs.ts"; import { loop } from "./loop.ts"; import { queryIsAllowListed, queryIsIncentivized } from "./drand_contract.ts"; import { connectTendermint } from "./tendermint.ts"; @@ -53,7 +54,7 @@ if (import.meta.main) { const { default: config } = await import("./config.json", { assert: { type: "json" }, }); - assert(config.contract, `Config field "contract" must be set.`); + assert(config.drandAddress, `Config field "drandAddress" must be set.`); assert(config.rpcEndpoint, `Config field "rpcEndpoint" must be set.`); const mnemonic = await (async () => { @@ -106,7 +107,7 @@ if (import.meta.main) { const fee = calculateFee(gasLimitRegister, config.gasPrice); await client.execute( botAddress, - config.contract, + config.drandAddress, { register_bot: { moniker: moniker } }, fee, ); @@ -122,6 +123,10 @@ if (import.meta.main) { })(), ]); + if (config.gatewayAddress) { + const _jobs = new JobsObserver(client, config.gatewayAddress, new AbortController()); + } + // Initialize local sign data await resetSignData();