Skip to content

Commit

Permalink
feat: get inputs for dual funding
Browse files Browse the repository at this point in the history
  • Loading branch information
matthewjablack committed Mar 14, 2024
1 parent bc615a5 commit 40e3bc2
Show file tree
Hide file tree
Showing 19 changed files with 356 additions and 147 deletions.
7 changes: 7 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,12 @@ build:
test:
yarn run test

# sequential test
seqtest:
yarn test:integration:sequential

lint:
yarn run lint

clean:
find . -name "node_modules" -type d -exec rm -rf '{}' +
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
],
"devDependencies": {
"@changesets/cli": "^2.22.0",
"@node-dlc/core": "0.22.4",
"@node-dlc/messaging": "0.22.4",
"@node-dlc/bitcoin": "0.23.0",
"@node-dlc/core": "0.23.0",
"@node-dlc/messaging": "0.23.0",
"@swc/cli": "^0.1.57",
"@swc/core": "^1.2.172",
"@swc/register": "^0.1.10",
Expand Down Expand Up @@ -61,7 +62,7 @@
"test": "yarn run test:unit && yarn run test:integration",
"test:unit": "turbo run test",
"test:integration": "nyc --reporter=text --reporter=lcov cross-env NODE_ENV=test mocha --parallel",
"test:integration:single": "nyc --reporter=text --reporter=lcov cross-env NODE_ENV=test mocha",
"test:integration:sequential": "nyc --reporter=text --reporter=lcov cross-env NODE_ENV=test mocha",
"prepublishOnly": "yarn build",
"changeset": "changeset",
"version": "yarn changeset version",
Expand Down
41 changes: 21 additions & 20 deletions packages/bitcoin-dlc-provider/lib/BitcoinDlcProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
AddSignaturesToRefundTxResponse,
AddSignatureToFundTransactionRequest,
AddSignatureToFundTransactionResponse,
bitcoin,
CalculateEcSignatureRequest,
CreateBatchDlcTransactionsRequest,
CreateBatchDlcTransactionsResponse,
Expand Down Expand Up @@ -112,8 +111,6 @@ import {
outputsToPayouts,
} from './utils/Utils';

const ESTIMATED_SIZE = 312;

export default class BitcoinDlcProvider
extends Provider
implements Partial<DlcProvider> {
Expand Down Expand Up @@ -168,26 +165,27 @@ export default class BitcoinDlcProvider
}

async GetInputsForAmount(
amount: bigint,
amounts: bigint[],
feeRatePerVb: bigint,
fixedInputs: Input[] = [],
): Promise<Input[]> {
if (amount === BigInt(0)) return [];
const targets: bitcoin.OutputTarget[] = [
{
address: BurnAddress,
value: Number(amount) + ESTIMATED_SIZE * (Number(feeRatePerVb) - 1),
},
];
if (amounts.length === 0) return [];

const fixedUtxos = fixedInputs.map((input) => input.toUtxo());

let inputs: Input[];
try {
const inputsForAmount: InputsForAmountResponse = await this.getMethod(
'getInputsForAmount',
)(targets, Number(feeRatePerVb), fixedInputs);
const inputsForAmount: InputsForDualAmountResponse = await this.getMethod(
'getInputsForDualFunding',
)(amounts, feeRatePerVb, fixedUtxos);

inputs = inputsForAmount.inputs;
} catch (e) {
const errorMessage = e instanceof Error ? e.message : 'Unknown error';
if (fixedInputs.length === 0) {
throw Error('Not enough balance getInputsForAmount');
throw Error(
`Not enough balance getInputsForAmount. Error: ${errorMessage}`,
);
} else {
inputs = fixedInputs;
}
Expand Down Expand Up @@ -226,7 +224,7 @@ export default class BitcoinDlcProvider
throw Error('Address reuse');

const inputs: Input[] = await this.GetInputsForAmount(
collateral,
[collateral],
feeRatePerVb,
fixedInputs,
);
Expand Down Expand Up @@ -256,10 +254,8 @@ export default class BitcoinDlcProvider
): Promise<BatchInitializeResponse> {
const network = await this.getConnectedNetwork();

const collateral = collaterals.reduce((a, b) => a + b, BigInt(0));

const inputs: Input[] = await this.GetInputsForAmount(
collateral,
collaterals,
feeRatePerVb,
fixedInputs,
);
Expand Down Expand Up @@ -2928,7 +2924,7 @@ Payout Group found but incorrect group index',
let inputs: Input[] = _inputs;
if (!_inputs) {
const tempInputs = await this.GetInputsForAmount(
BigInt(20000),
[BigInt(20000)],
dlcOffer.feeRatePerVb,
_inputs,
);
Expand Down Expand Up @@ -3793,4 +3789,9 @@ export interface InputsForAmountResponse {
fee: number;
}

export interface InputsForDualAmountResponse {
inputs: Input[];
fee: number;
}

const BurnAddress = 'bcrt1qxcjufgh2jarkp2qkx68azh08w9v5gah8u6es8s';
7 changes: 5 additions & 2 deletions packages/bitcoin-dlc-provider/lib/utils/Utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@ import {
} from '@node-dlc/messaging';
import randomBytes from 'randombytes';

export async function asyncForEach(array: any, callback: any) {
export const asyncForEach = async (
array: any,
callback: any,
): Promise<void> => {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array);
}
}
};

export function generateSerialId(): bigint {
return randomBytes(4).reduce((acc, num, i) => acc + num ** i, 0);
Expand Down
4 changes: 2 additions & 2 deletions packages/bitcoin-dlc-provider/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
"@atomicfinance/provider": "^3.4.0",
"@atomicfinance/types": "^3.4.0",
"@atomicfinance/utils": "^3.4.0",
"@node-dlc/core": "0.22.4",
"@node-dlc/messaging": "0.22.4",
"@node-dlc/core": "0.23.0",
"@node-dlc/messaging": "0.23.0",
"@node-lightning/bitcoin": "0.26.1",
"@node-lightning/bufio": "0.26.1",
"@node-lightning/crypto": "0.26.1",
Expand Down
14 changes: 10 additions & 4 deletions packages/bitcoin-js-wallet-provider/lib/BitcoinJsWalletProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,10 @@ export default class BitcoinJsWalletProvider extends BitcoinWalletProvider(
feePerByte: number,
_outputs: Output[],
fixedInputs: Input[],
) {
): Promise<{
hex: string;
fee: number;
}> {
return this._buildSweepTransactionWithSetOutputs(
externalChangeAddress,
feePerByte,
Expand All @@ -317,7 +320,10 @@ export default class BitcoinJsWalletProvider extends BitcoinWalletProvider(
feePerByte: number,
_outputs: Output[] = [],
fixedInputs: Input[],
) {
): Promise<{
hex: string;
fee: number;
}> {
const _feePerByte =
feePerByte ||
(await this.getMethod('getFeePerByte')()) ||
Expand Down Expand Up @@ -605,15 +611,15 @@ export default class BitcoinJsWalletProvider extends BitcoinWalletProvider(
tx: any,
_lockTime?: number,
segwit?: boolean,
) {
): Promise<Buffer[]> {
const keyPairs = [];
for (const address of addresses) {
const wallet = await this.getWalletAddress(address);
const keyPair = await this.keyPair(wallet.derivationPath);
keyPairs.push(keyPair);
}

const sigs = [];
const sigs: Buffer[] = [];
for (let i = 0; i < inputs.length; i++) {
const index = inputs[i].txInputIndex
? inputs[i].txInputIndex
Expand Down
1 change: 1 addition & 0 deletions packages/bitcoin-js-wallet-provider/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"@atomicfinance/types": "^3.4.0",
"@atomicfinance/utils": "^3.4.0",
"@babel/runtime": "^7.12.1",
"@node-dlc/core": "0.23.0",
"bip32": "^2.0.6",
"bip39": "^3.0.2",
"bitcoin-networks": "^1.0.0",
Expand Down
47 changes: 25 additions & 22 deletions packages/bitcoin-utils/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,34 +18,34 @@ import { findKey } from 'lodash';

const AddressTypes = ['legacy', 'p2sh-segwit', 'bech32'];

function calculateFee(
const calculateFee = (
numInputs: number,
numOutputs: number,
feePerByte: number,
) {
): number => {
return (numInputs * 148 + numOutputs * 34 + 10) * feePerByte;
}
};

/**
* Get compressed pubKey from pubKey.
* @param {!string} pubKey - 65 byte string with prefix, x, y.
* @return {string} Returns the compressed pubKey of uncompressed pubKey.
*/
function compressPubKey(pubKey: string) {
const compressPubKey = (pubKey: string): string => {
const x = pubKey.substring(2, 66);
const y = pubKey.substring(66, 130);
const even = parseInt(y.substring(62, 64), 16) % 2 === 0;
const prefix = even ? '02' : '03';

return prefix + x;
}
};

/**
* Get a network object from an address
* @param {string} address The bitcoin address
* @return {Network}
*/
function getAddressNetwork(address: string) {
const getAddressNetwork = (address: string): BitcoinNetwork => {
// TODO: can this be simplified using just bitcoinjs-lib??
let networkKey;
// bech32
Expand All @@ -62,7 +62,7 @@ function getAddressNetwork(address: string) {
});
}
return (BitcoinNetworks as { [key: string]: BitcoinNetwork })[networkKey];
}
};

type CoinSelectTarget = {
value: number;
Expand All @@ -83,12 +83,12 @@ type CoinSelectFunction = (
feePerByte: number,
) => CoinSelectResponse;

function selectCoins(
const selectCoins = (
utxos: bT.UTXO[],
targets: CoinSelectTarget[],
feePerByte: number,
fixedInputs: bT.UTXO[] = [],
) {
): CoinSelectResponse => {
let selectUtxos = utxos;

// Default coinselect won't accumulate some inputs
Expand Down Expand Up @@ -121,17 +121,17 @@ function selectCoins(
}

return { inputs, outputs, fee, change };
}
};

const OUTPUT_TYPES_MAP = {
[classify.types.P2WPKH]: 'witness_v0_keyhash',
[classify.types.P2WSH]: 'witness_v0_scripthash',
};

function decodeRawTransaction(
const decodeRawTransaction = (
hex: string,
network: BitcoinNetwork,
): bT.Transaction {
): bT.Transaction => {
const bjsTx = bitcoin.Transaction.fromHex(hex);

const vin = bjsTx.ins.map((input) => {
Expand Down Expand Up @@ -184,13 +184,13 @@ function decodeRawTransaction(
vout,
hex,
};
}
};

function normalizeTransactionObject(
const normalizeTransactionObject = (
tx: bT.Transaction,
fee: number,
block?: { number: number; hash: string },
): Transaction<bT.Transaction> {
): Transaction<bT.Transaction> => {
const value = tx.vout.reduce(
(p, n) => p.plus(new BigNumber(n.value).times(1e8)),
new BigNumber(0),
Expand Down Expand Up @@ -220,11 +220,11 @@ function normalizeTransactionObject(
}

return result;
}
};

// TODO: This is copy pasta because it's not exported from bitcoinjs-lib
// https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/csv.spec.ts#L477
function witnessStackToScriptWitness(witness: Buffer[]): Buffer {
const witnessStackToScriptWitness = (witness: Buffer[]): Buffer => {
let buffer = Buffer.allocUnsafe(0);

function writeSlice(slice: Buffer): void {
Expand Down Expand Up @@ -252,9 +252,9 @@ function witnessStackToScriptWitness(witness: Buffer[]): Buffer {
writeVector(witness);

return buffer;
}
};

function getPubKeyHash(address: string, network: BitcoinNetwork) {
const getPubKeyHash = (address: string, network: BitcoinNetwork): Buffer => {
const outputScript = bitcoin.address.toOutputScript(address, network);
const type = classify.output(outputScript);
if (![classify.types.P2PKH, classify.types.P2WPKH].includes(type)) {
Expand All @@ -270,9 +270,12 @@ function getPubKeyHash(address: string, network: BitcoinNetwork) {
const base58 = bitcoin.address.fromBase58Check(address);
return base58.hash;
}
}
};

function validateAddress(_address: Address | string, network: BitcoinNetwork) {
const validateAddress = (
_address: Address | string,
network: BitcoinNetwork,
): void => {
const address = addressToString(_address);

if (typeof address !== 'string') {
Expand All @@ -291,7 +294,7 @@ function validateAddress(_address: Address | string, network: BitcoinNetwork) {
if (!pubKeyHash) {
throw new InvalidAddressError(`Invalid Address: ${address}`);
}
}
};

export {
calculateFee,
Expand Down
Loading

0 comments on commit 40e3bc2

Please sign in to comment.