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

Implement transaction-level absolute and input-level relative time locks #1339

Merged
merged 6 commits into from
Nov 18, 2020

Conversation

AndrejMitrovic
Copy link
Contributor

@AndrejMitrovic AndrejMitrovic commented Nov 9, 2020

This will be needed for Eltoo (#1267) and also for multi-hop payment channels.

Note that the execution engine itself is not in charge of checking the block height. The engine only cares about executing opcodes in the scope of the Transaction itself and the Output it is spending - it does not care about block heights. This allows transactions to be cached - otherwise a transaction's script would have to be re-evaluated at each new block height to determine if it's valid. There are many BIPs explaining how this mechanism works, if you're curious.

Fixes #243

@codecov
Copy link

codecov bot commented Nov 9, 2020

Codecov Report

Merging #1339 (0608445) into flash-layer (d89d262) will increase coverage by 1.61%.
The diff coverage is 98.07%.

Impacted file tree graph

@@               Coverage Diff               @@
##           flash-layer    #1339      +/-   ##
===============================================
+ Coverage        78.61%   80.23%   +1.61%     
===============================================
  Files              115      118       +3     
  Lines             9870     9990     +120     
===============================================
+ Hits              7759     8015     +256     
+ Misses            2111     1975     -136     
Flag Coverage Δ
integration 37.65% <42.85%> (?)
unittests 78.80% <98.07%> (+0.19%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
source/agora/consensus/EnrollmentManager.d 93.04% <ø> (+0.26%) ⬆️
source/agora/consensus/data/genesis/Coinnet.d 100.00% <ø> (ø)
source/agora/consensus/data/genesis/Test.d 100.00% <ø> (ø)
source/agora/test/QuorumPreimage.d 72.22% <ø> (+1.95%) ⬆️
source/agora/utils/PrettyPrinter.d 90.40% <ø> (ø)
source/agora/test/HeightLock.d 95.65% <95.65%> (ø)
source/agora/test/UnlockAge.d 97.82% <97.82%> (ø)
source/agora/consensus/data/Block.d 100.00% <100.00%> (ø)
source/agora/consensus/data/Transaction.d 97.72% <100.00%> (+0.29%) ⬆️
source/agora/consensus/validation/Transaction.d 98.88% <100.00%> (-0.32%) ⬇️
... and 20 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update d89d262...0608445. Read the comment docs.

AndrejMitrovic added a commit to AndrejMitrovic/agora that referenced this pull request Nov 10, 2020
@AndrejMitrovic AndrejMitrovic added this to the 3. Flash Layer milestone Nov 10, 2020
@AndrejMitrovic AndrejMitrovic added the type-feature An addition to the system introducing new functionalities label Nov 10, 2020
AndrejMitrovic added a commit to AndrejMitrovic/agora that referenced this pull request Nov 10, 2020
AndrejMitrovic added a commit to AndrejMitrovic/agora that referenced this pull request Nov 10, 2020
@AndrejMitrovic AndrejMitrovic marked this pull request as ready for review November 16, 2020 07:34
@AndrejMitrovic
Copy link
Contributor Author

This is ready for review.

Note that the lock is based on the block height, and not an absolute time. We don't yet have block timestamping.

@AndrejMitrovic
Copy link
Contributor Author

@hewison-chris / @linked0 could I get a review of this?

/// The UTXO this `Input` references must be at least `unlock_age` older
/// than the block height at which the spending transaction wants to be
/// included in the block. Use for implementing relative time locks.
public uint unlock_age = 0;
Copy link
Member

Choose a reason for hiding this comment

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

Oh. Stoa needs to respond to this change.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, to both changes.

/// This transaction may only be included in a block with `height >= unlock_height`.
/// Note that another tx with a lower lock time could double-spend this tx.
/// Using `uint` instead of `Height` as it's enough to cover 80k years.
public uint unlock_height = 0;
Copy link
Member

Choose a reason for hiding this comment

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

Yeah, here it is. um.. uint

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, here it is. um.. uint

There is a comment above, please read it.

@AndrejMitrovic
Copy link
Contributor Author

AndrejMitrovic commented Nov 17, 2020

@TrustHenry the changes should be fairly simple. Just the addition of two new fields. I think it should be an easy change on the Stoa side?

Copy link
Contributor

@linked0 linked0 left a comment

Choose a reason for hiding this comment

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

LGTM.
Additionally, there's some point where someone could confuse about why the two kinds of heights have different types although uint is enough. And, It would be good to show a situation or a use case where the unlock_age is used.

@AndrejMitrovic
Copy link
Contributor Author

Additionally, there's some point where someone could confuse about why the two kinds of heights have different types although uint is enough.

I could change it back to Height. I guess we could change this later if we want to optimize it..

And, It would be good to show a situation or a use case where the unlock_age is used.

The primary use-case is with hashed time-locked contracts (https://en.bitcoin.it/wiki/Hash_Time_Locked_Contracts), but this requires execution engine support. This is in another PR that I will rebase soon: #1341

@AndrejMitrovic AndrejMitrovic marked this pull request as draft November 17, 2020 04:35
@TrustHenry
Copy link
Member

@TrustHenry the changes should be fairly simple. Just the addition of two new fields. I think it should be an easy change on the Stoa side?

Structural change itself is simple, but Stoa test codes are not.
Stoa needs to flexibly replace Agora's changes.
But now Stoa is exactly aligned with the Agora block structure.
And before merging PR, it would be good to have related issues in Stoa together.

@TrustHenry
Copy link
Member

The word unlock_height has similar functions as Transaction.unlock_height and UTXO.unlock_height, but the target is different.
When we talk about unlock_height later, we need to distinguish between transaction or UTXO.

@AndrejMitrovic
Copy link
Contributor Author

Alright, then to keep it simple I will rename it to time_lock which is similar to Bitcoin.

@AndrejMitrovic
Copy link
Contributor Author

AndrejMitrovic commented Nov 17, 2020

Although Height time_lock; will look weird..

@AndrejMitrovic
Copy link
Contributor Author

Height height_lock; it is then. At least it will be easy to search for the different symbols.

@AndrejMitrovic AndrejMitrovic marked this pull request as ready for review November 17, 2020 10:09
AndrejMitrovic added a commit to AndrejMitrovic/agora that referenced this pull request Nov 17, 2020
AndrejMitrovic added a commit to AndrejMitrovic/agora that referenced this pull request Nov 18, 2020
@AndrejMitrovic
Copy link
Contributor Author

AndrejMitrovic commented Nov 18, 2020

@bpfkorea/core I have an idea.

Since there's a lot of breaking changes that the flash layer introduces (transaction time locks, Input / Output changes), what do you think about adding a new flash-layer branch into agora so all of my flash layer pull requests would target this branch?

This way we can avoid breaking Stoa / Faucet until the flash layer is stabilized.

I would periodically merge the changes from v0.x.x into flash-layer. So you don't have to worry about this branch, your own PRs can still target only v0.x.x.

Then when Mathias returns we could get a final review, and merge flash-layer into v0.x.x.

Thoughts?

// it here right away and force the party to send it again at the right
// time. However, we run into a risk if we ever implement caching of
// rejecting them if they're submitted too early.
if (height < utxo_value.unlock_height + input.unlock_age)
Copy link
Member

Choose a reason for hiding this comment

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

I think there's not enough testing on this, but it all looks good.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Can you explain? The unittests are not enough?

Copy link
Member

Choose a reason for hiding this comment

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

This is a question,
are you thinking of replacing the utxo_value.unlock_height with the height_lock later?
In my Think Verification methods are confusing when it is a Freezing transaction or a melting transaction.

Copy link
Contributor Author

@AndrejMitrovic AndrejMitrovic Nov 18, 2020

Choose a reason for hiding this comment

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

This code you're commenting on does not use height_lock. This is the transaction-level lock.

The code instead uses unlock_age, which is an input-level lock. Unlock age is the number of externalized blocks between when a UTXO is first ready to be spent and the current block height.

In my Think Verification methods are confusing when it is a Freezing transaction or a melting transaction.

Yes it's a little bit complicated right now because we have this special freeze transaction.

But we cannot build the flash layer with just freeze transactions. We need transaction-level and input-level time locks too. This is issue #243.

Copy link
Contributor Author

@AndrejMitrovic AndrejMitrovic Nov 18, 2020

Choose a reason for hiding this comment

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

#243 will be part of my upcoming demo, so this will all be explained there.

Copy link
Member

@TrustHenry TrustHenry left a comment

Choose a reason for hiding this comment

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

LGTM!

@AndrejMitrovic AndrejMitrovic marked this pull request as draft November 18, 2020 05:35
The `unlock_height` field is used for a new consensus rule.
Transactions may only be included in blocks whose height
is >= `unlock_height`.

The new field is hashed and therefore changes all UTXOs
in the tests.

The consensus rules are moved into the next commit
to better split the diff.
It sets the `height_lock` of the transaction.
Transactions which are not unlocked yet will not be
accepted by the node.

In the future this may be relaxed: A node may accept
a Transaction with a future unlock height into its
pool, but it should never accept it into a block.

Note that a transaction with a future unlock height
can be double-spent before it's externalized,
assuming the proper behavior of the transaction pool
(see bosagora#1332).

The `unlock_height` feature is primarily useful for
implementing HTLCs which will be necessary to support
multi-hop payment channels (Flash layer).
The `unlock_age` field of an `Input` specifies the
minimum age difference between the referenced externalized
UTXO height, and the height at which the spending
transaction would be included in a block.

This changeset changes the hashing of the `Input`,
and therefore consensus rule changes were moved to
the next commit to make it easier to review.

Note that the Genesis block has not changed as it
has no inputs, therefore hashing is unaffected.
Used to set the `unlock_age` in the `Input`.
The `unlock_age` specifies the minimum age difference
between the height of the block of the spending
transaction, and the unlock height of the UTXO
it references.

This feature is needed for our Flash layer,
where transactions can attach to UTXOs only
after the utxo has been externalized.

Note that the `unlock_age` is compared to the
`Output's` unlock height, and not its externalize
height (unlock height is typically +1 of the
externalize height, for pay transactions.

This way an `unlock_age` of 0 and 1
have separate meanings:

`unlock_age` of 0 allows the `Input` to spend
the UTXO as soon as its unlock height is reached.

`unlock_age` of 1 allows the `Input` to spend
the UTXO when there was at least 1 block
externalized after the unlock height
of the UTXO being spent.

Fixed bosagora#243
@AndrejMitrovic AndrejMitrovic changed the base branch from v0.x.x to flash-layer November 18, 2020 05:45
@AndrejMitrovic AndrejMitrovic marked this pull request as ready for review November 18, 2020 05:46
@AndrejMitrovic
Copy link
Contributor Author

I changed the target branch to flash-layer. This means merging this PR will not break Stoa / Faucet.

@kchulj
Copy link

kchulj commented Nov 18, 2020

Thoughts?

Good idea.

@AndrejMitrovic
Copy link
Contributor Author

Ok, there's two approvals so I'll merge this and continue with the work.

Just to make it clear again, this will not require changes to Stoa / Faucet until next year.

@AndrejMitrovic AndrejMitrovic merged commit 34d1ce1 into bosagora:flash-layer Nov 18, 2020
@AndrejMitrovic AndrejMitrovic deleted the lock-times branch November 18, 2020 06:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type-feature An addition to the system introducing new functionalities
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Define and implement time locks on transactions
4 participants