Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Major fixes & improvements for getwork #583

Merged
merged 4 commits into from
Apr 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/blockchain/chain.js
Original file line number Diff line number Diff line change
Expand Up @@ -3223,7 +3223,7 @@ class Chain extends AsyncEmitter {
const state = await this.getState(prev, deployment);

if (state === thresholdStates.LOCKED_IN
|| state === thresholdStates.STARTED) {
|| (state === thresholdStates.STARTED && deployment.force)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this effectively the same fix we did here?

5e20a5a

version |= 1 << deployment.bit;
}
}
Expand Down
25 changes: 1 addition & 24 deletions lib/mining/template.js
Original file line number Diff line number Diff line change
Expand Up @@ -361,29 +361,6 @@ class BlockTemplate {
this.coinbase = cb;
}

/**
* Generate a random mask (2 bytes less than target).
* @returns {Buffer[]}
*/

randomMask() {
const mask = random.randomBytes(32);

for (let i = 0; i < this.target.length; i++) {
mask[i] = 0;

if (this.target[i] !== 0) {
if (i + 1 < mask.length)
mask[i + 1] = 0;
break;
}
}

const hash = BLAKE2b.multi(this.prevBlock, mask);

return [mask, hash];
}

/**
* Create raw block header with given parameters.
* @param {Buffer} extraNonce
Expand Down Expand Up @@ -468,7 +445,7 @@ class BlockTemplate {
*/

toCoinbase() {
return this.coinbase;
return this.coinbase.clone();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense to return a clone here, but I can't find reference to this function - what's it used for?

}

/**
Expand Down
135 changes: 59 additions & 76 deletions lib/node/rpc.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,8 @@ class RPC extends RPCBase {
this.lastActivity = 0;
this.boundChain = false;
this.mask = Buffer.alloc(32, 0x00);
this.maskMap = new BufferMap();
this.merkleMap = new BufferMap();
this.merkleList = [];
this.pollers = [];

this.init();
Expand Down Expand Up @@ -1127,36 +1128,35 @@ class RPC extends RPCBase {
* Mining
*/

async handleWork(data) {
async handleWork(data, mask) {
const unlock = await this.locker.lock();
try {
return await this._handleWork(data);
return await this._handleWork(data, mask);
} finally {
unlock();
}
}

async _handleWork(data) {
const attempt = this.attempt;

if (!attempt)
return [false, 'no-mining-job'];

async _handleWork(data, mask) {
if (data.length !== 256)
return [false, 'invalid-data-length'];

const hdr = Headers.fromMiner(data);
const maskHash = blake2b.multi(hdr.prevBlock, mask);

if (!hdr.maskHash().equals(maskHash))
return [false, 'bad-maskhash'];

const attempt = this.merkleMap.get(hdr.witnessRoot);

if (!attempt)
return [false, 'stale'];

if (!hdr.prevBlock.equals(attempt.prevBlock)
|| hdr.bits !== attempt.bits) {
return [false, 'stale'];
}

const mask = this.maskMap.get(hdr.maskHash());

if (!mask)
return [false, 'stale'];

const {nonce, time, extraNonce} = hdr;
const proof = attempt.getProof(nonce, time, extraNonce, mask);

Expand Down Expand Up @@ -1186,19 +1186,21 @@ class RPC extends RPCBase {
return [true, 'valid'];
}

async createWork(data) {
async createWork() {
const unlock = await this.locker.lock();
try {
return await this._createWork(data);
return await this._createWork();
} finally {
unlock();
}
}

async _createWork() {
const [mask, attempt] = await this.updateWork();
const attempt = await this.updateWork();
const time = attempt.time;
const data = attempt.getHeader(0, time, consensus.ZERO_NONCE, mask);
const nonce = consensus.ZERO_NONCE;
const mask = consensus.ZERO_HASH;
const data = attempt.getHeader(0, time, nonce, mask);

return {
network: this.network.type,
Expand All @@ -1215,34 +1217,32 @@ class RPC extends RPCBase {
}

async getWork(args, help) {
if (help || args.length > 1)
throw new RPCError(errs.MISC_ERROR, 'getwork ( "maskhash" )');

if (args.length === 1) {
const valid = new Validator(args);
const maskHash = valid.bhash(0);

if (!maskHash)
throw new RPCError(errs.TYPE_ERROR, 'Invalid mask hash.');

if (this.maskMap.has(maskHash))
return null;
}
if (help || args.length !== 0)
throw new RPCError(errs.MISC_ERROR, 'getwork');

return this.createWork();
}

async submitWork(args, help) {
if (help || args.length !== 1)
throw new RPCError(errs.MISC_ERROR, 'submitwork ( "data" )');
if (help || args.length < 1 || args.length > 2)
throw new RPCError(errs.MISC_ERROR, 'submitwork ( "data" "mask" )');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: "data" isn't optional, shouldn't be inside parentheses.


const valid = new Validator(args);
const data = valid.buf(0);

if (!data)
throw new RPCError(errs.TYPE_ERROR, 'Invalid work data.');

return this.handleWork(data);
let mask = consensus.ZERO_HASH;

if (args.length === 2) {
mask = valid.bhash(1);

if (!mask)
throw new RPCError(errs.TYPE_ERROR, 'Invalid mask.');
}

return this.handleWork(data, mask);
}

async submitBlock(args, help) {
Expand Down Expand Up @@ -1323,16 +1323,6 @@ class RPC extends RPCBase {
}
}

// BIP22 states that we can't have coinbasetxn
// _and_ coinbasevalue in the same template.
// The problem is, many clients _say_ they
// support coinbasetxn when they don't (ckpool).
// To make matters worse, some clients will
// parse an undefined `coinbasevalue` as zero.
// Because of all of this, coinbasetxn is
// disabled for now.
valueCap = true;

if (txnCap && !valueCap) {
if (this.miner.addresses.length === 0) {
throw new RPCError(errs.MISC_ERROR,
Expand Down Expand Up @@ -1488,9 +1478,11 @@ class RPC extends RPCBase {
vbrequired: 0,
height: attempt.height,
previousblockhash: attempt.prevBlock.toString('hex'),
merkleroot: undefined,
witnessroot: undefined,
treeroot: attempt.treeRoot.toString('hex'),
reservedroot: attempt.reservedRoot.toString('hex'),
mask: attempt.randomMask()[0].toString('hex'),
mask: consensus.ZERO_HASH.toString('hex'), // Compat.
target: attempt.target.toString('hex'),
bits: hex32(attempt.bits),
noncerange:
Expand All @@ -1509,7 +1501,7 @@ class RPC extends RPCBase {
coinbaseaux: {
flags: attempt.coinbaseFlags.toString('hex')
},
coinbasevalue: undefined,
coinbasevalue: attempt.getReward(),
coinbasetxn: undefined,
claims: attempt.claims.map((claim) => {
return {
Expand Down Expand Up @@ -1544,14 +1536,10 @@ class RPC extends RPCBase {
// The client wants a coinbasetxn
// instead of a coinbasevalue.
if (coinbase) {
const tx = attempt.toCoinbase();
const input = tx.inputs[0];

// Pop off the nonces.
input.witness.pop();
input.witness.compile();
const tx = attempt.coinbase;

tx.refresh();
json.merkleroot = attempt.merkleRoot.toString('hex');
json.witnessroot = attempt.witnessRoot.toString('hex');
Comment on lines +1541 to +1542
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a convenient feature - client can still modify TXs in the block before submitting?


json.coinbasetxn = {
data: tx.toHex(),
Expand All @@ -1562,8 +1550,6 @@ class RPC extends RPCBase {
sigops: tx.getSigops() / scale | 0,
weight: tx.getWeight()
};
} else {
json.coinbasevalue = attempt.getReward();
}

return json;
Expand Down Expand Up @@ -2565,7 +2551,6 @@ class RPC extends RPCBase {

this.attempt = null;
this.lastActivity = 0;
this.maskMap.clear();
this.pollers = [];

for (const job of pollers)
Expand All @@ -2578,31 +2563,32 @@ class RPC extends RPCBase {

this.boundChain = true;

this.node.on('connect', () => {
const refresh = () => {
if (!this.attempt)
return;

this.refreshBlock();
});
this.merkleMap.clear();
this.merkleList.length = 0;
};

this.node.on('connect', refresh);
this.node.on('reset', refresh);

if (!this.mempool)
return;

this.node.on('tx', () => {
const tryRefresh = () => {
if (!this.attempt)
return;

if (util.now() - this.lastActivity > 10)
this.refreshBlock();
});

this.node.on('claim', () => {
if (!this.attempt)
return;
};

if (util.now() - this.lastActivity > 10)
this.refreshBlock();
});
this.node.on('tx', tryRefresh);
this.node.on('claim', tryRefresh);
this.node.on('airdrop', tryRefresh);
}

async getTemplate() {
Expand Down Expand Up @@ -2634,11 +2620,7 @@ class RPC extends RPCBase {

this.miner.updateTime(attempt);

const [mask, maskHash] = attempt.randomMask();

this.maskMap.set(maskHash, mask);

return [mask, attempt];
return attempt;
}

if (this.miner.addresses.length === 0) {
Expand All @@ -2647,15 +2629,16 @@ class RPC extends RPCBase {
}

attempt = await this.miner.createBlock();
attempt.version = 0;

const [mask, maskHash] = attempt.randomMask();
if (this.merkleMap.size >= 10)
this.merkleMap.delete(this.merkleList.shift());

this.attempt = attempt;
this.lastActivity = util.now();
this.maskMap.set(maskHash, mask);
this.merkleMap.set(attempt.witnessRoot, attempt);
this.merkleList.push(attempt.witnessRoot);

return [mask, attempt];
return attempt;
}

async addBlock(block) {
Expand Down
Loading