Background
Can you double-check me on this?
I created a Daml-based number guessing game. Ala, “I’m thinking of a number between 1 and 100.” I’m wondering if the Player
can cheat and peek at Owner
’s contract that holds my secret number.
Details
Here is the Daml model, in case you are interested in following along the details. (The game play consists of alternating back-and-forth between a PlayersTurn
and a OwnersTurn
.)
Daml model
module DivulgedTest where
import Daml.Script
template SecretNumber
with
owner : Party
secret : Int
where
signatory owner
nonconsuming choice GetHint : Text
with guess : Int
controller owner
do
let msg | guess < secret = "Too low"
| guess > secret = "Too high"
| otherwise = "You guessed it!"
return msg
template PlayersTurn
with
owner : Party
player : Party
secretNumberId : ContractId SecretNumber
response : Text
where
signatory owner
observer player
choice Guess : ContractId OwnersTurn
with guess : Int
controller player
do create OwnersTurn with
owner, player, secretNumberId, guess
template OwnersTurn
with
owner : Party
player : Party
secretNumberId : ContractId SecretNumber
guess : Int
where
signatory player
observer owner
choice Respond : ContractId PlayersTurn
controller owner
do
response <- exercise secretNumberId $ GetHint guess
create PlayersTurn with
owner, player, secretNumberId, response
test = script do
owner <- allocatePartyWithHint "Owner" (PartyIdHint "Owner")
player <- allocatePartyWithHint "Player" (PartyIdHint "Player")
secret <- submit owner do
createCmd SecretNumber with owner, secret = 42
guessTurn0 <- submit owner do
createCmd PlayersTurn with
owner = owner
player = player
secretNumberId = secret
response = "Guess a number."
hintTurn0 <- submit player do
exerciseCmd guessTurn0 Guess with guess = 10
guessTurn1 <- submit owner do
exerciseCmd hintTurn0 Respond
lst <- query @SecretNumber player
debug ("Secret number contracts queryable by player: " <> show lst)
s <- queryContractId player secret
debug ("This specific secret number queryied by player: " <> show s)
return ()
There is a specific reason I’m wondering if the player can cheat. The “Script Results” in Daml Studio shows that my SecretNumber
contract has been divulged to the player.
Notice that D in the row with my secret number.
To test, I tried without success to get the secret number for the player using Daml Script.
To further investigate, I’ve used the Ledger API (via grpcui) calling GetTransactionTrees
. With the following request body:
gRPC body
{
"begin": {
"boundary": "LEDGER_BEGIN"
},
"end": {
"boundary": "LEDGER_END"
},
"filter": {
"filtersByParty": {
"Player::12201...8f9ed": {}
}
},
"verbose": true
}
Calling GetTransactionTrees
returns several GetTransactionTreesResponse
s.
- The transaction that created the
SecretNumber
is not returned for the player. - The later transaction that divulged to the player is returned. However, I do not see the actual secret number anywhere in that gRPC response.
Here is that transaction, returned by the call to GetTransactionTrees
. (For reference, the divulgence occurs when the Owner
exercises the nonconsuming choice to GetHint
from the SecretNumber
. It is the first child event.)
divulging transaction
{
"transactions": [
{
"transaction_id": "12201...68542",
"command_id": "",
"workflow_id": "",
"effective_at": "2022-11-22T15:22:40.659871Z",
"offset": "00000000000000000e",
"events_by_id": {
"#12201...68542:0": {
"exercised": {
"event_id": "#12201...68542:0",
"contract_id": "007fe...d6bf5",
"template_id": {
"package_id": "758d9...f0df4",
"module_name": "DivulgedTest",
"entity_name": "OwnersTurn"
},
"choice": "Respond",
"choice_argument": {
"record": {
"record_id": {
"package_id": "758d9...f0df4",
"module_name": "DivulgedTest",
"entity_name": "Respond"
},
"fields": []
}
},
"acting_parties": [
"Owner::12201...8f9ed"
],
"consuming": true,
"witness_parties": [
"Player::12201...8f9ed"
],
"child_event_ids": [
"#12201...68542:1",
"#12201...68542:2"
],
"exercise_result": {
"contract_id": "00c13...789bb"
},
"interface_id": null
}
},
"#12201...68542:1": {
"exercised": {
"event_id": "#12201...68542:1",
"contract_id": "0045f...752b5",
"template_id": {
"package_id": "758d9...f0df4",
"module_name": "DivulgedTest",
"entity_name": "SecretNumber"
},
"choice": "GetHint",
"choice_argument": {
"record": {
"record_id": {
"package_id": "758d9...f0df4",
"module_name": "DivulgedTest",
"entity_name": "GetHint"
},
"fields": [
{
"label": "guess",
"value": {
"int64": "20"
}
}
]
}
},
"acting_parties": [
"Owner::12201...8f9ed"
],
"consuming": false,
"witness_parties": [
"Player::12201...8f9ed"
],
"child_event_ids": [],
"exercise_result": {
"text": "Too low"
},
"interface_id": null
}
},
"#12201...68542:2": {
"created": {
"event_id": "#12201...68542:2",
"contract_id": "00c13...789bb",
"template_id": {
"package_id": "758d9...f0df4",
"module_name": "DivulgedTest",
"entity_name": "PlayersTurn"
},
"create_arguments": {
"record_id": {
"package_id": "758d9...f0df4",
"module_name": "DivulgedTest",
"entity_name": "PlayersTurn"
},
"fields": [
{
"label": "owner",
"value": {
"party": "Owner::12201...8f9ed"
}
},
{
"label": "player",
"value": {
"party": "Player::12201...8f9ed"
}
},
{
"label": "secretNumberId",
"value": {
"contract_id": "0045f...752b5"
}
},
{
"label": "response",
"value": {
"text": "Too low"
}
}
]
},
"witness_parties": [
"Player::12201...8f9ed"
],
"agreement_text": "",
"contract_key": null,
"signatories": [
"Owner::12201...8f9ed"
],
"observers": [
"Player::12201...8f9ed"
],
"metadata": null,
"interface_views": []
}
}
},
"root_event_ids": [
"#12201...68542:0"
]
}
]
}
Question
My conclusion is that nothing was divulged to the player that would allow him to get the secret number. Is that true?
I suspect that if I had had a nonconsuming choice GetSecretNumber
instead of GetHint
then that would be a problem.