-
Notifications
You must be signed in to change notification settings - Fork 112
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
Sequencing events #274
Sequencing events #274
Changes from 25 commits
06ca649
b6b4e22
56ed414
3a35ed6
61cbf2b
829c0d4
761f34b
a4a1691
1dc8eb2
b004963
3580c8a
ddacc84
a46489a
7c17b28
bd8d31b
39e4ec3
515e672
6561480
fc4dcf7
528fa94
8e725af
9075114
f9d34cc
d0ecb1d
3260855
60882d7
925a57f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
import { | ||
Field, | ||
state, | ||
State, | ||
method, | ||
PrivateKey, | ||
SmartContract, | ||
Experimental, | ||
Mina, | ||
Party, | ||
isReady, | ||
Permissions, | ||
} from 'snarkyjs'; | ||
|
||
await isReady; | ||
|
||
const INCREMENT = Field.one; | ||
|
||
class CounterZkapp extends SmartContract { | ||
// the "reducer" field describes a type of action that we can dispatch, and reduce later | ||
reducer = Experimental.Reducer({ actionType: Field }); | ||
|
||
// on-chain version of our state. it will typically lag behind the | ||
// version that's implicitly represented by the list of actions | ||
@state(Field) counter = State<Field>(); | ||
// helper field to store the point in the action history that our on-chain state is at | ||
@state(Field) actionsHash = State<Field>(); | ||
|
||
@method incrementCounter() { | ||
this.reducer.dispatch(INCREMENT); | ||
} | ||
|
||
@method rollupIncrements() { | ||
// get previous counter & actions hash, assert that they're the same as on-chain values | ||
let counter = this.counter.get(); | ||
this.counter.assertEquals(counter); | ||
let actionsHash = this.actionsHash.get(); | ||
this.actionsHash.assertEquals(actionsHash); | ||
|
||
// compute the new counter and hash from pending actions | ||
// remark: it's not feasible to pass in the pending actions as method arguments, because they have dynamic size | ||
let { state: newCounter, actionsHash: newActionsHash } = | ||
this.reducer.reduce( | ||
pendingActions, | ||
// state type | ||
Field, | ||
// function that says how to apply an action | ||
(state: Field, _action: Field) => { | ||
return state.add(1); | ||
}, | ||
{ state: counter, actionsHash } | ||
); | ||
|
||
// update on-chain state | ||
this.counter.set(newCounter); | ||
this.actionsHash.set(newActionsHash); | ||
} | ||
} | ||
|
||
const doProofs = false; | ||
const initialCounter = Field.zero; | ||
|
||
// this is a data structure where we internally keep track of the pending actions | ||
// TODO: get these from a Mina node / the local blockchain | ||
// note: each entry in pendingActions is itself an array -- the list of actions dispatched by one method | ||
// this is the structure we need to do the hashing correctly | ||
let pendingActions: Field[][] = []; | ||
|
||
let Local = Mina.LocalBlockchain(); | ||
Mina.setActiveInstance(Local); | ||
|
||
// a test account that pays all the fees, and puts additional funds into the zkapp | ||
let feePayer = Local.testAccounts[0].privateKey; | ||
|
||
// the zkapp account | ||
let zkappKey = PrivateKey.random(); | ||
let zkappAddress = zkappKey.toPublicKey(); | ||
|
||
let zkapp = new CounterZkapp(zkappAddress); | ||
if (doProofs) { | ||
console.log('compile'); | ||
await CounterZkapp.compile(zkappAddress); | ||
} | ||
|
||
console.log('deploy'); | ||
let tx = await Mina.transaction(feePayer, () => { | ||
Party.fundNewAccount(feePayer); | ||
zkapp.deploy({ zkappKey }); | ||
if (!doProofs) { | ||
zkapp.setPermissions({ | ||
...Permissions.default(), | ||
editState: Permissions.proofOrSignature(), | ||
editSequenceState: Permissions.proofOrSignature(), | ||
}); | ||
} | ||
zkapp.counter.set(initialCounter); | ||
zkapp.actionsHash.set(Experimental.Reducer.initialActionsHash); | ||
}); | ||
tx.send(); | ||
|
||
console.log('action 1'); | ||
tx = await Mina.transaction(feePayer, () => { | ||
zkapp.incrementCounter(); | ||
if (!doProofs) zkapp.sign(zkappKey); | ||
}); | ||
if (doProofs) await tx.prove(); | ||
tx.send(); | ||
// update internal state | ||
pendingActions.push([INCREMENT]); | ||
|
||
console.log('action 2'); | ||
tx = await Mina.transaction(feePayer, () => { | ||
zkapp.incrementCounter(); | ||
if (!doProofs) zkapp.sign(zkappKey); | ||
}); | ||
if (doProofs) await tx.prove(); | ||
tx.send(); | ||
// update internal state | ||
pendingActions.push([INCREMENT]); | ||
|
||
console.log('state (on-chain): ' + zkapp.counter.get()); | ||
console.log('pending actions:', JSON.stringify(pendingActions)); | ||
|
||
console.log('rollup transaction'); | ||
tx = await Mina.transaction(feePayer, () => { | ||
zkapp.rollupIncrements(); | ||
if (!doProofs) zkapp.sign(zkappKey); | ||
}); | ||
if (doProofs) await tx.prove(); | ||
tx.send(); | ||
// reset pending actions | ||
pendingActions = []; | ||
|
||
console.log('state (on-chain): ' + zkapp.counter.get()); | ||
console.log('pending actions:', JSON.stringify(pendingActions)); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { Bool, Circuit, isReady, shutdown, Int64 } from '../../dist/server'; | ||
|
||
describe('circuit', () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you add tests expecting failures if there are more than one true and zero true? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done. for some reason returning 0 for zero true seemed natural to me. but I can also throw in that case if you think that's better There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. good point yeah 0 is fine, but can you add a test for the zero case succeeding too? |
||
beforeAll(() => isReady); | ||
afterAll(() => setTimeout(shutdown, 0)); | ||
|
||
it('Circuit.switch picks the right value', () => { | ||
const x = Circuit.switch([Bool(false), Bool(true), Bool(false)], Int64, [ | ||
Int64.from(-1), | ||
Int64.from(-2), | ||
Int64.from(-3), | ||
]); | ||
expect(x.toString()).toBe('-2'); | ||
}); | ||
|
||
it('Circuit.switch throws when mask has >1 nonzero elements', () => { | ||
expect(() => | ||
Circuit.switch([Bool(true), Bool(true), Bool(false)], Int64, [ | ||
Int64.from(-1), | ||
Int64.from(-2), | ||
Int64.from(-3), | ||
]) | ||
).toThrow(/`mask` must have 0 or 1 true element, found 2/); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can do this later when we revisit this API to move it out of experimental, but shouldn't we have some way of reaching into the reducer inside the zkapp to pull the pendingActions out for testing? It's brittle to need to remember to push the actions.
I really like how this library handles testing reducers https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/teststore
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah I agree. I think this would be achieved if
reducer.getActions
-- which will fetch actions in the real network interaction -- gets them from memory in the LocalBlockchain case