com.wavesenterprise.serialization.ProtoAdapter.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of we-models Show documentation
Show all versions of we-models Show documentation
Library for Waves Enterprise blockchain platform
package com.wavesenterprise.serialization
import cats.implicits._
import com.google.common.io.ByteStreams.newDataOutput
import com.google.protobuf.ByteString
import com.google.protobuf.duration.{Duration => PbDuration}
import com.wavesenterprise.account.{Address, AddressOrAlias, Alias, PublicKeyAccount}
import com.wavesenterprise.acl.{OpType, PermissionOp, Role}
import com.wavesenterprise.crypto.internals.HashBytes
import com.wavesenterprise.crypto.internals.confidentialcontracts.Commitment
import com.wavesenterprise.docker.StoredContract.{DockerContract, WasmContract}
import com.wavesenterprise.docker.{ContractApiVersion, StoredContract}
import com.wavesenterprise.docker.validator.ValidationPolicy
import com.wavesenterprise.privacy.PolicyItemInfo
import com.wavesenterprise.protobuf.service.privacy.{PolicyItemInfoResponse, PolicyItemFileInfo => PbPolicyItemFileInfo}
import com.wavesenterprise.state._
import com.wavesenterprise.transaction.ValidationError.{ContractIsMissingError, GenericError, InvalidContractApiVersion, InvalidContractId}
import com.wavesenterprise.transaction._
import com.wavesenterprise.transaction.docker.ContractTransactionEntryOps.DataEntryMap
import com.wavesenterprise.transaction.docker._
import com.wavesenterprise.transaction.docker.assets.ContractAssetOperation.{
ContractAssetOperationMap,
ContractBurnV1,
ContractCancelLeaseV1,
ContractIssueV1,
ContractLeaseV1,
ContractPaymentV1,
ContractReissueV1,
ContractTransferOutV1
}
import com.wavesenterprise.transaction.docker.assets.{ContractAssetOperation, ContractTransferInV1}
import com.wavesenterprise.transaction.protobuf.StoredContract.Value
import com.wavesenterprise.transaction.protobuf.ValidationPolicy.Type
import com.wavesenterprise.transaction.protobuf.docker.{
ContractBurn,
ContractCancelLease,
ContractIssue,
ContractLease,
ContractPayment,
ContractReissue,
ContractTransferOut,
ContractTransferIn => PbContractTransferIn
}
import com.wavesenterprise.transaction.protobuf.{
AtomicBadge => PbAtomicBadge,
ContractApiVersion => PbContractApiVersion,
ContractAssetOperation => PbContractAssetOperation,
ContractAssetOperationList => PbAssetOperationsList,
ContractAssetOperationMap => PbAssetOperationsMap,
DataEntry => PbDataEntry,
DataEntryList => PbDataEntryList,
DataEntryMap => PbDataEntryMap,
DockerContract => PbDockerContract,
ExecutableTransaction => PbExecutableTransaction,
OpType => PbOpType,
PermissionOp => PbPeprmissionOp,
Role => PbRole,
StoredContract => PbStoredContract,
Transfer => PbTransfer,
ValidationPolicy => PbValidationPolicy,
ValidationProof => PbValidationProof,
WasmContract => PbWasmContract
}
import com.wavesenterprise.transaction.transfer.ParsedTransfer
import scala.concurrent.duration.Duration
object ProtoAdapter {
implicit def scalaDurationToProto(duration: Duration): PbDuration =
PbDuration(duration.toMillis / 1000, (duration.toMillis % 1000 * 1000).toInt)
def toProto(dataEntry: DataEntry[_]): PbDataEntry = {
import PbDataEntry.Value._
dataEntry match {
case IntegerDataEntry(key, value) => PbDataEntry(key, IntValue(value))
case StringDataEntry(key, value) => PbDataEntry(key, StringValue(value))
case BooleanDataEntry(key, value) => PbDataEntry(key, BoolValue(value))
case BinaryDataEntry(key, value) => PbDataEntry(key, BinaryValue(ByteString.copyFrom(value.arr)))
}
}
def fromProto(protoDataEntry: PbDataEntry): Either[ValidationError, DataEntry[_]] = {
import PbDataEntry.Value._
protoDataEntry match {
case PbDataEntry(key, IntValue(value), _) => Right(IntegerDataEntry(key, value))
case PbDataEntry(key, StringValue(value), _) => Right(StringDataEntry(key, value))
case PbDataEntry(key, BoolValue(value), _) => Right(BooleanDataEntry(key, value))
case PbDataEntry(key, BinaryValue(value), _) => Right(BinaryDataEntry(key, ByteStr(value.toByteArray)))
case PbDataEntry(key, Empty, _) => Left(GenericError(s"Empty data entry value with key '$key'"))
}
}
def toProto(tx: ExecutableTransaction): PbExecutableTransaction = {
val pbTransaction = tx match {
case callTx: CallContractTransaction => PbExecutableTransaction.Transaction.CallContractTransaction(callTx.toInnerProto)
case createTx: CreateContractTransaction => PbExecutableTransaction.Transaction.CreateContractTransaction(createTx.toInnerProto)
case updateTx: UpdateContractTransaction => PbExecutableTransaction.Transaction.UpdateContractTransaction(updateTx.toInnerProto)
}
PbExecutableTransaction(tx.version, pbTransaction)
}
def fromProto(protoTx: PbExecutableTransaction): Either[ValidationError, ExecutableTransaction] = {
protoTx.transaction match {
case PbExecutableTransaction.Transaction.CreateContractTransaction(value) => CreateContractTransaction.fromProto(protoTx.version, value)
case PbExecutableTransaction.Transaction.CallContractTransaction(value) => CallContractTransaction.fromProto(protoTx.version, value)
case PbExecutableTransaction.Transaction.UpdateContractTransaction(value) => UpdateContractTransaction.fromProto(protoTx.version, value)
case PbExecutableTransaction.Transaction.Empty => Left(GenericError("Empty executable transaction"))
}
}
def toProto(contractAssetOperation: ContractAssetOperation): PbContractAssetOperation = {
contractAssetOperation match {
case o: ContractAssetOperation.ContractIssueV1 =>
val innerOperation = ContractIssue(
o.assetId.base58,
o.name,
o.description,
o.quantity,
o.decimals,
o.isReissuable,
o.nonce
)
PbContractAssetOperation(PbContractAssetOperation.Operation.ContractIssue(innerOperation))
case o: ContractAssetOperation.ContractReissueV1 =>
val innerOperation = ContractReissue(
o.assetId.base58,
o.quantity,
o.isReissuable
)
PbContractAssetOperation(PbContractAssetOperation.Operation.ContractReissue(innerOperation))
case o: ContractAssetOperation.ContractBurnV1 =>
val innerOperation = ContractBurn(
o.assetId.map(_.base58),
o.amount
)
PbContractAssetOperation(PbContractAssetOperation.Operation.ContractBurn(innerOperation))
case o: ContractAssetOperation.ContractTransferOutV1 =>
val innerOperation = ContractTransferOut(
o.assetId.map(_.base58),
o.recipient.stringRepr,
o.amount
)
PbContractAssetOperation(PbContractAssetOperation.Operation.ContractTransferOut(innerOperation))
case o: ContractAssetOperation.ContractLeaseV1 =>
val innerOperation = ContractLease(
o.leaseId.base58,
o.nonce,
o.recipient.stringRepr,
o.amount
)
PbContractAssetOperation(PbContractAssetOperation.Operation.ContractLease(innerOperation))
case o: ContractAssetOperation.ContractCancelLeaseV1 =>
val innerOperation = ContractCancelLease(
o.leaseId.base58,
)
PbContractAssetOperation(PbContractAssetOperation.Operation.ContractCancelLease(innerOperation))
case o: ContractAssetOperation.ContractPaymentV1 =>
val innerOperation = ContractPayment(
o.assetId.map(_.base58),
o.recipient.base58,
o.amount
)
PbContractAssetOperation(PbContractAssetOperation.Operation.ContractPayment(innerOperation))
}
}
def fromProto(protoContractAssetOperation: PbContractAssetOperation): Either[ValidationError, ContractAssetOperation] = {
protoContractAssetOperation.operation match {
case PbContractAssetOperation.Operation.Empty =>
Left(GenericError(s"Empty contract asset operation value"))
case inner: PbContractAssetOperation.Operation.ContractIssue =>
val value = inner.value
ByteStr
.decodeBase58(value.assetId)
.toEither
.leftMap(err => GenericError(s"Error decoding 'assetId' of ContractIssue asset operation: $err"))
.map { decodedAssetId =>
ContractIssueV1(
decodedAssetId,
value.name,
value.description,
value.quantity,
value.decimals.toByte,
value.isReissuable,
value.nonce.toByte
)
}
case inner: PbContractAssetOperation.Operation.ContractReissue =>
val value = inner.value
ByteStr
.decodeBase58(value.assetId)
.toEither
.leftMap(err => GenericError(s"Error decoding 'assetId' of ContractReissue asset operation: $err"))
.map { decodedAssetId =>
ContractReissueV1(decodedAssetId, value.quantity, value.isReissuable)
}
case inner: PbContractAssetOperation.Operation.ContractBurn =>
val value = inner.value
value.assetId
.traverse(str => ByteStr.decodeBase58(str).toEither)
.leftMap(err => GenericError(s"Error decoding 'assetId' of ContractBurn asset operation: $err"))
.map { assetIdOpt =>
ContractBurnV1(assetIdOpt, value.amount)
}
case inner: PbContractAssetOperation.Operation.ContractTransferOut =>
val value = inner.value
for {
assetIdOpt <- value.assetId
.traverse(str => ByteStr.decodeBase58(str).toEither)
.leftMap(err => GenericError(s"Error decoding 'assetId' of ContractTransferOut asset operation: $err"))
recipient <- AddressOrAlias
.fromString(value.recipient)
.leftMap(ValidationError.fromCryptoError)
} yield ContractTransferOutV1(assetIdOpt, recipient, value.amount)
case inner: PbContractAssetOperation.Operation.ContractLease =>
val value = inner.value
for {
leaseId <- ByteStr
.decodeBase58(value.leaseId)
.toEither
.leftMap(err => GenericError(s"Error decoding 'leaseId' of ContractLease asset operation: $err"))
recipient <- AddressOrAlias
.fromString(value.recipient)
.leftMap(ValidationError.fromCryptoError)
} yield ContractLeaseV1(leaseId, value.nonce.toByte, recipient, value.amount)
case inner: PbContractAssetOperation.Operation.ContractCancelLease =>
val value = inner.value
for {
leaseId <- ByteStr
.decodeBase58(value.leaseId)
.toEither
.leftMap(err => GenericError(s"Error decoding 'leaseId' of ContractCancelLease asset operation: $err"))
} yield ContractCancelLeaseV1(leaseId)
case inner: PbContractAssetOperation.Operation.ContractPayment =>
val value = inner.value
for {
assetId <- value.assetId
.fold(Either.right[GenericError, Option[AssetId]](Option.empty[AssetId])) { assetId =>
ByteStr
.decodeBase58(assetId)
.toEither
.map(assetId => Option(assetId))
.leftMap(err => GenericError(s"Error decoding 'assetId' of ContractPayment asset operation: $err"))
}
recipient <- ByteStr
.decodeBase58(value.recipient)
.toEither
.leftMap(err => GenericError(s"error decoding 'recipient' of ContractPayment asset operation: $err"))
} yield ContractPaymentV1(assetId = assetId, recipient = recipient, amount = value.amount)
}
}
def toProto(contractTransferIn: ContractTransferInV1): PbContractTransferIn =
PbContractTransferIn(
contractTransferIn.assetId.map(_.base58),
contractTransferIn.amount
)
def fromProto(protoContractTransferIn: PbContractTransferIn): Either[ValidationError, ContractTransferInV1] = {
protoContractTransferIn.assetId
.traverse(str => ByteStr.decodeBase58(str).toEither)
.leftMap(err => GenericError(s"Error decoding 'assetId' of ContractTransferIn asset operation: $err"))
.map(assetId => ContractTransferInV1(assetId, protoContractTransferIn.amount))
}
def toProto(validationProof: ValidationProof): PbValidationProof = {
PbValidationProof(
byteArrayToByteString(validationProof.validatorPublicKey.publicKey.getEncoded),
byteArrayToByteString(validationProof.signature.arr)
)
}
def fromProto(protoValidationProof: PbValidationProof): Either[ValidationError, ValidationProof] = {
for {
publicKey <- byteArrayFromProto(protoValidationProof.validatorPublicKey).map(PublicKeyAccount.apply)
signature <- byteStrFromProto(protoValidationProof.signature)
} yield ValidationProof(publicKey, signature)
}
def toProto(opType: OpType): PbOpType = {
opType match {
case OpType.Add => PbOpType.ADD
case OpType.Remove => PbOpType.REMOVE
}
}
def fromProto(opType: PbOpType): Either[ValidationError, OpType] = {
opType match {
case PbOpType.ADD => Right(OpType.Add)
case PbOpType.REMOVE => Right(OpType.Remove)
case PbOpType.UNKNOWN_OP_TYPE => Left(GenericError(s"Unknown OpType"))
case PbOpType.Unrecognized(value) => Left(GenericError(s"Unrecognized OpType '$value'"))
}
}
def toProto(permissionOp: PermissionOp): PbPeprmissionOp = {
PbPeprmissionOp(
toProto(permissionOp.opType),
Some(toProto(permissionOp.role)),
permissionOp.timestamp,
permissionOp.dueTimestampOpt
)
}
def fromProto(protoPermissionOp: PbPeprmissionOp): Either[ValidationError, PermissionOp] = {
for {
opType <- fromProto(protoPermissionOp.opType)
role <- protoPermissionOp.role.fold(GenericError(s"Role cannot be empty").asLeft[Role])(fromProto)
} yield PermissionOp(opType, role, protoPermissionOp.timestamp, protoPermissionOp.dueTimestamp)
}
def toProto(role: Role): PbRole = {
PbRole(role.byte)
}
def fromProto(protoRole: PbRole): Either[GenericError, Role] = {
Role.fromByte(protoRole.id.toByte)
}
def toProto(transfer: ParsedTransfer): PbTransfer = {
PbTransfer(byteArrayToByteString(transfer.recipient.bytes.arr), transfer.amount)
}
def fromProto(protoTransfer: PbTransfer): Either[ValidationError, ParsedTransfer] = {
addressOrAliasFromProto(protoTransfer.recipient)
.map(ParsedTransfer(_, protoTransfer.amount))
}
def proofsToProto(value: Proofs): Seq[ByteString] = {
value.proofs.map(str => byteArrayToByteString(str.arr))
}
def proofsFromProto(value: Seq[ByteString]): Either[ValidationError, Proofs] = {
Right(Proofs(value.map(bytes => ByteStr(bytes.toByteArray))))
}
def toProto(badge: AtomicBadge): PbAtomicBadge = {
PbAtomicBadge(badge.trustedSender.map(sender => ProtoAdapter.byteArrayToByteString(sender.bytes.arr)))
}
def fromProto(protoBadge: PbAtomicBadge): Either[ValidationError, AtomicBadge] =
protoBadge.trustedSender.fold(Either.right[ValidationError, AtomicBadge](AtomicBadge(None))) { trustedSenderPb =>
for {
trustedSender <- ProtoAdapter.byteArrayFromProto(trustedSenderPb)
address <- Address.fromBytes(trustedSender).left.map(ValidationError.fromCryptoError)
} yield AtomicBadge(Some(address))
}
def addressOrAliasFromProto(value: ByteString): Either[ValidationError, AddressOrAlias] =
for {
bytes <- byteArrayFromProto(value)
aliasWithPosition <- AddressOrAlias.fromBytes(bytes, 0).left.map(ValidationError.fromCryptoError)
(alias, _) = aliasWithPosition
} yield alias
def addressFromProto(value: ByteString): Either[ValidationError, Address] =
byteArrayFromProto(value).flatMap(Address.fromBytes(_).left.map(ValidationError.fromCryptoError))
def aliasFromProto(value: ByteString): Either[ValidationError, Alias] =
byteArrayFromProto(value).flatMap(Alias.fromBytes(_).left.map(ValidationError.fromCryptoError))
def byteFromProto(value: Int): Either[GenericError, Byte] = {
Either.cond(value.isValidByte, value.toByte, GenericError("Invalid byte value"))
}
def byteStrFromProto(value: ByteString): Either[ValidationError, ByteStr] = {
Right(ByteStr(value.toByteArray))
}
def byteArrayFromProto(value: ByteString): Either[ValidationError, Array[Byte]] = {
Right(value.toByteArray)
}
def byteArrayToByteString(value: Array[Byte]): ByteString = {
ByteString.copyFrom(value)
}
def toProto(value: ValidationPolicy): PbValidationPolicy = {
val `type` = value match {
case ValidationPolicy.Any =>
PbValidationPolicy.Type.Any(PbValidationPolicy.Any())
case ValidationPolicy.Majority =>
PbValidationPolicy.Type.Majority(PbValidationPolicy.Majority())
case ValidationPolicy.MajorityWithOneOf(addresses) =>
val addressesBytes = addresses.map(address => byteArrayToByteString(address.bytes.arr))
val pbAddresses = PbValidationPolicy.MajorityWithOneOf(addressesBytes)
PbValidationPolicy.Type.MajorityWithOneOf(pbAddresses)
}
PbValidationPolicy(`type`)
}
def fromProto(value: PbValidationPolicy): Either[ValidationError, ValidationPolicy] =
value.`type` match {
case Type.Empty =>
Left(GenericError(s"Empty validation policy value"))
case Type.Any(_) =>
Right(ValidationPolicy.Any)
case Type.Majority(_) =>
Right(ValidationPolicy.Majority)
case Type.MajorityWithOneOf(addressesValue) =>
addressesValue.addresses.toList
.map(addressFromProto)
.sequence
.map(ValidationPolicy.MajorityWithOneOf)
}
def toProto(value: ContractApiVersion): PbContractApiVersion =
PbContractApiVersion(value.majorVersion, value.minorVersion)
def fromProto(value: PbContractApiVersion): Either[ValidationError, ContractApiVersion] =
(Either.cond(value.majorVersion.isValidShort && value.majorVersion >= 0, value.majorVersion.toShort, GenericError(s"Invalid major version")),
Either.cond(value.minorVersion.isValidShort && value.minorVersion >= 0, value.minorVersion.toShort, GenericError(s"Invalid minor version")))
.mapN(ContractApiVersion(_, _))
def toProto(info: PolicyItemInfo): PolicyItemInfoResponse = {
val pbInfo = PbPolicyItemFileInfo(
filename = info.info.filename,
size = info.info.size,
timestamp = info.info.timestamp,
author = info.info.author,
comment = info.info.comment
)
PolicyItemInfoResponse(
senderAddress = info.sender,
policyId = info.policy,
info = Some(pbInfo),
dataHash = info.hash
)
}
def commitmentToProto(commitment: Commitment): ByteString = {
ByteString.copyFrom(commitment.hash.arr)
}
def commitmentFromProto(byteString: ByteString): Either[ValidationError, Commitment] = Right {
Commitment.fromHash(HashBytes(ByteStr(byteString.toByteArray)))
}
def toProto(readingsHash: ReadingsHash): ByteString = {
ByteString.copyFrom(readingsHash.hash.arr)
}
def readingsHashFromProto(byteString: ByteString): Either[ValidationError, ReadingsHash] = Right {
ReadingsHash(ByteStr(byteString.toByteArray))
}
def toProto(readDescriptor: ReadDescriptor): ByteString = ByteString.copyFrom {
val output = newDataOutput()
ReadDescriptor.writeBytes(readDescriptor, output)
output.toByteArray
}
def readDescriptorFromProto(byteString: ByteString): Either[ValidationError, ReadDescriptor] = Right {
ReadDescriptor.fromBytes(byteString.toByteArray, 0)._1
}
def toProto(storedContract: StoredContract): PbStoredContract = {
val value = storedContract match {
case DockerContract(image, imageHash, apiVersion) => PbStoredContract.Value.DockerBytecode(PbDockerContract(
image = image,
imageHash = imageHash,
apiVersion = Some(PbContractApiVersion(apiVersion.majorVersion, apiVersion.majorVersion))
))
case WasmContract(bytecode, bytecodeHash) => PbStoredContract.Value.WasmBytecode(
PbWasmContract(
bytecode = byteArrayToByteString(bytecode.array),
bytecodeHash = bytecodeHash
))
}
PbStoredContract(value)
}
def fromProto(pb: PbStoredContract): Either[ValidationError, StoredContract] = {
pb.value match {
case Value.Empty => Left(ContractIsMissingError)
case Value.WasmBytecode(value) => Right(WasmContract(
value.bytecode.toByteArray,
value.bytecodeHash
))
case Value.DockerBytecode(value) => Either.fromOption(
value.apiVersion,
InvalidContractApiVersion("contract api version is missing")
).map { apiVersion =>
DockerContract(
value.image,
value.imageHash,
ContractApiVersion(apiVersion.majorVersion.toShort, apiVersion.minorVersion.toShort)
)
}
}
}
def toProto(dataEntryMap: DataEntryMap): PbDataEntryMap = {
PbDataEntryMap(dataEntryMap.mapping.map(kv => kv._1.base58 -> PbDataEntryList(kv._2.map(toProto))))
}
def fromProto(pb: PbDataEntryMap): Either[ValidationError, DataEntryMap] = {
pb.dataEntryList.toList.traverse { case (key, value) =>
for {
decodedKey <- ByteStr.decodeBase58(key).toEither.left.map(ex => InvalidContractId(ex.getMessage))
dataEntries <- value.entries.toList.traverse(fromProto)
} yield decodedKey -> dataEntries
}.map(list => DataEntryMap(list.toMap))
}
def toProto(operationMap: ContractAssetOperationMap): PbAssetOperationsMap = {
PbAssetOperationsMap(operationMap.mapping.map(kv => kv._1.base58 -> PbAssetOperationsList(kv._2.map(toProto))))
}
def fromProto(pb: PbAssetOperationsMap): Either[ValidationError, ContractAssetOperationMap] = {
pb.operationList.toList.traverse { case (key, value) =>
for {
decodedKey <- ByteStr.decodeBase58(key).toEither.left.map(ex => InvalidContractId(ex.getMessage))
dataEntries <- value.operations.toList.traverse(fromProto)
} yield decodedKey -> dataEntries
}.map(list => ContractAssetOperationMap(list.toMap))
}
}