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

feat: add batch funding group tlv #207

Merged
merged 1 commit into from
Mar 14, 2024
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
52 changes: 52 additions & 0 deletions packages/messaging/__tests__/messages/BatchFundingGroup.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Value } from '@node-dlc/bitcoin';
import { expect } from 'chai';

import { BatchFundingGroup } from '../../lib';

describe('BatchFundingGroup TLV', () => {
it('should serialize and deserialize without contract ids', () => {
const batchFundingGroup = new BatchFundingGroup();

const eventIds = ['event1', 'event2', 'event3'];

batchFundingGroup.eventIds = eventIds;
batchFundingGroup.allocatedCollateral = Value.fromBitcoin(0.5);

const deserialized = BatchFundingGroup.deserialize(
batchFundingGroup.serialize(),
);

expect(deserialized.eventIds).to.deep.equal(eventIds);
expect(batchFundingGroup.serialize()).to.deep.equal(
deserialized.serialize(),
);
});

it('should serialize and deserialize with contract ids', () => {
const batchFundingGroup = new BatchFundingGroup();

const eventIds = ['event1', 'event2', 'event3'];

batchFundingGroup.eventIds = eventIds;
batchFundingGroup.allocatedCollateral = Value.fromBitcoin(0.5);
batchFundingGroup.tempContractIds = [
Buffer.from('tempContractId1'),
Buffer.from('tempContractId2'),
Buffer.from('tempContractId3'),
];
batchFundingGroup.contractIds = [
Buffer.from('contractId1'),
Buffer.from('contractId2'),
Buffer.from('contractId3'),
];

const deserialized = BatchFundingGroup.deserialize(
batchFundingGroup.serialize(),
);

expect(deserialized.eventIds).to.deep.equal(eventIds);
expect(batchFundingGroup.serialize()).to.deep.equal(
deserialized.serialize(),
);
});
});
2 changes: 2 additions & 0 deletions packages/messaging/lib/MessageType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ export enum MessageType {

AddressCache = 65132,

BatchFundingGroup = 65430,

IrcMessageV0 = 59314,

NodeAnnouncement = 51394,
Expand Down
1 change: 1 addition & 0 deletions packages/messaging/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export * from './chain/IChainFilterChainClient';
export * from './domain/Address';
export * from './irc/IrcMessage';
export * from './messages/AddressCache';
export * from './messages/BatchFundingGroup';
export * from './messages/CetAdaptorSignaturesV0';
export * from './messages/ContractDescriptor';
export * from './messages/ContractInfo';
Expand Down
131 changes: 131 additions & 0 deletions packages/messaging/lib/messages/BatchFundingGroup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { Value } from '@node-dlc/bitcoin';
import { BufferReader, BufferWriter } from '@node-lightning/bufio';

import { MessageType } from '../MessageType';
import { IDlcMessage } from './DlcMessage';

/**
* The BatchFundingGroup TLV contains information about the intent to
* enter multiple DLCs simulatenously within one batch dlc funding
* transaction in the contract negotiation stage of the peer protocol
*
* This is the first step toward creating a batch dlc funding transaction
*
* A DlcOffer or DlcAccept can contain one or multiple BatchFundingInfo
* TLVs to specify one or more groupings. This allows specification of
* collateral put towards different types of contracts, such as options
* contracts, futures contracts, or other investment types.
*
* Attributes:
* - tempContractIds: Temporary identifiers for contracts proposed in DlcOffers.
* - contractIds: Identifiers for contracts that have been accepted and are
* part of the funding transaction. These are derived from DlcOffers and DlcAccepts.
* - allocatedCollateral: The amount of collateral allocated to the contracts
* within this group. This is specified early in the negotiation process.
* - eventIds: Oracle event identifiers for the contracts in this group. These
* are also specified early in the negotiation process.
*
* Note: During the early stages of the negotiation protocol, only allocatedCollateral
* and eventIds are specified. tempContractIds and contractIds are added to the
* DlcAccept upon creation.
*/
export class BatchFundingGroup implements IDlcMessage {
public static type = MessageType.BatchFundingGroup;

/**
* Deserializes a batch_contract_info message
* @param buf
*/
public static deserialize(buf: Buffer): BatchFundingGroup {
const instance = new BatchFundingGroup();
const reader = new BufferReader(buf);

reader.readBigSize(); // read type
const tempContractIdsCount = reader.readBigSize();
for (let i = 0; i < Number(tempContractIdsCount); i++) {
const length = reader.readBigSize();
instance.tempContractIds.push(reader.readBytes(Number(length)));
}

const contractIdsCount = reader.readBigSize();
for (let i = 0; i < Number(contractIdsCount); i++) {
const length = reader.readBigSize();
instance.contractIds.push(reader.readBytes(Number(length)));
}

instance.allocatedCollateral = Value.fromSats(reader.readUInt64BE());

const eventIdsCount = reader.readBigSize();
for (let i = 0; i < Number(eventIdsCount); i++) {
const length = reader.readBigSize();
instance.eventIds.push(reader.readBytes(Number(length)).toString());
}

return instance;
}

/**
* The type for batch_contract_info message.
*/
public type = BatchFundingGroup.type;

public tempContractIds: Buffer[] = [];

public contractIds: Buffer[] = [];

public allocatedCollateral: Value;

public eventIds: string[] = [];

/**
* Converts batch_funding_info to JSON
*/
public toJSON(): IBatchFundingGroupJSON {
return {
type: this.type,
tempContractIds: this.tempContractIds.map((id) => id.toString('hex')),
contractIds: this.contractIds.map((id) => id.toString('hex')),
totalCollateral: Number(this.allocatedCollateral.sats),
eventIds: this.eventIds,
};
}

/**
* Serializes the batch_funding_info message into a Buffer
*/
public serialize(): Buffer {
const writer = new BufferWriter();
writer.writeBigSize(this.type);

writer.writeBigSize(this.tempContractIds.length);
this.tempContractIds.forEach((id) => {
writer.writeBigSize(id.length);
writer.writeBytes(id);
});

writer.writeBigSize(this.contractIds.length);
this.contractIds.forEach((id) => {
writer.writeBigSize(id.length);
writer.writeBytes(id);
});

writer.writeUInt64BE(this.allocatedCollateral.sats);

writer.writeBigSize(this.eventIds.length);
this.eventIds.forEach((id) => {
const idBuffer = Buffer.from(id);
writer.writeBigSize(idBuffer.length);
writer.writeBytes(idBuffer);
});

return writer.toBuffer();
}
}

export interface IBatchFundingGroupJSON {
type: number;
tempContractIds: string[];
contractIds: string[];
totalCollateral: number;
eventIds: string[];
}
38 changes: 38 additions & 0 deletions packages/messaging/lib/messages/DlcAccept.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import { address } from 'bitcoinjs-lib';
import secp256k1 from 'secp256k1';

import { MessageType } from '../MessageType';
import { deserializeTlv } from '../serialize/deserializeTlv';
import { getTlv, skipTlv } from '../serialize/getTlv';
import { BatchFundingGroup, IBatchFundingGroupJSON } from './BatchFundingGroup';
import {
CetAdaptorSignaturesV0,
ICetAdaptorSignaturesV0JSON,
Expand Down Expand Up @@ -85,6 +87,23 @@ export class DlcAcceptV0 extends DlcAccept implements IDlcMessage {
instance.refundSignature = reader.readBytes(64);
instance.negotiationFields = NegotiationFields.deserialize(getTlv(reader));

while (!reader.eof) {
const buf = getTlv(reader);
const tlvReader = new BufferReader(buf);
const { type } = deserializeTlv(tlvReader);

switch (Number(type)) {
case MessageType.BatchFundingGroup:
if (!instance.batchFundingGroups) {
instance.batchFundingGroups = [];
}
instance.batchFundingGroups.push(BatchFundingGroup.deserialize(buf));
break;
default:
break;
}
}

return instance;
}

Expand Down Expand Up @@ -115,6 +134,8 @@ export class DlcAcceptV0 extends DlcAccept implements IDlcMessage {

public negotiationFields: NegotiationFields;

public batchFundingGroups?: BatchFundingGroup[];

/**
* Get funding, change and payout address from DlcOffer
* @param network Bitcoin Network
Expand Down Expand Up @@ -196,6 +217,14 @@ export class DlcAcceptV0 extends DlcAccept implements IDlcMessage {
* Converts dlc_accept_v0 to JSON
*/
public toJSON(): IDlcAcceptV0JSON {
const tlvs = [];

if (this.batchFundingGroups) {
this.batchFundingGroups.forEach((group) => {
tlvs.push(group.serialize());
});
}

return {
type: this.type,
tempContractId: this.tempContractId.toString('hex'),
Expand All @@ -209,6 +238,7 @@ export class DlcAcceptV0 extends DlcAccept implements IDlcMessage {
cetSignatures: this.cetSignatures.toJSON(),
refundSignature: this.refundSignature.toString('hex'),
negotiationFields: this.negotiationFields.toJSON(),
tlvs,
};
}

Expand Down Expand Up @@ -237,6 +267,11 @@ export class DlcAcceptV0 extends DlcAccept implements IDlcMessage {
writer.writeBytes(this.refundSignature);
writer.writeBytes(this.negotiationFields.serialize());

if (this.batchFundingGroups)
this.batchFundingGroups.forEach((fundingInfo) =>
writer.writeBytes(fundingInfo.serialize()),
);

return writer.toBuffer();
}

Expand All @@ -251,6 +286,7 @@ export class DlcAcceptV0 extends DlcAccept implements IDlcMessage {
this.changeSPK,
this.changeSerialId,
this.negotiationFields,
this.batchFundingGroups,
);
}
}
Expand All @@ -266,6 +302,7 @@ export class DlcAcceptWithoutSigs {
readonly changeSPK: Buffer,
readonly changeSerialId: bigint,
readonly negotiationFields: NegotiationFields,
readonly batchFundingGroups?: BatchFundingGroup[],
) {}
}

Expand All @@ -285,6 +322,7 @@ export interface IDlcAcceptV0JSON {
| INegotiationFieldsV0JSON
| INegotiationFieldsV1JSON
| INegotiationFieldsV2JSON;
tlvs: IBatchFundingGroupJSON[];
}

export interface IDlcAcceptV0Addresses {
Expand Down
24 changes: 23 additions & 1 deletion packages/messaging/lib/messages/DlcOffer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import secp256k1 from 'secp256k1';
import { MessageType } from '../MessageType';
import { deserializeTlv } from '../serialize/deserializeTlv';
import { getTlv } from '../serialize/getTlv';
import { BatchFundingGroup, IBatchFundingGroupJSON } from './BatchFundingGroup';
import {
ContractInfo,
IContractInfoV0JSON,
Expand Down Expand Up @@ -111,6 +112,12 @@ export class DlcOfferV0 extends DlcOffer implements IDlcMessage {
case MessageType.OrderPositionInfoV0:
instance.positionInfo = OrderPositionInfo.deserialize(buf);
break;
case MessageType.BatchFundingGroup:
if (!instance.batchFundingGroups) {
instance.batchFundingGroups = [];
}
instance.batchFundingGroups.push(BatchFundingGroup.deserialize(buf));
break;
default:
break;
}
Expand Down Expand Up @@ -158,6 +165,8 @@ export class DlcOfferV0 extends DlcOffer implements IDlcMessage {

public positionInfo?: OrderPositionInfo;

public batchFundingGroups?: BatchFundingGroup[];

/**
* Get funding, change and payout address from DlcOffer
* @param network Bitcoin Network
Expand Down Expand Up @@ -294,6 +303,10 @@ export class DlcOfferV0 extends DlcOffer implements IDlcMessage {
if (this.metadata) tlvs.push(this.metadata.toJSON());
if (this.ircInfo) tlvs.push(this.ircInfo.toJSON());
if (this.positionInfo) tlvs.push(this.positionInfo.toJSON());
if (this.batchFundingGroups)
this.batchFundingGroups.forEach((fundingInfo) =>
tlvs.push(fundingInfo.toJSON()),
);

return {
type: this.type,
Expand Down Expand Up @@ -346,6 +359,10 @@ export class DlcOfferV0 extends DlcOffer implements IDlcMessage {
if (this.metadata) writer.writeBytes(this.metadata.serialize());
if (this.ircInfo) writer.writeBytes(this.ircInfo.serialize());
if (this.positionInfo) writer.writeBytes(this.positionInfo.serialize());
if (this.batchFundingGroups)
this.batchFundingGroups.forEach((fundingInfo) =>
writer.writeBytes(fundingInfo.serialize()),
);

return writer.toBuffer();
}
Expand All @@ -367,7 +384,12 @@ export interface IDlcOfferV0JSON {
feeRatePerVb: number;
cetLocktime: number;
refundLocktime: number;
tlvs: (IOrderMetadataJSON | IOrderIrcInfoJSON | IOrderPositionInfoJSON)[];
tlvs: (
| IOrderMetadataJSON
| IOrderIrcInfoJSON
| IOrderPositionInfoJSON
| IBatchFundingGroupJSON
)[];
}

export interface IDlcOfferV0Addresses {
Expand Down
Loading
Loading