Skip to content

Time and obligations

Keeping time

Clock

Internally, the engine uses the clock of the underlying JVM.

The meaning of now

The now() function in the standard library is tied to the engine clock. This implies that during a transaction the function will always return the same instant, as a value of type DateTime. Time stands still during a transaction, so to speak, and the following NPL test will always pass.

function testClockStandsStill(test: Test) -> {
    var start: DateTime = now();
    // solveTravellingSalesmanProblem();
    var stop: DateTime = now();
    test.assertEquals(start, stop, "Time should not pass during execution");
};

Obligations

The Noumena Platform introduces the concept of an obligation, which basically is a permission with two crucial extensions:

  1. The action has a mandatory temporal constraint in the form of a before clause. Its DateTime expression specifies a deadline in the future, prior to which the obliged party must fulfill the obligation.

  2. The action body is followed by a punitive clause, which the engine will execute at the given deadline, should the obliged party have failed to fulfill the obligation by then.

As always any state guards apply, so if the protocol is stateful, it must be in a state where the action would have been active for the punitive clause to be executed. In other words, the engine can not breach an obligation as long as the obliged party has the possibility to fulfill it.

Tasks

The engine keeps track of obligations by means of tasks. Tasks serve as a to-do list for the platform, and a task scheduler will invoke the punitive otherwise clause of obligations for which the deadline has been met.

The punitive clause of an obligation follows the action body and is restricted to a simple state transfer, e.g. it will have the form otherwise become some_state;

If an obligation on a specific protocol gets executed, all tasks for the protocol become voided. In case the protocol (at the end of the invoking transaction) is in a protocol state for which the obligation is active (i.e. it either has no action guard or the resulting protocol state is mentioned in the action guard), a new task will be scheduled.

In effect the engine is replacing the old tasks on the to-do list with new tasks, as long as the obligation has not been fulfilled or defaulted.

If, at the end of a transaction or in a migration script, some protocol ends up in state such that a deadline for one of its obligations would occur in the past, the transaction is aborted.

Engine behaviour for obligations

In the following example, when the Iou issuer has not fulfilled the obligation before the payDeadline, the engine invokes the state transition to breached. Therefore, the protocol parameter payDeadline is monitored and any change to the payDeadline will impact the schedule of the breach condition (so to say: if we change the payDeadline to a timestamp in the past, the breach clause gets triggered immediately; if we change the payDeadline to a future time, the schedule of the triggering of the breach clause gets rescheduled).

protocol[issuer, payee] Iou(var forAmount: Number, var payDeadline: DateTime) {
    initial state unpaid;
    final state paid;
    final state breached;

    obligation[issuer] pay() before payDeadline | unpaid {
        // Payment logic
        become paid;
    } otherwise become breached;

    // Protocol body
};

The deadline on an obligation should be a field of the obligation’s enclosing protocol.

Using protocol states to handle protocol breaches:

  • allows for the exposure of multiple punitive actions, to one or many counterparties; furthermore, it allows these parties to choose whether to invoke these punitive actions (but it may still be automated through services if preferred)
  • it allows for the exposure of different subsequent obligations
  • it avoids having to determine which party is said to execute an obligation