Skip to content

Commit f6ca0fa

Browse files
committed
added limit, checks and abortion reason
1 parent 3320b1b commit f6ca0fa

7 files changed

Lines changed: 169 additions & 18 deletions

File tree

console/network/src/consensus_heights.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ pub enum ConsensusVersion {
5757
/// V15: Introduces the record-existence check and `commit.*.raw` instruction variants.
5858
/// Increase the anchor time to 35.
5959
/// Moves the block's spend limit check to the finalize phase.
60+
/// Introduces block-wide deployment limits.
6061
V15 = 15,
6162
}
6263

console/network/src/lib.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,11 +149,21 @@ pub trait Network:
149149
const EXECUTION_STORAGE_PENALTY_THRESHOLD: u64 = 5000;
150150
/// The cost in microcredits per constraint for the deployment transaction.
151151
const SYNTHESIS_FEE_MULTIPLIER: u64 = 25; // 25 microcredits per constraint
152-
/// The maximum number of variables in a deployment.
152+
/// The maximum number of variables in a deployment. This limit was enforced at the transaction level up to
153+
/// consensus version V14 (inclusive). This corresponds to ~0.5 second single-threaded runtime at
154+
/// mainnet launch reference validator hardware.
153155
const MAX_DEPLOYMENT_VARIABLES: u64 = 1 << 21; // 2,097,152 variables
154-
/// The maximum number of constraints in a deployment.
156+
/// The maximum number of constraints in a deployment. This limit was enforced at the transaction level up to
157+
/// consensus version V14 (inclusive). This corresponds to ~0.5 second single-threaded runtime at mainnet
158+
/// launch reference validator hardware.
155159
const MAX_DEPLOYMENT_CONSTRAINTS: u64 = 1 << 21; // 2,097,152 constraints
156-
/// The maximum number of instances to verify in a batch proof.
160+
/// The maximum number of non-zero entries across all circuits of all deployments in a block. This limit is
161+
/// enforced starting at consensus version V15 and overrides the two per-transaction limits above.
162+
// TODO (Antonio) make sure this is correct
163+
// As an additional sanity check, the total number of constraints and variables of each individual
164+
// function is also limited to this bound (at >= V15).
165+
// TODO (Antonio) time estimation
166+
const MAX_DEPLOY_DENSITY_PER_PROPOSAL: u64 = 1 << 24; // 16,777,216 non-zero entries.
157167
const MAX_BATCH_PROOF_INSTANCES: usize = 128;
158168
/// The maximum number of microcredits that can be spent as a fee.
159169
const MAX_FEE: u64 = 1_000_000_000_000_000;

ledger/block/src/transaction/deployment/mod.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,51 @@ impl<N: Network> Deployment<N> {
293293
Ok(num_combined_constraints)
294294
}
295295

296+
/// Returns the total density of the function and record-translation circuits in this deployment.
297+
pub fn combined_density(&self) -> u64 {
298+
self.combined_function_density()
299+
.saturating_add(self.combined_translation_density())
300+
}
301+
302+
/// Returns the total density of the function circuits in this deployment.
303+
pub fn combined_function_density(&self) -> u64 {
304+
// Initialize the accumulator.
305+
let mut combined_density = 0u64;
306+
307+
// Iterate over the function verifying keys.
308+
for (_, (vk, _)) in self.function_verifying_keys() {
309+
let info = vk.circuit_info;
310+
311+
let function_density = (info.num_non_zero_a as u64)
312+
.saturating_add(info.num_non_zero_b as u64)
313+
.saturating_add(info.num_non_zero_c as u64);
314+
315+
combined_density = combined_density.saturating_add(function_density);
316+
}
317+
318+
combined_density
319+
}
320+
321+
/// Returns the total density of the record-translation circuits in this deployment.
322+
pub fn combined_translation_density(&self) -> u64 {
323+
// Initialize the accumulator.
324+
let mut combined_density = 0u64;
325+
326+
// Iterate over the translation verifying keys, if any.
327+
if let Some(record_vks) = self.translation_verifying_keys() {
328+
for (_, (vk, _)) in record_vks {
329+
let info = vk.circuit_info;
330+
let circuit_density = (info.num_non_zero_a as u64)
331+
.saturating_add(info.num_non_zero_b as u64)
332+
.saturating_add(info.num_non_zero_c as u64);
333+
334+
combined_density = combined_density.saturating_add(circuit_density);
335+
}
336+
}
337+
338+
combined_density
339+
}
340+
296341
/// Returns the deployment ID.
297342
pub fn to_deployment_id(&self) -> Result<Field<N>> {
298343
Ok(*Transaction::deployment_tree(self)?.root())

ledger/narwhal/batch-header/src/lib.rs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -351,17 +351,23 @@ mod tests {
351351

352352
#[test]
353353
fn test_max_synthesis_cost_below_batch_spend_limit() {
354-
fn max_synthesis_cost_valid<N: Network>() {
355-
let max_synthesis_cost = N::MAX_DEPLOYMENT_VARIABLES.saturating_add(N::MAX_DEPLOYMENT_CONSTRAINTS)
356-
* N::SYNTHESIS_FEE_MULTIPLIER
357-
/ N::ARC_0005_COMPUTE_DISCOUNT;
354+
fn max_synthesis_cost_valid<N: Network>(v15_plus: bool) {
355+
let max_basis = if v15_plus {
356+
N::MAX_DEPLOY_DENSITY_PER_PROPOSAL
357+
} else {
358+
N::MAX_DEPLOYMENT_VARIABLES.saturating_add(N::MAX_DEPLOYMENT_CONSTRAINTS)
359+
};
360+
let max_synthesis_cost = max_basis * N::SYNTHESIS_FEE_MULTIPLIER / N::ARC_0005_COMPUTE_DISCOUNT;
358361
for (_, height) in N::CONSENSUS_VERSION_HEIGHTS().iter() {
359362
assert!(max_synthesis_cost < BatchHeader::<N>::batch_spend_limit(*height));
360363
}
361364
}
362365

363-
max_synthesis_cost_valid::<CanaryV0>();
364-
max_synthesis_cost_valid::<TestnetV0>();
365-
max_synthesis_cost_valid::<MainnetV0>();
366+
max_synthesis_cost_valid::<CanaryV0>(false);
367+
max_synthesis_cost_valid::<TestnetV0>(false);
368+
max_synthesis_cost_valid::<MainnetV0>(false);
369+
max_synthesis_cost_valid::<CanaryV0>(true);
370+
max_synthesis_cost_valid::<TestnetV0>(true);
371+
max_synthesis_cost_valid::<MainnetV0>(true);
366372
}
367373
}

synthesizer/process/src/cost.rs

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ pub fn deployment_cost<N: Network>(
4343
deployment: &Deployment<N>,
4444
consensus_version: ConsensusVersion,
4545
) -> Result<(MinimumCost, DeployCostDetails)> {
46-
if consensus_version >= ConsensusVersion::V10 {
46+
if consensus_version >= ConsensusVersion::V15 {
47+
deployment_cost_v3(process, deployment)
48+
} else if consensus_version >= ConsensusVersion::V10 {
4749
deployment_cost_v2(process, deployment)
4850
} else {
4951
deployment_cost_v1(process, deployment)
@@ -201,6 +203,66 @@ pub fn execute_compute_cost_in_microcredits(
201203
}
202204
}
203205

206+
/// Returns the *minimum* cost in microcredits to publish the given deployment using at `ConsensusVersion::V15` or later.
207+
// This function only differs from `deployment_cost_v2` in that it replaces the factor (`num_combined_variables` + `num_combined_constraints`)
208+
// of the synthesis cost by the deployment's combined density.
209+
pub fn deployment_cost_v3<N: Network>(
210+
process: &Process<N>,
211+
deployment: &Deployment<N>,
212+
) -> Result<(MinimumCost, DeployCostDetails)> {
213+
// Determine the number of bytes in the deployment.
214+
let size_in_bytes = deployment.size_in_bytes()?;
215+
// Retrieve the program ID.
216+
let program_id = deployment.program_id();
217+
// Determine the number of characters in the program ID.
218+
let num_characters = u32::try_from(program_id.name().to_string().len())?;
219+
// Compute the combined density of all circuits in the deployment.
220+
let combined_density = deployment.combined_density();
221+
222+
// Compute the storage cost in microcredits.
223+
let storage_cost = size_in_bytes
224+
.checked_mul(N::DEPLOYMENT_FEE_MULTIPLIER)
225+
.ok_or(anyhow!("The storage cost computation overflowed for a deployment"))?;
226+
227+
// Compute the synthesis cost in microcredits.
228+
let synthesis_cost = combined_density * N::SYNTHESIS_FEE_MULTIPLIER
229+
/ N::ARC_0005_COMPUTE_DISCOUNT;
230+
231+
// Compute a Stack for the deployment.
232+
let stack = Stack::new(process, deployment.program())?;
233+
234+
// Compute the constructor cost in microcredits.
235+
let constructor_cost = constructor_cost_in_microcredits_v2(&stack)?;
236+
237+
// Check that the functions are valid.
238+
for function in deployment.program().functions().values() {
239+
// Get the finalize cost.
240+
let finalize_cost = minimum_cost_in_microcredits_v3(&stack, function.name())?;
241+
// Check that the finalize cost does not exceed the maximum.
242+
ensure!(
243+
finalize_cost <= N::TRANSACTION_SPEND_LIMIT[1].1,
244+
"Finalize block '{}' has a cost '{finalize_cost}' which exceeds the transaction spend limit '{}'",
245+
function.name(),
246+
N::TRANSACTION_SPEND_LIMIT[1].1
247+
);
248+
}
249+
250+
// Compute the namespace cost in microcredits: 10^(10 - num_characters) * 1e6
251+
let namespace_cost = 10u64
252+
.checked_pow(10u32.saturating_sub(num_characters))
253+
.ok_or(anyhow!("The namespace cost computation overflowed for a deployment"))?
254+
.saturating_mul(1_000_000); // 1 microcredit = 1e-6 credits.
255+
256+
// Compute the minimum cost in microcredits.
257+
let minimum_cost = storage_cost
258+
.checked_add(synthesis_cost)
259+
.and_then(|x| x.checked_add(constructor_cost))
260+
.and_then(|x| x.checked_add(namespace_cost))
261+
.ok_or(anyhow!("The total cost computation overflowed for a deployment"))?;
262+
263+
Ok((minimum_cost, (storage_cost, synthesis_cost, constructor_cost, namespace_cost)))
264+
}
265+
204266
/// Returns the *minimum* cost in microcredits to publish the given deployment using the ARC_0005_COMPUTE_DISCOUNT.
205267
pub fn deployment_cost_v2<N: Network>(
206268
process: &Process<N>,

synthesizer/process/src/stack/deploy.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ impl<N: Network> Stack<N> {
8787
#[inline]
8888
pub fn verify_deployment<A: circuit::Aleo<Network = N>, R: Rng + CryptoRng>(
8989
&self,
90-
_consensus_version: ConsensusVersion,
90+
consensus_version: ConsensusVersion,
9191
deployment: &Deployment<N>,
9292
rng: &mut R,
9393
) -> Result<()> {
@@ -120,10 +120,12 @@ impl<N: Network> Stack<N> {
120120
// Get the program ID.
121121
let program_id = self.program.id();
122122

123-
// Check that the number of combined variables does not exceed the deployment limit.
124-
ensure!(deployment.num_combined_variables()? <= N::MAX_DEPLOYMENT_VARIABLES);
125-
// Check that the number of combined constraints does not exceed the deployment limit.
126-
ensure!(deployment.num_combined_constraints()? <= N::MAX_DEPLOYMENT_CONSTRAINTS);
123+
if consensus_version >= ConsensusVersion::V15 {
124+
// Check that the number of combined variables does not exceed the deployment limit.
125+
ensure!(deployment.num_combined_variables()? <= N::MAX_DEPLOYMENT_VARIABLES);
126+
// Check that the number of combined constraints does not exceed the deployment limit.
127+
ensure!(deployment.num_combined_constraints()? <= N::MAX_DEPLOYMENT_CONSTRAINTS);
128+
}
127129

128130
// Construct the call stacks and assignments used to verify the certificates.
129131
let mut call_stacks = Vec::with_capacity(deployment.function_verifying_keys().len());

synthesizer/src/vm/finalize.rs

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,7 @@ impl<N: Network, C: ConsensusStorage<N>> VM<N, C> {
428428
block_spend,
429429
transaction_spend_limit,
430430
block_spend_limit,
431+
None,
431432
consensus_version,
432433
) {
433434
PrepareSpeculateResult::Abort(abort_reason) => {
@@ -957,6 +958,7 @@ impl<N: Network, C: ConsensusStorage<N>> VM<N, C> {
957958
block_spend: u64,
958959
transaction_spend_limit: u64,
959960
block_spend_limit: Option<u64>,
961+
block_combined_density: Option<u64>,
960962
consensus_version: ConsensusVersion,
961963
) -> PrepareSpeculateResult {
962964
// Ensure that the transaction is not a fee transaction.
@@ -1028,10 +1030,10 @@ impl<N: Network, C: ConsensusStorage<N>> VM<N, C> {
10281030
}
10291031
}
10301032

1031-
// Before V15, we return without tracking any compute spend.
1033+
// Before V15, we return without tracking any compute spend and checking deployment limits.
10321034
if consensus_version < ConsensusVersion::V15 {
10331035
PrepareSpeculateResult::Finalize(0)
1034-
// If the consensus version is >= V15, ensure that the transaction is not exceeding spend limits.
1036+
// If the consensus version is >= V15, ensure that the transaction is not exceeding spend or deployment limits.
10351037
} else {
10361038
// Compute microcredit spend from deployment or execution cost details.
10371039
let compute_spend = match transaction {
@@ -1068,6 +1070,18 @@ impl<N: Network, C: ConsensusStorage<N>> VM<N, C> {
10681070
));
10691071
}
10701072
}
1073+
// If we are keeping track of block-wide circuit density and this transaction contains a deployment, make sure its
1074+
// density does not make the running total exceed the limit.
1075+
if let Some(combined_density) = block_combined_density && let Transaction::Deploy(_, _, _, deployment, _) = transaction {
1076+
if combined_density.saturating_add(deployment.combined_density()) > N::MAX_DEPLOY_DENSITY_PER_PROPOSAL {
1077+
return PrepareSpeculateResult::Abort(format!(
1078+
"Deployment density '{}' added to current accumulated block-wide density '{}' exceeds the limit '{}'",
1079+
deployment.combined_density(),
1080+
combined_density,
1081+
N::MAX_DEPLOY_DENSITY_PER_PROPOSAL
1082+
));
1083+
}
1084+
}
10711085

10721086
PrepareSpeculateResult::Finalize(compute_spend)
10731087
}
@@ -1100,6 +1114,8 @@ impl<N: Network, C: ConsensusStorage<N>> VM<N, C> {
11001114
consensus_config_value_by_version!(N, TRANSACTION_SPEND_LIMIT, consensus_version).unwrap();
11011115
// Determine the block spend limit.
11021116
let block_spend_limit = state.block_spend_limit();
1117+
// Initialize a block-wide total of the combined density of all circuits in all deployments.
1118+
let mut block_combined_density = 0u64;
11031119

11041120
// Abort duplicate, overspending, invalid, or disallowed transactions before verification.
11051121
for transaction in transactions.iter() {
@@ -1109,6 +1125,7 @@ impl<N: Network, C: ConsensusStorage<N>> VM<N, C> {
11091125
block_spend,
11101126
transaction_spend_limit,
11111127
block_spend_limit,
1128+
Some(block_combined_density),
11121129
consensus_version,
11131130
) {
11141131
PrepareSpeculateResult::Abort(abort_reason) => {
@@ -1119,6 +1136,14 @@ impl<N: Network, C: ConsensusStorage<N>> VM<N, C> {
11191136
if consensus_version >= ConsensusVersion::V15 {
11201137
// Track the compute_spend used so far.
11211138
block_spend = block_spend.saturating_add(compute_spend);
1139+
1140+
// If the transaction contains a deployment, add its combined density to the running total.
1141+
match transaction {
1142+
Transaction::Deploy(_, _, _, deployment, _) => {
1143+
block_combined_density = block_combined_density.saturating_add(deployment.combined_density());
1144+
},
1145+
_ => {}
1146+
}
11221147
}
11231148
// Track the accepted transaction details.
11241149
candidate_transaction_details.record_accepted_transaction(transaction);

0 commit comments

Comments
 (0)