mirror of
https://github.com/seigler/dips
synced 2025-07-27 09:46:12 +00:00
249 lines
12 KiB
Markdown
249 lines
12 KiB
Markdown
<pre>
|
||
DIP: 0010
|
||
Title: LLMQ InstantSend
|
||
Author(s): Alexander Block
|
||
Special-Thanks:
|
||
Status: Proposed
|
||
Type: Standard
|
||
Created: 2019-05-16
|
||
License: MIT License
|
||
</pre>
|
||
|
||
## Table of Contents
|
||
|
||
1. [Abstract](#abstract)
|
||
1. [Motivation](#motivation)
|
||
1. [Prior Work](#prior-work)
|
||
1. [Making all transactions instant](#making-all-transactions-instant)
|
||
1. [Used LLMQ type](#used-llmq-type)
|
||
1. [Eligible transactions for InstantSend](#eligible-transactions-for-instantsend)
|
||
1. [Locking Transaction Inputs](#locking-transaction-inputs)
|
||
1. [Finalization and creation of ISLOCK messages](#finalization-and-creation-of-islock-messages)
|
||
1. [Detecting and handling double-spend attempts](#detecting-and-handling-double-spend-attempts)
|
||
1. [Conflicts between ChainLocks and InstantSend](#conflicts-between-chainlocks-and-instantsend)
|
||
1. [Retroactive signing of transactions in blocks](#retroactive-signing-of-transactions-in-blocks)
|
||
1. [Dangling Partial Locks](#dangling-partial-locks)
|
||
1. [Persistence of InstantSend locks](#persistence-of-instantsend-locks)
|
||
1. [Copyright](#copyright)
|
||
|
||
## Abstract
|
||
|
||
This DIP defines a new implementation for InstantSend based on DIP0006 LLMQs
|
||
and DIP0007 LLMQ Signing Requests/Sessions.
|
||
|
||
## Motivation
|
||
|
||
InstantSend is a feature to allow instant confirmations of payments. It works
|
||
by locking transaction inputs through masternode quorums. It has been present
|
||
in Dash for a few years and been proven to work. Nevertheless, there are some
|
||
limitations which could theoretically be fixed in the old system. However,
|
||
fixing these limits in the old system (i.e. before LLMQs) would have created
|
||
risks in terms of scalability and security.
|
||
|
||
With LLMQs, these limitations can be lifted to enable more scaling and a better
|
||
user experience.
|
||
|
||
## Prior work
|
||
|
||
- [DIP 0006: Long Living Masternode Quorums](https://github.com/dashpay/dips/blob/master/dip-0006.md)
|
||
- [DIP 0007: LLMQ Signing Requests / Sessions](https://github.com/dashpay/dips/blob/master/dip-0007.md)
|
||
- [DIP 0008: LLMQ based ChainLocks](https://github.com/dashpay/dips/blob/master/dip-0008.md)
|
||
- [Transaction Locking and masternode Consensus: A Mechanism for Mitigating Double Spending Attacks](https://github.com/dashpay/docs/blob/master/binary/Dash%20Whitepaper%20-%20Transaction%20Locking%20and%20Masternode%20Consensus.pdf)
|
||
|
||
## Making all transactions instant
|
||
|
||
LLMQ-based InstantSend allows all transactions to be treated as InstantSend
|
||
transactions. The old system differentiated transactions as InstantSend
|
||
transactions by using the P2P message `ix` instead of `tx`. In the new system,
|
||
such distinction is not required anymore as LLMQs will try to lock every valid
|
||
transaction by default. If an `ix` P2P message is received from an older
|
||
client, nodes should convert these to simple `tx` messages and treat them as
|
||
such (including processing and propagating as `tx`).
|
||
|
||
## Used LLMQ type
|
||
|
||
All signing sessions/requests involved in InstantSend must use the LLMQ_50_60
|
||
LLMQ type.
|
||
|
||
## Eligible transactions for InstantSend
|
||
|
||
When a transaction is received and successfully added to the mempool (which
|
||
means it also passes the usual validation checks), each masternode should check
|
||
if the transaction is eligible for InstantSend.
|
||
|
||
A transaction is eligible for InstantSend when each of its inputs is considered
|
||
confirmed. This is the case when the previous transaction referred to by the
|
||
input is confirmed with 6 blocks, confirmed through an older InstantSend lock
|
||
or the block containing it is ChainLocked. When checking the previous
|
||
transaction for an InstantSend lock, it is important to also do this on mempool
|
||
(non-mined) transactions. This allows chained InstantSend locking.
|
||
|
||
## Locking Transaction Inputs
|
||
|
||
When a transaction is eligible for InstantSend, each masternode should try to
|
||
initiate a signing request for each of the transaction’s inputs. The request
|
||
id is:
|
||
|
||
hash("inlock", prevTxHash, prevTxOut)
|
||
|
||
`"inlock"` is a static string, prepended with the length (6, as a compact int,
|
||
which is a single byte) of the string. The message hash of the signing request
|
||
is the txid/hash of the transaction to be locked.
|
||
|
||
The [DIP0007 LLMQ Signing Request/Sessions](https://github.com/dashpay/dips/blob/master/dip-0007.md)
|
||
subsystem should handle the signing and recovery of signatures afterwards. It
|
||
will also automatically determine if the local masternode has to sign a share
|
||
or if it should skip it. In case of conflicts between transactions which are
|
||
not fully locked yet, the DIP0007 subsystem will handle this as well by
|
||
skipping signing of conflicting inputs.
|
||
|
||
Each masternode should then wait for the recovered signatures for each input to
|
||
appear. When all recovered signatures have appeared, each masternode should
|
||
initiate the finalization phase.
|
||
|
||
## Finalization and creation of ISLOCK messages
|
||
|
||
When a masternode has observed all recovered signatures for each input of a
|
||
transaction, it should initiate the finalization and creation of an `ISLOCK`
|
||
message.
|
||
|
||
Finalization is simply another signing request, performed on a (potentially)
|
||
different LLMQ than used before. The request id of the new signing request is:
|
||
|
||
hash("islock", inputCount, prevTxHash1, prevTxOut1, prevTxHash2,
|
||
prevTxOut2, ...)
|
||
|
||
`"islock"` is a static string, prepended with the length (6, as a compact int,
|
||
which is a single byte) of the string. `inputCount` is a compact int,
|
||
comparable to what is typically used in serialized arrays/vectors to serialize
|
||
the size. After `inputCount`, multiple pairs of <`prevTxHash`, `prevTxOut`>
|
||
follow, which are corresponding to the individual inputs of the transaction to
|
||
be locked. The message hash of the signing request is the txid/hash of the
|
||
transaction to be locked.
|
||
|
||
The LLMQ Signing Request/Sessions subsystem will handle this the same way as in
|
||
the previous section.
|
||
|
||
When a masternode receives the recovered signature for this signing request, it
|
||
should use the signature to create a new p2p message, which is the `ISLOCK`
|
||
message. It has the following structure:
|
||
|
||
| Field | Type | Size | Description |
|
||
|--|--|--|--|
|
||
| inputCount | compactSize uint | 1 - 9 | Number of inputs in the transaction |
|
||
| inputs | COutpoint[] | `inputCount` * 36 | Inputs of the transaction. COutpoint is a uint256 (hash of previous transaction) and a uint32 (output index) |
|
||
| txid | uint256 | 32 | txid/hash of the transaction |
|
||
| sig | BLSSig | 96 | Recovered signature from the signing request/session |
|
||
|
||
This p2p message should be propagated to all full nodes, including
|
||
non-masternodes. This is done using the `INV`/`GETDATA` subsystem. It should
|
||
also be propagated to SPV nodes if the transaction referred to by the txid
|
||
matches the bloom filter of the SPV node. Receiving nodes should verify the
|
||
message by checking the signature against the responsible LLMQ’s public key.
|
||
The responsible LLMQ can be determined by re-calculating the request id from
|
||
the data found in the `ISLOCK` message and then choosing the responsible LLMQ
|
||
with the help of the LLMQ Signing Requests/Sessions subsystem. If the `ISLOCK`
|
||
message is determined to be valid, it should be propagated further.
|
||
|
||
All full nodes and SPV nodes should only consider a transaction to be locked
|
||
when a valid `ISLOCK` for the transaction is available. The individual inputs
|
||
previously locked and their corresponding recovered signatures should not be
|
||
used when it comes to `ISLOCK` validation.
|
||
|
||
## Detecting and handling double-spend attempts
|
||
|
||
When an `ISLOCK` message is present, it can be used to easily detect
|
||
conflicting transactions which try to double spend the locked transaction
|
||
inputs. This is even possible when the original transaction is not present and
|
||
only the `ISLOCK` message is present (which might be the case due to the
|
||
first-seen rule).
|
||
|
||
Each time an `ISLOCK` message is received, nodes should check the mempool and
|
||
recent non-ChainLocked blocks for conflicting transactions. A transaction is
|
||
considered conflicting when it spends an outpoint found in the `ISLOCK` message
|
||
but its txid/hash does not match the txid from the `ISLOCK` message.
|
||
|
||
The same checks should be performed when a transaction is received before the
|
||
corresponding (or conflicting) `ISLOCKs` are received. This includes
|
||
transactions received through normal transaction propagation or through mined
|
||
blocks.
|
||
|
||
If a conflict is found in the mempool, the conflicting transaction
|
||
should be removed from the mempool. The originally locked transaction will
|
||
naturally not be present in this situation, as the first-seen rule would have
|
||
already filtered it. Thus the transaction referenced in the `ISLOCK` message
|
||
should be re-requested from one of the nodes that had previously announced it.
|
||
|
||
If a conflict is found in a recently mined block, conflict resolution depends
|
||
on the ChainLock state of the block. If the block is not ChainLocked, the whole
|
||
block must be invalidated. This might result in a reorganization of the chain
|
||
and re-acceptance of non-conflicting transactions in the local mempool. If the
|
||
block (or one of its descendants) has a ChainLock, the `ISLOCK` (and all
|
||
chained descendants) must be ignored and pruned from memory (including
|
||
persistent memory).
|
||
|
||
## Conflicts between ChainLocks and InstantSend
|
||
|
||
The latter case with a conflicting block receiving a ChainLock is very unlikely
|
||
to happen. The ChainLocks system usually does not try to lock blocks which
|
||
contain non-InstantSend locked transactions. This means that even if a miner
|
||
manages to include a conflicting TX into a block, masternodes following normal
|
||
consensus rules will not create a ChainLock for this block. Consequently the
|
||
block would be invalidated on all nodes when the `ISLOCK` appears.
|
||
|
||
Only in the case of an attack, where an attacker managed to get control over
|
||
large parts of the masternode network, would this situation becomes a
|
||
possibility. In this situation, ChainLocks have a higher priority when it comes
|
||
to consensus. Please see [DIP0008 - ChainLocks](https://github.com/dashpay/dips/blob/master/dip-0008.md)
|
||
for more details.
|
||
|
||
## Retroactive signing of transactions in blocks
|
||
|
||
When a block is received that contains transactions which were previously
|
||
unknown to the receiving masternode, the masternode should try to lock these
|
||
transactions the same way as those received through normal transaction
|
||
propagation.
|
||
|
||
In most cases, this will be a no-op as the `ISLOCK` will already be present
|
||
locally and thus the locking attempt can be skipped. Retroactive signing
|
||
however becomes important when miners include transactions in new blocks which
|
||
are not known by the remainder of the network. As it is desirable to lock all
|
||
transactions, retroactive signing ensures that even these transactions get
|
||
locked.
|
||
|
||
This also allows the ChainLocks system to retroactively lock blocks which
|
||
include unsafe transactions, as these transactions will become safe after a
|
||
short delay.
|
||
|
||
## Dangling Partial Locks
|
||
|
||
In rare cases, a transaction input may not be locked. This could result in a
|
||
transaction where only some of the inputs are successfully locked.
|
||
|
||
This would happen if a LLMQ becomes inactive in the middle of a signing session
|
||
due to a fresh LLMQ pushing an old LLMQ out of the active list. In this case,
|
||
some members of the old LLMQ may not receive the transaction in a timely manner
|
||
and thus do not perform the threshold signing. It might also happen if a LLMQ
|
||
becomes dysfunctional, e.g. due to too many members being offline or
|
||
unresponsive.
|
||
|
||
In the initial implementation, we will ignore this unlikely scenario and simply
|
||
consider the transaction as not locked, which also means that the `ISLOCK`
|
||
message is never created. Such a transaction reverts to being a normal
|
||
transaction and will be mined with a delay of a few minutes. See
|
||
["Safe Transactions” in DIP0008 - ChainLocks](https://github.com/dashpay/dips/blob/master/dip-0008.md#safe-transactions)
|
||
for more details.
|
||
|
||
## Persistence of InstantSend locks
|
||
|
||
An InstantSend lock should be persistent until the corresponding transaction is
|
||
mined on-chain and either confirmed by 24 blocks or a ChainLock. InstantSend
|
||
locks for non-mined transaction should never expire or time out.
|
||
|
||
Persistency is meant to be persistent on-disk and able to survive restarts of
|
||
the node. Storing InstantSend locks in RAM only is not enough.
|
||
|
||
## Copyright
|
||
|
||
Copyright (c) 2019 Dash Core Group, Inc. [Licensed under the MIT
|
||
License](https://opensource.org/licenses/MIT)
|