Skip to content

Commit

Permalink
LF: Introduce configurable limits on produced transactions (#11948)
Browse files Browse the repository at this point in the history
This is part of #11691

This PR allows to limits:
- the number of signatories,
- the number of observers,
- the number of controllers,
- the number of inputContracts,

CHANGELOG_BEGIN
CHANGELOG_END
  • Loading branch information
remyhaemmerle-da authored Dec 2, 2021
1 parent 4571a48 commit b02ed77
Show file tree
Hide file tree
Showing 19 changed files with 684 additions and 56 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,16 @@ final class Conversions(
builder.setCrash(s"Contract Id comparability Error")
case NonComparableValues =>
builder.setComparableValueError(proto.Empty.newBuilder)
case ValueExceedsMaxNesting =>
builder.setValueExceedsMaxNesting(proto.Empty.newBuilder)
case Limit(limitError) =>
limitError match {
case Limit.ValueNesting(_) =>
builder.setValueExceedsMaxNesting(proto.Empty.newBuilder)
// TODO https://github.com/digital-asset/daml/issues/11691
// Handle the other cases properly.
case _ =>
builder.setCrash(s"A limit was overpass when building the transaction")
}

case _: ChoiceGuardFailed =>
// TODO https://github.com/digital-asset/daml/issues/11703
// Implement this.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ class Engine(val config: EngineConfig = Engine.StableConfig) {
readAs = readAs,
validating = validating,
contractKeyUniqueness = config.contractKeyUniqueness,
limits = config.limits,
)
interpretLoop(machine, ledgerTime)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ final case class EngineConfig(
contractKeyUniqueness: ContractKeyUniquenessMode = ContractKeyUniquenessMode.On,
forbidV0ContractId: Boolean = false,
requireSuffixedGlobalContractId: Boolean = false,
limits: interpretation.Limits = interpretation.Limits.Lenient,
) {

private[lf] def getCompilerConfig: speedy.Compiler.Config =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,44 @@ private[lf] object Pretty {
case ContractIdInContractKey(key) =>
text("Contract IDs are not supported in contract keys:") &
prettyContractId(key.cids.head)
case ValueExceedsMaxNesting =>
text(s"Value exceeds maximum nesting value of 100")
case Limit(error) =>
error match {
case Limit.ValueNesting(limit) =>
text(s"Value exceeds maximum nesting value of $limit")
case Limit.ContractSignatories(cid @ _, templateId, arg @ _, signatories, limit) =>
text(
s"Create Fetch or exercise a Contract of type $templateId with ${signatories.size} signatories but the limit is $limit"
)
case Limit.ContractObservers(cid @ _, templateId, arg @ _, observers, limit) =>
text(
s"Create Fetch or exercise a Contract of type $templateId ${observers.size} observes but the limit is $limit"
)
case Limit.ChoiceControllers(
cid @ _,
templateId,
choiceName,
arg @ _,
controllers,
limit,
) =>
text(
s"Exercise the choice $templateId:$choiceName with ${controllers.size} controllers but the limit is $limit"
)
case Limit.ChoiceObservers(
cid @ _,
templateId,
choiceName,
arg @ _,
observers,
limit,
) =>
text(
s"Exercise the choice $templateId:$choiceName with ${observers.size} observers but the limit is $limit"
)
case Limit.TransactionInputContracts(limit) =>
text(s"Transaction exceeds maximum input contract number of $limit")
}

case ChoiceGuardFailed(cid, templateId, choiceName, byInterface) => (
text(s"Choice guard failed for") & prettyTypeConName(templateId) &
text(s"contract") & prettyContractId(cid) &
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -961,7 +961,7 @@ private[lf] object SBuiltin {
byInterface = byInterface,
)

machine.addLocalContract(coid, templateId, createArg, sigs, obs, mbKey)
onLedger.addLocalContract(coid, templateId, createArg, sigs, obs, mbKey)
onLedger.ptx = newPtx
checkAborted(onLedger.ptx)
machine.returnValue = SContractId(coid)
Expand Down Expand Up @@ -1002,8 +1002,9 @@ private[lf] object SBuiltin {
val sigs = cached.signatories
val templateObservers = cached.observers
val ctrls = extractParties(NameOf.qualifiedNameOfCurrentFunc, args.get(2))
val choiceObservers = extractParties(NameOf.qualifiedNameOfCurrentFunc, args.get(3))

onLedger.enforceChoiceControllersLimit(ctrls, coid, templateId, choiceId, chosenValue)
val obsrs = extractParties(NameOf.qualifiedNameOfCurrentFunc, args.get(3))
onLedger.enforceChoiceObserversLimit(obsrs, coid, templateId, choiceId, chosenValue)
val mbKey = cached.key
val auth = machine.auth

Expand All @@ -1018,7 +1019,7 @@ private[lf] object SBuiltin {
actingParties = ctrls,
signatories = sigs,
stakeholders = sigs union templateObservers,
choiceObservers = choiceObservers,
choiceObservers = obsrs,
mbKey = mbKey,
byKey = byKey,
chosenValue = chosenValue,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@ sealed trait SValue {

def go(v: SValue, maxNesting: Int = V.MAXIMUM_NESTING): V = {
if (maxNesting < 0)
throw SError.SErrorDamlException(interpretation.Error.ValueExceedsMaxNesting)
throw SError.SErrorDamlException(
interpretation.Error.Limit(interpretation.Error.Limit.ValueNesting(V.MAXIMUM_NESTING))
)
val nextMaxNesting = maxNesting - 1
v match {
case SInt64(x) => V.ValueInt64(x)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import java.util

import com.daml.lf.data.Ref._
import com.daml.lf.data.{FrontStack, ImmArray, Ref, Time}
import com.daml.lf.interpretation.{Error => IError}
import com.daml.lf.language.Ast._
import com.daml.lf.language.{LookupError, Util => AstUtil}
import com.daml.lf.ledger.Authorize
Expand Down Expand Up @@ -109,6 +110,10 @@ private[lf] object Speedy {
private[lf] val stakeholders: Set[Party] = signatories union observers;
}

private[this] def enforceLimit(actual: Int, limit: Int, error: Int => IError.Limit.Error) =
if (actual > limit)
throw SError.SErrorDamlException(IError.Limit(error(limit)))

private[lf] final case class OnLedger(
val validating: Boolean,
val contractKeyUniqueness: ContractKeyUniquenessMode,
Expand All @@ -124,6 +129,8 @@ private[lf] object Speedy {
var dependsOnTime: Boolean,
// global contract discriminators, that are discriminators from contract created in previous transactions
var cachedContracts: Map[V.ContractId, CachedContract],
var numInputContracts: Int,
val limits: interpretation.Limits,
) extends LedgerMode {
private[lf] val visibleToStakeholders: Set[Party] => SVisibleToStakeholders =
if (validating) { _ => SVisibleToStakeholders.Visible }
Expand All @@ -134,6 +141,83 @@ private[lf] object Speedy {
private[lf] def ptxInternal: PartialTransaction = ptx //deprecated
private[lf] def incompleteTransaction: IncompleteTransaction = ptx.finishIncomplete

private[this] def updateCachedContracts(coid: V.ContractId, contract: CachedContract): Unit = {
enforceLimit(
contract.signatories.size,
limits.contractSignatories,
IError.Limit
.ContractSignatories(
coid,
contract.templateId,
contract.value.toUnnormalizedValue,
contract.signatories,
_,
),
)
enforceLimit(
contract.observers.size,
limits.contractObservers,
IError.Limit
.ContractObservers(
coid,
contract.templateId,
contract.value.toUnnormalizedValue,
contract.observers,
_,
),
)
cachedContracts = cachedContracts.updated(coid, contract)
}

private[speedy] def addLocalContract(
coid: V.ContractId,
templateId: Ref.TypeConName,
value: SValue,
signatories: Set[Party],
observers: Set[Party],
key: Option[Node.KeyWithMaintainers],
): Unit =
updateCachedContracts(
coid,
CachedContract(templateId, value, signatories, observers, key),
)

private[speedy] def addGlobalContract(coid: V.ContractId, contract: CachedContract): Unit = {
numInputContracts += 1
enforceLimit(
numInputContracts,
limits.transactionInputContracts,
IError.Limit.TransactionInputContracts,
)
updateCachedContracts(coid, contract)
}

private[speedy] def enforceChoiceControllersLimit(
controllers: Set[Party],
cid: V.ContractId,
templateId: TypeConName,
choiceName: ChoiceName,
arg: V,
): Unit =
enforceLimit(
controllers.size,
limits.choiceControllers,
IError.Limit.ChoiceControllers(cid, templateId, choiceName, arg, controllers, _),
)

private[speedy] def enforceChoiceObserversLimit(
observers: Set[Party],
cid: V.ContractId,
templateId: TypeConName,
choiceName: ChoiceName,
arg: V,
): Unit =
enforceLimit(
observers.size,
limits.choiceObservers,
IError.Limit.ChoiceObservers(cid, templateId, choiceName, arg, observers, _),
)

}

private[lf] final case object OffLedger extends LedgerMode
Expand Down Expand Up @@ -349,21 +433,6 @@ private[lf] object Speedy {

private[lf] def auth: Authorize = Authorize(this.contextActors)

def addLocalContract(
coid: V.ContractId,
templateId: Ref.TypeConName,
arg: SValue,
signatories: Set[Party],
observers: Set[Party],
key: Option[Node.KeyWithMaintainers],
) =
withOnLedger("addLocalContract") { onLedger =>
onLedger.cachedContracts = onLedger.cachedContracts.updated(
coid,
CachedContract(templateId, arg, signatories, observers, key),
)
}

/** Reuse an existing speedy machine to evaluate a new expression.
* Do not use if the machine is partway though an existing evaluation.
* i.e. run() has returned an `SResult` requiring a callback.
Expand Down Expand Up @@ -765,6 +834,7 @@ private[lf] object Speedy {
warningLog: WarningLog = newWarningLog,
contractKeyUniqueness: ContractKeyUniquenessMode = ContractKeyUniquenessMode.On,
commitLocation: Option[Location] = None,
limits: interpretation.Limits = interpretation.Limits.Lenient,
): Machine = {
val pkg2TxVersion =
compiledPackages.interface.packageLanguageVersion.andThen(
Expand Down Expand Up @@ -794,7 +864,9 @@ private[lf] object Speedy {
commitLocation = commitLocation,
dependsOnTime = false,
cachedContracts = Map.empty,
numInputContracts = 0,
contractKeyUniqueness = contractKeyUniqueness,
limits = limits,
),
traceLog = traceLog,
warningLog = warningLog,
Expand All @@ -812,10 +884,11 @@ private[lf] object Speedy {
compiledPackages: CompiledPackages,
transactionSeed: crypto.Hash,
updateE: Expr,
committer: Party,
committers: Set[Party],
limits: interpretation.Limits = interpretation.Limits.Lenient,
): Machine = {
val updateSE: SExpr = compiledPackages.compiler.unsafeCompile(updateE)
fromUpdateSExpr(compiledPackages, transactionSeed, updateSE, committer)
fromUpdateSExpr(compiledPackages, transactionSeed, updateSE, committers, limits)
}

@throws[PackageNotFound]
Expand All @@ -825,16 +898,18 @@ private[lf] object Speedy {
compiledPackages: CompiledPackages,
transactionSeed: crypto.Hash,
updateSE: SExpr,
committer: Party,
committers: Set[Party],
limits: interpretation.Limits = interpretation.Limits.Lenient,
traceLog: TraceLog = newTraceLog,
): Machine = {
Machine(
compiledPackages = compiledPackages,
submissionTime = Time.Timestamp.MinValue,
initialSeeding = InitialSeeding.TransactionSeed(transactionSeed),
expr = SEApp(updateSE, Array(SEValue.Token)),
committers = Set(committer),
committers = committers,
readAs = Set.empty,
limits = limits,
traceLog = traceLog,
)
}
Expand Down Expand Up @@ -1283,7 +1358,7 @@ private[lf] object Speedy {
machine.withOnLedger("KCacheContract") { onLedger =>
val cached = SBuiltin.extractCachedContract(onLedger, templateId, sv)
machine.checkContractVisibility(onLedger, cid, cached);
onLedger.cachedContracts = onLedger.cachedContracts.updated(cid, cached)
onLedger.addGlobalContract(cid, cached)
machine.returnValue = cached.value
}
}
Expand Down Expand Up @@ -1342,14 +1417,7 @@ private[lf] object Speedy {
byInterface: Option[TypeConName],
) extends Kont {
def abort[E](): E =
throw SErrorDamlException(
interpretation.Error.ChoiceGuardFailed(
coid,
templateId,
choiceName,
byInterface,
)
)
throw SErrorDamlException(IError.ChoiceGuardFailed(coid, templateId, choiceName, byInterface))

def execute(v: SValue) = {
v match {
Expand Down Expand Up @@ -1408,7 +1476,7 @@ private[lf] object Speedy {
machine.env.clear()
machine.envBase = 0
throw SErrorDamlException(
interpretation.Error.UnhandledException(excep.ty, excep.value.toUnnormalizedValue)
IError.UnhandledException(excep.ty, excep.value.toUnnormalizedValue)
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class EvaluationOrderTest extends AnyWordSpec with Matchers {
val se = pkgs.compiler.unsafeCompile(e)
val traceLog = new TestTraceLog()
val res = Speedy.Machine
.fromUpdateSExpr(pkgs, seed, SEApp(se, args.map(SEValue(_))), party, traceLog)
.fromUpdateSExpr(pkgs, seed, SEApp(se, args.map(SEValue(_))), Set(party), traceLog = traceLog)
.run()
val msgs = traceLog.getMessages
(res, msgs)
Expand Down
Loading

0 comments on commit b02ed77

Please sign in to comment.