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).
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
.
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.