-
Notifications
You must be signed in to change notification settings - Fork 29
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
[WIP] Relative Kernels #19
Conversation
A few days ago while pondering relative kernels, I thought it might be useful to introduce reference kernels. A relative kernel would only be able to refer to a reference kernel. Which would require a slightly higher fee. That means there would be much fewer kernels to refer to, which would reduce resource usage of the necessary indexing data structures. But then I realized such a distinction on kernel types is a privacy leak, so I scrapped the idea... Anyway, thought I'd write it down for future reference:-) |
Regarding
It only needs 8 bytes, namely the leaf or full index of the reference kernel in the kernel MMR. We could allow the broadcasted tx to have the full 32 byte reference kernel, and let the miners convert it to an 8 byte index. But normally you would only broadcast such a tx if you see the references kernel being confirmed, so it may be simpler to require the broadcaster to set the index. Then only the slate would have the 32 byte reference kernel. |
Yes. We have discussed this before I think? And then I forgot about it...
Agreed. I think this is the conclusion we came to previously. The parties transacting (via a slate) would require the full excess commitment. But once the tx is built and ready to broadcast we can translate these to the more compact MMR indices. The kernel excess would be required when verifying the signature, but verifiers can quickly lookup the excess based via the provided MMR index. The one edge-case here that we need to think through. If we do support a height of 0 for a relative lock height then we can potentially build a tx that references a kernel that does not yet exist in the MMR, it will be added in the same block as the new tx. In this situation we would not yet have a MMR index for the referenced kernel. Maybe we do not allow this and the relative height must be non-zero? If we do allow relative height of 0 then we would potentially also need to handle the case where The big benefit of requiring relative height to be non-zero is the fact that a referenced kernel must have a lower MMR index than the kernel referencing it. This would not always be the case for two txs within the same block as no guarantees are made about the order of kernels within a block. |
Indeed this was discussed before. |
The other complexity here is how to deal with forks and reorgs if we are passing the MMR index as part of the tx (and not the referenced kernel excess). So we may need to reference kernels by the excess commitment up to the point where they are actually added to a block (by the miner). |
Ah yes, you're right; replacement of the commitment by an index can only happen when the later kernel is added to a block (as the past is immutable from this block's viewpoint); i.e. by a miner. |
Yes. I remember some of the earlier conversations now (one reason why we should be documenting these proposals as RFCs...) As long as txs are re-orderable (i.e. before inclusion in a block) we need to explicitly reference the excess commitment itself. And we do need to be careful with handling reorgs and forks from the perspective of the txpool as a reorg can order previously accepted txs differently - txs with relative lock heights can re-enter the txpool and we need to revalidate the lock height criteria carefully. |
In the top comment I wrote:
After more careful thought, there is another resource we should be more concerned about. BUT this approach would be severely handicapped by kernels that can freely reference older kernels. It may therefore be wise to limit the relative height to that same horizon. Actually, I find 2 days to be a little short, and would prefer to change STATE_SYNC_THRESHOLD to match CUT_THROUGH_HORIZON, i.e. one week. Can anyone think of a use case of relative kernels where one week would be considered insufficient? |
Ignoring specifics around STARKs for a second - it sounds like the issue could be restated to - Do we want to allow arbitrary relative heights between relative kernels and the kernels they reference? And if we want to limit this to a shorter period what should the limit be? Intuitively it makes sense to have some kind of limit and not simply allow arbitrary relative heights here. Some follow up questions -
|
I would argue that we don't.
I think a week is quite reasonable. With payment channels, monitoring the chain for illicit close transactions is a minimal burden with a 1 week window. I suspect very few people would want to wait longer than a week to reclaim funds from a channel closure. Most will probably set it shorter.
Yes, a graph-rate majority of miners can enforce shorter limits, by considering longer limits invalid.
That is indeed a hard-fork, as all nodes would need to upgrade to accept the longer limits. |
Just starting to pick this up again. A lot of the preparation work has been done in advance -
I think adding the new kernel feature variant will be relatively straightforward. We would need to support this additional kernel functionality in the wallet If we decide to support both commitment and the more compact index representation (once previous kernel is in the MMR we know the index) we would need to implement the mechanism to look these up and to make kernel validation aware of the MMR (for the lookup to take place). The 1 week limit would give us -
On that 2nd point - a tx that was initially valid (within the 1 week limit) could still be in the txpool after the 1 week limit had passed, invalidating the tx. We would need some way to handle this reliably and robustly. They would effectively age out and need to be removed from txpool over time. |
To summarise previous discussions around "relative kernels" we have three different ways of representing the reference between one kernel and another earlier kernel -
Each is beneficial under different conditions. Transaction kernels are serialized as part of various larger structures -
The excess commitment is included as part of the hash, so we must always know this. And hashing is pervasive and deeply embedded in the Grin code. There is no easy way to provide additional "extra data" when hashing various data structures. When building a tx we may not necessarily know the MMR index so we can only use the excess commitment. So slates will need the excess commitment. When broadcasting transactions and accepting them in the txpool we can assume the referenced kernel has already been appended to the kernel MMR and thus has a known MMR index. Recent kernels indices are subject to change though in a fork/reorg scenario. We want the transaction acceptance decision to be fast when handling transactions at the txpool layer. So ideally transaction would be self-contained and would not require db lookups or reads from disk. So the MMR index here is not ideal as we would need to go look the referenced kernel up from the current kernel MMR. At the block level the kernel MMR index is sufficient. MMR indices will be known and will be stable for the processing of a given block. There is another possible complication with relying on kernel MMR indices. We currently only require nodes to maintain full kernel history to allow other nodes to fully sync on the network. There is no technical reason why a node could not process and validate all historical kernels and then discard them. Once validated they are no longer required. If we assume some nodes do not maintain full kernel histories (to support this in the future) then we need to use Merkle proofs for relative kernels. So I'm leaning toward the following - Relative kernels always include the excess commitment of the referenced kernel. We need the excess commitment to hash the kernel correctly and cannot rely on needing to lookup the external referenced kernel during the hashing operation. At the slate level (during tx construction) we must specify an excess commitment for the referenced kernel for relative lock heights. We may not yet know the kernel MMR index. During tx broadcast we provide an associated Merkle proof, defining the exact index in the kernel MMR and therefore allowing the relative lockheight to be directly verified. Transaction kernels in blocks also require an associated Merkle proof for the same reasons. We may be able to optimize this and omit the Merkle proof for recent referenced kernels if we assume all nodes will maintain a limited full history of recent kernels. So recent referenced kernels could be reference via an MMR index and older referenced kernels via a full Merkle proof. But this would be in addition to the actual excess commitment which would always be provided. Edit: Let's ignore Merkle proofs for now. Each relative kernel contains both -
During tx building we set the kernel MMR index to 0 and effectively ignore it. Merkle proofs will come in useful for lightweight nodes that do not maintain full kernel history - but these can opt-in in some way and left as a future enhancement. |
# Motivation | ||
[motivation]: #motivation | ||
|
||
Absolute timelocks are useful but are susceptible to delay. Any delay between creation and use can have an adverse impact on their usage. An absolute timelock starts counting down as soon as the transaction is built. Relative timelocks can be more useful in some scenarios allowing transactions to be created well in advance of their actual use without impacting the lock period. |
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.
Perhaps the notion of delay could be introduced by explaining that all timelocks serve to delay the spending of an output.
It that output is already on-chain or about to be added, then absolute locks suffice. But if the time of adding the output is unknown, as is the case in channel updates, then relative locks are needed.
|
||
Absolute timelocks are useful but are susceptible to delay. Any delay between creation and use can have an adverse impact on their usage. An absolute timelock starts counting down as soon as the transaction is built. Relative timelocks can be more useful in some scenarios allowing transactions to be created well in advance of their actual use without impacting the lock period. | ||
|
||
Robust atomic swap implementations can be implemented with relative timelocks on the refund transactions, insulating both parties from delays during the swap process, intentional or otherwise. |
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 funding tx for atomic swap are expected to take place imminently. If we signed for it but don't receive the other party's signature within a reasonable time, then we would consider them uncooperative and perhaps decide to permanently abort by spending our inputs, rendering the incomplete funding tx moot. So absolute timelocks should suffice here.
|
||
Robust atomic swap implementations can be implemented with relative timelocks on the refund transactions, insulating both parties from delays during the swap process, intentional or otherwise. | ||
|
||
Similarly, Lightning style payment channels can leverage relative timelocks to implement refunds during non-cooperative payment channel close scenarios. |
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.
This is indeed the only known application where absolute locks clearly fall short.
pub enum KernelFeatures { | ||
... | ||
/// A kernel with a relative lock height. | ||
RelativeHeightLocked = 3, |
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.
Is this still a good name for the NSKR feature?
} | ||
``` | ||
|
||
A relative height locked kernel would reference a previous kernel by its excess (commitment) |
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.
would reference a possible recent kernel
``` | ||
|
||
A relative height locked kernel would reference a previous kernel by its excess (commitment) | ||
along with a relative height (height in blocks between the two kernels). |
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.
along with the number of recent (including current) blocks that must not contain the referenced kernel.
{ | ||
"fee": 8, | ||
"lock_height": 1044, | ||
"rel_kernel": "088305235baac64e90daca81b0bad7afbce3a6e49c989572095a892e857e681429" |
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.
maybe "no_such_kernel" instead of rel_kernel.
# Relative Height Locked | ||
{ | ||
"fee": 8, | ||
"lock_height": 1044, |
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.
maybe "recent_blocks" instead.
} | ||
``` | ||
|
||
In the example above the transaction containing this kernel would only be valid 1044 blocks (approx 24 hours) after the referenced kernel. |
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.
would be invalid within 1440 blocks (0 to 1439 blocks later) of one with the referenced kernel.
|
||
In the example above the transaction containing this kernel would only be valid 1044 blocks (approx 24 hours) after the referenced kernel. | ||
|
||
While we require a full 32 bytes of data to reference a previous kernel we can optimize this when storing the kernel in the MMR. We append kernels to the MMR based on immutable history and this allows us to reference the prior kernel by MMR position. At a given block height the MMR is immutable and the kernel at a given position is deterministic and immutable. Every node sees a consistent view of kernel ordering in their local copy of the kernel MMR. |
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.
We limit the recent_blocks to at most WEEK_HEIGHT, so that a one week window of recent kernels suffices to check validity of relative kernels.
Closing, superseded by #47. |
Rendered link to RFC document