I’ve submitted a list of commands to the ledger API in a single transaction using the command service (sandbox ledger). I am getting the following error message:
INVALID_ARGUMENT: Disputed: Interpretation error: Error: CRASH: Conflicting discriminators between a global and local contract ID
This definitely looks like a bug on our side. To help us debug and fix this, it would be great if youcould you share the following information with us:
SDK version
Whether you are using daml sandbox or daml sandbox-classic.
Any --contract-id-seeding flags you specify.
If you can share anything more about the project, maybe even the code and steps to reproduce, that would be fantastic. Feel free to send any confidential information in a PM.
I can’t share the original code I was using but after some debugging I’ve got a minimal dummy example that demonstrates the problem:
Main.daml:
module Main where
import Daml.Script
import DA.Set (Set)
import qualified DA.Set as Set
template A
with
id : Int
mntr : Party
observers : Set Party
where
key (id, mntr) : (Int, Party)
maintainer key._2
signatory mntr
observer observers
controller mntr can
A_AddObservers : ContractId A
with
moreObservers : Set Party
do
create this with observers = Set.union observers moreObservers
template Manager
with
manager : Party
where
key manager : Party
maintainer key
signatory manager
controller manager can
nonconsuming Manager_CreateA : ContractId A
with
id : Int
observers : Set Party
do
create A with mntr = manager, ..
-- test passes using `daml test` but when using the ledger API java bindings the same thing fails
test : Script ()
test = do
a <- allocateParty "a"
b <- allocateParty "b"
submit a do createCmd Manager with manager = a
submit a $
(exerciseByKeyCmd @Manager a Manager_CreateA with id = 1, observers = Set.empty) *>
(exerciseByKeyCmd @A (1, a) A_AddObservers with moreObservers = Set.singleton b)
pure ()
Test.java which calls the package defined above using the ledger API:
package test;
import com.daml.ledger.api.v1.admin.PackageManagementServiceGrpc;
import com.daml.ledger.api.v1.admin.PackageManagementServiceOuterClass;
import com.daml.ledger.javaapi.data.Unit;
import com.daml.ledger.rxjava.DamlLedgerClient;
import com.google.protobuf.ByteString;
import da.types.Tuple2;
import io.grpc.ManagedChannel;
import io.grpc.netty.NettyChannelBuilder;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.UUID;
import main.A;
import main.Manager;
import org.junit.jupiter.api.Test;
class DamlKeyUpdateTest {
private ByteString loadDarFile() throws IOException {
return ByteString.readFrom(getClass().getResourceAsStream("/daml-key-update-test-1.0.0.dar"));
}
@Test
void test() throws IOException {
String host = "localhost";
int port = 6865;
DamlLedgerClient client = DamlLedgerClient.newBuilder(host, port).build();
client.connect();
ManagedChannel channel = NettyChannelBuilder.forAddress(host, port).usePlaintext().build();
PackageManagementServiceGrpc.newBlockingStub(channel)
.uploadDarFile(
PackageManagementServiceOuterClass.UploadDarFileRequest.newBuilder()
.setDarFile(loadDarFile())
.build());
channel.shutdownNow();
client
.getCommandClient()
.submitAndWait(
UUID.randomUUID().toString(),
UUID.randomUUID().toString(),
UUID.randomUUID().toString(),
"a",
Collections.singletonList(Manager.create("a")))
.blockingGet();
client
.getCommandClient()
.submitAndWait(
UUID.randomUUID().toString(),
UUID.randomUUID().toString(),
UUID.randomUUID().toString(),
"a",
Arrays.asList(
Manager.exerciseByKeyManager_CreateA(
"a", 1L, new da.set.types.Set<>(Collections.emptyMap())),
A.exerciseByKeyA_AddObservers(
new Tuple2<>(1L, "a"),
new da.set.types.Set<>(Collections.singletonMap("b", Unit.getInstance())))))
.blockingGet(); // this will produce INVALID_ARGUMENT: Disputed: Interpretation error: Error: CRASH: Conflicting discriminators between a global and local contract ID.
}
}
Hi @huw, we found a bug and are working on a fix. In the meantime, here are two workarounds:
If transactionality is not important here, you can split the two exerciseByKey calls into two separate submits.
test : Script ()
test = do
a <- allocateParty "a"
b <- allocateParty "b"
submit a do createCmd Manager with manager = a
submit a $
exerciseByKeyCmd @Manager a Manager_CreateA with id = 1, observers = Set.empty
submit a $
exerciseByKeyCmd @A (1, a) A_AddObservers with moreObservers = Set.singleton b
pure ()
If transactionality is important, you can move the two exerciseByKey calls into a choice and call that via createAndExerciseCmd
template Helper
with
p : Party
where
signatory p
choice Help : ContractId A
with
a : Party
b : Party
controller p
do exerciseByKey @Manager a Manager_CreateA with id = 1, observers = Set.empty
exerciseByKey @A (1, a) A_AddObservers with moreObservers = Set.singleton b
test2 : Script ()
test2 = do
a <- allocateParty "a"
b <- allocateParty "b"
submit a do createCmd Manager with manager = a
submit a $ createAndExerciseCmd (Helper a) (Help a b)
pure ()
Hi @cocreature, thanks for looking into this. For now I’ll use a workaround, or even easier just set the observers field of A to include all required observers when doing the create, rather than exercising the A_AddObservers choice after creating.