diff --git a/justfile b/justfile index 8020aac9..a9cd2cfc 100644 --- a/justfile +++ b/justfile @@ -1,6 +1,9 @@ install: yarn && yarn bootstrap +bootstrap: + yarn bootstrap + build: yarn build diff --git a/packages/rocksdb/__tests__/rocksdb-dlc-store.spec.ts b/packages/rocksdb/__tests__/rocksdb-dlc-store.spec.ts index 3fffc8c8..13989f0e 100644 --- a/packages/rocksdb/__tests__/rocksdb-dlc-store.spec.ts +++ b/packages/rocksdb/__tests__/rocksdb-dlc-store.spec.ts @@ -10,7 +10,7 @@ import { DlcTransactionsV0, FundingInputV0, } from '@node-dlc/messaging'; -import { OutPoint } from '@node-lightning/bitcoin'; +import { OutPoint, Value } from '@node-lightning/bitcoin'; import { sha256, xor } from '@node-lightning/crypto'; import { expect } from 'chai'; @@ -211,6 +211,23 @@ describe('RocksdbDlcStore', () => { const dlcClose = DlcCloseV0.deserialize(dlcCloseHex); + // Batch Dlc Messages + + // Increase funding input amount to match minimum collateral for batch + const dlcOfferForBatch = DlcOfferV0.deserialize(dlcOffer.serialize()); + (dlcOfferForBatch + .fundingInputs[0] as FundingInputV0).prevTx.outputs[0].value = Value.fromBitcoin( + 4, + ); + + // Increase funding input amount to match minimum collateral for batch + const dlcAcceptForBatch = DlcAcceptV0.deserialize(dlcAccept.serialize()); + dlcAcceptForBatch.tempContractId = sha256(dlcOfferForBatch.serialize()); + (dlcAcceptForBatch + .fundingInputs[0] as FundingInputV0).prevTx.outputs[0].value = Value.fromBitcoin( + 4, + ); + before(async () => { util.rmdir('.testdb'); sut = new RocksdbDlcStore('./.testdb/nested/dir'); @@ -365,6 +382,13 @@ describe('RocksdbDlcStore', () => { }); }); + describe('save dlc_accepts', () => { + it('should save batch dlc accepts', async () => { + await sut.saveDlcOffer(dlcOfferForBatch); + await sut.saveDlcAccepts([dlcAcceptForBatch, dlcAcceptForBatch]); + }); + }); + describe('delete dlc_offer', () => { it('should delete dlc_offer', async () => { await sut.deleteDlcOffer(contractId); diff --git a/packages/rocksdb/lib/rocksdb-dlc-store.ts b/packages/rocksdb/lib/rocksdb-dlc-store.ts index 0a72c174..f369bf60 100644 --- a/packages/rocksdb/lib/rocksdb-dlc-store.ts +++ b/packages/rocksdb/lib/rocksdb-dlc-store.ts @@ -1,6 +1,5 @@ -import { DlcTxBuilder } from '@node-dlc/core'; +import { BatchDlcTxBuilder, DlcTxBuilder } from '@node-dlc/core'; import { - ContractInfo, ContractInfoV0, ContractInfoV1, DlcAcceptV0, @@ -257,6 +256,50 @@ export class RocksdbDlcStore extends RocksdbBase { await this._db.put(key3, contractId); } + // NOTE: ONLY USE FOR BATCH FUNDED DLCs + public async saveDlcAccepts(dlcAccepts: DlcAcceptV0[]): Promise { + const dlcOffers: DlcOfferV0[] = []; + for (let i = 0; i < dlcAccepts.length; i++) { + const dlcOffer = await this.findDlcOffer(dlcAccepts[i].tempContractId); + dlcOffers.push(dlcOffer); + } + const txBuilder = new BatchDlcTxBuilder(dlcOffers, dlcAccepts); + const tx = txBuilder.buildFundingTransaction(); + const fundingTxId = tx.txId.serialize(); + const contractIds = dlcAccepts.map((dlcAccepts) => + xor(fundingTxId, dlcAccepts.tempContractId), + ); + for (let i = 0; i < dlcAccepts.length; i++) { + const value = dlcAccepts[i].serialize(); + const key = Buffer.concat([ + Buffer.from([Prefix.DlcAcceptV0]), + contractIds[i], + ]); + await this._db.put(key, value); + + // store funding input outpoint reference + for (let i = 0; i < dlcAccepts[i].fundingInputs.length; i++) { + const fundingInput = dlcAccepts[i].fundingInputs[i] as FundingInputV0; + + const outpoint = OutPoint.fromString( + `${fundingInput.prevTx.txId.toString()}:${fundingInput.prevTxVout}`, + ); + + const key2 = Buffer.concat([ + Buffer.from([Prefix.Outpoint]), + Buffer.from(outpoint.toString()), + ]); + await this._db.put(key2, contractIds[i]); + } + + const key3 = Buffer.concat([ + Buffer.from([Prefix.TempContractId]), + dlcAccepts[i].tempContractId, + ]); + await this._db.put(key3, contractIds[i]); + } + } + public async deleteDlcAccept(contractId: Buffer): Promise { const key = Buffer.concat([Buffer.from([Prefix.DlcAcceptV0]), contractId]); await this._db.del(key);