Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import cats.syntax.either.*
import com.digitalasset.canton.serialization.ProtoConverter
import com.digitalasset.canton.serialization.ProtoConverter.ParsingResult
import com.digitalasset.canton.{LfVersioned, ProtoDeserializationError}
import com.digitalasset.daml.lf.data.Ref
import com.digitalasset.daml.lf.data.{Bytes, Ref}
import com.digitalasset.daml.lf.value.{ValueCoder, ValueOuterClass}
import com.digitalasset.daml.lf.crypto

object GlobalKeySerialization {

Expand All @@ -22,7 +23,7 @@ object GlobalKeySerialization {
.leftMap(_.errorMessage)
} yield v30.GlobalKey(
templateId = templateIdP.toByteString,
key = keyP.toByteString,
key = globalKey.unversioned.hash.bytes.toByteString.concat(keyP.toByteString),
globalKey.unversioned.packageName,
)
}
Expand All @@ -33,37 +34,48 @@ object GlobalKeySerialization {

def fromProtoV30(globalKeyP: v30.GlobalKey): ParsingResult[LfVersioned[LfGlobalKey]] = {
val v30.GlobalKey(templateIdBytes, keyBytes, packageNameP) = globalKeyP
for {
templateIdP <- ProtoConverter.protoParser(ValueOuterClass.Identifier.parseFrom)(
templateIdBytes
)
templateId <- ValueCoder
.decodeIdentifier(templateIdP)
.leftMap(err =>
ProtoDeserializationError
.ValueDeserializationError("GlobalKey.templateId", err.errorMessage)
val hashBytesLength = crypto.Hash.underlyingHashLength
if (keyBytes.size < hashBytesLength) {
Left(
ProtoDeserializationError.OtherError(
s"ByteString too short to contain a GlobalKey hash. Expected at least $hashBytesLength bytes, got ${keyBytes.size}"
)

keyP <- ProtoConverter.protoParser(ValueOuterClass.VersionedValue.parseFrom)(
keyBytes
)
versionedKey <- ValueCoder
.decodeVersionedValue(keyP)
.leftMap(err =>
ProtoDeserializationError.ValueDeserializationError("GlobalKey.proto", err.toString)
} else {
val hashBs = keyBytes.substring(0, hashBytesLength)
val valueBs = keyBytes.substring(hashBytesLength)
for {
templateIdP <- ProtoConverter.protoParser(ValueOuterClass.Identifier.parseFrom)(
templateIdBytes
)
templateId <- ValueCoder
.decodeIdentifier(templateIdP)
.leftMap(err =>
ProtoDeserializationError
.ValueDeserializationError("GlobalKey.templateId", err.errorMessage)
)

packageName <- Ref.PackageName
.fromString(packageNameP)
.leftMap(err => ProtoDeserializationError.ValueDeserializationError("GlobalKey.proto", err))
hash <- crypto.Hash.fromBytes(Bytes.fromByteString(hashBs)).left.map(ProtoDeserializationError.OtherError(_))
keyP <- ProtoConverter.protoParser(ValueOuterClass.VersionedValue.parseFrom)(valueBs)
versionedKey <- ValueCoder
.decodeVersionedValue(keyP)
.leftMap(err =>
ProtoDeserializationError.ValueDeserializationError("GlobalKey.proto", err.toString)
)

globalKey <- LfGlobalKey
.build(templateId, versionedKey.unversioned, packageName)
.leftMap(err =>
ProtoDeserializationError.ValueDeserializationError("GlobalKey.key", err.toString)
)
packageName <- Ref.PackageName
.fromString(packageNameP)
.leftMap(err =>
ProtoDeserializationError.ValueDeserializationError("GlobalKey.proto", err)
)

} yield LfVersioned(versionedKey.version, globalKey)
}
globalKey <- LfGlobalKey
.build(templateId, packageName, versionedKey.unversioned, hash)
.leftMap(err =>
ProtoDeserializationError.ValueDeserializationError("GlobalKey.key", err.toString)
)

} yield LfVersioned(versionedKey.version, globalKey)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ class ContractIdAbsolutizer(
}
.map { absolutizedKey =>
keyWithMaintainers.copy(globalKey =
LfGlobalKey.assertBuild(gkey.templateId, absolutizedKey, gkey.packageName)
LfGlobalKey.assertBuild(gkey.templateId, gkey.packageName, absolutizedKey, gkey.hash)
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ object ExampleContractFactory extends EitherValues {
value: Value = ValueInt64(random.nextLong()),
maintainers: Set[Ref.Party] = Set(signatory),
): GlobalKeyWithMaintainers =
GlobalKeyWithMaintainers.assertBuild(templateId, value, maintainers, packageName)
GlobalKeyWithMaintainers.assertBuild(templateId, value, null, maintainers, packageName)

def modify[Time <: CreationTime](
base: GenContractInstance { type InstCreatedAtTime <: Time },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ message CreatedEvent {
// Optional
Value contract_key = 5;

// The hash of contract_key.
// This will be set if and only if ``template_id`` defines a contract key.
// Optional
bytes contract_key_hash = 16;

// The arguments that have been used to create the contract.
//
// Required
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,9 @@ final class PreparedTransactionDecoder(override val loggerFactory: NamedLoggerFa
templateId: TypeConId,
key: Value,
packageName: Ref.PackageName,
hash: lf.crypto.Hash,
): Result[lf.transaction.GlobalKey] =
lf.transaction.GlobalKey.build(templateId, key, packageName).leftMap(_.msg).toResult
lf.transaction.GlobalKey.build(templateId, packageName, key, hash).leftMap(_.msg).toResult

PartialTransformer
.define[iscd.GlobalKey, lf.transaction.GlobalKey]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import com.digitalasset.daml.lf.transaction.Node.{Create, Exercise}
import org.apache.pekko.NotUsed
import org.apache.pekko.stream.FlowShape
import org.apache.pekko.stream.scaladsl.{Broadcast, Flow, GraphDSL, Merge, Sink, Source}
import com.digitalasset.daml.lf.crypto

import scala.concurrent.duration.FiniteDuration
import scala.concurrent.{ExecutionContext, Future}
Expand Down Expand Up @@ -384,8 +385,11 @@ private[platform] object InMemoryStateUpdater {
globalKey = createdEvent.contractKey.map(k =>
Key.assertBuild(
createdEvent.templateId,
k.unversioned,
createdEvent.packageName,
k.unversioned,
createdEvent.createKeyHash.getOrElse(
throw new IllegalStateException("contractKey is defined but createKeyHash is not")
),
)
),
)
Expand All @@ -395,11 +399,14 @@ private[platform] object InMemoryStateUpdater {
if exercisedEvent.consuming && exercisedEvent.flatEventWitnesses.nonEmpty =>
ContractStateEvent.Archived(
contractId = exercisedEvent.contractId,
globalKey = exercisedEvent.contractKey.map(k =>
Key.assertBuild(
exercisedEvent.templateId,
k.unversioned,
exercisedEvent.packageName,
globalKey = exercisedEvent.contractKey.flatMap(k =>
exercisedEvent.contractKeyHash.map(hash =>
Key.assertBuild(
exercisedEvent.templateId,
exercisedEvent.packageName,
k.unversioned,
hash,
)
)
),
)
Expand Down Expand Up @@ -487,6 +494,7 @@ private[platform] object InMemoryStateUpdater {
contractKey = exercise.keyOpt.map(k =>
com.digitalasset.daml.lf.transaction.Versioned(exercise.version, k.value)
),
contractKeyHash = exercise.keyOpt.map(_.globalKey.hash),
treeEventWitnesses = blinding.disclosure.getOrElse(nodeId, Set.empty),
flatEventWitnesses =
if (exercise.consuming && txAccepted.isAcsDelta(exercise.targetCoid))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import io.grpc.Status.Code
import java.io.ByteArrayInputStream
import scala.concurrent.{ExecutionContext, Future}
import scala.util.chaining.*
import com.digitalasset.daml.lf.crypto

/** Serializes and deserializes Daml-Lf values and events.
*
Expand Down Expand Up @@ -300,7 +301,7 @@ final class LfValueTranslation(
loggingContext: LoggingContextWithTrace,
): Future[CreatedEvent] = {
val createArgument = fatContractInstance.createArg
val createKey = fatContractInstance.contractKeyWithMaintainers.map(_.globalKey.key)
val createKey = fatContractInstance.contractKeyWithMaintainers.map(_.globalKey)

val representativeTemplateId =
fatContractInstance.templateId
Expand All @@ -323,7 +324,8 @@ final class LfValueTranslation(
templateId = Some(
LfEngineToApi.toApiIdentifier(fatContractInstance.templateId)
),
contractKey = apiContractData.contractKey,
contractKey = apiContractData.contractKey.map(_._1),
contractKeyHash = apiContractData.contractKey.fold(ByteString.EMPTY)(_._2.bytes.toByteString),
createArguments = Some(apiContractData.createArguments),
createdEventBlob = apiContractData.createdEventBlob.getOrElse(ByteString.EMPTY),
interfaceViews = apiContractData.interfaceViews,
Expand All @@ -342,7 +344,7 @@ final class LfValueTranslation(

private def toApiContractData(
value: Value,
keyO: Option[Value],
keyO: Option[GlobalKey],
representativeTemplateId: FullIdentifier,
witnesses: Set[String],
eventProjectionProperties: EventProjectionProperties,
Expand All @@ -363,14 +365,14 @@ final class LfValueTranslation(
.map(toContractArgumentApi(verbose))
def asyncContractKey = keyO match {
case None => Future.successful(None)
case Some(key) =>
case Some(gkey) =>
enrichAsync(
verbose = verbose,
value = key,
value = gkey.key,
enrich = enricher.enrichContractKey(representativeTemplateId.toIdentifier, _),
)
.map(toContractKeyApi(verbose))
.map(Some(_))
.map(Some(_, gkey.hash))
}

def asyncInterfaceViews =
Expand Down Expand Up @@ -549,7 +551,7 @@ object LfValueTranslation {
final case class ApiContractData(
createArguments: ApiRecord,
createdEventBlob: Option[ByteString],
contractKey: Option[ApiValue],
contractKey: Option[(ApiValue, crypto.Hash)],
interfaceViews: Seq[InterfaceView],
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,9 @@ private[events] object TransactionLogUpdatesConversions {
GlobalKeyWithMaintainers.assertBuild(
templateId = createdEvent.templateId,
value = keyVersionedValue.unversioned,
valueHash = createdEvent.createKeyHash.getOrElse(
throw new IllegalStateException("contractKey is defined but createKeyHash is not")
),
maintainers = maintainers,
packageName = createdEvent.packageName,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ object TransactionLogUpdate {
commandId: String,
workflowId: String,
contractKey: Option[LfValue.VersionedValue],
contractKeyHash: Option[Hash],
treeEventWitnesses: Set[Party],
flatEventWitnesses: Set[Party],
submitters: Set[Party],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ object DisclosedContractCreator {
None -> LfValue.ValueText("some key"),
),
),
null,
api.keyMaintainers,
Ref.PackageName.assertFromString(api.packageName),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ class CommandSubmissionServiceImplSpec
LfError.Interpretation.DamlException(
LfInterpretationError.DuplicateContractKey(
GlobalKey
.assertBuild(tmplId, Value.ValueUnit, PackageName.assertFromString("pkg-name"))
.assertBuild(tmplId, packageName = PackageName.assertFromString("pkg-name"), key = Value.ValueUnit, keyHash = null)
)
),
None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ private[backend] trait StorageBackendTestsContracts
val key1 = GlobalKey.assertBuild(
Identifier.assertFromString("A:B:C"),
ValueUnit,
someTemplateId.pkg.name,
keyHash = null,
)
val key2 = GlobalKey.assertBuild(
Identifier.assertFromString("A:B:C"),
ValueText("value"),
someTemplateId.pkg.name,
keyHash = null,
)
val internalContractId = 123L
val internalContractId2 = 223L
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ class UpdateToDbDtoSpec extends AnyWordSpec with Matchers {
internal_contract_id = 42L,
create_key_hash = Some(
GlobalKey
.assertBuild(contractTemplate, keyValue, createNode.packageName)
.assertBuild(contractTemplate, key = keyValue, keyHash = null)
.hash
.bytes
.toHexString
Expand Down Expand Up @@ -1344,11 +1344,7 @@ class UpdateToDbDtoSpec extends AnyWordSpec with Matchers {
internal_contract_id = 42L,
create_key_hash = Some(
GlobalKey
.assertBuild(
Ref.Identifier.assertFromString("P:M:T2"),
Value.ValueUnit,
createNodeC.packageName,
)
.assertBuild(Ref.Identifier.assertFromString("P:M:T2"), key = Value.ValueUnit, keyHash = null)
.hash
.bytes
.toHexString
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,9 @@ private[dao] trait JdbcLedgerDaoContractsSpec extends LoneElement with Inside wi
val aTextValue = ValueText(scala.util.Random.nextString(10))

val key = GlobalKeyWithMaintainers.assertBuild(
someTemplateId,
aTextValue,
Set(alice, bob),
somePackageName,
value = aTextValue,
valueHash = null,
maintainers = Set(alice, bob),
)

for {
Expand Down Expand Up @@ -132,10 +131,9 @@ private[dao] trait JdbcLedgerDaoContractsSpec extends LoneElement with Inside wi
def genContractWithKey(string: String = scala.util.Random.nextString(5)) = {
val aTextValue = ValueText(string)
val key = GlobalKeyWithMaintainers.assertBuild(
someTemplateId,
aTextValue,
Set(alice),
somePackageName,
value = aTextValue,
valueHash = null,
maintainers = Set(alice),
)
createAndStoreContract(
submittingParties = Set(alice),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ object JsSchema {
contractId: String,
templateId: Identifier,
contractKey: Option[Json],
contractKeyHash: protobuf.ByteString,
createArgument: Option[Json],
createdEventBlob: protobuf.ByteString,
interfaceViews: Seq[JsInterfaceView],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class LedgerEffectAbsolutizer(absolutizer: ContractIdAbsolutizer) {
absolutizedKey <- gkey.key.traverseCid(absolutizer.absolutizeContractId)
absolutizedResolution <- absolutizeKeyResolution(resolution.unversioned)
} yield (
LfGlobalKey.assertBuild(gkey.templateId, absolutizedKey, gkey.packageName),
LfGlobalKey.assertBuild(gkey.templateId, gkey.packageName, absolutizedKey, gkey.hash),
resolution.copy(unversioned = absolutizedResolution),
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,9 @@ object Enricher {
private def impoverish(key: GlobalKey): GlobalKey =
GlobalKey.assertBuild(
templateId = key.templateId,
key = impoverish(key.key),
key.packageName,
key = impoverish(key.key),
key.hash,
)

private def impoverish(key: GlobalKeyWithMaintainers): GlobalKeyWithMaintainers =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ private[lf] final class CommandPreprocessor(
keys.map(unsafePreprocessApiContractKey(pkgResolution, _))

@throws[Error.Preprocessing.Error]
private def unsafePreprocessApiContractKey(
def unsafePreprocessApiContractKey(
pkgResolution: Map[Ref.PackageName, Ref.PackageId],
key: ApiContractKey,
): GlobalKey = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,13 @@ class AuthorizationSpec extends AnyFreeSpec with Matchers with Inside with TestI
init.copy(
// By default maintainers are added to signatories so do this to allow error case testing
keyOpt = Some(
GlobalKeyWithMaintainers.assertBuild(templateId, Value.ValueUnit, maintainers, pkgName)
GlobalKeyWithMaintainers.assertBuild(
templateId,
Value.ValueUnit,
null,
maintainers,
pkgName,
)
)
)
}
Expand Down
Loading
Loading