Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use MerkleList for reducer #1577

Merged
merged 44 commits into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
11ee09b
return MerkleList, temp
Trivo25 Apr 14, 2024
9498a7b
fix example
Trivo25 Apr 14, 2024
1b92f0a
temp working
Trivo25 Apr 14, 2024
f3f8306
add row counter
Trivo25 Apr 14, 2024
90d28b0
reduce reducer work
mitschabaude Apr 17, 2024
1fec588
delete later: constraints investigation file
mitschabaude Apr 17, 2024
445cf27
fix
mitschabaude Apr 17, 2024
0b1c446
this seems enough
mitschabaude Apr 17, 2024
66f6fdc
change from hacky next override to previous()
mitschabaude Apr 17, 2024
f4c2c40
comment
mitschabaude Apr 17, 2024
afa0434
unsafe iteration
mitschabaude Apr 17, 2024
8c1c2dc
even more unsafe actions iteration
mitschabaude Apr 17, 2024
7da81a8
better dummy protection
mitschabaude Apr 17, 2024
6d3a4c0
i think this is close to equivalent to the old logic
mitschabaude Apr 17, 2024
5122aff
delete testing file
mitschabaude Apr 17, 2024
1eb08df
revert moving isDummy out of the circuit, this isn't safe here
mitschabaude Apr 17, 2024
46c6c1d
minor
mitschabaude Apr 17, 2024
3e8cead
better =1 case
mitschabaude Apr 17, 2024
5e12b8a
api tweak and docs
mitschabaude Apr 17, 2024
e66c061
Merge branch 'breaking-reducer' into reducer-v1-prep
mitschabaude Apr 18, 2024
ca5c26c
fix merge conflict and `next()`
mitschabaude Apr 18, 2024
27fe476
reverse unsafe stepping functions
mitschabaude Apr 18, 2024
901a3de
reverse reduce impl
mitschabaude Apr 18, 2024
16d2d92
collapse prover blocks
mitschabaude Apr 18, 2024
f6d7a9e
minor
mitschabaude Apr 18, 2024
9c1ea70
Merge branch 'main' into reducer-v1-prep
mitschabaude Apr 18, 2024
da6da5d
fixup
mitschabaude Apr 18, 2024
172e96d
Merge branch 'breaking-reducer' into reducer-v1-prep
mitschabaude Apr 18, 2024
7722248
fix examples build and fetchActions input type
mitschabaude Apr 18, 2024
8d15d45
Merge branch 'breaking-reducer' into reducer-v1-prep
mitschabaude Apr 18, 2024
9503ad8
change reducer config names to be more precise
mitschabaude Apr 18, 2024
b4eb456
fix action order
mitschabaude Apr 18, 2024
a295c9c
fix merkle list element access
mitschabaude Apr 18, 2024
72c7dc2
fix foreign field comparison
mitschabaude Apr 18, 2024
e415d11
fix and tweak low-level merkle action examples
mitschabaude Apr 18, 2024
4b2665b
fix reducer iteration order
mitschabaude Apr 18, 2024
9f2724c
fix merkle list out of range case
mitschabaude Apr 18, 2024
0deba4e
comment
mitschabaude Apr 18, 2024
65bee91
remove action state from reducer signature
mitschabaude Apr 18, 2024
f894b0e
reinterpret index based on direction
mitschabaude Apr 18, 2024
0af4208
dump vks
mitschabaude Apr 18, 2024
959c1e0
Update CHANGELOG.md
Trivo25 Apr 18, 2024
5477966
fix tests
Trivo25 Apr 18, 2024
af580c0
disable part of test
Trivo25 Apr 18, 2024
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Added `Poseidon.Unsafe.hashToGroup()` as a more efficient, non-deterministic version for advanced use cases
- A `Transaction`'s `prove` method no longer returns the proofs promise directly, but rather returns a `Transaction` promise, the resolved value of which contains a `proofs` prop. https://github.com/o1-labs/o1js/pull/1567
- The `Transaction` type now has two type params `Proven extends boolean` and `Signed extends boolean`, which are used to conditionally show/hide relevant state. https://github.com/o1-labs/o1js/pull/1567
- Improved functionality of `MerkleList` and `MerkleListIterator` for easier traversal of `MerkleList`s. https://github.com/o1-labs/o1js/pull/1562
- Simplified internal logic of reducer. https://github.com/o1-labs/o1js/pull/1577
- `contract.getActions()` now returns a `MerkleList`

### Added

Expand Down
13 changes: 6 additions & 7 deletions src/examples/zkapps/dex/dex-with-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,16 +261,16 @@ class DexTokenHolder extends SmartContract {
let l = Provable.witness(UInt64, (): UInt64 => {
let l = dex.totalSupply.get().toBigInt();
// dex.totalSupply.assertNothing();
for (let [action] of actions) {
l += action.dl.toBigInt();
for (let action of actions.data.get()) {
l += action.element.data.get()[0].element.dl.toBigInt();
}
return UInt64.from(l);
});

// get our token balance
let x = this.account.balance.getAndRequireEquals();

let redeemActionState = dex.reducer.forEach(
dex.reducer.forEach(
actions,
({ address, dl }) => {
// for every user that redeemed liquidity, we calculate the token output
Expand All @@ -286,19 +286,18 @@ class DexTokenHolder extends SmartContract {
l = l.sub(dl);
x = x.add(dx);
},
fromActionState,
{
maxTransactionsWithActions: DexTokenHolder.redeemActionBatchSize,
maxUpdatesWithActions: DexTokenHolder.redeemActionBatchSize,
// DEX contract doesn't allow setting preconditions from outside (= w/o proof)
skipActionStatePrecondition: true,
}
);

// update action state so these payments can't be triggered a 2nd time
this.redeemActionState.set(redeemActionState);
this.redeemActionState.set(actions.hash);

// precondition on the DEX contract, to prove we used the right actions & token supply
await dex.assertActionsAndSupply(redeemActionState, l);
await dex.assertActionsAndSupply(actions.hash, l);
}

// this works for both directions (in our case where both tokens use the same contract)
Expand Down
74 changes: 29 additions & 45 deletions src/examples/zkapps/reducer/actions-as-merkle-list-iterator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,8 @@
* a blueprint for processing actions in a custom and more explicit way.
*/
import {
AccountUpdate,
Bool,
Field,
MerkleList,
Mina,
Provable,
State,
state,
Reducer,
Expand All @@ -20,28 +16,6 @@ import {
assert,
} from 'o1js';

const { Actions } = AccountUpdate;

// in this example, an action is just a increment of type Field
// the actions within one account update are a Merkle list with a custom hash
const emptyHash = Actions.empty().hash;
const nextHash = (hash: Field, action: Field) =>
Actions.pushEvent({ hash, data: [] }, action.toFields()).hash;

class MerkleActions extends MerkleList.create(Field, nextHash, emptyHash) {}

// the "action state" / actions from many account updates is a Merkle list
// of the above Merkle list, with another custom hash
let emptyActionsHash = Actions.emptyActionState();
const nextActionsHash = (hash: Field, actions: MerkleActions) =>
Actions.updateSequenceState(hash, actions.hash);

class MerkleActionss extends MerkleList.create(
MerkleActions.provable,
nextActionsHash,
emptyActionsHash
) {}

// constants for our static-sized provable code
const MAX_UPDATES_WITH_ACTIONS = 100;
const MAX_ACTIONS_PER_UPDATE = 2;
Expand All @@ -60,32 +34,42 @@ class ActionsContract extends SmartContract {
this.reducer.dispatch(inc);
}

@method
async twoIncrements(inc1: Field, inc2: Field) {
this.reducer.dispatch(inc1);
this.reducer.dispatch(inc2);
}

@method
async accumulate() {
// get actions and, in a witness block, wrap them in a Merkle list of lists

// get all actions
let actionss = this.reducer.getActions();

let merkleActionss = Provable.witness(MerkleActionss.provable, () =>
MerkleActionss.from(actionss.map((as) => MerkleActions.from(as)))
);
let actions = this.reducer.getActions();

// prove that we know the correct action state
this.account.actionState.requireEquals(merkleActionss.hash);
this.account.actionState.requireEquals(actions.hash);

let counter = Field(0);

let iter = merkleActionss.startIterating();
let iter = actions.startIterating();
let lastAction = Field(0);

for (let i = 0; i < MAX_UPDATES_WITH_ACTIONS; i++) {
let merkleActions = iter.next();
let innerIter = merkleActions.startIterating();
for (let j = 0; j < MAX_ACTIONS_PER_UPDATE; j++) {
let action = innerIter.next();
counter = counter.add(action);

// we require that every action is greater than the previous one, except for dummy (0) actions
// this checks that actions are applied in the right order
assert(action.equals(0).or(action.greaterThan(lastAction)));
lastAction = action;
}
innerIter.assertAtEnd();
}
iter.assertAtEnd();

this.counter.set(this.counter.getAndRequireEquals().add(counter));
}
Expand All @@ -98,9 +82,9 @@ class ActionsContract extends SmartContract {
let Local = await Mina.LocalBlockchain({ proofsEnabled: false });
Mina.setActiveInstance(Local);

let [sender, zkappAddress] = Local.testAccounts;
let [sender, contractAddress] = Local.testAccounts;

let zkapp = new ActionsContract(zkappAddress);
let contract = new ActionsContract(contractAddress);

// deploy the contract

Expand All @@ -109,28 +93,28 @@ console.log(
`rows for ${MAX_UPDATES_WITH_ACTIONS} updates with actions`,
(await ActionsContract.analyzeMethods()).accumulate.rows
);
let deployTx = await Mina.transaction(sender, async () => zkapp.deploy());
await deployTx.sign([sender.key, zkappAddress.key]).send();
let deployTx = await Mina.transaction(sender, async () => contract.deploy());
await deployTx.sign([sender.key, contractAddress.key]).send();

// push some actions

let dispatchTx = await Mina.transaction(sender, async () => {
await zkapp.increment(Field(1));
await zkapp.increment(Field(3));
await zkapp.increment(Field(1));
await zkapp.increment(Field(9));
await zkapp.increment(Field(18));
await contract.increment(Field(1));
await contract.increment(Field(3));
await contract.increment(Field(5));
await contract.increment(Field(9));
await contract.twoIncrements(Field(18), Field(19));
});
await dispatchTx.prove();
await dispatchTx.sign([sender.key]).send();

assert(zkapp.reducer.getActions().length === 5);
assert(contract.reducer.getActions().data.get().length === 5);

// accumulate actions

Local.setProofsEnabled(true);
let accTx = await Mina.transaction(sender, () => zkapp.accumulate());
let accTx = await Mina.transaction(sender, () => contract.accumulate());
await accTx.prove();
await accTx.sign([sender.key]).send();

assert(zkapp.counter.get().toBigInt() === 32n);
assert(contract.counter.get().toBigInt() === 55n);
46 changes: 7 additions & 39 deletions src/examples/zkapps/reducer/actions-as-merkle-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,44 +6,19 @@
* a blueprint for processing actions in a custom and more explicit way.
*/
import {
AccountUpdate,
Bool,
Field,
MerkleList,
Mina,
Provable,
PublicKey,
Reducer,
SmartContract,
method,
assert,
} from 'o1js';

const { Actions } = AccountUpdate;

// in this example, an action is just a public key
type Action = PublicKey;
const Action = PublicKey;

// the actions within one account update are a Merkle list with a custom hash
const emptyHash = Actions.empty().hash;
const nextHash = (hash: Field, action: Action) =>
Actions.pushEvent({ hash, data: [] }, action.toFields()).hash;

class MerkleActions extends MerkleList.create(Action, nextHash, emptyHash) {}

// the "action state" / actions from many account updates is a Merkle list
// of the above Merkle list, with another custom hash
let emptyActionsHash = Actions.emptyActionState();
const nextActionsHash = (hash: Field, actions: MerkleActions) =>
Actions.updateSequenceState(hash, actions.hash);

class MerkleActionss extends MerkleList.create(
MerkleActions.provable,
nextActionsHash,
emptyActionsHash
) {}

// constants for our static-sized provable code
const MAX_UPDATES_WITH_ACTIONS = 100;
const MAX_ACTIONS_PER_UPDATE = 2;
Expand All @@ -68,33 +43,26 @@ class MerkleListReducing extends SmartContract {

@method
async assertContainsAddress(address: PublicKey) {
// get actions and, in a witness block, wrap them in a Merkle list of lists

// note: need to reverse here because `getActions()` returns the last pushed action last,
// but MerkleList.from() wants it to be first to match the natural iteration order
let actionss = this.reducer.getActions();

let merkleActionss = Provable.witness(MerkleActionss.provable, () =>
MerkleActionss.from(actionss.map((as) => MerkleActions.fromReverse(as)))
);
let actions = this.reducer.getActions();

// prove that we know the correct action state
this.account.actionState.requireEquals(merkleActionss.hash);
this.account.actionState.requireEquals(actions.hash);

// now our provable code to process the actions is very straight-forward
// our provable code to process actions in reverse order is very straight-forward
// (note: if we're past the actual sizes, `.pop()` returns a dummy Action -- in this case, the "empty" public key which is not equal to any real address)
let hasAddress = Bool(false);

for (let i = 0; i < MAX_UPDATES_WITH_ACTIONS; i++) {
let merkleActions = merkleActionss.pop();
let merkleActions = actions.pop();

for (let j = 0; j < MAX_ACTIONS_PER_UPDATE; j++) {
let action = merkleActions.pop();
hasAddress = hasAddress.or(action.equals(address));
}
}

assert(hasAddress);
assert(actions.isEmpty()); // we processed all actions
assert(hasAddress); // we found the address
}
}

Expand Down Expand Up @@ -132,7 +100,7 @@ let dispatchTx = await Mina.transaction(sender, async () => {
await dispatchTx.prove();
await dispatchTx.sign([sender.key]).send();

assert(contract.reducer.getActions().length === 5);
assert(contract.reducer.getActions().data.get().length === 5);

// check if the actions contain the `sender` address

Expand Down
9 changes: 3 additions & 6 deletions src/examples/zkapps/reducer/map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class StorageContract extends SmartContract {

let keyHash = Poseidon.hash(key.toFields());

let { state: optionValue } = this.reducer.reduce(
let optionValue = this.reducer.reduce(
pendingActions,
Option,
(state, action) => {
Expand All @@ -73,11 +73,8 @@ class StorageContract extends SmartContract {
value: Provable.if(currentMatch, action.value, state.value),
};
},
{
state: Option.empty(),
actionState: Reducer.initialActionState,
},
{ maxTransactionsWithActions: k }
Option.empty(),
{ maxUpdatesWithActions: k }
);

return optionValue;
Expand Down
35 changes: 19 additions & 16 deletions src/examples/zkapps/reducer/reducer-composite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,31 +40,29 @@ class Counter extends SmartContract {

@method async rollupIncrements() {
// get previous counter & actions hash, assert that they're the same as on-chain values
let counter = this.counter.get();
this.counter.requireEquals(counter);
let actionState = this.actionState.get();
this.actionState.requireEquals(actionState);
let counter = this.counter.getAndRequireEquals();
let actionState = this.actionState.getAndRequireEquals();

// compute the new counter and hash from pending actions
let pendingActions = this.reducer.getActions({
fromActionState: actionState,
});

let { state: newCounter, actionState: newActionState } =
this.reducer.reduce(
pendingActions,
// state type
Field,
// function that says how to apply an action
(state: Field, action: MaybeIncrement) => {
return Provable.if(action.isIncrement, state.add(1), state);
},
{ state: counter, actionState }
);
let newCounter = this.reducer.reduce(
pendingActions,
// state type
Field,
// function that says how to apply an action
(state: Field, action: MaybeIncrement) => {
return Provable.if(action.isIncrement, state.add(1), state);
},
counter,
{ maxUpdatesWithActions: 10 }
);

// update on-chain state
this.counter.set(newCounter);
this.actionState.set(newActionState);
this.actionState.set(pendingActions.hash);
}
}

Expand All @@ -88,6 +86,11 @@ if (doProofs) {
await Counter.compile();
}

console.log(
'rows: ',
(await Counter.analyzeMethods())['rollupIncrements'].rows
);

console.log('deploy');
let tx = await Mina.transaction(feePayer, async () => {
AccountUpdate.fundNewAccount(feePayer);
Expand Down
6 changes: 3 additions & 3 deletions src/examples/zkapps/voting/demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ await tx.sign([feePayer.key]).send();
*/
console.log(
'3 events?? ',
contracts.voterContract.reducer.getActions({}).length === 3
(await contracts.voterContract.reducer.fetchActions()).length === 3
);

/*
Expand Down Expand Up @@ -194,7 +194,7 @@ await tx.sign([feePayer.key]).send();
*/
console.log(
'2 events?? ',
contracts.candidateContract.reducer.getActions({}).length === 2
(await contracts.candidateContract.reducer.fetchActions()).length === 2
);

/*
Expand Down Expand Up @@ -270,7 +270,7 @@ vote(0n);

console.log(
'1 vote sequence event? ',
contracts.voting.reducer.getActions({}).length === 1
(await contracts.voting.reducer.fetchActions()).length === 1
);

/*
Expand Down
Loading
Loading