From 2a7814517087334ee60e0e47b30f0c3bde7e6dfb Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Sat, 7 Dec 2024 20:33:25 -0800 Subject: [PATCH] Debugging counter-offer --- package-lock.json | 24 +++++------ package.json | 4 +- src/adapters/nostr.js | 9 +++- src/adapters/wallet.js | 14 +++++-- src/entities/offer.js | 4 +- src/use-cases/offer/index.js | 81 ++++++++++++++++++++---------------- src/use-cases/order.js | 47 ++++++++++++++++++++- 7 files changed, 125 insertions(+), 58 deletions(-) diff --git a/package-lock.json b/package-lock.json index b6ef82e..a3a5e46 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ "@psf/bch-js": "6.7.3", "axios": "0.27.2", "bch-message-lib": "2.2.1", - "bch-nostr": "1.3.2", + "bch-nostr": "1.3.3", "bch-token-sweep": "2.2.1", "bcryptjs": "2.4.3", "bitcoincashjs-lib": "3.3.3", @@ -53,7 +53,7 @@ "koa2-ratelimit": "0.9.1", "libp2p": "1.9.1", "line-reader": "0.4.0", - "minimal-slp-wallet": "5.12.0", + "minimal-slp-wallet": "5.12.1", "mongoose": "5.13.14", "node-fetch": "npm:@achingbrain/node-fetch@2.6.7", "nodemailer": "6.7.5", @@ -7420,9 +7420,9 @@ } }, "node_modules/bch-consumer": { - "version": "1.6.1", - "resolved": "http://94.130.170.209:4873/bch-consumer/-/bch-consumer-1.6.1.tgz", - "integrity": "sha512-ZhyZ54kgF6f3sB7J4ylMBJHSoBXozUdM2bvsm2nTz31515LFfHra9d9w4Ommlmy5BvuDkdX4lUeGSLRvggXnFg==", + "version": "1.5.1", + "resolved": "http://94.130.170.209:4873/bch-consumer/-/bch-consumer-1.5.1.tgz", + "integrity": "sha512-tEmds6Mk7g074+fGflm0d9At2ZV8DFhWErEN0nUzzW4vG5Ega9+yjREY16P/QuwwykzodnleQqOsNwcmvvgnAg==", "license": "MIT", "dependencies": { "@psf/bch-js": "6.7.3", @@ -7597,9 +7597,9 @@ } }, "node_modules/bch-nostr": { - "version": "1.3.2", - "resolved": "http://94.130.170.209:4873/bch-nostr/-/bch-nostr-1.3.2.tgz", - "integrity": "sha512-MdbuuTns1MhEd205uabl7Y8X+caphnb6ZgHs574bet+cSE0aw4QoMr6hpc+EmLtuKK90r5Eqob1peuQ9jzVlIg==", + "version": "1.3.3", + "resolved": "http://94.130.170.209:4873/bch-nostr/-/bch-nostr-1.3.3.tgz", + "integrity": "sha512-2Mod/PEDr/GJzQq20oSJKwJDtiXYm/Qj5KxIvFqsvBY1SYsHGyDu1FMBQ1UvOhY3ZP5fgiVLbIGvMRuWXqS4oQ==", "license": "MIT", "dependencies": { "apidoc": "0.53.0", @@ -17185,15 +17185,15 @@ } }, "node_modules/minimal-slp-wallet": { - "version": "5.12.0", - "resolved": "http://94.130.170.209:4873/minimal-slp-wallet/-/minimal-slp-wallet-5.12.0.tgz", - "integrity": "sha512-zSJcBLF/tt7NH0xkuukTJ4BxnC2GIR8kjUJRWW91/LwhI6c2K9qNVV/G5E7SjYx125aHkGk5lq3pjGePsJ18rA==", + "version": "5.12.1", + "resolved": "http://94.130.170.209:4873/minimal-slp-wallet/-/minimal-slp-wallet-5.12.1.tgz", + "integrity": "sha512-DnggKv1KC/Y6qxT6GiplrxfKPgBOdYbu7uK4bWVO+6RgTtcgMxrd4pxCFh4kHV3nV7vKBpE1wsmVZgtZ3x9qcg==", "license": "MIT", "dependencies": { "@chris.troutner/retry-queue-commonjs": "1.0.8", "@psf/bch-js": "6.8.0", "apidoc": "0.51.0", - "bch-consumer": "1.6.1", + "bch-consumer": "1.5.1", "bch-donation": "1.1.2", "crypto-js": "4.0.0" } diff --git a/package.json b/package.json index 393144b..ca64ad7 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "@psf/bch-js": "6.7.3", "axios": "0.27.2", "bch-message-lib": "2.2.1", - "bch-nostr": "1.3.2", + "bch-nostr": "1.3.3", "bch-token-sweep": "2.2.1", "bcryptjs": "2.4.3", "bitcoincashjs-lib": "3.3.3", @@ -72,7 +72,7 @@ "koa2-ratelimit": "0.9.1", "libp2p": "1.9.1", "line-reader": "0.4.0", - "minimal-slp-wallet": "5.12.0", + "minimal-slp-wallet": "5.12.1", "mongoose": "5.13.14", "node-fetch": "npm:@achingbrain/node-fetch@2.6.7", "nodemailer": "6.7.5", diff --git a/src/adapters/nostr.js b/src/adapters/nostr.js index 7812b16..2ff4089 100644 --- a/src/adapters/nostr.js +++ b/src/adapters/nostr.js @@ -2,8 +2,10 @@ Adapter library for working with Nostr. */ +// Global npm libraries import BchNostr from 'bch-nostr' import { RelayPool } from 'nostr' +import RetryQueue from '@chris.troutner/retry-queue' class NostrAdapter { constructor (localConfig = { nostrRelay: '', nostrTopic: '' }) { @@ -25,6 +27,11 @@ class NostrAdapter { // Encapsulate dependencies this.bchNostr = new BchNostr() this.RelayPool = RelayPool + this.retryQueue = new RetryQueue({ + concurrency: 1, + attempts: 5, + retryPeriod: 1000 + }) // Bind the 'this' object this.start = this.start.bind(this) @@ -71,7 +78,7 @@ class NostrAdapter { msg, tags: [['t', this.topic]] } - const eventId = await this.bchNostr.post.uploadToNostr(inObj) + const eventId = await this.retryQueue.addToQueue(this.bchNostr.post.uploadToNostr, inObj) return eventId } catch (error) { console.log(`Error in nostr.js/post() ${error.message} `) diff --git a/src/adapters/wallet.js b/src/adapters/wallet.js index a40e99b..7055e27 100644 --- a/src/adapters/wallet.js +++ b/src/adapters/wallet.js @@ -5,12 +5,11 @@ // Public npm libraries import BchWallet from 'minimal-slp-wallet' import BchTokenSweep from 'bch-token-sweep/index.js' - import bitcoinJs from 'bitcoincashjs-lib' +import RetryQueue from '@chris.troutner/retry-queue' // Local libraries import JsonFiles from './json-files.js' - import config from '../../config/index.js' // Hack to get __dirname back. @@ -35,6 +34,11 @@ class WalletAdapter { this.BchWallet = BchWallet this.bchWallet = {} // Will be replaced when initialized. // this.advancedConfig = localConfig.advancedConfig + this.retryQueue = new RetryQueue({ + concurrency: 1, + attempts: 5, + retryPeriod: 5000 + }) // Bind the 'this' object this.moveTokens = this.moveTokens.bind(this) @@ -441,7 +445,7 @@ class WalletAdapter { return txObj2 } catch (err) { - console.error('Error in wallet.js/deserializePartialTx()') + console.error('Error in wallet.js/deserializeTx()') throw err } } @@ -508,7 +512,9 @@ class WalletAdapter { // return csTxHex // Broadcast transaction to the network - const txid = await this.bchWallet.ar.sendTx(csTxHex) + // const txid = await this.bchWallet.ar.sendTx(csTxHex) + const txid = await this.retryQueue.addToQueue(this.bchWallet.broadcast, { hex: csTxHex }) + console.log('completeTx() txid: ', txid) return txid } catch (err) { diff --git a/src/entities/offer.js b/src/entities/offer.js index c0116d7..8e2caed 100644 --- a/src/entities/offer.js +++ b/src/entities/offer.js @@ -17,7 +17,7 @@ class OfferEntity { ) } - console.log('offer entity validate() offerData: ', offerData) + // console.log('offer entity validate() offerData: ', offerData) const { messageType, @@ -105,7 +105,7 @@ class OfferEntity { tokenType, nostrEventId } - console.log('offer entity validatedOfferData: ', validatedOfferData) + // console.log('offer entity validatedOfferData: ', validatedOfferData) return validatedOfferData } diff --git a/src/use-cases/offer/index.js b/src/use-cases/offer/index.js index ceb2114..017ed91 100644 --- a/src/use-cases/offer/index.js +++ b/src/use-cases/offer/index.js @@ -94,7 +94,7 @@ class OfferUseCases { } // const utxoStatus = await this.adapters.wallet.bchWallet.utxoIsValid(utxo) const utxoStatus = await this.retryQueue.addToQueue(this.adapters.wallet.bchWallet.utxoIsValid, utxo) - console.log('utxoStatus: ', utxoStatus) + // console.log('utxoStatus: ', utxoStatus) // if (utxoStatus === null) return false if (!utxoStatus) return false @@ -175,7 +175,7 @@ class OfferUseCases { const nsfwSetTrue = mutableData.nsfw === true const nsfwStringTrue = mutableData.nsfw === 'true' const nsfwDetected = hasNsfw && (nsfwSetTrue || nsfwStringTrue) - console.log(`hasNsfw: ${hasNsfw}, nsfwSetTrue: ${nsfwSetTrue}, nsfwStringTrue: ${nsfwStringTrue}, nsfwDetected: ${nsfwDetected}`) + // console.log(`hasNsfw: ${hasNsfw}, nsfwSetTrue: ${nsfwSetTrue}, nsfwStringTrue: ${nsfwStringTrue}, nsfwDetected: ${nsfwDetected}`) if (nsfwDetected) { console.log('NSFW flag set as true') @@ -318,7 +318,7 @@ class OfferUseCases { // Note : should be added to retry-queue? const utxoStatus = await this.adapters.wallet.bchWallet.utxoIsValid(utxo) - console.log('utxoStatus: ', utxoStatus) + // console.log('utxoStatus: ', utxoStatus) if (!utxoStatus) { console.log(`utxo txid: ${offerInfo.utxoTxid}, vout: ${offerInfo.utxoVout}`) @@ -377,11 +377,11 @@ class OfferUseCases { data: takenOfferInfo, appId: this.config.p2wdbAppId } - const resultEventId = await this.adapters.nostr.post(nostrData) + const resultEventId = await this.adapters.nostr.post(JSON.stringify(nostrData)) // Delete the Offer from the database, so that the user doesn't attempt // to take the offer more than once. - offerInfo.remove() + // offerInfo.remove() // Return the P2WDB CID return resultEventId @@ -472,27 +472,27 @@ class OfferUseCases { } async findOfferByTxid (utxoTxid) { - try { - // try { - if (typeof utxoTxid !== 'string' || !utxoTxid) { - throw new Error('utxoTxid must be a string') - } - - const offer = await this.OfferModel.findOne({ utxoTxid }) - - // TODO: Offer should be found by TXID, then if there is more than one - // result, they should be filtered by the vout property. That will leave - // one remaining UTXO. - - if (!offer) { - throw new Error('offer not found') - } - - return offer - } catch (error) { - console.error('Error in use-cases/offer/findOfferByTxid(): ', error.message) - throw error + // try { + // try { + if (typeof utxoTxid !== 'string' || !utxoTxid) { + throw new Error('utxoTxid must be a string') } + + const offer = await this.OfferModel.findOne({ utxoTxid }) + + // TODO: Offer should be found by TXID, then if there is more than one + // result, they should be filtered by the vout property. That will leave + // one remaining UTXO. + + if (!offer) { + throw new Error('offer not found') + } + + return offer + // } catch (error) { + // // console.error('Error in use-cases/offer/findOfferByTxid(): ', error.message) + // throw error + // } } // This function is called by the P2WDB webhook REST API handler. When a @@ -505,17 +505,18 @@ class OfferUseCases { // See if this instance of bch-dex is managing the Order associated with // the incoming Counter Offer. - - // Note : this should be handle by nostrEvent id or UtxoId? - const orderHash = offerData.data.nostrEventId + console.log('ping11') + // Note : this should be handled by nostrEvent id or UtxoId? + // const orderHash = offerData.data.nostrEventId let orderData = {} try { - orderData = await this.orderUseCase.findOrderByEvent(orderHash) + orderData = await this.orderUseCase.findOrderByUtxo(offerData) console.log(`orderData: ${JSON.stringify(orderData, null, 2)}`) } catch (err) { - console.log('Order matching this Counter Offer is not managed by this instance of bch-dex. Exiting.') + console.log('Order matching this Counter Offer is not managed by this instance of bch-dex. Skipping.') return 'N/A' } + console.log('ping12') // Deserialize the partially signed transaction. const txHex = offerData.data.partialTxHex @@ -549,7 +550,7 @@ class OfferUseCases { return txid } catch (err) { - console.error('Error in acceptCounterOffer()') + console.error('Error in acceptCounterOffer(): ', err) throw err } } @@ -637,7 +638,7 @@ class OfferUseCases { // If the Offer UTXO is spent, delete the Offer model. if (utxoStatus === false) { - console.log('utxoStatus: ', utxoStatus) + // console.log('utxoStatus: ', utxoStatus) console.log(`Spent UTXO detected. Deleting this Offer: ${JSON.stringify(thisOffer, null, 2)}`) await thisOffer.remove() } @@ -698,7 +699,7 @@ class OfferUseCases { try { // Retrieve offers array. const offers = await this.adapters.nostr.read() - console.log('offers: ', offers) + // console.log('offers: ', offers) for (let i = 0; i < offers.length; i++) { try { @@ -709,9 +710,19 @@ class OfferUseCases { // Append the Nostr Event ID to the offer object offerObj.data.nostrEventId = offer.eventId + // console.log('loadOffers() offerObj: ', offerObj) - // Try to create new offer - await this.createOffer(offerObj) + if (offerObj.data.dataType === 'offer') { + // Try to create new offer + await this.createOffer(offerObj) + } + + if (offerObj.data.dataType === 'counter-offer') { + console.log('Counter offer detected: ', offerObj) + await this.acceptCounterOffer(offerObj) + } + + // Ignore the post if it doesn't fit the above filters. } catch (error) { /* exit quietly */ } diff --git a/src/use-cases/order.js b/src/use-cases/order.js index 3aa6841..1048a3d 100644 --- a/src/use-cases/order.js +++ b/src/use-cases/order.js @@ -31,6 +31,8 @@ class OrderLib { // Bind subfunctions to the 'this' object. this.ensureFunds = this.ensureFunds.bind(this) + this.createOrder = this.createOrder.bind(this) + this.findOrderByUtxo = this.findOrderByUtxo.bind(this) } // Create a new order model and add it to the Mongo database. @@ -191,6 +193,47 @@ class OrderLib { } } + // Retrieve an Order model from the database. Find it by its UTXO (TXID & Vout) + async findOrderByUtxo (offerData = {}) { + try { + console.log('findOrderByUtxo() offerData: ', offerData) + + // if (typeof nostrEventId !== 'string' || !nostrEventId) { + // throw new Error('nostrEventId must be a string') + // } + + console.log('ping01') + + // const allOrders = await this.OrderModel.find({}) + // console.log('allOrders: ', allOrders) + + console.log('ping02') + + // const order = await this.OrderModel.findOne({ nostrEventId }) + const order = await this.OrderModel.findOne({ utxoTxid: offerData.data.utxoTxid, utxoVout: offerData.data.utxoVout }) + // const order = await this.OrderModel.findOne({ utxoTxid: offerData.data.utxoTxid }) + // const order = await this.OrderModel.findOne({ tokenId: offerData.data.tokenId }) + console.log('findOrderByUtxo() order: ', order) + + console.log('ping03') + + if (!order) { + throw new Error('order not found') + } + if (!order.nostrEventId) { + throw new Error('order not found') + } + + const orderObject = order.toObject() + // return this.offerEntity.validateFromModel(offerObject) + + return orderObject + } catch (err) { + console.error('Error in findOrderByUtxo(): ', err) + throw err + } + } + // This function is called by the garbage collection timer controller. It // checks the UTXO associated with each Order in the database. If the UTXO // has been spent, the Order is deleted from the database. @@ -201,7 +244,7 @@ class OrderLib { // Get all Orders in the database. const orders = await this.OrderModel.find({}) - console.log('orders: ', orders) + // console.log('orders: ', orders) // Loop through each Order and ensure the UTXO is still valid. for (let i = 0; i < orders.length; i++) { @@ -239,7 +282,7 @@ class OrderLib { // If the Order UTXO is spent, delete the Order model. if (utxoStatus === false) { - console.log('utxoStatus: ', utxoStatus) + // console.log('utxoStatus: ', utxoStatus) console.log(`Spent UTXO detected. Deleting this Order: ${JSON.stringify(thisOrder, null, 2)}`) await thisOrder.remove() }