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
InstantSend overhaul #1288
InstantSend overhaul #1288
Conversation
+ cleanup for IS and partial protobump
- allow InstantSend tx to have 10 inputs max - store many unique tx hashes in mapVotedOutpoints - more checks in AcceptToMemoryPoolWorker (moved from ProcessMessage + CTxLockRequest(tx).IsValid() )
- let multiple lock candidates compete for votes - fail to vote on the same outpoint twice early
- notify CInstantSend on UpdatedBlockTip -> remove cs_main from CheckAndRemove() - notify CInstantSend on SyncTransaction -> count expiration block starting from the block corresponding tx was confirmed instead of the block lock candidate/vote was created - fixed few locks
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.
Great work.
Please check a few code issues/comments.
I think biggest points are the logic of the argument to CTxLockRequest::IsValid and limiting the depth of recursion in ResolveConflicts.
LogPrint("instantsend", "CTxLockRequest::IsValid -- Failed to find UTXO %s\n", txin.prevout.ToStringShort()); | ||
// Normally above sould be enough, but in case we are reprocessing this because of | ||
// a lot of legit orphan votes we should also check already spent outpoints. | ||
if(!fRequireUnspent) return false; |
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.
Shouldn't this be "if(fRequireUnspent)" ?
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.
👍 see above
// Normally we should require all outpoints to be unspent, but in case we are reprocessing | ||
// because of a lot of legit orphan votes we should also check already spent outpoints. | ||
uint256 txHash = txLockRequest.GetHash(); | ||
if(!txLockRequest.IsValid(IsEnoughOrphanVotesForTx(txLockRequest))) return false; |
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.
Seems like it should be: "IsValid(!IsEnoughOrphanVotesForTx)"
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.
Hah, good catch! I actually started with fAllowSpent
in my draft and changed it to fRequireUnspent
later but didn't change the param and if
it inside of the function to match this, so it works but looks confusing now. Will fix!
} else if (mempool.mapNextTx.count(txin.prevout)) { | ||
// conflicting with tx in mempool | ||
fMempoolConflict = true; | ||
const CTransaction *ptxConflicting = mempool.mapNextTx[txin.prevout].ptx; |
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.
Looks like the rest of the code in this block is only for printing debug messages, is that right ?
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, could break
here instead but I'm just trying to be very verbose about conflicting tx and what's going to happen next. Actually this block of code needs a bit more work, will fix.
// | ||
LogPrintf("CTxLockRequest::ResolveConflicts -- Failed to find UTXO %s - disconnecting tip...\n", txin.prevout.ToStringShort()); | ||
DisconnectBlocks(1); | ||
// Recursively check at "new" old height. Conflicting tx should be rejected by AcceptToMemoryPool. |
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.
I think it would be safer to limit the depth of recursion to a "reasonable" number of blocks (maybe 6) than to allow the entire chain to be unrolled.
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.
Makes sense to limit depth here. Not sure about 6, probably should use nInstantSendKeepLock
to make sure that we didn't miss it somewhere in the past.
// forcing external script notification. | ||
if(IsInstantSendReadyToLock(txHash)) { | ||
if(ResolveConflicts(txLockCandidate)) { | ||
// the order here matters |
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.
"The order matters", but why this order. Seems like normally you would want to establish the locks before sending notifications.
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.
Actually it's just wrong :/ will fix 👍
} | ||
return false; | ||
// we have votes by other masternodes only (so far), let's continue and see who will win |
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.
I think vote should be added to matching mapVotedOutpoints set here.
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.
👍
if(ResolveConflicts(it->second)) { | ||
// the order here matters | ||
UpdateLockedTransaction(it->second); | ||
if(!IsLockedInstantSendTransaction(txHash)) { |
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.
If the tx is already locked, it seems like we should return earlier than this (ie. why call ResolveConflicts/UpdateLockedTransaction ?).
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.
Will fix this one too
|
||
void CInstantSend::AcceptLockRequest(const CTxLockRequest& txLockRequest) | ||
{ | ||
mapLockRequestAccepted.insert(make_pair(txLockRequest.GetHash(), txLockRequest)); |
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.
Missing mutex lock.
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.
👍
|
||
void CInstantSend::RejectLockRequest(const CTxLockRequest& txLockRequest) | ||
{ | ||
mapLockRequestRejected.insert(make_pair(txLockRequest.GetHash(), txLockRequest)); |
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.
Missing mutex lock.
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.
👍
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.
Thanks, will fix shortly! 👍
// forcing external script notification. | ||
if(IsInstantSendReadyToLock(txHash)) { | ||
if(ResolveConflicts(txLockCandidate)) { | ||
// the order here matters |
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.
Actually it's just wrong :/ will fix 👍
// Normally we should require all outpoints to be unspent, but in case we are reprocessing | ||
// because of a lot of legit orphan votes we should also check already spent outpoints. | ||
uint256 txHash = txLockRequest.GetHash(); | ||
if(!txLockRequest.IsValid(IsEnoughOrphanVotesForTx(txLockRequest))) return false; |
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.
Hah, good catch! I actually started with fAllowSpent
in my draft and changed it to fRequireUnspent
later but didn't change the param and if
it inside of the function to match this, so it works but looks confusing now. Will fix!
} | ||
return false; | ||
// we have votes by other masternodes only (so far), let's continue and see who will win |
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.
👍
if(ResolveConflicts(it->second)) { | ||
// the order here matters | ||
UpdateLockedTransaction(it->second); | ||
if(!IsLockedInstantSendTransaction(txHash)) { |
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.
Will fix this one too
} else if (mempool.mapNextTx.count(txin.prevout)) { | ||
// conflicting with tx in mempool | ||
fMempoolConflict = true; | ||
const CTransaction *ptxConflicting = mempool.mapNextTx[txin.prevout].ptx; |
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, could break
here instead but I'm just trying to be very verbose about conflicting tx and what's going to happen next. Actually this block of code needs a bit more work, will fix.
// | ||
LogPrintf("CTxLockRequest::ResolveConflicts -- Failed to find UTXO %s - disconnecting tip...\n", txin.prevout.ToStringShort()); | ||
DisconnectBlocks(1); | ||
// Recursively check at "new" old height. Conflicting tx should be rejected by AcceptToMemoryPool. |
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.
Makes sense to limit depth here. Not sure about 6, probably should use nInstantSendKeepLock
to make sure that we didn't miss it somewhere in the past.
|
||
void CInstantSend::AcceptLockRequest(const CTxLockRequest& txLockRequest) | ||
{ | ||
mapLockRequestAccepted.insert(make_pair(txLockRequest.GetHash(), txLockRequest)); |
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.
👍
|
||
void CInstantSend::RejectLockRequest(const CTxLockRequest& txLockRequest) | ||
{ | ||
mapLockRequestRejected.insert(make_pair(txLockRequest.GetHash(), txLockRequest)); |
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.
👍
LogPrint("instantsend", "CTxLockRequest::IsValid -- Failed to find UTXO %s\n", txin.prevout.ToStringShort()); | ||
// Normally above sould be enough, but in case we are reprocessing this because of | ||
// a lot of legit orphan votes we should also check already spent outpoints. | ||
if(!fRequireUnspent) return false; |
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.
👍 see above
- fix logic for locking inputs and notifying - see UpdateLockedTransaction, TryToFinalizeLockCandidate - add missing hash inserting in ProcessTxLockVote - add nMaxBlocks param to ResolveConflicts to limit max depth allowed to disconnect blocks recursively - fix false positive mempool conflict - add missing mutex locks - fix fRequireUnspent logic in CTxLockRequest::IsValid
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.
utACK
This is a complete refactoring of InstantSend and implementation of multi-quorum idea i.e. to use a separate masternode quorum to vote on every outpoint, not only on the tx itself which should dramatically reduce the possibility of conflicts (and save resources needed to resolve them). Using multi-quorum on the other hand can potentially rise resource usage for quorum (for huge txes). With this in mind, max number of inputs for InstantSend transaction was limited to 10 for now and final InstantSend fee is now also calculated as
number of inputs * fee per input
wherefee per input
is the same as currentINSTANTSEND_MIN_FEE
(i.e.0.001 * COIN
) to dis-incentivize huge txes economically.As part of re-implementation this also changes buffer behavior to expire lock request candidates/votes based on the block corresponding tx was confirmed rather than on the block lock candidate/vote was created and fixes race condition "block vs lock".
NOTE:
PROTOCOL_VERSION
andMIN_INSTANTSEND_PROTO_VERSION
were bumped becauseTXLOCKVOTE
p2p-message now has new format.