0

I have a contract with multiple signatories. Is there an example on how to do LookupBykey the contract? I am having trouble working out how to get all parties' authorization for LookupBykey to work.

Also is there anyone can explain to me that why LookupByKey requires more permission to run than FetchByKey?

Put up some codes to get the same contract using LookupByKey and FetchByKey. For the same party, FetchByKey works but LookupByKey doesn't.

LookupByKey got Scenario execution failed on commit at Main:38:3: #1: lookup by key of Sample:Sample at DA.Internal.Prelude:365:26 failed due to a missing authorization from 'partyA'


run = scenario do
  a <- getParty "partyA"
  b <- getParty "partyB"

  sample <- submit a do create Sample with sig = a, obs = b, content = "some text here" 

  caller <-submit b do create Caller with sig = b, obs = a

  submit b do exercise caller FetchByKey with company="test", text = "fetch by key sample"

  pure()



run2 = scenario do
  a <- getParty "partyA"
  b <- getParty "partyB"

  sample <- submit a do create Sample with sig = a, obs = b, content = "some text here" 

  caller <-submit b do create Caller with sig = b, obs = a

  submit b do  exercise caller LookupByKey with company="test", text = "look up by key sample"

  pure()


-- choices

    controller sig can
      FetchByKey : Bool
        with 
          company : Text
          text : Text
        do
          re <- fetchByKey @Sample (company, obs)

          cid_tr <- exercise (fst re) Operate with text = text

          return True


    controller sig can
      LookupByKey : Bool
        with 
          company : Text
          text : Text
        do
          re <- lookupByKey @Sample (company, obs)

          cid <- fetch (fromSome re) 

          res <- exercise (fromSome re) Operate with text = text

          return True

Frankie
  • 113
  • 5

1 Answers1

2

Let's take a look at both fetchByKey and lookupByKey in the context of a generic Sample template:


template Sample
  with
    maints : [Party]
    extraSigs : [Party]
    obs : [Party]
  where
    signatory maints ++ extraSigs
    observer obs

    key this : Sample
    maintainer key.maints

The key is the whole contract, maintained by a specified subset of the signatories. A simple proposal workflow can be used to instantiate such a contract:


template SampleProposal
  with
    sample : Sample
    sigs : [Party]
  where
    signatory sigs
    observer (signatory sample)

    choice Sign
      : ContractId SampleProposal
      with
        sig : Party
      controller sig
      do
        assert (sig `elem` signatory sample)
        assert (sig `notElem` sigs)
        create this with sigs = sig :: sigs

    choice Accept
      : ContractId Sample
      with
        sig : Party
      controller sig
      do
        assert (sig `elem` signatory sample)
        create sample

makeSample = scenario do
  maints <- mapA getParty ["main1", "main2", "maint3"]
  extraSigs <- mapA getParty ["sig1", "sig2", "sig3"]
  obs <- mapA getParty ["obs1", "obs2", "obs3"]

  let sample = Sample with ..

  prop <- submit (head maints) do
    create SampleProposal with
      sample
      sigs = [head maints]

  signedProp <- foldlA
    (\p sig -> submit sig do exercise p Sign with sig)
    prop
    (tail maints ++ tail extraSigs)

  submit (head extraSigs) do
    exercise signedProp Accept with sig = head extraSigs

Now, who can fetchByKey and who can lookupByKey this sample?

The first condition for either is that the submitting party has to know of the contract. Ie they must be an stakeholder (ie signatory or observer), or the contract must have been divulged to them.

Secondly, the operation has to be authorised correctly. fetchByKey is authorised like fetch, meaning you have to have the authority of at least one stakeholder. Thus, an observer of Sample can divulge the contract and delegate a fetchByKey.

template FetchByKeyDelegation
  with
    sampleObserver : Party
    agent : Party
  where
    signatory sampleObserver, agent

    nonconsuming choice FetchSampleByKey
      : (ContractId Sample, Sample)
      with
        ctl : Party
        sample : Sample
      controller ctl
      do
        fetchByKey @Sample sample

template FetchByKeyDelegationInvite
  with
    fbkd : FetchByKeyDelegation
  where
    signatory fbkd.sampleObserver

    controller fbkd.agent can
      AcceptFBKDI
        : ContractId FetchByKeyDelegation
        do
          create fbkd

delegateFetch = scenario do
  sample <- makeSample
  let obs = head sample.obs
  agent <- getParty "Agent"

  prop <- submit obs do
    create FetchByKeyDelegationInvite with
      fbkd = FetchByKeyDelegation with
        sampleObserver = obs
        agent

  fbkd <- submit agent do
    exercise prop AcceptFBKDI

  -- By calling FetchSampleByKey, `obs` divulges the contract to `agent`
  submit obs do
    exercise fbkd FetchSampleByKey with
      ctl = obs
      sample

  -- Now `agent` can use the authority of `obs` to `fetchByKey`
  submit agent do
  exercise fbkd FetchSampleByKey with
    ctl = agent
    sample

lookupByKey has stricter authorization rules. Rather than one stakeholder, you need the authority of all maintainers. Other than that, delegation works the same:

template LookupByKeyDelegation
  with
    maints : [Party]
    agent : Party
  where
    signatory maints, agent

    nonconsuming choice LookupSampleByKey
      : Optional (ContractId Sample)
      with
        ctl : Party
        sample : Sample
      controller ctl
      do
        lookupByKey @Sample sample


template LookupByKeyDelegationInvite
  with
    lbkd : LookupByKeyDelegation
    sigs : [Party]
  where
    signatory sigs
    observer (signatory lbkd)

    choice SignLBKDI
      : ContractId LookupByKeyDelegationInvite
      with
        sig : Party
      controller sig
      do
        assert (sig `elem` signatory lbkd)
        assert (sig `notElem` sigs)
        create this with sigs = sig :: sigs

    controller lbkd.agent can
      AcceptLBKDI
        : ContractId LookupByKeyDelegation
        do
          create lbkd

delegateLookup = scenario do
  sample <- makeSample
  let maints = sample.maints
  agent <- getParty "agent"

  lbkdi <- submit (head maints) do
    create LookupByKeyDelegationInvite with
      lbkd = LookupByKeyDelegation with
        maints
        agent
      sigs = [head maints]

  signedlbkdi <- foldlA
    (\p sig -> submit sig do exercise p SignLBKDI with sig)
    lbkdi
    (tail maints)

  lbkd <- submit agent do
    exercise signedlbkdi AcceptLBKDI 

  -- By calling LookupSampleByKey, a maintainer divulges the contract to `agent`
  submit (head maints) do
    exercise lbkd LookupSampleByKey with
      ctl = head maints
      sample

  -- Now `agent` can use the authority of `obs` to `lookupByKey`
  submit agent do
  exercise lbkd LookupSampleByKey with
    ctl = agent
    sample

In your specific model, it seems that partyB is an observer, but not a maintainer. Thus, they know the contract (the first conditions), and their actions are authorised by a stakeholder (the second condition for fetchByKey). However, the lookupByKey is not authorised by the maintainers so it fails.

The reason the for the difference in authorisation is the behaviour in case of negative lookups. fetchByKey fails on the submitter node so negative lookups never hit the network. lookupByKey allows negative lookups so they do hit the network.

DAML is designed around the principle that no party should ever have to do work unless they signed something. Validating key lookups is work so you should never have to do it unless you signed a contract. With fetchByKey this is true. Unless you signed a contract on which you are maintainer, no honest node will ever submit a fetchByKey on which you are a maintainer.

However, with lookupByKey this is not true. First of all, if you have a negative lookup, the only information you have is who the maintainers are, as only those are part of the key. Thus to run an authorisation check on the submitter node, the authorisation rule has to be about maintainers, not stakeholders.

Now suppose the authority of one maintainer, rather than all of them, was enough. Then the following would be a perfectly legal thing to do:

badLookup = scenario do
  frankie <- getParty "Frankie"
  spammer <- getParty "spammer"

  let
    sample = Sample with
      maints = [frankie, spammer]
      extraSigs = []
      obs = []

  forA [1..1000] (\_ -> do
    submit spammer do
      lookupByKey @Sample sample
    )

Ie a malicious party could legitimately spam you with expensive operations. This goes against DAMLs core principles so the authorisation rule has to be that all maintainers are needed.

The key take away is that one should consider very carefully whether lookupByKey is actually needed. It's often advisable to design workflows so that all key lookups are positive.

bame
  • 960
  • 5
  • 7