Skip to content

Commit

Permalink
ref!(dashp2p): allow shared and reusable buffers
Browse files Browse the repository at this point in the history
  • Loading branch information
coolaj86 committed Aug 17, 2024
1 parent b87ecba commit 8e199ea
Show file tree
Hide file tree
Showing 2 changed files with 266 additions and 113 deletions.
231 changes: 167 additions & 64 deletions public/dashjoin.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ var DashJoin = ('object' === typeof module && exports) || {};
'use strict';

let DashP2P = window.DashP2P || require('dashp2p');
let DashTx = window.DashTx || require('dashtx');

const DV_LITTLE_ENDIAN = true;

const DENOM_LOWEST = 100001;
const PREDENOM_MIN = DENOM_LOWEST + 193;
const COLLATERAL = 10000; // DENOM_LOWEST / 10
const PAYLOAD_SIZE_MAX = 4 * 1024 * 1024;

let STANDARD_DENOMINATIONS_MAP = {
// 0.00100001
Expand All @@ -24,11 +24,25 @@ var DashJoin = ('object' === typeof module && exports) || {};
0b00000001: 1000010000,
};

// Note: "mask" may be a misnomer. The spec seems to be more of an ID,
// but the implementation makes it look more like a mask...
let STANDARD_DENOMINATION_MASKS = {
// 0.00100001
100001: 0b00010000,
// 0.01000010
1000010: 0b00001000,
// 0.10000100
10000100: 0b00000100,
// 1.00001000
100001000: 0b00000010,
// 10.00010000
1000010000: 0b00000001,
};

// https://github.com/dashpay/dash/blob/v19.x/src/coinjoin/coinjoin.h#L39
// const COINJOIN_ENTRY_MAX_SIZE = 9; // real
// const COINJOIN_ENTRY_MAX_SIZE = 2; // just for testing right now

DashJoin.PAYLOAD_SIZE_MAX = PAYLOAD_SIZE_MAX;
DashJoin.DENOM_LOWEST = DENOM_LOWEST;
DashJoin.COLLATERAL = COLLATERAL;
DashJoin.PREDENOM_MIN = PREDENOM_MIN;
Expand Down Expand Up @@ -59,56 +73,177 @@ var DashJoin = ('object' === typeof module && exports) || {};
return 0;
};

Sizes.DSQ = 142;
Sizes.SENDDSQ = 1; // 1-byte bool
Sizes.DENOM = 4; // 32-bit uint
Sizes.PROTX = 32;
Sizes.TIME = 8; // 64-bit uint
Sizes.READY = 1; // 1-byte bool
Sizes.SIG = 97;

// Sizes.DSSU = 16;
// Sizes.SESSION_ID = 4;

/**
* Turns on or off DSQ messages (necessary for CoinJoin, but off by default)
* @param {Object} opts
* @param {NetworkName} opts.network - "mainnet", "testnet", etc
* @param {Uint8Array?} [opts.message]
* @param {Boolean?} [opts.send]
*/
Packers.senddsq = function ({
Packers.senddsq = function ({ network = 'mainnet', message, send = true }) {
const command = 'senddsq';
let [bytes, payload] = DashP2P.packers._alloc(message, Sizes.SENDDSQ);

let sendByte = [0x01];
if (!send) {
sendByte = [0x00];
}
payload.set(sendByte, 0);

void DashP2P.packers.message({ network, command, bytes });
return bytes;
};

/**
* Request to be allowed to join a CoinJoin pool. This may join an existing
* session - such as one already broadcast by a dsq - or may create a new one.
* @param {Object} opts
* @param {NetworkName} opts.network - "mainnet", "testnet", etc
* @param {Uint8Array?} [opts.message]
* @param {Uint32} opts.denomination
* @param {Uint8Array} opts.collateralTx
*/
Packers.dsa = function ({
network = 'mainnet',
message = null,
send = true,
message,
denomination,
collateralTx,
}) {
const command = 'senddsq';
const SENDDSQ_SIZE = 1; // 1-byte bool
const command = 'dsa';
let dsaSize = Sizes.DENOM + collateralTx.length;
let [bytes, payload] = DashP2P.packers._alloc(message, dsaSize);

//@ts-ignore - numbers can be used as map keys
let denomMask = STANDARD_DENOMINATION_MASKS[denomination];
if (!denomMask) {
throw new Error(
`contact your local Dash representative to vote for denominations of '${denomination}'`,
);
}

let dv = new DataView(payload.buffer);
let offset = 0;

dv.setUint32(offset, denomMask, DV_LITTLE_ENDIAN);
offset += Sizes.DENOM;

payload.set(collateralTx, offset);

void DashP2P.packers.message({ network, command, bytes });
return bytes;
};

/**
* @param {Object} opts
* @param {NetworkName} opts.network - "mainnet", "testnet", etc
* @param {Uint8Array?} [opts.message]
* @param {Array<import('dashtx').TxInput>} opts.inputs
* @param {Array<import('dashtx').TxOutput>} opts.outputs
* @param {Uint8Array} opts.collateralTx
*/
Packers.dsi = function ({
network = 'mainnet',
message,
inputs,
collateralTx,
outputs,
}) {
const command = 'dsi';

let neutered = [];
for (let input of inputs) {
let _input = {
txId: input.txId || input.txid,
txid: input.txid || input.txId,
outputIndex: input.outputIndex,
};
neutered.push(_input);
}

let inputsHex = DashTx.serializeInputs(inputs);
let inputHex = inputsHex.join('');
let outputsHex = DashTx.serializeOutputs(outputs);
let outputHex = outputsHex.join('');

if (!message) {
let dsqSize = DashP2P.sizes.HEADER_SIZE + SENDDSQ_SIZE;
message = new Uint8Array(dsqSize);
let dsiSize = collateralTx.length;
dsiSize += inputHex.length / 2;
dsiSize += outputHex.length / 2;

let [bytes, payload] = DashP2P.packers._alloc(message, dsiSize);

let offset = 0;
{
let j = 0;
for (let i = 0; i < inputHex.length; i += 2) {
let end = i + 2;
let hex = inputHex.slice(i, end);
payload[j] = parseInt(hex, 16);
j += 1;
}
offset += inputHex.length / 2;
}

let payload = message.subarray(DashP2P.sizes.HEADER_SIZE);
if (send) {
payload.set([0x01], 0);
} else {
payload.set([0x00], 0);
payload.set(collateralTx, offset);
offset += collateralTx.length;

{
let outputsPayload = payload.subarray(offset);
let j = 0;
for (let i = 0; i < outputHex.length; i += 2) {
let end = i + 2;
let hex = outputHex.slice(i, end);
outputsPayload[j] = parseInt(hex, 16);
j += 1;
}
offset += outputHex.length / 2;
}

void DashP2P.packers.message({ network, command, bytes: message });
return message;
// return { message, payload };
void DashP2P.packers.message({ network, command, bytes });
return bytes;
};

Sizes.DSQ_SIZE = 142;
// DSQ stuff??
Sizes.DENOM = 4;
Sizes.PROTX = 32;
Sizes.TIME = 8;
Sizes.READY = 1;
Sizes.SIG = 97;
//
/**
* @param {Object} opts
* @param {Uint8Array?} [opts.message]
* @param {NetworkName} opts.network - "mainnet", "testnet", etc
* @param {Array<import('dashtx').CoreUtxo>} [opts.inputs]
*/
Packers.dss = function ({ network = 'mainnet', message, inputs }) {
const command = 'dss';

if (!inputs?.length) {
// TODO make better
throw new Error('you must provide some inputs');
}

let txInputsHex = DashTx.serializeInputs(inputs);
let txInputHex = txInputsHex.join('');

// Sizes.DSSU_SIZE = 16;
// Sizes.SESSION_ID_SIZE = 4;
let dssSize = txInputHex.length / 2;
let [bytes, payload] = DashP2P.packers._alloc(message, dssSize);
void DashP2P.utils.hexToPayload(txInputHex, payload);

void DashP2P.packers.message({ network, command, bytes });
return bytes;
};

/**
* @param {Uint8Array} bytes
*/
Parsers.dsq = function (bytes) {
if (bytes.length !== Sizes.DSQ_SIZE) {
let msg = `developer error: 'dsq' must be ${Sizes.DSQ_SIZE} bytes, but received ${bytes.length}`;
if (bytes.length !== Sizes.DSQ) {
let msg = `developer error: 'dsq' must be ${Sizes.DSQ} bytes, but received ${bytes.length}`;
throw new Error(msg);
}
let dv = new DataView(bytes.buffer);
Expand Down Expand Up @@ -160,40 +295,8 @@ var DashJoin = ('object' === typeof module && exports) || {};
return dsqMessage;
};

Utils.hexToBytes = function (hex) {
let bufLen = hex.length / 2;
let u8 = new Uint8Array(bufLen);

let i = 0;
let index = 0;
let lastIndex = hex.length - 2;
for (;;) {
if (i > lastIndex) {
break;
}

let h = hex.slice(i, i + 2);
let b = parseInt(h, 16);
u8[index] = b;

i += 2;
index += 1;
}

return u8;
};

Utils.bytesToHex = function (u8) {
/** @type {Array<String>} */
let hex = [];

u8.forEach(function (b) {
let h = b.toString(16).padStart(2, '0');
hex.push(h);
});

return hex.join('');
};
// Utils.hexToBytes = DashTx.utils.hexToBytes;
// Utils.bytesToHex = DashTx.utils.bytesToHex;

Utils._evonodeMapToList = function (evonodesMap) {
console.log('[debug] get evonode list...');
Expand Down
Loading

0 comments on commit 8e199ea

Please sign in to comment.