I recall hearing about smart contract ‘oracles’; external sources of information that play a role in the control flow of the smart contract code execution. Is that supported in DAML in some way? If so, why would that be different to a DB query (which is not advised/possible in DAML)? Question prompted by Party permissions
“Oracle” is a pretty broad term for any sort of service that makes external information, like prices, the weather, or time(!) available on a distributed ledger platform. “Making it available” here means storing it on a smart contract so the information can be used by other contracts.
The simplest form of Oracle is just a single (trusted) party making a type of data available on the ledger:
template WeatherReport
with
weatherService : Party
location : Location
time : Time
weather : Weather
subscribers : [Party]
where
signatory weatherService
observer subscribers
A complete Oracle would add a small service to that, which reads weather data from some external source, and periodically creates Weather
contracts. To use such Oracle data, you need to decide to trust the issuer. In other words, any contract consuming that reference data needs to specify which oracles to trust.
template RainyDayFund
with
amount : Decimal
owner : Party
trustedService : Party
location : Location
where
signatory owner
controller owner can
Raid : ()
with
weatherCid : ContractId WeatherReport
do
weather <- fetch weatherCid
-- check the weather is less than a day old
assertBefore (addRelTime weather.time (days 1))
assert (location == weather.location)
...
As you can see above, modeling a simple Oracle service in DAML is trivial, which is why there is no special feature for it.
Most Oracle services try to establish trust by having a whole group of parties endorse a piece of information. You could do that by collecting multiple signatures on the same Weather
or - my preferred variant - correlating a number of Weather
contracts from different services:
template RainyDayFund
with
amount : Decimal
owner : Party
trustedServices : [Party]
neededServices : Int
location : Location
where
signatory owner
controller owner can
Raid : ()
with
weatherCids : [ContractId WeatherReport]
do
weathers <-mapA fetch weatherCids
forA weathers (\weather -> do
-- check the weathers is less than a day old
assertBefore (addRelTime weather.time (days 1))
assert (location == weather.location)
assert (weather.weatherService `elem` trustedServices))
-- make sure there are at least neededServices services
let services = dedup $ map (\w -> w.weatherService) weathers
assert (length services >= neededServices)
You could make all this a bit fancier still by abstracting the check of the data or using contract key lookups instead of passing in the reference data, but this illustrates what an Oracle service takes on the DAML side.
There is one Oracle that is special though, which DAML treats specially and that we actually used above: Time
Why is time special?
- It is fast moving
- There isn’t actually any such thing as a single time in a distributed system
- Most developers are used to working with system time and are thus accustomed to just having a time to work with
To reconcile these items, DAML ledgers have to establish, as part of consensus, a timestamp for the commit. Commits can’t be guaranteed to be ordered (DAML Ledgers aren’t Blockchains after all), so that timestamp can’t be guaranteed to always move forward. The commit timestamp (called Record Time) is therefore normalized into a reasonably monotonous Ledger Effective TIme. The monotinicity (eg creates happen before exercises) is enforced by the ledger.
In some cases it can still be advantageous to implement you own time Oracle. For example, if you want to have the ability to all mutually agree to turn back time on a contract, you need to take time from a custom time Oracle, rather than the inbuilt one. That can easily be done using the pattern above.
Awesome answer. This should be in the docs