Due to the deterministic design of Nervos CKB, timestamp does not naturally exist on chain. While you could use other solutions, such as since or header deps to obtain a lower bound on the timestamp, we don’t yet have a solution to set upper bound on the timestamp in CKB. Here, we are proposing a design that provides timestamp to Nervos CKB as an oracle.
The timestamp oracle is designed with the following principles:
- Decentralized: anyone should be able to update the timestamp value on chain.
- Profitable: participants who help update the timestamps should be rewarded from users who use the timestamp on chain.
- Parallel: the timestamp oracle should not require shared state so as to function well on CKB.
In order for the timestamp oracle to function, 4 scripts will be used:
- Micro payment script: a way to quickly make recurring payments to someone on CKB. For example, a user who uses the timestamp oracle, might keep paying the timestamp updater a small amount of tokens so as to be able to use the timestamp values. A typical payment solution won’t work here due to the nature of timestamp oracle: it should only cost a dime or so to be able to use a timestamp.
- Timestamp lock script: lock used to guard the timestamp cell.
- Invoking script: a script used to fetch current timestamp from timestamp cell, while also ensuring payments are made to the timestamp updater.
- Validating script: a script used to ensure consistent behavior from invoking script.
Later we will talk about the details of the 4 scripts used here, right now, we are merely introducing them, so we can discuss about the cells used in timestamp oracle:
Timestamp cell keeps the latest timestamp information. A timestamp cell has the following format:
- Timestamp lock script is used as lock script
- Validating script is used as type script
- The actual timestamp is kept in the cell data part
- An updater can also keep the identity information of themselves in the cell data part for receiving micro payments.
Any user should be able to update the timestamp cell, assuming:
- Correct format is used
- A pre-defined interval(for example, 1 minute) has been passed since last time someone updates the timestamp cell
Notice we don’t define the exact format of timestamp stored in the cell data part. In fact, the timestamp format here will never be in the specification of timestamp oracle. Later we shall see this is the key to ensuring users of timestamp oracle make proper payments.
Timestamp invoking cell
Timestamp invoking cell has the following format:
- Any lock script can be used as the lock script here, most likely, a governance lock from the timestamp oracle community is used
- Validating script is used as the type script
- The actual binary code of the invoking script is kept in the cell data part
Timestamp invoking cell is used as a container for the invoking script. When a user is actually using the timestamp, they should dynamically load the invoking script from the live timestamp invoking cell, then invoke a designated function provided by the invoking script to fetch the timestamp. The function will also require a proof of micro payment. This way we can ensure that proper payments are made to the timestamp updater, which ensures a sustainable ecosystem.
The reason invoking script is kept directly in the cell data part of timestamp invoking cell, is that invoking script is supposed to be updated periodically. Recall that previously, it is stated that the actual format of timestamp value stored in timestamp cell is not defined: the invoking script used here, actually defines the format, when a new invoking script is used, the format might also change. If someone cheats by reading the cell data of timestamp cell directly, their dapp might stop functioning when invoking script is updated, since the new timestamp format might not be properly parsed. This way, we are ensuring all users should make proper payments in order to use the timestamp order.
However, an updatable script brings a different problem: how can one ensure that invoking script won’t change its API? This is ensured by validating script. Validating script will ensure that given a micro-payment in correct format, invoking script will properly execute, parse timestamp cell, and return a timestamp value in correct format. While invoking script can be updated, validating script will be freezed, ensuring a unified behavior that dapp developers can rely on.
Here, real examples will be used to better illustrate the above points:
The following transaction updates the timestamp stored in timestamp oracle:
Inputs: Timestamp Cell Data: <timestamp: 1601187880> <updater: A> Type: Validating script Lock: Timestamp lock script <...> Outputs: Timestamp Cell Data: <timestamp: 1601187942> <updater: B> Type: Validating script Lock: Timestamp lock script <...> Deps: Timestamp Invoking Cell Data: Invoking script Type: Validating script Lock: <...> <...>
For simplicity, the above structure does not include auxiliary cells, such as cells used to pay transaction fees. Assuming an update interval of 60 seconds, user B here tries to update timestamp cell, with a new timestamp that is 62 seconds after the original timestamp, user B also updates the micro payment from user A to themselves.
The following transaction can be used to reference timestamp kept in timestamp oracle:
Inputs: Micro payment cell (if needed) <...> Outputs: Micro payment cell (if needed) <...> Deps: Timestamp Cell Data: <timestamp: 1601187942> <updater: B> Type: Validating script Lock: Timestamp lock script Timestamp Invoking Cell Data: Invoking script Type: Validating script Lock: <...> <...>
Any script executed in this transaction, will be able to load invoking script, then use invoking script to load the current timestamp stored on chain(which is 1601187942). A micro payment to updater B is also required by invoking script.
Due to incentive reasons, after 60 seconds of timestamp 1601187942, other timestamp updater might send new transactions updating the timestamp cell, which means the above transaction either gets accepted in the timestamp range from 1601187942 to (1601187942 + 60), or it gets rejected since the referenced timestamp cell is not live. In other words, incentive will help provide an upper bound on the timestamp used here.
Updating Invoking Scripot
And finally, the following transaction allows updating invoking script:
Inputs: Timestamp Invoking Cell Data: Old Invoking script Type: Validating script Lock: <...> <...> Outputs: Timestamp Invoking Cell Data: New Invoking script Type: Validating script Lock: <...> <...> Deps: Timestamp Cell Data: <timestamp: 1601187942> <updater: B> Type: Validating script Lock: Timestamp lock script
Validating script will ensure that the new invoking script provides the same ABI, so everyone can continue using the timestamp oracle in the same way as before this transaction is commited.
Here, the implementation details of each involved script are discussed
Micro payment script
A micro payment script is unique in the following ways:
- Each individual payment might be so small, that regular transaction is overkill.
- Frequent recurring payments are needed.
One obvious choice here is payment channel, which sounds perfect for the job. But even without payment channel, there are also some simple solutions we can explore:
Payment information in cell data
One solution, is that the payer can create a micro payment cell first, whose cell data part contains a list of payment information:
| Payee A | Amount | Payee B | Amount | Payee C | Amount | ... | |---------|--------|---------|--------|---------|--------|-----|
Each payment information just contains the identity(e.g., public key hash) of the payee, together with the amount due to the payee. One micro-payment transaction can then just be the insertion of a new payee entry, or updating of an existing payee entry. Each payee can freely transfer the amount due for them out of the micro payment cell. The payer can also withdraw those amounts that are not paid to a payee out of the micro payment cell, or the payer can destroy the cell and send out payments to each payee, hoping to reclaim all the CKB required in the cell.
One additional solution, is that a micro payment can just be a signature in the open transaction, however instead of signing a component in the transaction input, or output, it can instead sign the difference between the input amount, and the output amount. A payee then gathers all the signatures from all the micro payments, and do a full withdraw from the payer’s account. In the event that the payer wants to withdraw from his/her micro payment account, a wait period will be required, so payees can still safely get their payments, even though the payer might want to play evil and withdraw the amounts they have already paid someone.
Notice those are just some initial ideas on the implementation of a micro payment script, it is likely there are also more solutions we haven’t listed here.
Timestamp lock script
A timestamp lock script can be unlocked only when all of the following conditions are met:
- The since of the input cell is set to the pre-defined interval past the timestamp stored in the input cell data
- An output timestamp oracle cell is created with the following conditions:
- The since value above is set in the cell data
- The lock is the same as current oracle lock script
- The type is the same as current oracle type script
- An identity information denoting the payee(such as a secp256k1 public key hash) can also be attached to the cell data part.
Invoking script is a dynamically linked script. It offers one public function, which takes a micro payment as an input. If the micro payment is valid, it then parses the current timestamp from the referenced timestamp cell, then return the correct timestamp. Otherwise it returns an error.
A validating script loads the currently live invoking script, feed it a valid micro payment information, if the invoking script successfully returns a valid timestamp information, the validating script thus returns a success state, otherwise it returns a failure.
This concludes the design of the timestamp oracle. We believe this might be a feasible design on Nervos CKB that can achieve its decentralized, profitable, and parallel design goals.