Skip to content
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

Merged
merged 8 commits into from Jan 29, 2017
Merged

InstantSend overhaul #1288

merged 8 commits into from Jan 29, 2017

Conversation

UdjinM6
Copy link

@UdjinM6 UdjinM6 commented Jan 28, 2017

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 where fee per input is the same as current INSTANTSEND_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 and MIN_INSTANTSEND_PROTO_VERSION were bumped because TXLOCKVOTE p2p-message now has new format.

+ 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
Copy link

@tgflynn tgflynn left a 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;
Copy link

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)" ?

Copy link
Author

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;
Copy link

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)"

Copy link
Author

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;
Copy link

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 ?

Copy link
Author

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.
Copy link

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.

Copy link
Author

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
Copy link

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.

Copy link
Author

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
Copy link

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.

Copy link
Author

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)) {
Copy link

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 ?).

Copy link
Author

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));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing mutex lock.

Copy link
Author

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));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing mutex lock.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link
Author

@UdjinM6 UdjinM6 left a 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
Copy link
Author

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;
Copy link
Author

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
Copy link
Author

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)) {
Copy link
Author

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;
Copy link
Author

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.
Copy link
Author

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));
Copy link
Author

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));
Copy link
Author

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;
Copy link
Author

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
Copy link

@tgflynn tgflynn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utACK

@UdjinM6 UdjinM6 merged commit 60409df into dashpay:v0.12.1.x Jan 29, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants