Skip to main content

How to work with timestamps on Chromia using Rell

Since blockchains run on multiple nodes, there is no clear way to determine when some code was executed. If we could access, for example, System.currentTimeMillis(), this would return different results on different nodes. Even if we sync the node's clocks, the primary node will always execute the code first, and then the validating nodes, but they may not execute the time simultaneously due to network delays and hardware speed.

This means that the only way to get a reliable time is to look at the latest known time, or the previous block time. Since a timestamp is added to each block as part of the block header, which becomes accepted by the validating nodes, this would be a suitable choice. To access this timestamp, we can query against the system block entity.

block @ {} (@max .timestamp);

However, during blockchain initialization, blocks are yet to be built and therefore this will return null. For example, when setting the value on an object, we must first initialize it with zero:

object my_object {
mutable timestamp = 0;
}

and then update it manually with an operation.

operation set_time() {
my_object.timestamp = block @ {} (@max .timestamp);
}

In the scope of an operation, there is a special context called op_context, which has a last_block_time property. This could be used to simplify the above expression. It may be tempting to use this as the default value for timestamps on entities, however, if the attribute was added to an entity that has data already, we must rely on the block query to get a proper timestamp on those entities:

entity my_entity {
timestamp = if (op_context.exists) op_context.last_block_time else block @ {} (@max .timestamp)!!;
}

This is because the default value will be set on all existing entities when the blockchain starts up again after the update, and thus the op_context will be null.

To make this less inconsistent, we can create a function last_known_time() which covers all cases:

function last_known_time() 
= if (op_context.exists) op_context.last_block_time else block @ {} (@max .timestamp) ?: 0;

This function can safely be used in all scenarios:

object my_object {
timestamp = last_known_time();
}

entity my_entity {
timestamp = last_known_time();
}

operation my_operation() {
create my_entity();
}

query get_last_known_time() = last_known_time();