Packages

trait RequestTracker extends AutoCloseable with NamedLogging

The request tracker handles all the tasks around conflict detection that are difficult to parallelize. In detail, it tracks in-flight requests, performs activeness checks, detects conflicts between requests, keeps track of the sequencer time, checks for timeouts, and orchestrates the updates to the com.digitalasset.canton.participant.store.ActiveContractStore. It does not write to the com.digitalasset.canton.participant.protocol.RequestJournal, though. The request tracker lives entirely in memory on a single compute node.

The request tracker is a deterministic component that depends only on the ACS state and the sequencer messages. The order and timing in which the request tracker receives this information does not matter. (Even more broadly, the participant has no functional notion of “wall-clock” time; all time and timestamps come from sequencer messages.) These properties of determinism and sequencer time are both important parts of Canton and critical to the crash recovery model.

To keep track of the sequencer time, the request tracker must be notified of every message that is received from the sequencer. These notifications need not happen in order, but they must happen eventually. Every such message has an associated monotonically increasing SequencerCounter. If one of the sequencer counters is skipped, the request tracker may stall indefinitely. Every message has an associated timestamp. Time must increase with every message in the order given by the sequencer counter. This means that for sequencer counters sc1 and sc2 with sc1 < sc2, the associated timestamps ts1 and ts2 must satisfy ts1 < ts2.

Requests are identified by RequestCounters, which themselves must be a monotonically increasing sequence without gaps.

The request tracker uses these time signals to determine on which requests a verdict can be issued, which requests have timed out, and which requests and contract states can be safely evicted from the tracker's internal state. We say that the request tracker has observed a timestamp if it has been signalled this timestamp or a later one. The request tracker can progress to a timestamp ts if it has observed all timestamps up to and including ts and all commit sets with commit times up to ts have been added with RequestTracker.addCommitSet.

If the request tracker can progress to a timestamp ts, then it must eventually complete all futures that RequestTracker.addRequest and RequestTracker.addCommitSet have returned and that are associated with a timestamp equal or prior to ts. The futures are associated to the following timestamps:

  • Activeness result: the activeness time of the request, typically the timestamp on the request
  • Timeout result: the decision time of the request
  • Finalization result: the commit time of the request

The three methods RequestTracker.addRequest, RequestTracker.addResult, and RequestTracker.addCommitSet must normally be called in the given sequence; the latter two need not be called if the request has timed out. A request is in flight from the corresponding call to RequestTracker.addRequest until the request tracker has progressed to its decision time (if the request times out) or its commit time.

These three methods are idempotent while the request is in flight. That is, if one of the methods is called twice with the same arguments, provided that the first one succeeded, then the second call has no effect, but its return value is equivalent to what the first call returned. If the method is called twice for the same request counter with different arguments, the subsequent behavior of the request tracker becomes unspecified. The same applies if the same sequencer counter is supplied several times with different timestamps.

A request tracker is typically initialized with a SequencerCounter and a com.digitalasset.canton.data.CantonTimestamp, causing it to be ready to handle requests starting from the given sequencer counter (inclusive) and timestamp (exclusive).

Conflict detection

The request tracker checks whether a request uses only contracts that are active at the activeness timestamp of the request. To that end, it determines the ActivenessResult of the request. A non-conflicting request should be approved in a response by the participant. A conflicting request should be rejected.

To describe conflict behavior, we introduce a few concepts:

A conflict detection time is a triple (com.digitalasset.canton.data.CantonTimestamp, Kind, SequencerCounter). There are three kinds, and they are ordered by

Finalization < Timeout < Activeness

. Conflict detection times are ordered lexicographically. In other words,

(ts1, kind1, sc1) < (ts2, kind2, sc2)

if and only if

ts1 < ts2

or

ts1 == ts2 && kind1 < kind2

or

ts1 == ts2 && kind1 == kind2 && sc1 < sc2.

A request with SequencerCounter sc and com.digitalasset.canton.data.CantonTimestamp ts induces two conflict detection times: The sequencing time at (ts, Activeness, sc) and the timeout time at (decisionTime, Timeout, sc) where decisionTime is the decision time of the request. Without logical reordering, the sequencing time is also the request's activeness time. (Technically, the activeness time must lie between the sequencing time and the decision time; see RequestTracker.addRequest.) A result with SequencerCounter sc and com.digitalasset.canton.data.CantonTimestamp ts also defines two conflict detection times: The verdict time (ts, Result, sc) and the finalization time (commitTime, Finalize, sc) where commitTime is the commit time of the request.

A request is active at some conflict detection time t if all of the following holds:

  • Its sequencing time is equal or prior to t.
  • If no finalization time is known for the request, then t is equal or prior to the request's timeout time.
  • If a finalization time is known for the request, then t is equal or prior to the finalization time.

The finalization time of a request is known if the result has been successfully signalled to the request tracker using RequestTracker.addResult.

A request is finalized at some conflict detection time t if its finalization time is known and equal or prior to t.

A contract is active at time t if a request with activeness time at most t activates it, and there is no finalized request at time t that has deactivated it. Activation happens through creation and transfer-in. Deactivation happens through archival and transfer-out. An active contract c is locked at time t if one of the following cases holds:

  • There is an active request at time t that activates c. In that case, we say that the contract is in activation.
  • There is an active request at time t that deactivates c and the request does not activate c.

A contract may be locked because of activation and deactivation at the same time if there are two active requests, one activating the contract and another deactivating it. For example, if one request r1 creates a contract with sequencing time t1 and finalization time t1' and another request r2 with sequencing time t2 between t1 and t1' archives it (despite that r2's activeness check fails), then the contract is created at time t1 and archived at t2.

Activeness of contracts is checked at the request's activeness time. The ActivenessResult lists all contracts from the ActivenessSet of the request that are either locked or whose precondition fails. The activeness check succeeds if the activeness result is empty.

Linear Supertypes
Ordering
  1. Alphabetic
  2. By Inheritance
Inherited
  1. RequestTracker
  2. NamedLogging
  3. AutoCloseable
  4. AnyRef
  5. Any
  1. Hide All
  2. Show All
Visibility
  1. Public
  2. Protected

Abstract Value Members

  1. abstract def addCommitSet(requestCounter: RequestCounter, commitSet: Try[CommitSet])(implicit traceContext: TraceContext): Either[CommitSetError, EitherT[Future, NonEmptyChain[RequestTrackerStoreError], Unit]]

    Informs the request tracker of the effect of a request that is to be committed.

    Informs the request tracker of the effect of a request that is to be committed. The effect is described by a CommitSet, namely which contracts should be deactivated and which ones activated.

    This method may be called only after an addResult call for the same request.

    requestCounter

    The request counter of the request

    commitSet

    The commit set to describe the committed effect of the request A scala.util.Failure$ indicates that result processing failed and no commit set can be provided.

    returns

    A future to indicate whether the request was successfully finalized. When this future completes, the request's effect is persisted to the store.ActiveContractStore and the store.TransferStore. The future fails with an exception if the commit set tries to activate or deactivate a contract that was not locked during the activeness check. Otherwise, activeness irregularities are reported as scala.Left$.

    See also

    ConflictDetector.finalizeRequest

  2. abstract def addRequest(requestCounter: RequestCounter, sequencerCounter: SequencerCounter, requestTimestamp: CantonTimestamp, activenessTime: CantonTimestamp, decisionTime: CantonTimestamp, activenessSet: ActivenessSet)(implicit traceContext: TraceContext): Either[RequestAlreadyExists, Future[RequestFutures]]

    Adds the confirmation request to the request tracker.

    Adds the confirmation request to the request tracker. It schedules the activeness check for the activenessTime and the timeout of the request for the decisionTime, whereby a result exactly at the decisionTime is considered to be in time. If a scala.Right$ is returned, it also signals the arrival of the message with sequencerCounter and timestamp requestTimestamp, like the RequestTracker!.tick operation.

    The activeness check request is expressed as an activeness set comprising all the contracts that must be active and those that may be created or transferred-in. This operation returns a pair of futures indicating the outcome of the activeness check and the timeout state of the request, described below.

    If a request with requestCounter and sequencerCounter is added, the request tracker expects that eventually every request prior to requestCounter will be added, and that every sequencer counter prior to sequencerCounter will be signaled.

    If the request has been added before with the same parameters, but it is not yet finalized nor has it timed out, the method ignores the second call and returns the same futures as in the first call.

    requestCounter

    The request counter. It must correspond to the sequencer counter and timestamp in the request journal. Must not be Long.MaxValue.

    sequencerCounter

    The sequencer counter on the request. Must not be Long.MaxValue.

    requestTimestamp

    The timestamp on the request.

    activenessTime

    The timestamp when the activeness check should happen. Must be at least requestTimestamp and less than the decisionTime.

    decisionTime

    The timeout for the request. Must be after the activenessTime.

    activenessSet

    The activeness set that determines the checks at the activenessTime.

    returns

    scala.Right$ if the request was added successfully or this is a duplicate call for the request. One future for the result of the activeness check and one for signalling the timeout state of a request. These futures complete only when their result is determined: The result of the activeness check when the request tracker has progressed to the activenessTime. The timeout state when the request tracker has progressed to the decisionTime or a transaction result was added for the request. scala.Left$ if a request with the same requestCounter is in flight with different parameters.

    Exceptions thrown

    • If the requestTimestamp or sequencerCounter is Long.MaxValue.
    • If the requestTimestamp is earlier than to where the request tracking has already progressed
    • If the activenessTimestamp is not between the requestTimestamp (inclusive) and the decisionTime (exclusive).
  3. abstract def addResult(requestCounter: RequestCounter, sequencerCounter: SequencerCounter, resultTimestamp: CantonTimestamp, commitTime: CantonTimestamp)(implicit traceContext: TraceContext): Either[ResultError, Unit]

    Informs the request tracker that a result message has arrived for the given request.

    Informs the request tracker that a result message has arrived for the given request. This marks the request as not having timed out. The actual effect of the result must be supplied via a subsequent call to RequestTracker!.addCommitSet. The request tracker may not progress beyond the commitTime until the commit set is provided.

    This method may be called only after a RequestTracker!.addRequest call for the same request.

    requestCounter

    The request counter of the request

    sequencerCounter

    The sequencer counter on the result message.

    resultTimestamp

    The timestamp of the result message. Must be after the request's timestamp and at most the decision time.

    commitTime

    The commit time, i.e., when the result takes effect Must be no earlier than the result timestamp.

    returns

    scala.Right$ if the result was added successfully or this is a duplicate call for the request. The timeout future returned by the corresponding call to RequestTracker!.addRequest completes with RequestTracker$.NoTimeout. scala.Left$ if the result could not be added (see the RequestTracker$.ResultErrors for the possible cases). The timestamp is nevertheless considered as observed.

    Exceptions thrown

    java.lang.IllegalArgumentException if the commitTime is before the resultTimestamp, or if the request is in flight and the resultTimestamp is after its decision time. The timestamp is not observed in this case.

  4. abstract def awaitTimestamp(timestamp: CantonTimestamp): Option[Future[Unit]]

    Returns a future that completes after the request has progressed to the given timestamp.

    Returns a future that completes after the request has progressed to the given timestamp. If the request tracker has already progressed to the timestamp, scala.None is returned.

  5. abstract def close(): Unit
    Definition Classes
    AutoCloseable
    Annotations
    @throws(classOf[java.lang.Exception])
  6. abstract def getApproximateStates(coid: Seq[LfContractId])(implicit traceContext: TraceContext): Future[Map[LfContractId, ContractState]]

    Returns a possibly outdated state of the contract.

  7. abstract def loggerFactory: NamedLoggerFactory
    Attributes
    protected
    Definition Classes
    NamedLogging
  8. abstract def tick(sequencerCounter: SequencerCounter, timestamp: CantonTimestamp)(implicit traceContext: TraceContext): Unit

    Tells the request tracker that a message with the given sequencer counter and timestamp has been received.

    Tells the request tracker that a message with the given sequencer counter and timestamp has been received.

    If RequestTracker!.addRequest or RequestTracker!.addResult shall be called for the same message, RequestTracker!.tick may only be called after those methods as they may schedule tasks at the timestamp or later and task scheduling for a timestamp must be done before the time is observed.

    A given sequencer counter may be signalled several times provided that all calls signal the same timestamp. Only the first such call is taken into account. Since RequestTracker!.addRequest and RequestTracker!.addResult implicitly signal the sequencer counter and timestamp to the request tracker (unless specified otherwise), it is safe to call this method after calling these methods.

    The timestamps must increase with the sequencer counters.

    sequencerCounter

    The sequencer counter of the message. Must not be Long.MaxValue.

    timestamp

    The timestamp on the message.

    Exceptions thrown

    If one of the following conditions hold:

    • If sequencerCounter is Long.MaxValue.
    • If not all sequencer counters below sequencerCounter have been signalled and the timestamp does not increase with sequencer counters.
    • If all sequencer counters below sequencerCounter have been signalled and the timestamp is at most the timestamp of an earlier sequencer counter.
    • If sequencerCounter has been signalled before with a different timestamp and not all sequencer counters below sequencerCounter have been signalled.

Concrete Value Members

  1. final def !=(arg0: Any): Boolean
    Definition Classes
    AnyRef → Any
  2. final def ##: Int
    Definition Classes
    AnyRef → Any
  3. final def ==(arg0: Any): Boolean
    Definition Classes
    AnyRef → Any
  4. final def asInstanceOf[T0]: T0
    Definition Classes
    Any
  5. def clone(): AnyRef
    Attributes
    protected[lang]
    Definition Classes
    AnyRef
    Annotations
    @throws(classOf[java.lang.CloneNotSupportedException]) @native() @HotSpotIntrinsicCandidate()
  6. final def eq(arg0: AnyRef): Boolean
    Definition Classes
    AnyRef
  7. def equals(arg0: AnyRef): Boolean
    Definition Classes
    AnyRef → Any
  8. implicit def errorLoggingContext(implicit traceContext: TraceContext): ErrorLoggingContext
    Attributes
    protected
    Definition Classes
    NamedLogging
  9. final def getClass(): Class[_ <: AnyRef]
    Definition Classes
    AnyRef → Any
    Annotations
    @native() @HotSpotIntrinsicCandidate()
  10. def hashCode(): Int
    Definition Classes
    AnyRef → Any
    Annotations
    @native() @HotSpotIntrinsicCandidate()
  11. final def isInstanceOf[T0]: Boolean
    Definition Classes
    Any
  12. def logger: TracedLogger
    Attributes
    protected
    Definition Classes
    NamedLogging
  13. implicit def namedLoggingContext(implicit traceContext: TraceContext): NamedLoggingContext
    Attributes
    protected
    Definition Classes
    NamedLogging
  14. final def ne(arg0: AnyRef): Boolean
    Definition Classes
    AnyRef
  15. def noTracingLogger: Logger
    Attributes
    protected
    Definition Classes
    NamedLogging
  16. final def notify(): Unit
    Definition Classes
    AnyRef
    Annotations
    @native() @HotSpotIntrinsicCandidate()
  17. final def notifyAll(): Unit
    Definition Classes
    AnyRef
    Annotations
    @native() @HotSpotIntrinsicCandidate()
  18. final def synchronized[T0](arg0: => T0): T0
    Definition Classes
    AnyRef
  19. def toString(): String
    Definition Classes
    AnyRef → Any
  20. final def wait(arg0: Long, arg1: Int): Unit
    Definition Classes
    AnyRef
    Annotations
    @throws(classOf[java.lang.InterruptedException])
  21. final def wait(arg0: Long): Unit
    Definition Classes
    AnyRef
    Annotations
    @throws(classOf[java.lang.InterruptedException]) @native()
  22. final def wait(): Unit
    Definition Classes
    AnyRef
    Annotations
    @throws(classOf[java.lang.InterruptedException])

Deprecated Value Members

  1. def finalize(): Unit
    Attributes
    protected[lang]
    Definition Classes
    AnyRef
    Annotations
    @throws(classOf[java.lang.Throwable]) @Deprecated
    Deprecated

Inherited from NamedLogging

Inherited from AutoCloseable

Inherited from AnyRef

Inherited from Any

Ungrouped