-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmine.ts
218 lines (185 loc) · 6.88 KB
/
mine.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
import {
Constr,
Lucid,
Network,
} from "https://deno.land/x/lucid@0.10.1/mod.ts"
import { PieCUDAMiner } from "./miners/piecuda.ts"
import { Miner } from "./miner.ts";
import { delay } from "./util.ts";
import { loadSync } from "https://deno.land/std@0.199.0/dotenv/mod.ts";
export type MiningSubmissionEntry = {
nonce: string
}
type MiningSubmission = {
address: string,
entries: MiningSubmissionEntry[]
}
type SubmissionResponse = {
num_accepted: number,
nonce: string,
working_block: Block,
}
type Block = {
block_number: number
current_hash: string
leading_zeroes: number
difficulty_number: number
epoch_time: number
current_time: number
extra: string
interlink: string[]
}
type Work = {
nonce: string
current_block: Block
miner_id: number,
min_zeroes: number,
}
export type TargetState = Constr<string | bigint | string[]>
function blockToTargetState(block: Block, poolNonce: string): TargetState {
return new Constr(0, [
poolNonce,
BigInt(block.block_number),
block.current_hash,
BigInt(block.leading_zeroes),
BigInt(block.difficulty_number),
BigInt(block.epoch_time)
])
}
loadSync({ export: true })
const samplingDifficulty = Number(Deno.env.get("SAMPLING_DIFFICULTY")) || 8
const cardanoNetwork = Deno.env.get("NETWORK") as Network || "Mainnet"
const lucid = await Lucid.new(undefined, cardanoNetwork)
const miners = [
"PIECUDA"
]
function selectMinerFromEnvironment(): Miner {
const miner = Deno.env.get("MINER")
if (!miner) {
throw Error(`The environment variable MINER must be set. Options are ${miners.join(",")}`)
}
if (miner === "PIECUDA") {
const exePath = Deno.env.get("PIECUDA_EXEPATH")
if (!exePath) {
throw Error(`To use the PIECUDA miner, the environment variable PIECUDA_EXEPATH must be set.`)
}
return new PieCUDAMiner(exePath, cardanoNetwork === "Preview")
} else {
throw Error(`Unsupported miner ${miner} selected.`)
}
}
const DELAY = 2000
async function doWork(
poolUrl: string,
miner: Miner,
address: string,
targetState: TargetState,
minerID: number,
samplingZeroes: number
) {
log(`Working on block ${targetState.fields[1]}.`)
const results = await miner.pollResults(targetState, samplingZeroes)
if (results.length === 0) {
await delay(DELAY)
} else {
try {
log(`Submitting ${results.length} results.`)
const submissionResponse = await submitWork(poolUrl, address, results)
if (submissionResponse.working_block) {
const newTargetState = blockToTargetState(submissionResponse.working_block, submissionResponse.nonce)
return doWork(poolUrl, miner, address, newTargetState, minerID, samplingZeroes)
}
} catch (e) {
await delay(DELAY)
}
}
try {
const newWork = await getWork(poolUrl)
if (!newWork) {
throw Error("Could not get new work from submission response or work endpoint. Is the pool down? Are you connected to the internet?");
}
const newTargetState = blockToTargetState(newWork.current_block, newWork.nonce)
return doWork(poolUrl, miner, address, newTargetState, minerID, samplingZeroes)
} catch {
console.warn("Warning: Failed to get new work. Continuing to mine previous block.")
return doWork(poolUrl, miner, address, targetState, minerID, samplingZeroes)
}
}
async function submitWork(poolUrl: string, address: string, work: MiningSubmissionEntry[]): Promise<SubmissionResponse> {
const submission: MiningSubmission = {
address: address,
entries: work,
}
const submitResult = await fetch(`${poolUrl}/submit`, {
method: 'POST',
body: JSON.stringify(submission),
headers: {
['Content-Type']: 'application/json'
}
})
if (submitResult.status != 200) {
const submissionResponse: SubmissionResponse = await submitResult.json()
log("Server was unable to submit work: " + JSON.stringify(submissionResponse))
throw Error("Server was unable to submit work.")
} else {
const submissionResponse: SubmissionResponse = await submitResult.json()
const rejectedResultCount = work.length - submissionResponse.num_accepted
if (rejectedResultCount > 0) {
console.warn(`Pool rejected ${rejectedResultCount} results. Check your miner output.`)
}
return submissionResponse
}
}
async function getWork(poolUrl: string): Promise<Work | undefined> {
const address = await lucid.wallet.address()
const workResponse = await fetch(`${poolUrl}/work?address=${address}&sample_diff=${samplingDifficulty}`, {
method: 'GET',
headers: {
['Content-Type']: 'application/json'
}
})
if (workResponse.status != 200) {
console.debug(workResponse)
console.log("Couldn't get any work!")
} else {
const work = await workResponse.json() as Work
return work
}
}
interface HashrateResponse {
estimated_hash_rate: number
}
async function displayHashrate(poolUrl: string, minerID: number, startTime: number) {
const currentTimeInSeconds = Math.floor(Date.now() / 1000);
const fiveMinutesAgo = currentTimeInSeconds - 300;
const fifteenMinutesAgo = currentTimeInSeconds - 300 * 3
const effectiveStartTime = startTime < fifteenMinutesAgo ? fifteenMinutesAgo : startTime;
try {
const hashrateResult = await fetch(`${poolUrl}/hashrate?miner_id=${minerID}&start_time=${effectiveStartTime}`);
const hashrateJson: HashrateResponse = await hashrateResult.json();
log(`Last 15 minute hashrate: ${Math.trunc(hashrateJson.estimated_hash_rate)}/s.`);
} catch (e) {
log(`Can't get hashrate while server is unavailable.`)
}
}
export async function mine(poolUrl: string) {
lucid.selectWalletFromSeed(Deno.readTextFileSync("seed.txt"))
const address = await lucid.wallet.address()
const maybeWork = await getWork(poolUrl)
if (!maybeWork) {
throw Error("Can't start main loop, no initial work!");
}
const startTime = Math.floor(Date.now() / 1000)
const { nonce, miner_id, current_block, min_zeroes } = maybeWork
log(`Began mining at ${startTime} for miner ID ${miner_id}. Sampling difficulty is currently ${min_zeroes}.`)
const miner = selectMinerFromEnvironment()
const targetState = blockToTargetState(current_block, nonce)
setInterval(() => {
displayHashrate(poolUrl, maybeWork.miner_id, startTime)
}, 30_000)
await doWork(poolUrl, miner, address, targetState, maybeWork.miner_id, min_zeroes)
}
export function log(...args: any[]): void {
const timestamp = new Date().toLocaleString().split(", ")[1];
console.log(`[${timestamp}]`, ...args);
}