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