Do fetch nodes include the contract data?

Fetch nodes are hidden in the public API, but they exist in the transaction. It seems reasonable that the fetch node merely records that we fetched a contract with a given id, because you only need the contract data at interpretation, but is that actually the case?

3 Likes

We give two important guarantees:

  1. Witnessing an action gives you no information about parent actions
  2. If you witness an action, you can verify its model conformance

Let’s say we have

choice Foo : ()
  with cid : ContractId Bar
  observer alice
  controller bob
  do
    bar <- fetch cid
    bar <- fetch cid 
    bar.baz === None

bob witnesses both the exercise and the fetch. By rule 2 above, bob needs to be able to verify them, but to do that, bob needs to get the value of bar to locally execute the line

bar <- fetch cid

and the follow-on

bar.baz === None

The way Canton gives bob that value is via divulgence. The contract arguments are attached to the view containing the fetch node. So you incur the cost of the fetch node.

Now let’s assume carol is a signatory of cid. She doesn’t see the exercise so we have three views. The exercise plus the two fetches. Since carol is not allowed to find out anything about the exercise, we have to include the fetch (ie the contract arguments) in both views. You pay for both fetches.

Conversely, assume cid has only alice and bob as observers. Now all three actions are in the same view so we can optimize away the duplication. But we still have to include it once since bob is not allowed to learn whether alice called the choice directly, or from a context with other observers that are thus witnessing the exercise and fetch.

In short, it’s best to assume that you pay for every fetch, but if you are careful, you can optimize away a few.

1 Like

@bernhard If one were to fetch an inteface, what would be stored in the Daml transaction stream? The viewtype of the interface?

You still store the whole contract. When you say you have interface IFoo and an implementation Foo. Let’s say you have cid : ContractId IFoo that you happen to know is a actually of type Foo and you want to get your hands on the Foo. You can do this two ways:

ifoo <- fetch cid
let Some foo = fromInterface ifoo
let cid' = fromInterfaceContractId @Foo cid
foo <- fetch cid'

In both cases you get the same safety/security guarantees and thus also have to put the same data on the ledger. In the above, the fetch is the only thing that has a side-effect so you can infer from this that the two fetches do exactly the same thing at the ledger-level, but merely represent the value differently at the language level.

But what if I do not want Foo but just the view ifoo ?

This would allow us to write smaller fetch nodes to the transaction log and save space. Presumably all that you need to store is the payload of view ifoo and the Partys used for authorization?

I apologize for resurrecting this thread, but there is one detail I want to confirm (@bernhard ). You mention that Carol’s view would contain only the fetch, and that fetch would contain the contract payload. I assume that this is because Carol needs to verify that the contract payload is consistent with the contract id. (Presumably she would also need to verify that the contract exists in her participant store, but that doesn’t require the payload).

Is this correct?

It’s a little bit more than just checking that he contractid matches. Carol needs to run the line bar.baz === None during her validation. For that, she needs to access the value bar.baz. That line could equally have been bar == Bar with .., which means Carol needs to have access to the full value bar. That value needs to come from somewhere and that somewhere is the contract arguments attached to the view.

But if Carol’s view is just the fetch, why does she need to evaluate bar.baz == None?

My bad, that’s what happens trying to get back into an example 9 months later… It’s not Carol that does the evaluation of bar.baz == None, but Alice. Thus Alice needs the data. And Alice wants confirmation from Carol that she has the right data so that’s why the “fetch” contains the data: Alice and Carol both see it, and Carol confirms it giving Alice the guarantee that she’s working with valid data.

Thanks, that makes sense.

If Alice is solely concerned with confirming she has good data, wouldn’t the contract id suffice (since it’s derived from the payload, among other things)? In that case, Carol could simply check that a contract with that id exists in her participant store.

The payload would be necessary to block malicious behavior from Alice, e.g it prevents her from faking the payload to manipulate the None check, because Carol would see that the id doesn’t match the payload.

But where does Alice get the data in the first place? Answer: From the fetch node.

Yes, but in terms of the view provided to Carol it seems that contract id is enough for Carol to verify that the data is good, assuming that Alice isn’t malicious, unless I’m missing something.

Yes, I guess if we assumed that every witness of the exercise node above the Fetch “magically” had the contract arguments available so that they can validate it, then it would be enough for Carol’s view to act as a pure activeness check and thus only contain the contractId.

But neither Alice nor Carol can assume such magic. Bob, the submitter, may have added any number of additional witnesses by performing these actions through a bigger transaction, and Alice and Carol don’t know about these witnesses. They somehow need to ensure that if a witness sees the Exercise, then they can verify it. And the only way to ensure that is to include the contract arguments.

So, it’s not possible for Bob (who prepares the views) to provide just Carol with a fetch node eliding the payload, but preserving the payload in the views for the other witnesses? So eliding the payload for Carol would mean eliding it for everyone else?

I don’t quite understand what hypothetical you are exploring here, or why.

The Canton protocol’s security relies on the transparency property that informees (observers/controllers/signatories) know about each other and about validators (signatories/controllers) and thus can keep each other honest. Alice and Carol should be able to validate that the other and any other third party also receives consistent and valid transactions.

So both Alice and Carol need to see enough information that they can be sure that some other witness also has correct contract information.

Maybe you could decompose a Fetch node into a Disclose node and a Live node where the Disclose node just shares the data and is not visible to Carol and the Live node only performs the liveness check. But the only thing you’d gain here is that Carol doesn’t have to download the contract payload from the sequencer. Not a good efficiency/complexity tradeoff.

I’m not saying that omitting the payload would be a good idea, I’m just trying to check my reasoning for why it’s included in Carol’s view.

Carol can, from the id alone, verify that a contract with that id exists in her participant store. This gives other validators who are privy to the enclosing exercise (and thus do need the payload to verify model conformance) an assurance that the fetched contract is live.

It’s also my understanding that, since the contract id is derived from the payload, those validators can check whether the payload is is consistent with the id, with no input needed from Carol. So given that Carol has validated that the contract is live, and given that the validators have verified that the id matches the payload, the validators know that Carol has a live contract with the same payload, i.e the data is good.

However, if, for example, both Bob and Alice are malicious and Bob submits a bogus payload with a live contract id, and Alice colludes by accepting the transaction, then the payload in Carol’s view allows her to reject the transaction. (I suppose this doesn’t require actual malice, but just that both Bob and Alice behave incorrectly in such a way that Carol is the deciding validator).

TL;DR version: I’m trying to understand the scenarios where Carol having the payload allows her to reject bad behavior which would not already be caught by the validators who do see the exercise (and thus would need the payload anyway).

TL;DR version: I’m trying to understand the scenarios where Carol having the payload allows her to reject bad behavior which would not already be caught by the validators who do see the exercise (and thus would need the payload anyway).

Ok, as I said above, I don’t know the answer. I’m not sure there are such scenarios, it may indeed be possible to split Fetch into Disclose and Live nodes. But even if it was, there’d be little benefit to doing so.