-
Notifications
You must be signed in to change notification settings - Fork 92
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
swap,market: add TxMonitored #95
Changes from all commits
4567e9d
2f91a4a
e13cd8a
6fdfaf5
62dfbb2
e5c9c1a
7f98b18
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -379,8 +379,8 @@ func (s *Swapper) tryConfirmSwap(status *swapStatus) { | |
// the appropriate flags in the swapStatus structures. | ||
func (s *Swapper) processBlock(block *blockNotification) { | ||
completions := make([]*matchTracker, 0) | ||
s.matchMtx.RLock() | ||
defer s.matchMtx.RUnlock() | ||
s.matchMtx.Lock() | ||
defer s.matchMtx.Unlock() | ||
for _, match := range s.matches { | ||
// If it's neither of the match assets, nothing to do. | ||
if match.makerStatus.swapAsset != block.assetID && match.takerStatus.swapAsset != block.assetID { | ||
|
@@ -409,37 +409,113 @@ func (s *Swapper) processBlock(block *blockNotification) { | |
case order.MatchComplete: | ||
// Once both redemption transactions have SwapConf confirmations, the | ||
// order is complete. | ||
var makerRedeemed, takerRedeemed bool | ||
mStatus, tStatus := match.makerStatus, match.takerStatus | ||
if !mStatus.redeemTime.IsZero() { | ||
confs, err := mStatus.redemption.Confirmations() | ||
makerRedeemed = err == nil && confs >= int64(s.coins[tStatus.swapAsset].SwapConf) | ||
} | ||
if makerRedeemed && !tStatus.redeemTime.IsZero() { | ||
confs, err := tStatus.redemption.Confirmations() | ||
takerRedeemed = err == nil && confs >= int64(s.coins[mStatus.swapAsset].SwapConf) | ||
} | ||
makerRedeemed, takerRedeemed := s.redeemStatus(match) | ||
// TODO: Can coins be unlocked now regardless of redemption? | ||
if makerRedeemed && takerRedeemed { | ||
completions = append(completions, match) | ||
} | ||
} | ||
} | ||
|
||
for _, match := range completions { | ||
s.unlockOrderCoins(match.Taker) | ||
s.unlockOrderCoins(match.Maker) | ||
// Remove the completed match. Note that checkInaction may also remove | ||
// matches, so this entire function must lock even if there are no | ||
// completions. | ||
delete(s.matches, match.ID().String()) | ||
} | ||
} | ||
|
||
func (s *Swapper) redeemStatus(match *matchTracker) (makerRedeemComplete, takerRedeemComplete bool) { | ||
makerRedeemComplete = s.makerRedeemStatus(match) | ||
tStatus := match.takerStatus | ||
// Taker is only complete if the maker is complete because | ||
// order.MatchComplete follows order.MakerRedeemed. | ||
if makerRedeemComplete && !tStatus.redeemTime.IsZero() { | ||
confs, err := tStatus.redemption.Confirmations() | ||
if err != nil { | ||
log.Errorf("Confirmations failed for taker redemption %v: err", | ||
tStatus.redemption.TxID(), err) | ||
return | ||
} | ||
takerRedeemComplete = confs >= int64(s.coins[match.makerStatus.swapAsset].SwapConf) | ||
} | ||
return | ||
} | ||
|
||
func (s *Swapper) makerRedeemStatus(match *matchTracker) (makerRedeemComplete bool) { | ||
mStatus, tStatus := match.makerStatus, match.takerStatus | ||
if !mStatus.redeemTime.IsZero() { | ||
confs, err := mStatus.redemption.Confirmations() | ||
if err != nil { | ||
log.Errorf("Confirmations failed for maker redemption %v: err", | ||
mStatus.redemption.TxID(), err) // Severity? | ||
return | ||
} | ||
makerRedeemComplete = confs >= int64(s.coins[tStatus.swapAsset].SwapConf) | ||
} | ||
return | ||
} | ||
|
||
// TxMonitored determines whether the transaction for the given user is involved | ||
// in a DEX-monitored trade. Note that the swap contract tx is considered | ||
// monitored until the swap is complete, regardless of confirms. This allows | ||
// change outputs from a dex-monitored swap contract to be used to fund | ||
// additional swaps prior to FundConf. e.g. OrderRouter may allow coins to fund | ||
// orders where: (coins.confs >= FundConf) OR TxMonitored(coins.tx). | ||
func (s *Swapper) TxMonitored(user account.AccountID, asset uint32, txid string) bool { | ||
s.matchMtx.RLock() | ||
defer s.matchMtx.RUnlock() | ||
|
||
for _, match := range s.matches { | ||
// The swap contract of either the maker or taker must correspond to | ||
// specified asset to be of interest. | ||
switch asset { | ||
case match.makerStatus.swapAsset: | ||
// Maker's swap transaction is the asset of interest. | ||
if user == match.Maker.User() && match.makerStatus.swap.TxID() == txid { | ||
// The swap contract tx is considered monitored until the swap | ||
// is complete, regardless of confirms. | ||
return true | ||
} | ||
|
||
// Taker's redemption transaction is the asset of interest. | ||
_, takerRedeemDone := s.redeemStatus(match) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The throwaway here is not ideal. Every call to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the next case, we can now do |
||
if !takerRedeemDone && user == match.Taker.User() && | ||
match.takerStatus.redemption.TxID() == txid { | ||
return true | ||
} | ||
case match.takerStatus.swapAsset: | ||
// Taker's swap transaction is the asset of interest. | ||
if user == match.Taker.User() && match.takerStatus.swap.TxID() == txid { | ||
// The swap contract tx is considered monitored until the swap | ||
// is complete, regardless of confirms. | ||
return true | ||
} | ||
|
||
// Maker's redemption transaction is the asset of interest. | ||
makerRedeemDone := s.makerRedeemStatus(match) | ||
if !makerRedeemDone && user == match.Maker.User() && | ||
match.makerStatus.redemption.TxID() == txid { | ||
return true | ||
} | ||
default: | ||
continue | ||
} | ||
} | ||
|
||
return false | ||
} | ||
|
||
// checkInaction scans the swapStatus structures relevant to the specified | ||
// asset. If a client is found to have not acted when required, a match may be | ||
// revoked and a penalty assigned to the user. | ||
func (s *Swapper) checkInaction(assetID uint32) { | ||
oldestAllowed := time.Now().Add(-s.bTimeout).UTC() | ||
deletions := make([]string, 0) | ||
s.matchMtx.RLock() | ||
defer s.matchMtx.RUnlock() | ||
s.matchMtx.Lock() | ||
defer s.matchMtx.Unlock() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is a lot going on in this locked section, also in processBlock, but to keep the read and subsequent possible writes ( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should be good. There are no RPC calls here anyway, so I expect this check to go quick. The only thing that might be remotely slow is the calls to May want to poke here during load testing though. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It may be, but it's not clear yet that it's worth the potential complications that come with that. |
||
for _, match := range s.matches { | ||
if match.makerStatus.swapAsset != assetID && match.takerStatus.swapAsset != assetID { | ||
continue | ||
|
@@ -505,6 +581,7 @@ func (s *Swapper) checkInaction(assetID uint32) { | |
// Nothing to do here right now. | ||
} | ||
} | ||
|
||
for _, matchID := range deletions { | ||
delete(s.matches, matchID) | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems prudent to specify the asset given that an asset may be structured in a way that makes cloning a txid from another asset possible. The same reasoning is used for including asset ID in the CoinLocker functions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep. I actually have a comment to this effect in the review I'm working on for #85.