Hacky soft fork to make "current time" accessible to all CKB scripts

Inside CKB-VM, there is no concept of time. This is by design, because CKB is intended for verification. Contrarily, in an execution focused-environment time is an important component of many protocols.

Time is used in the Nervos DAO in this process, however this involves multiple steps and is unlikely to interest many builders.

There are no defi applications on CKB. I suggest a backward compatible consensus change to make time available to scripts running on CKB. This would enable many applications that aren’t possible today. Maybe I will have time to consider them later, right now that exercise is left to you.

The change:

At a certain block height, the last transaction in the block MUST be a state transition on a special “time” cell. This cell has an always return lockscript, and a typescript that enforces some conditions (not sure exactly how this gets implemented), but the consensus rule is that a miner must construct a transaction that loads the previous block’s header with header_deps and writes that block’s timestamp into the data field of the cell.

Inside a block, any time-dependent transactions can reference this time cell with the same deterministic timestamp. All scripts running in the block operate with the same time value.

For applications where time is non-deterministic, open transactions can be used to allow aggregators to fill in this time field later (maybe with some conditions specified by users).


Yayyy, finally a proposal about recent header data in transactions!! :raised_hands:

I’d like to start with the following quote from @jm9k about the current L1 off-chain determinism, which is the underlying L1 design principle from which this proposal is seemingly departing from:

What is being called off-chain determinism is not really true. It is an optimization of the mempool. Absolute off-chain determinism without any exceptions is likely impossible.

In the case of CKB, the optimization is that it only has to check UTXOs again before inclusion. Since is checked to enter the mempool, so nothing actually resides in the mempool until it becomes valid, and then it is valid forever.

On my side, I agree that a Nervos L1 script should have some ways to check if the outside world has changed since the transaction was sent, but this has to be done in a way as smart as possible to avoid breaking the Off-chain determinism optimization of the mempool.

Back to the current proposal, if the open transaction aggregator and the block producer are either the very same entity or very tightly integrated together, then this proposal completely sidestep the issue of the Off-chain determinism as the aggregated transaction never actually enter the public mempool. Very smart :+1::+1:

Now it’s an issue of the aggregator’s mempool tho … and I’d like to see how they handle an open transaction whose script whose validity is:

  • True if and only if the timestamp is even.
  • False otherwise.

That said, why limiting the scope to the timestamp?? I’d like to see the possibility to access the full header of the previous block!! Luckily this is accomplished easily, the cell data instead of being a timestamp, it’s the header hash of the previous block.

Then again, we don’t strictly need to include this information as a cell, we just need:

  • A conventional way to inform the scripts inside an open transaction that a certain Header Deps is the header of the previous block. For example: “the first header deps is the header deps of the previous block” or “the header deps with maximum blocknumber is the the header of the previous block”
  • A way for other miners to validate the previous convention being respected, the only consensus change needed.
1 Like

You want to store the time directly, if we only store the hash, each script will need to present the preimage to verify the time is correct.

Fortunately I think we do need to store the header hash in data to verify linkage between the blocks, so there are 2 fields, prevHeaderHash and time

Process goes:

(block being mined is h)

  1. script loads entire header through header_deps for block h-1
  2. verifies that prevHeader (header h-2) value in data matches prevHeader value loaded in header_deps
  3. stores new header hash and time value in cell
1 Like

You want to store the time directly

My issue with this is: What if in the future other comparable needs emerge that need other header data? Why restricting our-self to the timestamp? :thinking:

For example for ickb/v1-bot performing a dao withdrawal request transaction, it would be nice knowing that the 180 epochs has already passed or not.

I think we do need to store the header hash in data to verify linkage between the blocks, so there are 2 fields, prevHeaderHash and time

Sure, if I’m able to tell the aggregator that it needs to include prevHeaderHash (so h-2) in the headerDeps, my script should still be able to compute the required information to understand if 180 epochs has already passed or not :+1::+1:

  1. script loads entire header through header_deps for block h-1
  2. verifies that prevHeader (header h-2) value in data matches prevHeader value loaded in header_deps
  3. stores new header hash and time value in cell

A few considerations:

  1. The idea you propose is a trust-less oracle. It could work even without a consensus change with a signature based oracle, but in this case the special cell users would need to trust the cell creator.
  2. Without a consensus change, it cannot be proven (feel free to show me wrong) that it is actually loading the header of block h-1, it is just loading a header which happens to be the header of the block after the one notarized in the previous cell. Maybe the previous notarized cell is a few epochs old…
  3. With the Script Verification you proposed this consensus change is a breaking change. After the consensus change, no block without this special cell can be mined as it would either break the script chain of verification or lead to load older timestamps.
  4. The verification can be accomplished without checking anything in the script and fully relying on the consensus rules to check the changes to this cell. Not saying it’s an improvement, just it’s a possibility.

if we only store the hash, each script will need to present the preimage to verify the time is correct.

Can you explain a bit more this point?

I’m a bit conflicted about storing this kind of transient information into a cell.

On one side, it’s a great idea because:

  • Once a special cell is consumed, any transaction referencing it is invalid, sooo mission accomplished :+1::+1:

On the other, traditional L1 transactions are kind of left into the dust as they would need to:

  • Get super fast this cell (from the miner packing the block? :thinking:)

  • Pack super fast a transaction referencing it

  • Send it super fast so that it get included in the current block or ends up invalid

Then again, how would a script be able to reference this Cell Dep?

Looking at ckb-std functions, I see no method able to directly fetch the data from this special cell as all functions depends on the index of the cell.

The only ways I see to do it would be either:

  • Iterate over all Cell Deps and find the one whose Type Script matches the special cell type script (this can be wrapped into a utility function of course)

  • Put the special cell in a conventional position of Cell Deps (first, last…) and directly load that one.

As you see, this issue is very similar to issue with the solution I was proposing yesterday, a similar but different solution which basically boils down to the following:

  • Script flags itself as wanting to access the tip header (h-1), this could be done by reserving one bit in Script Type. Think of (“Type”, “Data”, “Data1”, “Data2” …) x (“Not access tip Header”, “Access tip header”), so it’s backward compatible.

  • The consensus rejects the transaction if tip header is not included in header deps.

  • To get the tip header in a script, either the script know already which is the tip header at construction time or it’s in a conventional position or iterates thru all Header Deps and gets the one with maximum block number.

Another competing idea to play with the Off-chain determinism TxPool Optimization is to introduce a transaction expiration. A cell in a transaction can readily specify a since epoch, so an epoch before which is invalid. What if additionally we could specify an expiration epoch, so an epoch after which is invalid?

Closing this reply, the solution you propose is great as it fits with the L1 model and it doesn’t create issues, but since changing the consensus is on the table, I’d like for all possibilities to be explored and then choose the best :grin:

1 Like

heh this solution optimizes to minimize consensus changes

if the transaction structure is changed to include a until epoch, every piece of tooling that creates a transaction will need to be updated to be compatible with this change, at a specific block height. It seems messy

you can compare the prevHeader preimage to prevBlockHash in data and you have that information (maybe i’m missing something). I do think there could be an argument made for including other fields in the data cell but (correct if i’m wrong) they will have to be included in the output of the transaction. Because this would be in every block (or a defined interval), efficiency is important.

because this change would be checking the context of CKB-VM from outside CKB-VM, it seems to materially increase the surface area of the consensus change

this bit is challenging but I do think with open transactions miners and aggegrators are closely linked together (especially when MEV is introduced) so I don’t worry all that much about having to update transactions

From a user end, if they wanted to do an expiring transaction (forgive me if my ignorance glosses over details) they could include a script condition in the open transaction that was only valid compared to the time value brought in by the cell_dep (and an aggregator/miner would appropriately populate the info)

1 Like