01 / 01Guide
Handling timestamps on Chromia with Rell
A blockchain runs on many nodes, so there is no single, well-defined moment at which a piece of code executes. A call such as System.currentTimeMillis() would produce a different value on each node. Synchronized clocks don't fix this: the primary node runs the code first and the validating nodes follow, and network latency and hardware differences mean they never read the time at exactly the same instant.
A reliable time value must therefore come from something every node agrees on, namely the latest known time or previous block time. Every block header carries a timestamp that the validating nodes accept, which makes it a good source. You can read it by querying the system block entity.
block @ {} (@max .timestamp);While the blockchain is initializing, no blocks exist yet, so this query returns null. If you set a timestamp on an object, for example, you first have to initialize it to zero:
object my_object { mutable timestamp = 0;}You then update the value yourself through an operation.
operation set_time() { my_object.timestamp = block @ {} (@max .timestamp);}Inside an operation, you have access to a special context, op_context, which exposes a last_block_time property. You can use it to shorten the expression above. You might want to make it the default value for timestamp attributes on entities. However, when you add such an attribute to an entity that already holds data, you need the block query to assign a correct timestamp to the existing records:
entity my_entity { timestamp = if (op_context.exists) op_context.last_block_time else block @ {} (@max .timestamp)!!;}The reason is that the default value is applied to all existing entities when the blockchain restarts after the update. At that point no operation is running, so op_context is not available (op_context.exists is false).
To handle every case the same way, define a function last_known_time():
function last_known_time() = if (op_context.exists) op_context.last_block_time else block @ {} (@max .timestamp) ?: 0;You can call this function safely in any context:
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();