Skip to content

Commit

Permalink
Merge pull request #620 from o1-labs/party-limit
Browse files Browse the repository at this point in the history
  • Loading branch information
Trivo25 committed Dec 2, 2022
2 parents 84b15fc + 837ca2f commit 0969069
Show file tree
Hide file tree
Showing 7 changed files with 250 additions and 3 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Type inference for Structs with instance methods https://github.com/o1-labs/snarkyjs/pull/567
- also fixes `Struct.fromJSON`

### Changed

- New option `enforceTransactionLimits` for `LocalBlockchain` (default value: `true`), to disable the enforcement of protocol transaction limits (maximum events, maximum sequence events and enforcing certain layout of `AccountUpdate`s depending on their authorization) https://github.com/o1-labs/snarkyjs/pull/620

## [0.7.3](https://github.com/o1-labs/snarkyjs/compare/5f20f496...d880bd6e)

### Fixed
Expand Down
10 changes: 8 additions & 2 deletions src/examples/zkapps/dex/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ import { expect } from 'expect';
await isReady;
let doProofs = false;

let Local = Mina.LocalBlockchain({ proofsEnabled: doProofs });
let Local = Mina.LocalBlockchain({
proofsEnabled: doProofs,
enforceTransactionLimits: false,
});
Mina.setActiveInstance(Local);
let accountFee = Mina.accountCreationFee();
let [{ privateKey: feePayerKey }] = Local.testAccounts;
Expand All @@ -37,7 +40,10 @@ await TokenContract.compile();
await main({ withVesting: false });

// swap out ledger so we can start fresh
Local = Mina.LocalBlockchain({ proofsEnabled: doProofs });
Local = Mina.LocalBlockchain({
proofsEnabled: doProofs,
enforceTransactionLimits: false,
});
Mina.setActiveInstance(Local);
[{ privateKey: feePayerKey }] = Local.testAccounts;
feePayerAddress = feePayerKey.toPublicKey();
Expand Down
1 change: 1 addition & 0 deletions src/examples/zkapps/voting/demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {

let Local = Mina.LocalBlockchain({
proofsEnabled: false,
enforceTransactionLimits: false,
});
Mina.setActiveInstance(Local);

Expand Down
2 changes: 2 additions & 0 deletions src/examples/zkapps/voting/deployContracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export async function deployContracts(
}> {
let Local = Mina.LocalBlockchain({
proofsEnabled,
enforceTransactionLimits: false,
});
Mina.setActiveInstance(Local);

Expand Down Expand Up @@ -119,6 +120,7 @@ export async function deployInvalidContracts(
}> {
let Local = Mina.LocalBlockchain({
proofsEnabled: false,
enforceTransactionLimits: false,
});
Mina.setActiveInstance(Local);

Expand Down
127 changes: 127 additions & 0 deletions src/lib/mina.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ export {
fetchEvents,
getActions,
FeePayerSpec,
// for internal testing only
filterGroups,
};
interface TransactionId {
wait(): Promise<void>;
Expand Down Expand Up @@ -180,6 +182,7 @@ function createTransaction(
let accountUpdates = currentTransaction.get().accountUpdates;
CallForest.addCallers(accountUpdates);
accountUpdates = CallForest.toFlatList(accountUpdates);

try {
// check that on-chain values weren't used without setting a precondition
for (let accountUpdate of accountUpdates) {
Expand Down Expand Up @@ -287,6 +290,7 @@ const defaultAccountCreationFee = 1_000_000_000;
function LocalBlockchain({
accountCreationFee = defaultAccountCreationFee as string | number,
proofsEnabled = true,
enforceTransactionLimits = true,
} = {}) {
const msPerSlot = 3 * 60 * 1000;
const startTime = new Date().valueOf();
Expand Down Expand Up @@ -377,6 +381,9 @@ function LocalBlockchain({
JSON.stringify(zkappCommandToJson(txn.transaction))
);

if (enforceTransactionLimits)
verifyTransactionLimits(txn.transaction.accountUpdates);

for (const update of txn.transaction.accountUpdates) {
let account = ledger.getAccount(
update.body.publicKey,
Expand Down Expand Up @@ -617,6 +624,8 @@ function Network(graphqlEndpoint: string): Mina {
async sendTransaction(txn: Transaction) {
txn.sign();

verifyTransactionLimits(txn.transaction.accountUpdates);

let [response, error] = await Fetch.sendZkapp(txn.toJSON());
let errors: any[] | undefined;
if (error === undefined) {
Expand Down Expand Up @@ -1039,3 +1048,121 @@ async function verifyAccountUpdate(
);
}
}

function verifyTransactionLimits(accountUpdates: AccountUpdate[]) {
// constants used to calculate cost of a transaction - originally defined in the genesis_constants file in the mina repo
const proofCost = 10.26;
const signedPairCost = 10.08;
const signedSingleCost = 9.14;
const costLimit = 69.45;

// constants that define the maximum number of events in one transaction
const maxSequenceEventElements = 16;
const maxEventElements = 16;

let eventElements = {
events: 0,
sequence: 0,
};

let authTypes = filterGroups(
accountUpdates.map((update) => {
let json = update.toJSON();
eventElements.events += update.body.events.data.length;
eventElements.sequence += update.body.sequenceEvents.data.length;
return json.body.authorizationKind;
})
);
/*
np := proof
n2 := signedPair
n1 := signedSingle
10.26*np + 10.08*n2 + 9.14*n1 < 69.45
formula used to calculate how expensive a zkapp transaction is
*/

let totalTimeRequired =
proofCost * authTypes['proof'] +
signedPairCost * authTypes['signedPair'] +
signedSingleCost * authTypes['signedSingle'];

let isWithinCostLimit = totalTimeRequired < costLimit;

let isWithinEventsLimit = eventElements['events'] <= maxEventElements;
let isWithinSequenceEventsLimit =
eventElements['sequence'] <= maxSequenceEventElements;

let error = '';

if (!isWithinCostLimit) {
// TODO: we should add a link to the docs explaining the reasoning behind it once we have such an explainer
error += `Error: The transaction is too expensive, try reducing the number of AccountUpdates that are attached to the transaction.
Each transaction needs to be processed by the snark workers on the network.
Certain layouts of AccountUpdates require more proving time than others, and therefore are too expensive.
${JSON.stringify(authTypes)}
\n\n`;
}

if (!isWithinEventsLimit) {
error += `Error: The AccountUpdates in your transaction are trying to emit too many events. The maximum allowed amount of events is ${maxEventElements}, but you tried to emit ${eventElements['events']}.\n\n`;
}

if (!isWithinSequenceEventsLimit) {
error += `Error: The AccountUpdates in your transaction are trying to emit too many actions. The maximum allowed amount of actions is ${maxSequenceEventElements}, but you tried to emit ${eventElements['sequence']}.\n\n`;
}

if (error) throw Error('Error during transaction sending:\n\n' + error);
}

let S = 'Signature';
let N = 'None_given';
let P = 'Proof';

const isPair = (pair: string) =>
pair == S + N || pair == N + S || pair == S + S || pair == N + N;

function filterPairs(xs: string[]): {
xs: string[];
pairs: number;
} {
if (xs.length <= 1)
return {
xs,
pairs: 0,
};
if (isPair(xs[0].concat(xs[1]))) {
let rec = filterPairs(xs.slice(2));
return {
xs: rec.xs,
pairs: rec.pairs + 1,
};
} else {
let rec = filterPairs(xs.slice(1));
return {
xs: [xs[0]].concat(rec.xs),
pairs: rec.pairs,
};
}
}

function filterGroups(xs: string[]) {
let pairs = filterPairs(xs);
xs = pairs.xs;

let singleCount = 0;
let proofCount = 0;

xs.forEach((t) => {
if (t == P) proofCount++;
else singleCount++;
});

return {
signedPair: pairs.pairs,
signedSingle: singleCount,
proof: proofCount,
};
}
104 changes: 104 additions & 0 deletions src/lib/mina.unit-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { filterGroups } from './mina.js';
import { expect } from 'expect';
import { shutdown } from '../index.js';

let S = 'Signature';
let N = 'None_given';
let P = 'Proof';

expect(filterGroups([S, S, S, S, S, S])).toEqual({
proof: 0,
signedPair: 3,
signedSingle: 0,
});

expect(filterGroups([N, N, N, N, N, N])).toEqual({
proof: 0,
signedPair: 3,
signedSingle: 0,
});

expect(filterGroups([N, S, S, N, N, S])).toEqual({
proof: 0,
signedPair: 3,
signedSingle: 0,
});

expect(filterGroups([S, P, S, S, S, S])).toEqual({
proof: 1,
signedPair: 2,
signedSingle: 1,
});

expect(filterGroups([N, P, N, P, N, P])).toEqual({
proof: 3,
signedPair: 0,
signedSingle: 3,
});

expect(filterGroups([N, P])).toEqual({
proof: 1,
signedPair: 0,
signedSingle: 1,
});

expect(filterGroups([N, S])).toEqual({
proof: 0,
signedPair: 1,
signedSingle: 0,
});

expect(filterGroups([P, P])).toEqual({
proof: 2,
signedPair: 0,
signedSingle: 0,
});

expect(filterGroups([P, P, S, N, N])).toEqual({
proof: 2,
signedPair: 1,
signedSingle: 1,
});

expect(filterGroups([P])).toEqual({
proof: 1,
signedPair: 0,
signedSingle: 0,
});

expect(filterGroups([S])).toEqual({
proof: 0,
signedPair: 0,
signedSingle: 1,
});

expect(filterGroups([N])).toEqual({
proof: 0,
signedPair: 0,
signedSingle: 1,
});

expect(filterGroups([N, N])).toEqual({
proof: 0,
signedPair: 1,
signedSingle: 0,
});

expect(filterGroups([N, S])).toEqual({
proof: 0,
signedPair: 1,
signedSingle: 0,
});

expect(filterGroups([S, N])).toEqual({
proof: 0,
signedPair: 1,
signedSingle: 0,
});

expect(filterGroups([S, S])).toEqual({
proof: 0,
signedPair: 1,
signedSingle: 0,
});
shutdown();
5 changes: 4 additions & 1 deletion src/lib/token.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,10 @@ let zkAppCAddress: PublicKey;
let zkAppC: ZkAppC;

function setupAccounts() {
let Local = Mina.LocalBlockchain({ proofsEnabled: true });
let Local = Mina.LocalBlockchain({
proofsEnabled: true,
enforceTransactionLimits: false,
});
Mina.setActiveInstance(Local);
feePayerKey = Local.testAccounts[0].privateKey;

Expand Down

0 comments on commit 0969069

Please sign in to comment.