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:
-
The action has a mandatory temporal constraint in the form of a
before
clause. ItsDateTime
expression specifies a deadline in the future, prior to which the obliged party must fulfill the obligation. -
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