Is there a library in DAML that natively handles UUIDs? I can’t see any, is that correct and if so why ?
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.
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).
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
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
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.
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.