Runtime

Runtime Info Provider

The Runtime contract provides runtime information to developers. It exposes functions to retrieve pseudo-process IDs (PIDs) and pseudo-random UUIDs.

Random Number Generators

pid(): Returns the pseudo-process ID (PID) of the current execution unit (EU). Since all EUs are identical and stateless, pid() is useful for generating deterministic, job-scoped keys or values within parallel execution.

uuid(): Returns a deterministic, job-scoped pseudo-random UUID. This value is unique per job and can be used to generate non-colliding keys or identifiers within the concurrent execution context.

Deferred Execution

Consider the following example contracts. The following contracts are an educational lottery example. It demonstrates another key feature called deferred execution, where the scheduler moves transactions to create an aggregation point after parallel execution.

Example 1

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "@arcologynetwork/concurrentlib/lib/array/Address.sol"; 
import "@arcologynetwork/concurrentlib/lib/runtime/Runtime.sol"; 

// SUPER SIMPLE educational lottery — not secure for real funds.
// join(): send ≥0.005 ETH to join the round.
contract EduLottery {
    Address private _players = new Address(false);

    error InsufficientFee(uint256 sent, uint256 required);
    error NoPlayers();

    constructor() {
        // Inform the scheduler that when there are multiple invocations, move one of 
        // them to the next generation for aggregation. All the senders need to pay
        // extra gas fees of 20,000 wei for the deferred execution.
        Runtime.defer("join()", 20000);
    }

    // Joins the lottery by sending ETH. This function can be called in parallel 
    // by multiple users.
    function join() external payable {
        if (msg.value < 0.005 ether) revert InsufficientFee(msg.value, 0.005 ether);
        _players.push(msg.sender);

        // Only draw if this is the deferred transaction
        if (Runtime.isInDeferred()) {
            _draw();
        }
    }

    // Generates a pseudo-random seed.
    function _randomSeed() private view returns (uint256) {
        // Educational pseudo-randomness (prev blockhash, timestamp, contract)
        return uint256(
            keccak256(
                abi.encodePacked(
                    blockhash(block.number - 1),
                    block.timestamp,
                    address(this)
                )
            )
        );
    }

    // Draws a random winner from the players.
    function _draw() internal {
        require(Runtime.isInDeferred(), "only deferred");

        if (_players.fullLength() == 0) revert NoPlayers();
        uint256 idx = _randomSeed() % _players.fullLength();
        address winner = _players.get(idx);

        _players.clear(); // Clear players for the next round
        uint256 prize = address(this).balance;
        (bool ok, ) = payable(winner).call{value: prize}("");
        require(ok, "transfer failed");
    }
}

Example 2

VisitCounterWithDeferred uses U256Cumulative for parallel-safe increments and adds a deferred join step: all visit() calls in a generation run in parallel, with one promoted to the next generation to emit the aggregated result after the generation boundary.

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0;

import "@arcologynetwork/concurrentlib/lib/commutative/U256Cum.sol"; 
import "@arcologynetwork/concurrentlib/lib/runtime/Runtime.sol"; 

// This counter supports commutative updates, allowing safe aggregation
// of increments from multiple transactions in parallel.
contract VisitCounterWithDeferred { 
    // A cumulative counter instance that allows concurrent increments
    // from multiple transactions without conflicts.   
    U256Cumulative totalVisit; 
    
    event CounterQuery(uint256 value);

    constructor()  {
        // Initialize the cumulative counter with a starting value of 0
        // and an upper limit of 1,000,000. Any attempt to exceed the
        // range will cause the transaction to revert.
        totalVisit = new U256Cumulative(0, 1000000);

        // Inform the scheduler when there are multiple invocations
        // to visit() function, one of them will be executed in the
        // deferred transaction. Each call to visit() is charged an 
        // extra 2,000 gas to fund the deferred join step run later
        // in the block. If the deferred transaction fails, 
        // the unused gas is refunded.
        Runtime.defer("visit()", 2000);
    }

    // Increments the visit counter by 1.
    // Multiple invocations in the same generation can be executed in parallel
    // and will be merged deterministically by Arcology at commit time.
    function visit() public {
        // The line below adds 1 to the visit counter, which will executed anyway.
        totalVisit.add(1);

        // If the transaction is in a deferred generation, emit the counter value.
        // The code is executed in the deferred transaction only.
        if (Runtime.isInDeferred()) {
            emit CounterQuery(totalVisit.get());
        }
    }
}

Last updated