UUID missing in DAML

Is there a library in DAML that natively handles UUIDs? I can’t see any, is that correct and if so why ?

2 Likes

I’m not aware of any library for that at the moment. You cannot generate random UUIDs in DAML due to the lack of randomness but you can of course store them. Usually you would just store Text or a a wrapper around that (data UUID = UUID with value : Text) and treat it as an opaque value.

Given that they are just treated as an opaque value and the wrapper is one line, I’m not sure you gain much by putting this in a library.

2 Likes

If the requirement is purely to ensure uniqueness at the possible expense of predictability, you could implement the UUIDv1 algorithm purely in DAML as its only real external requirement is a clock. This may not be suitable if you have a requirement that UUIDs not be guessable—this is why UUIDv4 is frequently used over UUIDv1.

UUIDv4 requires a source of randomness, but DAML requires that it runs on a ledger such that distributed nodes can independently evaluate DAML commands and come to the same value with the same input; this makes it tough to impossible to implement UUIDv4 in a safe way (deterministic across all nodes that may need to “produce” the same UUIDv4 value).

UUIDv1

If UUIDv1 is acceptable, a possible implementation that uses Party in place of where a MAC address would typically be supplied (note that you can only produce UUIDs in the Update monad because you need to be able to fetch the current time):

data UUID = UUID
  with
    timeLow : Int
    timeMid : Int
    timeHiAndVersion: Int
    clock: Int
    node: Int
  deriving Eq


instance Show UUID where
  show = formatUUIDCanonical


uuidv1 : Party -> Update UUID
uuidv1 party = do
  uuidCid <- findOrCreateUUIDv1 party
  (_, uuid) <- exercise uuidCid GenerateUUID
  return uuid


-- Return the number of 100-nanosecond intervals from the Gregorian epoch
-- (midnight UTC, 15 October 1582).
getGregorianEpochMicroseconds : Time -> Int
getGregorianEpochMicroseconds = convertRelTimeToMicroseconds . sinceGregorianEpoch


-- Build a UUID struct from the current time since the Gregorian epoch, in microseconds*, the clock
-- sequence, and a 48-bit integer that represents the node.
--
-- *The UUID standard calls for 100-nanosecond intervals to be used here instead of microseconds;
-- for convenience within DAML, this function takes microseconds instead, and the conversion to
-- 100-nanosecond intervals happens within this function, thereby keeping these UUIDs compliant with
-- with the spec.
buildUUIDv1 : Int -> Int -> Int -> UUID
buildUUIDv1 micros clock node =
  let heptos = micros * 10
  in UUID
    with
      timeLow = heptos % 4294967296
      timeMid = (heptos / 4294967296) % 65536
      timeHiAndVersion = (heptos / 281474976710656) % 4096 + 4096
      clock = clock % 16384 + 32768
      node = node % 281474976710656


formatUUIDCanonical : UUID -> Text
formatUUIDCanonical uuid = 
  (padLeft 8 "0" $ hexFromInt uuid.timeLow) <> "-" <>
  (padLeft 4 "0" $ hexFromInt uuid.timeMid) <> "-" <>
  (padLeft 4 "0" $ hexFromInt uuid.timeHiAndVersion) <> "-" <>
  (padLeft 4 "0" $ hexFromInt uuid.clock) <> "-" <>
  (padLeft 12 "0" $ hexFromInt uuid.node)


template UUIDv1State
  with
    party : Party
    clock : Int
    mac : Int
    node : Int
  where
    signatory party
    key party : Party
    maintainer key

    ensure clock >= 0 && clock < (2 ^ 16) && mac >= 0 && mac < (2 ^ 48)

    choice GenerateUUID : (ContractId UUIDv1State, UUID)
      controller party
        do
          now <- getTime
          newCid <- create this with clock = clock + 1
          return (newCid, buildUUIDv1 (convertRelTimeToMicroseconds . sinceGregorianEpoch $ now) clock node)


findOrCreateUUIDv1 : Party -> Update (ContractId UUIDv1State)
findOrCreateUUIDv1 party = do
  maybeUuidCid <- lookupByKey @UUIDv1State party
  case maybeUuidCid of
    Some uuidCid -> do return uuidCid
    None -> create UUIDv1State with party ; clock = 0; mac = 0; node = partyToUUIDNode party


partyToUUIDNode : Party -> Int
partyToUUIDNode = fromSome . hexToInt . (take 12) . sha256 . partyToText

UUIDv4

If UUIDv4 is a requirement, then your best bet is to generate UUIDs outside the ledger and inject them in upon request; a general request/response pattern to an oracle-like process could accomplish this:

template RequestUUID
  with
    requester: party
    oracle: party
  where
    signatory requester
    observer oracle

    controller oracle can
      ProvideUUID: ContractId ResponseUUID
        with uuid : Text
           create ResponseUUID with ..

template ResponseUUID
  with
    requester: party
    oracle: party
    uuid : Text
  where
    signatory oracle
    observer requester

    controller requester can
      Finish: ()
        do
          return ()

The UUID oracle would be expected to respond to all requests for new UUIDs by calling ProvideUUID.

3 Likes

If you can avoid needing UUID strings within your DAML code directly, then the best option is just to use an empty UUID contract id, and delegate formatting the contract-id as a UUID to the application UX.

template UUID
  with
    creator: Party
  where
    signatory creator

In our project need to generate UUID strings and manipulate them in DAML. We use both v1 and v4 UUIDs, and have implemented handling that looks very similar to what @dtanabe posted. As he mentioned, v4 requires a source of entropy from which to build a pseudo-rng which we have also implemented using a the builtin SHA256 chained in counting mode.

The entropy itself needs to answer two questions: 1. Where to get the random seed; and, 2. How to manage the entropy state

The random seed is always going to be something passed in as an argument to a choice. The real problem is preventing reusing a seed. Fortunately, uniqueness is easily handled by creating small uniqueness contracts using contract-keys, this is easier to handle if you add the current time or some other explicit window to the seed in your DAML.

template Seed
  with
    creator: Party
    entropy: Text
    window: Time
  where
    signatory creator
    key (creator, entropy, window)
    maintainer key._1

  non-consuming controller creator can
    Retrieve: Text
      do
        pure $ partyToText creator <> entropy <> show window

As to managing the state, we have used two approaches. Originally, we stored the current random state in a transient contract that would return the next random number in a consuming choice that also created the next random seed — and ensured the function that created the random seed contract took a continuation so we could guarantee the state was cleaned up after use.

We have since moved to something closer to a traditional Haskell solution by storing the random state in the StateT monad transformer in our application monad transformer stack.

For simple DAML applications I recommend the former approach, as I would not introduce a transformer stack just to handle entropy. For more complex applications that already have a stack for other reasons, it is going to be easier to fold entropy into your existing state record than deal with the contract-id/key juggling necessary to use the ledger for this.

A third approach would be a custom monad transformer that emulates the StateT approach, but does its state management on the ledger alla the first approach. I would consider this where you have an application complex enough to require a transformer stack AND require independent workflow composition, in which case using the ledger resolves the usual epistemological issues with function arguments.

1 Like

Because it can be a bit surprising at first, I do feel the need to mention that getTime has the same constraint of reproducibility that makes randomness impossible in DAML: all nodes have to see the same time for the same transaction, so getTime is not getting the “current time”, but the timestamp associated to the transaction by the submitter.

This means that all the suggestions in this thread basically boil down to having a client supply the seed in such a way that they can easily predict the UUID, as they have access to the DAML code that will be run. So I would recommend considering the simpler approach of generating the UUID as a string in a client application and have that be provided to the ledger directly (and treating it as an opaque value in DAML that can only be compared for equality), possibly using @dtanabe’s request/response cycle if not all parties can be trusted to generate UUIDs.

2 Likes