|
| 1 | +import { ethers } from "ethers"; |
| 2 | +import { GAS_PREMIUM, PROVIDER_RPC, ZERO_ADDRESS } from "../constants"; |
| 3 | +import { DataBase } from "../db"; |
| 4 | +import { sleep } from "../utils/program"; |
| 5 | +import { stringToHex } from "../utils/hex"; |
| 6 | +import Spinnies from "spinnies"; |
| 7 | +import { printer } from "../utils/log4js"; |
| 8 | +import { bnUtils } from "../utils/bn"; |
| 9 | +import { sayMinerLog } from "../utils/prompts"; |
| 10 | +import dayjs from "dayjs"; |
| 11 | +import { generateNonce } from "../utils"; |
| 12 | + |
| 13 | +interface IMineOptions { |
| 14 | + account: string; |
| 15 | +} |
| 16 | +const tick = "BTCI-POW8"; |
| 17 | +let unique = 0; |
| 18 | +export const runMintPow = async (workc: string, options: IMineOptions) => { |
| 19 | + sayMinerLog(); |
| 20 | + if (!(workc.length >= 6 && workc.length <= 22 && ethers.utils.isHexString(workc))) { |
| 21 | + throw new Error("The workc is invalid"); |
| 22 | + } |
| 23 | + const { account } = options; |
| 24 | + if (!(await DataBase.miner.exists(`/${account}`))) { |
| 25 | + console.log(` |
| 26 | +This mining user configuration was not found! |
| 27 | +💡 Tips: |
| 28 | +1. cli wallet ---set <privateKey> Import the user. |
| 29 | +2. cli wallet --all to see configured users |
| 30 | +- For more information, use cli help wallet |
| 31 | + `); |
| 32 | + throw new Error("Mining user configuration not found"); |
| 33 | + } |
| 34 | + |
| 35 | + printer.trace(`Start mining with ${account}`); |
| 36 | + const { privateKey } = await DataBase.miner.getObject<{ privateKey: string }>(`/${account}`); |
| 37 | + const provider = new ethers.providers.JsonRpcProvider(PROVIDER_RPC); |
| 38 | + const miner = new ethers.Wallet(privateKey, provider); |
| 39 | + |
| 40 | + const [network, currentGasPrice, blockNumber, nonce, balance] = await Promise.all([ |
| 41 | + provider.getNetwork(), |
| 42 | + provider.getGasPrice(), |
| 43 | + provider.getBlockNumber(), |
| 44 | + miner.getTransactionCount(), |
| 45 | + miner.getBalance(), |
| 46 | + ]); |
| 47 | + printer.trace(`network is ${network.name} (chainID: ${network.chainId})`); |
| 48 | + const targetGasFee = currentGasPrice.div(100).mul(GAS_PREMIUM); |
| 49 | + |
| 50 | + printer.trace(`Current gas price usage ${bnUtils.fromWei(targetGasFee.toString(), 9)} gwei`); |
| 51 | + printer.trace(`nonce is ${nonce}`); |
| 52 | + printer.trace(`balance is ${bnUtils.fromWei(balance.toString(), 18).dp(4).toString()}`); |
| 53 | + |
| 54 | + const spinnies = new Spinnies(); |
| 55 | + printer.trace(`The current mining difficulty is ${workc}`); |
| 56 | + printer.trace(`Expected to take 1-2 minutes to calculate...`); |
| 57 | + spinnies.add("mining", { text: "start mining...", color: "blue" }); |
| 58 | + await sleep(1000); |
| 59 | + let timer = Date.now(), |
| 60 | + startTimer = timer, |
| 61 | + mineCount = 0; |
| 62 | + |
| 63 | + while (true) { |
| 64 | + mineCount += 1; |
| 65 | + const toMintBlockNumber = blockNumber + 5; |
| 66 | + const transaction = { |
| 67 | + type: 2, |
| 68 | + chainId: network.chainId, |
| 69 | + to: ZERO_ADDRESS, |
| 70 | + maxPriorityFeePerGas: targetGasFee, |
| 71 | + maxFeePerGas: targetGasFee, |
| 72 | + gasLimit: ethers.BigNumber.from("25000"), |
| 73 | + nonce: nonce, |
| 74 | + value: ethers.utils.parseEther("0"), |
| 75 | + data: stringToHex( |
| 76 | + `data:application/json,${JSON.stringify({ |
| 77 | + p: "ierc-pow", |
| 78 | + op: "mint", |
| 79 | + tick: tick, |
| 80 | + // use_point: '0', |
| 81 | + block: String(toMintBlockNumber), |
| 82 | + nonce: `${generateNonce()}${unique++}`, |
| 83 | + })}` |
| 84 | + ), |
| 85 | + }; |
| 86 | + const rawTransaction = ethers.utils.serializeTransaction(transaction); |
| 87 | + const transactionHash = ethers.utils.keccak256(rawTransaction); |
| 88 | + // console.log("🚀 ~ transactionHash:", transactionHash) |
| 89 | + |
| 90 | + const signingKey = miner._signingKey(); |
| 91 | + const signature = signingKey.signDigest(transactionHash); |
| 92 | + // console.log("🚀 ~ signature:", signature) |
| 93 | + |
| 94 | + const recreatedSignature = ethers.utils.joinSignature(signature); |
| 95 | + // console.log("🚀 ~ recreatedSignature:", recreatedSignature) |
| 96 | + |
| 97 | + const predictedTransactionHash = ethers.utils.keccak256( |
| 98 | + ethers.utils.serializeTransaction(transaction, recreatedSignature) |
| 99 | + ); |
| 100 | + |
| 101 | + // console.log("🚀 ~ predictedTransactionHash:", predictedTransactionHash) |
| 102 | + const now = Date.now(); |
| 103 | + if (now - timer > 100) { |
| 104 | + await sleep(1); |
| 105 | + spinnies.update("mining", { |
| 106 | + text: `[${dayjs(now).format( |
| 107 | + "YYYY-MM-DD HH:mm:ss" |
| 108 | + )}] ${mineCount} - ${predictedTransactionHash}`, |
| 109 | + color: "red", |
| 110 | + }); |
| 111 | + timer = now; |
| 112 | + } |
| 113 | + if (predictedTransactionHash.includes(workc)) { |
| 114 | + unique = 0; |
| 115 | + const currentBlockNumber = await provider.getBlockNumber(); |
| 116 | + if (Math.abs(currentBlockNumber - toMintBlockNumber) > 5) { |
| 117 | + spinnies.fail("mining", { |
| 118 | + text: `The block height is too high, please try again later`, |
| 119 | + color: "red", |
| 120 | + }); |
| 121 | + return; |
| 122 | + } |
| 123 | + spinnies.succeed("mining", { |
| 124 | + text: `${mineCount} - ${predictedTransactionHash}`, |
| 125 | + color: "green", |
| 126 | + }); |
| 127 | + const mineTime = (Date.now() - startTimer) / 1000; |
| 128 | + printer.info( |
| 129 | + `Total time spent ${mineTime}s, average arithmetic ${Math.ceil(mineCount / mineTime)} c/s` |
| 130 | + ); |
| 131 | + // console.log("🚀 ~ transaction:", transaction) |
| 132 | + const realTransaction = await miner.sendTransaction(transaction); |
| 133 | + // console.log("🚀 ~ realTransaction:", realTransaction) |
| 134 | + printer.info(`mining hash: ${realTransaction.hash}`); |
| 135 | + await realTransaction.wait(); |
| 136 | + |
| 137 | + return printer.info("mining success"); |
| 138 | + } |
| 139 | + } |
| 140 | +}; |
0 commit comments