UNPKG

o1js

Version:

TypeScript framework for zk-SNARKs and zkApps

114 lines 7.1 kB
import { Field } from '../../../provable/wrapped.js'; import { Actions } from '../account-update.js'; import { provable } from '../../../provable/types/provable-derivers.js'; import { Provable } from '../../../provable/provable.js'; import * as Mina from '../mina.js'; import { MerkleList } from '../../../provable/merkle-list.js'; export { Reducer, getReducer }; const Reducer = Object.defineProperty(function (reducer) { // we lie about the return value here, and instead overwrite this.reducer with // a getter, so we can get access to `this` inside functions on this.reducer (see constructor) return reducer; }, 'initialActionState', { get: Actions.emptyActionState }); function getReducer(contract) { let reducer = (contract._ ??= {}).reducer; if (reducer === undefined) throw Error('You are trying to use a reducer without having declared its type.\n' + `Make sure to add a property \`reducer\` on ${contract.constructor.name}, for example: class ${contract.constructor.name} extends SmartContract { reducer = Reducer({ actionType: Field }); }`); return { dispatch(action) { let accountUpdate = contract.self; let canonical = Provable.toCanonical(reducer.actionType, action); let eventFields = reducer.actionType.toFields(canonical); accountUpdate.body.actions = Actions.pushEvent(accountUpdate.body.actions, eventFields); }, reduce(actionLists, stateType, reduce, state, { maxUpdatesWithActions = 32, maxActionsPerUpdate = 1, skipActionStatePrecondition = false, } = {}) { Provable.asProver(() => { if (actionLists.data.get().length > maxUpdatesWithActions) { throw Error(`reducer.reduce: Exceeded the maximum number of lists of actions, ${maxUpdatesWithActions}. Use the optional \`maxUpdatesWithActions\` argument to increase this number.`); } }); if (!skipActionStatePrecondition) { // the actionList.hash is the hash of all actions in that list, appended to the previous hash (the previous list of historical actions) // this must equal one of the action states as preconditions to build a chain to that we only use actions that were dispatched between the current on chain action state and the initialActionState contract.account.actionState.requireEquals(actionLists.hash); } const listIter = actionLists.startIterating(); for (let i = 0; i < maxUpdatesWithActions; i++) { let { element: merkleActions, isDummy } = listIter.Unsafe.next(); let actionIter = merkleActions.startIterating(); let newState = state; if (maxActionsPerUpdate === 1) { // special case with less work, because the only action is a dummy iff merkleActions is a dummy let action = Provable.witness(reducer.actionType, () => actionIter.data.get()[0]?.element ?? actionIter.innerProvable.empty()); let emptyHash = actionIter.Constructor.emptyHash; let finalHash = actionIter.nextHash(emptyHash, action); finalHash = Provable.if(isDummy, emptyHash, finalHash); // note: this asserts nothing in the isDummy case, because `actionIter.hash` is not well-defined // but it doesn't matter because we're also skipping all state and action state updates in that case actionIter.hash.assertEquals(finalHash); newState = reduce(newState, action); } else { for (let j = 0; j < maxActionsPerUpdate; j++) { let { element: action, isDummy } = actionIter.Unsafe.next(); newState = Provable.if(isDummy, stateType, newState, reduce(newState, action)); } // note: this asserts nothing about the iterated actions if `MerkleActions` is a dummy // which doesn't matter because we're also skipping all state and action state updates in that case actionIter.assertAtEnd(); } state = Provable.if(isDummy, stateType, state, newState); } // important: we check that by iterating, we actually reached the claimed final action state listIter.assertAtEnd(); return state; }, forEach(actionLists, callback, config) { const stateType = provable(null); this.reduce(actionLists, stateType, (_, action) => { callback(action); return null; }, null, config); }, getActions(config) { const Action = reducer.actionType; const emptyHash = Actions.empty().hash; const nextHash = (hash, action) => Actions.pushEvent({ hash, data: [] }, Action.toFields(action)).hash; class ActionList extends MerkleList.create(Action, nextHash, emptyHash) { } class MerkleActions extends MerkleList.create(ActionList, (hash, actions) => Actions.updateSequenceState(hash, actions.hash), // if no "start" action hash was specified, this means we are fetching the entire history of actions, which started from the empty action state hash // otherwise we are only fetching a part of the history, which starts at `fromActionState` // TODO does this show that `emptyHash` should be part of the instance, not the class? that would make the provable representation bigger though config?.fromActionState ?? Actions.emptyActionState()) { } let actions = Provable.witness(MerkleActions, () => { let actionFields = Mina.getActions(contract.address, config, contract.tokenId); // convert string-Fields back into the original action type let actions = actionFields.map((event) => event.actions.map((action) => reducer.actionType.fromFields(action.map(Field)))); return MerkleActions.from(actions.map((a) => ActionList.fromReverse(a))); }); // note that we don't have to assert anything about the initial action state here, // because it is taken directly and not witnessed if (config?.endActionState !== undefined) { actions.hash.assertEquals(config.endActionState); } return actions; }, async fetchActions(config) { let result = await Mina.fetchActions(contract.address, config, contract.tokenId); if ('error' in result) { throw Error(JSON.stringify(result)); } return result.map((event) => // putting our string-Fields back into the original action type event.actions.map((action) => reducer.actionType.fromFields(action.map(Field)))); }, }; } //# sourceMappingURL=reducer.js.map