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

DLC close messagetype #122

Merged
merged 6 commits into from
Aug 11, 2021
Merged
Show file tree
Hide file tree
Changes from 4 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
130 changes: 130 additions & 0 deletions packages/messaging/__tests__/messages/DlcCloseV0.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { expect } from 'chai';

import { DlcClose, DlcCloseV0 } from '../../lib/messages/DlcClose';
import { FundingInputV0 } from '../../lib/messages/FundingInput';
import { MessageType } from '../../lib/MessageType';

describe('DlcClose', () => {
let instance: DlcCloseV0;

const type = Buffer.from('cbca', 'hex');

const contractId = Buffer.from(
'c1c79e1e9e2fa2840b2514902ea244f39eb3001a4037a52ea43c797d4f841269',
'hex',
);

const closeSignature = Buffer.from(
'7c8ad6de287b62a1ed1d74ed9116a5158abc7f97376d201caa88e0f9daad68fcda4c271cc003512e768f403a57e5242bd1f6aa1750d7f3597598094a43b1c7bb',
'hex',
);

const offerPayoutSatoshis = Buffer.from('0000000005f5e100', 'hex');
const acceptPayoutSatoshis = Buffer.from('0000000005f5e100', 'hex');

const fundingInputsLen = Buffer.from('0001', 'hex');
const fundingInputV0 = Buffer.from(
'fda714' + // type funding_input_v0
'3f' + // length
'000000000000dae8' + // input_serial_id
'0029' + // prevtx_len
'02000000000100c2eb0b000000001600149ea3bf2d6eb9c2ffa35e36f41e117403ed7fafe900000000' + // prevtx
'00000000' + // prevtx_vout
'ffffffff' + // sequence
'006b' + // max_witness_len
'0000', // redeem_script_len
'hex',
);

const dlcCloseHex = Buffer.concat([
type,
contractId,
closeSignature,
offerPayoutSatoshis,
acceptPayoutSatoshis,
fundingInputsLen,
fundingInputV0,
]);

beforeEach(() => {
instance = new DlcCloseV0();
instance.contractId = contractId;
instance.closeSignature = closeSignature;
instance.offerPayoutSatoshis = BigInt(100000000);
instance.acceptPayoutSatoshis = BigInt(100000000);
instance.fundingInputs = [FundingInputV0.deserialize(fundingInputV0)];
});

describe('deserialize', () => {
it('should throw if incorrect type', () => {
instance.type = 0x123;
expect(function () {
DlcClose.deserialize(instance.serialize());
}).to.throw(Error);
});

it('has correct type', () => {
expect(DlcClose.deserialize(instance.serialize()).type).to.equal(
instance.type,
);
});
});

describe('DlcCloseV0', () => {
describe('serialize', () => {
it('serializes', () => {
expect(instance.serialize().toString('hex')).to.equal(
dlcCloseHex.toString('hex'),
);
});
});

describe('deserialize', () => {
it('deserializes', () => {
const instance = DlcCloseV0.deserialize(dlcCloseHex);
expect(instance.contractId).to.deep.equal(contractId);
expect(instance.closeSignature).to.deep.equal(closeSignature);
expect(Number(instance.offerPayoutSatoshis)).to.equal(100000000);
expect(Number(instance.acceptPayoutSatoshis)).to.equal(100000000);
expect(instance.fundingInputs[0].serialize().toString('hex')).to.equal(
fundingInputV0.toString('hex'),
);
});

it('has correct type', () => {
expect(DlcCloseV0.deserialize(dlcCloseHex).type).to.equal(
MessageType.DlcCloseV0,
);
});
});

describe('toJSON', () => {
it('convert to JSON', async () => {
const json = instance.toJSON();
expect(json.contractId).to.equal(contractId.toString('hex'));
expect(json.closeSignature).to.equal(closeSignature.toString('hex'));
expect(json.fundingInputs[0].prevTx).to.equal(
instance.fundingInputs[0].prevTx.serialize().toString('hex'),
);
});
});

describe('validate', () => {
it('should throw if inputSerialIds arent unique', () => {
instance.fundingInputs = [
FundingInputV0.deserialize(fundingInputV0),
FundingInputV0.deserialize(fundingInputV0),
];
expect(function () {
instance.validate();
}).to.throw(Error);
});
it('should ensure funding inputs are segwit', () => {
instance.fundingInputs = [FundingInputV0.deserialize(fundingInputV0)];
expect(function () {
instance.validate();
}).to.throw(Error);
});
});
});
});
1 change: 1 addition & 0 deletions packages/messaging/lib/MessageType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export enum MessageType {
DlcAcceptV0 = 42780,
DlcSignV0 = 42782,

DlcCloseV0 = 52170, // TODO: Temporary type
DlcCancelV0 = 52172,

/**
Expand Down
136 changes: 136 additions & 0 deletions packages/messaging/lib/messages/DlcClose.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { BufferReader, BufferWriter } from '@node-lightning/bufio';

import { MessageType } from '../MessageType';
import { getTlv } from '../serialize/getTlv';
import { IDlcMessage } from './DlcMessage';
import { FundingInputV0, IFundingInputV0JSON } from './FundingInput';

export abstract class DlcClose {
public static deserialize(buf: Buffer): DlcCloseV0 {
const reader = new BufferReader(buf);

// console.log('OK', Number(reader.readUInt16BE()));

const type = Number(reader.readUInt16BE());

switch (type) {
case MessageType.DlcCloseV0:
return DlcCloseV0.deserialize(buf);
default:
throw new Error(`DLC Close message type must be DlcCloseV0`); // This is a temporary measure while protocol is being developed
}
}

public abstract type: number;

public abstract toJSON(): IDlcCloseV0JSON;

public abstract serialize(): Buffer;
}

/**
* DlcOffer message contains information about a node and indicates its
* desire to enter into a new contract. This is the first step toward
* creating the funding transaction and CETs.
zpv marked this conversation as resolved.
Show resolved Hide resolved
*/
export class DlcCloseV0 extends DlcClose implements IDlcMessage {
public static type = MessageType.DlcCloseV0;

/**
* Deserializes an offer_dlc_v0 message
* @param buf
*/
public static deserialize(buf: Buffer): DlcCloseV0 {
const instance = new DlcCloseV0();
const reader = new BufferReader(buf);

reader.readUInt16BE(); // read type
instance.contractId = reader.readBytes(32);
instance.closeSignature = reader.readBytes(64);
instance.offerPayoutSatoshis = reader.readUInt64BE();
instance.acceptPayoutSatoshis = reader.readUInt64BE();
const fundingInputsLen = reader.readUInt16BE();
for (let i = 0; i < fundingInputsLen; i++) {
instance.fundingInputs.push(FundingInputV0.deserialize(getTlv(reader)));
}

return instance;
}

/**
* The type for close_dlc_v0 message. close_dlc_v0 = 52170 // TODO
*/
public type = DlcCloseV0.type;

public contractId: Buffer;

public closeSignature: Buffer;

public offerPayoutSatoshis: bigint;

public acceptPayoutSatoshis: bigint;

public fundingInputs: FundingInputV0[] = [];

/**
* Serializes the close_dlc_v0 message into a Buffer
*/
public serialize(): Buffer {
const writer = new BufferWriter();
writer.writeUInt16BE(this.type);
writer.writeBytes(this.contractId);
writer.writeBytes(this.closeSignature);
writer.writeUInt64BE(this.offerPayoutSatoshis);
writer.writeUInt64BE(this.acceptPayoutSatoshis);
writer.writeUInt16BE(this.fundingInputs.length);

for (const fundingInput of this.fundingInputs) {
writer.writeBytes(fundingInput.serialize());
}

return writer.toBuffer();
}

/**
* Validates correctness of all fields
* @throws Will throw an error if validation fails
*/
public validate(): void {
// Type is set automatically in class

// Ensure input serial ids are unique
const inputSerialIds = this.fundingInputs.map(
(input: FundingInputV0) => input.inputSerialId,
);

if (new Set(inputSerialIds).size !== inputSerialIds.length) {
throw new Error('inputSerialIds must be unique');
}

// Ensure funding inputs are segwit
this.fundingInputs.forEach((input: FundingInputV0) => input.validate());
}

/**
* Converts dlc_close_v0 to JSON
*/
public toJSON(): IDlcCloseV0JSON {
return {
type: this.type,
contractId: this.contractId.toString('hex'),
closeSignature: this.closeSignature.toString('hex'),
offerPayoutSatoshis: Number(this.offerPayoutSatoshis),
acceptPayoutSatoshis: Number(this.acceptPayoutSatoshis),
fundingInputs: this.fundingInputs.map((input) => input.toJSON()),
};
}
}

export interface IDlcCloseV0JSON {
type: number;
contractId: string;
closeSignature: string;
offerPayoutSatoshis: number;
acceptPayoutSatoshis: number;
fundingInputs: IFundingInputV0JSON[];
}