From 1030097e435dd6d9d03c8f1ece511bf34cf84cc0 Mon Sep 17 00:00:00 2001 From: buck54321 Date: Fri, 11 Oct 2024 07:20:03 -0500 Subject: [PATCH] core: protect epoch checksum with its own mutex (#2977) * match checksum to separate mutex and add dc cancel tracking --- client/core/core.go | 113 ++++++++++++++++++++++++------------ client/core/core_test.go | 101 +++++++++++++++++++------------- client/core/simnet_trade.go | 2 +- client/core/trade.go | 53 ++++++++++++----- 4 files changed, 176 insertions(+), 93 deletions(-) diff --git a/client/core/core.go b/client/core/core.go index 8527266925..e80ec38351 100644 --- a/client/core/core.go +++ b/client/core/core.go @@ -173,6 +173,10 @@ type dexConnection struct { // processed by a dex server. inFlightOrders map[uint64]*InFlightOrder + // A map linking cancel order IDs to trade order IDs. + cancelsMtx sync.RWMutex + cancels map[order.OrderID]order.OrderID + blindCancelsMtx sync.Mutex blindCancels map[order.OrderID]order.Preimage @@ -253,6 +257,25 @@ func (dc *dexConnection) bondAssets() (map[uint32]*BondAsset, uint64) { return bondAssets, cfg.BondExpiry } +func (dc *dexConnection) registerCancelLink(cid, oid order.OrderID) { + dc.cancelsMtx.Lock() + dc.cancels[cid] = oid + dc.cancelsMtx.Unlock() +} + +func (dc *dexConnection) deleteCancelLink(cid order.OrderID) { + dc.cancelsMtx.Lock() + delete(dc.cancels, cid) + dc.cancelsMtx.Unlock() +} + +func (dc *dexConnection) cancelTradeID(cid order.OrderID) (order.OrderID, bool) { + dc.cancelsMtx.RLock() + defer dc.cancelsMtx.RUnlock() + oid, found := dc.cancels[cid] + return oid, found +} + // marketConfig is the market's configuration, as returned by the server in the // 'config' response. func (dc *dexConnection) marketConfig(mktID string) *msgjson.Market { @@ -577,17 +600,19 @@ func (dc *dexConnection) activeOrders() ([]*Order, []*InFlightOrder) { // findOrder returns the tracker and preimage for an order ID, and a boolean // indicating whether this is a cancel order. -func (dc *dexConnection) findOrder(oid order.OrderID) (tracker *trackedTrade, preImg order.Preimage, isCancel bool) { +func (dc *dexConnection) findOrder(oid order.OrderID) (tracker *trackedTrade, isCancel bool) { dc.tradeMtx.RLock() defer dc.tradeMtx.RUnlock() // Try to find the order as a trade. if tracker, found := dc.trades[oid]; found { - return tracker, tracker.preImg, false + return tracker, false } - // Search the cancel order IDs. - for _, tracker := range dc.trades { - if tracker.cancel != nil && tracker.cancel.ID() == oid { - return tracker, tracker.cancel.preImg, true + + if tid, found := dc.cancelTradeID(oid); found { + if tracker, found := dc.trades[tid]; found { + return tracker, true + } else { + dc.log.Errorf("Did not find trade for cancel order ID %s", oid) } } return @@ -645,7 +670,7 @@ func (c *Core) sendCancelOrder(dc *dexConnection, oid order.OrderID, base, quote // tryCancel will look for an order with the specified order ID, and attempt to // cancel the order. It is not an error if the order is not found. func (c *Core) tryCancel(dc *dexConnection, oid order.OrderID) (found bool, err error) { - tracker, _, _ := dc.findOrder(oid) + tracker, _ := dc.findOrder(oid) if tracker == nil { return // false, nil } @@ -771,7 +796,7 @@ func (dc *dexConnection) parseMatches(msgMatches []*msgjson.Match, checkSigs boo for _, msgMatch := range msgMatches { var oid order.OrderID copy(oid[:], msgMatch.OrderID) - tracker, _, isCancel := dc.findOrder(oid) + tracker, isCancel := dc.findOrder(oid) if tracker == nil { dc.blindCancelsMtx.Lock() _, found := dc.blindCancels[oid] @@ -4953,7 +4978,7 @@ func (c *Core) Order(oidB dex.Bytes) (*Order, error) { } // See if it's an active order first. for _, dc := range c.dexConnections() { - tracker, _, _ := dc.findOrder(oid) + tracker, _ := dc.findOrder(oid) if tracker != nil { return tracker.coreOrder(), nil } @@ -8104,6 +8129,7 @@ func (c *Core) newDEXConnection(acctInfo *db.AccountInfo, flag connectDEXFlag) ( ticker: newDexTicker(defaultTickInterval), // updated when server config obtained books: make(map[string]*bookie), trades: make(map[order.OrderID]*trackedTrade), + cancels: make(map[order.OrderID]order.OrderID), inFlightOrders: make(map[uint64]*InFlightOrder), blindCancels: make(map[order.OrderID]order.Preimage), apiVer: -1, @@ -8509,7 +8535,7 @@ func handleRevokeOrderMsg(c *Core, dc *dexConnection, msg *msgjson.Message) erro var oid order.OrderID copy(oid[:], revocation.OrderID) - tracker, _, isCancel := dc.findOrder(oid) + tracker, isCancel := dc.findOrder(oid) if tracker == nil { return fmt.Errorf("no order found with id %s", oid.String()) } @@ -8551,7 +8577,7 @@ func handleRevokeMatchMsg(c *Core, dc *dexConnection, msg *msgjson.Message) erro var oid order.OrderID copy(oid[:], revocation.OrderID) - tracker, _, _ := dc.findOrder(oid) + tracker, _ := dc.findOrder(oid) if tracker == nil { return fmt.Errorf("no order found with id %s (not an error if you've completed your side of the swap)", oid.String()) } @@ -8916,7 +8942,7 @@ func handlePreimageRequest(c *Core, dc *dexConnection, msg *msgjson.Message) err } if len(req.Commitment) != order.CommitmentSize { - return fmt.Errorf("received preimage request for %v with no corresponding order submission response.", oid) + return fmt.Errorf("received preimage request for %s with no corresponding order submission response", oid) } // See if we recognize that commitment, and if we do, just wait for the @@ -8950,7 +8976,8 @@ func handlePreimageRequest(c *Core, dc *dexConnection, msg *msgjson.Message) err } func processPreimageRequest(c *Core, dc *dexConnection, reqID uint64, oid order.OrderID, commitChecksum dex.Bytes) error { - tracker, preImg, isCancel := dc.findOrder(oid) + tracker, isCancel := dc.findOrder(oid) + var preImg order.Preimage if tracker == nil { var found bool dc.blindCancelsMtx.Lock() @@ -8962,7 +8989,8 @@ func processPreimageRequest(c *Core, dc *dexConnection, reqID uint64, oid order. } else { // Record the csum if this preimage request is novel, and deny it if // this is a duplicate request with an altered csum. - if !acceptCsum(tracker, isCancel, commitChecksum) { + var accept bool + if accept, preImg = acceptCsum(tracker, isCancel, commitChecksum); !accept { csumErr := errors.New("invalid csum in duplicate preimage request") resp, err := msgjson.NewResponse(reqID, nil, msgjson.NewError(msgjson.InvalidRequestError, "%v", csumErr)) @@ -9005,26 +9033,25 @@ func processPreimageRequest(c *Core, dc *dexConnection, reqID uint64, oid order. // the server may have used the knowledge of this preimage we are sending them // now to alter the epoch shuffle. The return value is false if a previous // checksum has been recorded that differs from the provided one. -func acceptCsum(tracker *trackedTrade, isCancel bool, commitChecksum dex.Bytes) bool { +func acceptCsum(tracker *trackedTrade, isCancel bool, commitChecksum dex.Bytes) (bool, order.Preimage) { // Do not allow csum to be changed once it has been committed to // (initialized to something other than `nil`) because it is probably a // malicious behavior by the server. - tracker.mtx.Lock() - defer tracker.mtx.Unlock() - + tracker.csumMtx.Lock() + defer tracker.csumMtx.Unlock() if isCancel { - if tracker.cancel.csum == nil { - tracker.cancel.csum = commitChecksum - return true + if tracker.cancelCsum == nil { + tracker.cancelCsum = commitChecksum + return true, tracker.cancelPreimg } - return bytes.Equal(commitChecksum, tracker.cancel.csum) + return bytes.Equal(commitChecksum, tracker.cancelCsum), tracker.cancelPreimg } if tracker.csum == nil { tracker.csum = commitChecksum - return true + return true, tracker.preImg } - return bytes.Equal(commitChecksum, tracker.csum) + return bytes.Equal(commitChecksum, tracker.csum), tracker.preImg } // handleMatchRoute processes the DEX-originating match route request, @@ -9103,7 +9130,7 @@ func handleNoMatchRoute(c *Core, dc *dexConnection, msg *msgjson.Message) error var oid order.OrderID copy(oid[:], nomatchMsg.OrderID) - tracker, _, _ := dc.findOrder(oid) + tracker, _ := dc.findOrder(oid) if tracker == nil { dc.blindCancelsMtx.Lock() _, found := dc.blindCancels[oid] @@ -9177,7 +9204,7 @@ func handleAuditRoute(c *Core, dc *dexConnection, msg *msgjson.Message) error { var oid order.OrderID copy(oid[:], audit.OrderID) - tracker, _, _ := dc.findOrder(oid) + tracker, _ := dc.findOrder(oid) if tracker == nil { return fmt.Errorf("audit request received for unknown order: %s", string(msg.Payload)) } @@ -9202,7 +9229,7 @@ func handleRedemptionRoute(c *Core, dc *dexConnection, msg *msgjson.Message) err var oid order.OrderID copy(oid[:], redemption.OrderID) - tracker, _, isCancel := dc.findOrder(oid) + tracker, isCancel := dc.findOrder(oid) if tracker != nil { if isCancel { return fmt.Errorf("redemption request received for cancel order %v, match %v (you ok server?)", @@ -10253,7 +10280,7 @@ func (c *Core) RemoveWalletPeer(assetID uint32, address string) error { // id. An error is returned if it cannot be found. func (c *Core) findActiveOrder(oid order.OrderID) (*trackedTrade, error) { for _, dc := range c.dexConnections() { - tracker, _, _ := dc.findOrder(oid) + tracker, _ := dc.findOrder(oid) if tracker != nil { return tracker, nil } @@ -10640,7 +10667,7 @@ func (c *Core) handleRetryRedemptionAction(actionB []byte) error { copy(oid[:], req.OrderID) var tracker *trackedTrade for _, dc := range c.dexConnections() { - tracker, _, _ = dc.findOrder(oid) + tracker, _ = dc.findOrder(oid) if tracker != nil { break } @@ -10738,6 +10765,15 @@ func (c *Core) checkEpochResolution(host string, mktID string) { } currentEpoch := dc.marketEpoch(mktID, time.Now()) lastEpoch := currentEpoch - 1 + + // Short path if we're already resolved. + dc.epochMtx.RLock() + resolvedEpoch := dc.resolvedEpoch[mktID] + dc.epochMtx.RUnlock() + if lastEpoch == resolvedEpoch { + return + } + ts, inFlights := dc.marketTrades(mktID) for _, ord := range inFlights { if ord.Epoch == lastEpoch { @@ -10745,18 +10781,23 @@ func (c *Core) checkEpochResolution(host string, mktID string) { } } for _, t := range ts { + // Is this order from the last epoch and still not booked or executed? if t.epochIdx() == lastEpoch && t.status() == order.OrderStatusEpoch { return } - if t.cancel != nil && t.cancelEpochIdx() == lastEpoch { - t.mtx.RLock() - matched := t.cancel.matches.taker != nil - t.mtx.RUnlock() - if !matched { - return - } + // Does this order have an in-flight cancel order that is not yet + // resolved? + t.mtx.RLock() + unresolvedCancel := t.cancel != nil && t.cancelEpochIdx() == lastEpoch && t.cancel.matches.taker == nil + t.mtx.RUnlock() + if unresolvedCancel { + return } } + + // We don't have any unresolved orders or cancel orders from the last epoch. + // Just make sure that not other thread has resolved the epoch and then send + // the notification. dc.epochMtx.Lock() sendUpdate := lastEpoch > dc.resolvedEpoch[mktID] dc.resolvedEpoch[mktID] = lastEpoch diff --git a/client/core/core_test.go b/client/core/core_test.go index 8f6fa9c5b0..95a73ddfb1 100644 --- a/client/core/core_test.go +++ b/client/core/core_test.go @@ -271,6 +271,7 @@ func testDexConnection(ctx context.Context, crypter *tCrypter) (*dexConnection, }, notify: func(Notification) {}, trades: make(map[order.OrderID]*trackedTrade), + cancels: make(map[order.OrderID]order.OrderID), inFlightOrders: make(map[uint64]*InFlightOrder), epoch: map[string]uint64{tDcrBtcMktName: 0}, resolvedEpoch: map[string]uint64{tDcrBtcMktName: 0}, @@ -3792,9 +3793,9 @@ func TestHandlePreimageRequest(t *testing.T) { // resetCsum resets csum for further preimage request since multiple // testing scenarios use the same tracker object. resetCsum := func(tracker *trackedTrade) { - tracker.mtx.Lock() + tracker.csumMtx.Lock() tracker.csum = nil - tracker.mtx.Unlock() + tracker.csumMtx.Unlock() } rig.dc.trades[oid] = tracker @@ -3923,15 +3924,17 @@ func TestHandlePreimageRequest(t *testing.T) { t.Fatal("no order note from preimage request handling") } - tracker.mtx.RLock() - if !bytes.Equal(commitCSum, tracker.csum) { + tracker.csumMtx.RLock() + csum := tracker.csum + tracker.csumMtx.RUnlock() + if !bytes.Equal(commitCSum, csum) { t.Fatalf( "handlePreimageRequest must initialize tracker csum, exp: %s, got: %s", commitCSum, - tracker.csum, + csum, ) } - tracker.mtx.RUnlock() + }) t.Run("more than one preimage request for order (different csums)", func(t *testing.T) { rig := newTestRig() @@ -3996,15 +3999,17 @@ func TestHandlePreimageRequest(t *testing.T) { t.Fatal("no msgjson.Error sent from preimage request handling") } - tracker.mtx.RLock() - if !bytes.Equal(firstCSum, tracker.csum) { + tracker.csumMtx.RLock() + csum := tracker.csum + tracker.csumMtx.RUnlock() + if !bytes.Equal(firstCSum, csum) { t.Fatalf( "[handlePreimageRequest] csum was changed, exp: %s, got: %s", firstCSum, - tracker.csum, + csum, ) } - tracker.mtx.RUnlock() + }) t.Run("more than one preimage request for order (same csum)", func(t *testing.T) { rig := newTestRig() @@ -4065,15 +4070,16 @@ func TestHandlePreimageRequest(t *testing.T) { t.Fatal("no order note from preimage request handling") } - tracker.mtx.RLock() - if !bytes.Equal(csum, tracker.csum) { + tracker.csumMtx.RLock() + checkSum := tracker.csum + tracker.csumMtx.RUnlock() + if !bytes.Equal(csum, checkSum) { t.Fatalf( "[handlePreimageRequest] csum was changed, exp: %s, got: %s", csum, - tracker.csum, + checkSum, ) } - tracker.mtx.RUnlock() }) t.Run("csum for cancel order", func(t *testing.T) { rig := newTestRig() @@ -4104,7 +4110,8 @@ func TestHandlePreimageRequest(t *testing.T) { epochLen: mkt.EpochLen, }, } - oid := tracker.cancel.ID() + oid := tracker.ID() + cid := tracker.cancel.ID() // Test the new path with rig.core.sentCommits. readyCommitment := func(commit order.Commitment) chan struct{} { @@ -4119,7 +4126,7 @@ func TestHandlePreimageRequest(t *testing.T) { commitCSum := dex.Bytes{2, 3, 5, 7, 11, 13} commitSig := readyCommitment(commit) payload := &msgjson.PreimageRequest{ - OrderID: oid[:], + OrderID: cid[:], Commitment: commit[:], CommitChecksum: commitCSum, } @@ -4127,7 +4134,8 @@ func TestHandlePreimageRequest(t *testing.T) { notes := rig.core.NotificationFeed() - rig.dc.trades[order.OrderID{}] = tracker + rig.dc.trades[oid] = tracker + rig.dc.registerCancelLink(cid, oid) err := handlePreimageRequest(rig.core, rig.dc, reqCommit) if err != nil { t.Fatalf("handlePreimageRequest error: %v", err) @@ -4146,15 +4154,17 @@ func TestHandlePreimageRequest(t *testing.T) { t.Fatal("no order note from preimage request handling") } - tracker.mtx.RLock() - if !bytes.Equal(commitCSum, tracker.cancel.csum) { + tracker.csumMtx.RLock() + cancelCsum := tracker.cancelCsum + tracker.csumMtx.RUnlock() + if !bytes.Equal(commitCSum, cancelCsum) { t.Fatalf( "handlePreimageRequest must initialize tracker cancel csum, exp: %s, got: %s", commitCSum, - tracker.cancel.csum, + cancelCsum, ) } - tracker.mtx.RUnlock() + }) t.Run("more than one preimage request for cancel order (different csums)", func(t *testing.T) { rig := newTestRig() @@ -4171,9 +4181,9 @@ func TestHandlePreimageRequest(t *testing.T) { db: rig.db, dc: rig.dc, metaData: &db.OrderMetaData{}, + // Simulate first preimage request by initializing csum here. + cancelCsum: firstCSum, cancel: &trackedCancel{ - // Simulate first preimage request by initializing csum here. - csum: firstCSum, CancelOrder: order.CancelOrder{ P: order.Prefix{ AccountID: rig.dc.acct.ID(), @@ -4188,7 +4198,8 @@ func TestHandlePreimageRequest(t *testing.T) { epochLen: mkt.EpochLen, }, } - oid := tracker.cancel.ID() + oid := tracker.ID() + cid := tracker.cancel.ID() // Test the new path with rig.core.sentCommits. readyCommitment := func(commit order.Commitment) chan struct{} { @@ -4203,7 +4214,7 @@ func TestHandlePreimageRequest(t *testing.T) { secondCSum := dex.Bytes{2, 3, 5, 7, 11, 14} commitSig := readyCommitment(commit) payload := &msgjson.PreimageRequest{ - OrderID: oid[:], + OrderID: cid[:], Commitment: commit[:], CommitChecksum: secondCSum, } @@ -4214,7 +4225,8 @@ func TestHandlePreimageRequest(t *testing.T) { rig.ws.sendMsgErrChan = make(chan *msgjson.Error, 1) defer func() { rig.ws.sendMsgErrChan = nil }() - rig.dc.trades[order.OrderID{}] = tracker + rig.dc.trades[oid] = tracker + rig.dc.registerCancelLink(cid, oid) err := handlePreimageRequest(rig.core, rig.dc, reqCommit) if err != nil { t.Fatalf("handlePreimageRequest error: %v", err) @@ -4232,15 +4244,16 @@ func TestHandlePreimageRequest(t *testing.T) { case <-time.After(time.Second): t.Fatal("no msgjson.Error sent from preimage request handling") } - tracker.mtx.RLock() - if !bytes.Equal(firstCSum, tracker.cancel.csum) { + tracker.csumMtx.RLock() + cancelCsum := tracker.cancelCsum + tracker.csumMtx.RUnlock() + if !bytes.Equal(firstCSum, cancelCsum) { t.Fatalf( "[handlePreimageRequest] cancel csum was changed, exp: %s, got: %s", firstCSum, - tracker.cancel.csum, + cancelCsum, ) } - tracker.mtx.RUnlock() }) t.Run("more than one preimage request for cancel order (same csum)", func(t *testing.T) { rig := newTestRig() @@ -4257,9 +4270,9 @@ func TestHandlePreimageRequest(t *testing.T) { db: rig.db, dc: rig.dc, metaData: &db.OrderMetaData{}, + // Simulate first preimage request by initializing csum here. + cancelCsum: csum, cancel: &trackedCancel{ - // Simulate first preimage request by initializing csum here. - csum: csum, CancelOrder: order.CancelOrder{ P: order.Prefix{ AccountID: rig.dc.acct.ID(), @@ -4274,7 +4287,8 @@ func TestHandlePreimageRequest(t *testing.T) { epochLen: mkt.EpochLen, }, } - oid := tracker.cancel.ID() + oid := tracker.ID() + cid := tracker.cancel.ID() // Test the new path with rig.core.sentCommits. readyCommitment := func(commit order.Commitment) chan struct{} { @@ -4288,7 +4302,7 @@ func TestHandlePreimageRequest(t *testing.T) { commit := preImg.Commit() commitSig := readyCommitment(commit) payload := &msgjson.PreimageRequest{ - OrderID: oid[:], + OrderID: cid[:], Commitment: commit[:], CommitChecksum: csum, } @@ -4296,7 +4310,8 @@ func TestHandlePreimageRequest(t *testing.T) { notes := rig.core.NotificationFeed() - rig.dc.trades[order.OrderID{}] = tracker + rig.dc.trades[oid] = tracker + rig.dc.registerCancelLink(cid, oid) err := handlePreimageRequest(rig.core, rig.dc, reqCommit) if err != nil { t.Fatalf("handlePreimageRequest error: %v", err) @@ -4315,15 +4330,16 @@ func TestHandlePreimageRequest(t *testing.T) { t.Fatal("no order note from preimage request handling") } - tracker.mtx.RLock() - if !bytes.Equal(csum, tracker.cancel.csum) { + tracker.csumMtx.RLock() + cancelCsum := tracker.cancelCsum + tracker.csumMtx.RUnlock() + if !bytes.Equal(csum, cancelCsum) { t.Fatalf( "[handlePreimageRequest] cancel csum was changed, exp: %s, got: %s", csum, - tracker.cancel.csum, + cancelCsum, ) } - tracker.mtx.RUnlock() }) } @@ -4391,6 +4407,7 @@ func TestHandleRevokeOrderMsg(t *testing.T) { tracker.cancel = &trackedCancel{CancelOrder: *co} coid := co.ID() rig.dc.trades[oid] = tracker + rig.dc.registerCancelLink(coid, oid) orderNotes, feedDone := orderNoteFeed(tCore) defer feedDone() @@ -5016,6 +5033,7 @@ func TestTradeTracking(t *testing.T) { } tracker.cancel = &trackedCancel{CancelOrder: *co, epochLen: mkt.EpochLen} coid := co.ID() + rig.dc.registerCancelLink(coid, tracker.ID()) m1 := &msgjson.Match{ OrderID: loid[:], MatchID: mid[:], @@ -7057,6 +7075,7 @@ func TestHandleNomatch(t *testing.T) { standingTracker.cancel = &trackedCancel{ CancelOrder: *cancelOrder, } + dc.registerCancelLink(cancelOID, standingOID) // 4. Market order. loWillBeMarket, dbOrder, preImgL, _ := makeLimitOrder(dc, true, dcrBtcLotSize*100, dcrBtcRateStep) @@ -7071,7 +7090,7 @@ func TestHandleNomatch(t *testing.T) { dc.trades[marketOID] = marketTracker runNomatch := func(tag string, oid order.OrderID) { - tracker, _, _ := dc.findOrder(oid) + tracker, _ := dc.findOrder(oid) if tracker == nil { t.Fatalf("%s: order ID not found", tag) } @@ -7084,7 +7103,7 @@ func TestHandleNomatch(t *testing.T) { } checkTradeStatus := func(tag string, oid order.OrderID, expStatus order.OrderStatus) { - tracker, _, _ := dc.findOrder(oid) + tracker, _ := dc.findOrder(oid) if tracker.metaData.Status != expStatus { t.Fatalf("%s: wrong status. expected %s, got %s", tag, expStatus, tracker.metaData.Status) } diff --git a/client/core/simnet_trade.go b/client/core/simnet_trade.go index e55e8c5b39..2e94f27341 100644 --- a/client/core/simnet_trade.go +++ b/client/core/simnet_trade.go @@ -2216,7 +2216,7 @@ func (client *simulationClient) findOrder(orderID string) (*trackedTrade, error) if err != nil { return nil, fmt.Errorf("error parsing order id %s -> %v", orderID, err) } - tracker, _, _ := client.dc().findOrder(oid) + tracker, _ := client.dc().findOrder(oid) return tracker, nil } diff --git a/client/core/trade.go b/client/core/trade.go index ef1fc0ccb9..fc3584caa5 100644 --- a/client/core/trade.go +++ b/client/core/trade.go @@ -213,8 +213,6 @@ func (m *matchTracker) token() string { // trackedCancel is always associated with a trackedTrade. type trackedCancel struct { order.CancelOrder - preImg order.Preimage - csum dex.Bytes // the commitment checksum provided in the preimage request epochLen uint64 matches struct { maker *msgjson.Match @@ -279,14 +277,18 @@ type trackedTrade struct { options map[string]string // metaData.Options (immutable) for Redeem and Swap redemptionReserves uint64 // metaData.RedemptionReserves (immutable) refundReserves uint64 // metaData.RefundReserves (immutable) + preImg order.Preimage + + csumMtx sync.RWMutex + csum dex.Bytes // the commitment checksum provided in the preimage request + cancelCsum dex.Bytes + cancelPreimg order.Preimage // mtx protects all read-write fields of the trackedTrade and the // matchTrackers in the matches map. mtx sync.RWMutex metaData *db.OrderMetaData wallets *walletSet - preImg order.Preimage - csum dex.Bytes // the commitment checksum provided in the preimage request coins map[string]asset.Coin coinsLocked bool change asset.Coin @@ -592,14 +594,18 @@ func (t *trackedTrade) cancelEpochIdx() uint64 { return uint64(t.cancel.Prefix().ServerTime.UnixMilli()) / epochLen } -func (t *trackedTrade) verifyCSum(csum dex.Bytes, epochIdx uint64) error { +func (t *trackedTrade) verifyCSum(vsum dex.Bytes, epochIdx uint64) error { t.mtx.RLock() defer t.mtx.RUnlock() + t.csumMtx.RLock() + csum, cancelCsum := t.csum, t.cancelCsum + t.csumMtx.RUnlock() + // First check the trade's recorded csum, if it is in this epoch. - if epochIdx == t.epochIdx() && !bytes.Equal(csum, t.csum) { + if epochIdx == t.epochIdx() && !bytes.Equal(vsum, csum) { return fmt.Errorf("checksum %s != trade order preimage request checksum %s for trade order %v", - csum, t.csum, t.ID()) + csum, csum, t.ID()) } if t.cancel == nil { @@ -607,9 +613,9 @@ func (t *trackedTrade) verifyCSum(csum dex.Bytes, epochIdx uint64) error { } // Check the linked cancel order if it is for this epoch. - if epochIdx == t.cancelEpochIdx() && !bytes.Equal(csum, t.cancel.csum) { + if epochIdx == t.cancelEpochIdx() && !bytes.Equal(vsum, cancelCsum) { return fmt.Errorf("checksum %s != cancel order preimage request checksum %s for cancel order %v", - csum, t.cancel.csum, t.cancel.ID()) + vsum, cancelCsum, t.cancel.ID()) } return nil // includes not in epoch @@ -731,19 +737,36 @@ func (t *trackedTrade) token() string { return (t.ID().String()) } +// clearCancel clears the unmatched cancel and deletes the cancel checksum and +// link to the trade in the dexConnection. clearCancel must be called with the +// trackedTrade.mtx locked. +func (t *trackedTrade) clearCancel(preImg order.Preimage) { + if t.cancel != nil { + t.dc.deleteCancelLink(t.cancel.ID()) + t.cancel = nil + } + t.csumMtx.Lock() + t.cancelCsum = nil + t.cancelPreimg = preImg + t.csumMtx.Unlock() +} + // cancelTrade sets the cancellation data with the order and its preimage. // cancelTrade must be called with the mtx write-locked. func (t *trackedTrade) cancelTrade(co *order.CancelOrder, preImg order.Preimage, epochLen uint64) error { + t.clearCancel(preImg) t.cancel = &trackedCancel{ CancelOrder: *co, - preImg: preImg, epochLen: epochLen, } - err := t.db.LinkOrder(t.ID(), co.ID()) + cid := co.ID() + oid := t.ID() + t.dc.registerCancelLink(cid, oid) + err := t.db.LinkOrder(oid, cid) if err != nil { - return fmt.Errorf("error linking cancel order %s for trade %s: %w", co.ID(), t.ID(), err) + return fmt.Errorf("error linking cancel order %s for trade %s: %w", cid, oid, err) } - t.metaData.LinkedOrder = co.ID() + t.metaData.LinkedOrder = cid return nil } @@ -766,7 +789,7 @@ func (t *trackedTrade) nomatch(oid order.OrderID) (assetMap, error) { t.dc.log.Errorf("DB error unlinking cancel order %s for trade %s: %v", oid, t.ID(), err) } // Clearing the trackedCancel allows this order to be canceled again. - t.cancel = nil + t.clearCancel(order.Preimage{}) t.metaData.LinkedOrder = order.OrderID{} subject, details := t.formatDetails(TopicMissedCancel, makeOrderToken(t.token())) @@ -1182,7 +1205,7 @@ func (t *trackedTrade) deleteCancelOrder() { t.dc.log.Errorf("Error updating status in db for cancel order %v to revoked: %v", cid, err) } // Unlink the cancel order from the trade. - t.cancel = nil + t.clearCancel(order.Preimage{}) t.metaData.LinkedOrder = order.OrderID{} // NOTE: caller may wish to update the trades's DB entry }