-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[auto-self-nominate] Implement repeating self nomination
- Loading branch information
Showing
1 changed file
with
155 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
import { U64 } from "codechain-primitives/lib"; | ||
import { SDK } from "codechain-sdk"; | ||
import { PlatformAddress } from "codechain-sdk/lib/core/classes"; | ||
import { Custom } from "codechain-sdk/lib/core/transaction/Custom"; | ||
|
||
const RLP = require("rlp"); | ||
|
||
const ACTION_TAG_SELF_NOMINATE = 4; | ||
const STAKE_ACTION_HANDLER_ID = 2; | ||
|
||
function getConfig(field: string): string { | ||
const c = process.env[field]; | ||
if (c == null) { | ||
throw new Error(`${field} is not specified`); | ||
} | ||
return c; | ||
} | ||
|
||
function rpcUrl(server: string): string { | ||
switch (server) { | ||
case "corgi": | ||
return "https://corgi-rpc.codechain.io/"; | ||
case "mainnet": | ||
return "https://rpc.codechain.io/"; | ||
default: | ||
throw Error("Invalid server configuration"); | ||
} | ||
} | ||
|
||
function networkId(server: string): string { | ||
switch (server) { | ||
case "beagle": | ||
return "bc"; | ||
case "corgi": | ||
return "wc"; | ||
case "mainnet": | ||
return "cc"; | ||
default: | ||
throw Error("Invalid server configuration"); | ||
} | ||
} | ||
|
||
const SERVER: string = (() => { | ||
const server = process.env.SERVER || "beagle"; | ||
if (["beagle", "corgi", "mainnet"].indexOf(server) >= 0) { | ||
return server; | ||
} else { | ||
throw Error("Invalid server configuration"); | ||
} | ||
})(); | ||
|
||
const sdk = (() => { | ||
console.log(`sdk ${SERVER} ${process.env.RPC_URL}`); | ||
return new SDK({ | ||
server: process.env.RPC_URL || rpcUrl(SERVER), | ||
networkId: networkId(SERVER), | ||
}); | ||
})(); | ||
|
||
function decodePlatformAddressFromPubkey(buffer: Buffer): PlatformAddress { | ||
const pubkey = buffer.toString("hex"); | ||
return PlatformAddress.fromPublic(pubkey, { | ||
networkId: sdk.networkId, | ||
}); | ||
} | ||
|
||
function decodeNumber(buffer: Buffer): number { | ||
return parseInt("0x" + buffer.toString("hex"), 16); | ||
} | ||
|
||
async function sendSelfNominateTransaction( | ||
accountInfo: { accountAddress: string; passphrase: string }, | ||
params: { deposit: U64; metadata: string }, | ||
) { | ||
const tx = createSelfNomination(params); | ||
const { accountAddress, passphrase } = accountInfo; | ||
await sdk.rpc.account.sendTransaction({ | ||
tx, | ||
account: accountAddress, | ||
passphrase, | ||
}); | ||
} | ||
|
||
function createSelfNomination(params: { deposit: U64; metadata: string }): Custom { | ||
const { deposit, metadata } = params; | ||
const handlerId = new U64(STAKE_ACTION_HANDLER_ID); | ||
const actionTag = ACTION_TAG_SELF_NOMINATE; | ||
const bytes = RLP.encode([actionTag, deposit, metadata]); | ||
return new Custom( | ||
{ | ||
handlerId, | ||
bytes, | ||
}, | ||
networkId(SERVER), | ||
); | ||
} | ||
|
||
async function needsNomination( | ||
address: string, | ||
nominationFrequencyLevel: number, | ||
): Promise<boolean> { | ||
const bestBlockNumber = await sdk.rpc.chain.getBestBlockNumber()!; | ||
const candidates: Map<string, number> = await (async () => { | ||
const retval = new Map(); | ||
const data = (await sdk.rpc.engine.getCustomActionData( | ||
2, | ||
["Candidates"], | ||
bestBlockNumber, | ||
))!; | ||
const list: Buffer[][] = RLP.decode(Buffer.from(data, "hex")); | ||
list.map(([encodedPubkey, _, encodedNominationEnd]) => | ||
retval.set( | ||
decodePlatformAddressFromPubkey(encodedPubkey).value, | ||
decodeNumber(encodedNominationEnd), | ||
), | ||
); | ||
return retval; | ||
})(); | ||
const nominationEndAt = candidates.get(address); | ||
if (nominationEndAt == null) { | ||
throw new Error(`SelfNominate first with specific deposit vakue before repeating`); | ||
} else { | ||
const bestBlock = (await sdk.rpc.chain.getBlock(bestBlockNumber))!; | ||
const currentTermId = Math.floor(bestBlock.timestamp / 3600); | ||
return nominationEndAt - currentTermId < nominationFrequencyLevel; | ||
} | ||
} | ||
|
||
async function main() { | ||
const accountAddress = getConfig("ACCOUNT_ADDRESS"); | ||
const passphrase = getConfig("PASSPHRASE"); | ||
const metadata = getConfig("METADATA"); | ||
const repeatedAdditionalDeposit = new U64(parseInt(getConfig("ADDITIONAL_DEPOSIT"), 10) || 0); | ||
const NEED_NOMINATION_UNDER_TERM_LEFT = 5; | ||
|
||
setInterval(async () => { | ||
if (await needsNomination(accountAddress, NEED_NOMINATION_UNDER_TERM_LEFT)) { | ||
await sendSelfNominateTransaction( | ||
{ | ||
accountAddress, | ||
passphrase, | ||
}, | ||
{ | ||
deposit: repeatedAdditionalDeposit, | ||
metadata, | ||
}, | ||
); | ||
} | ||
}, 600_000); // 10 minutes interval | ||
} | ||
|
||
main().catch(error => { | ||
console.log({ error }); | ||
throw error; | ||
}); |