UNPKG

pulsar-contracts

Version:

226 lines 9.37 kB
import { Bool, Field, Poseidon } from 'o1js'; import { ValidateReducePublicInput } from '../ValidateReduce.js'; import { log } from './loggers.js'; import { BATCH_SIZE, MAX_DEPOSIT_PER_BATCH, MAX_SETTLEMENT_PER_BATCH, MAX_WITHDRAWAL_PER_BATCH, } from './constants.js'; import { Batch, PulsarAction } from '../types/PulsarAction.js'; import { ReduceMask } from '../types/common.js'; import { GenerateActionStackProof } from './generateFunctions.js'; import { fetchActions } from './fetch.js'; import { actionListAdd, emptyActionListHash, merkleActionsAdd, } from '../types/actionHelpers.js'; import { ActionStackProof } from '../ActionStack.js'; export { MapFromArray, CalculateMax, PrepareBatch, PrepareBatchWithActions, PackActions, validateActionQueue, }; function MapFromArray(array) { const map = new Map(); for (const field of array.map((x) => x.toString())) { const count = map.get(field) || 0; map.set(field, count + 1); log('map:', map); log('map.get(field):', map.get(field)); } return map; } // maple function CalculateMax(includedActionsMap, contractInstance, packedActions) { let withdrawals = 0; let deposits = 0; let settlements = 0; const batchActions = []; let endActionState = 0n; let mask = new Array(BATCH_SIZE).fill(false); let publicInput = new ValidateReducePublicInput({ stateRoot: contractInstance.stateRoot.get(), merkleListRoot: contractInstance.merkleListRoot.get(), blockHeight: contractInstance.blockHeight.get(), depositListHash: contractInstance.depositListHash.get(), withdrawalListHash: contractInstance.withdrawalListHash.get(), rewardListHash: contractInstance.rewardListHash.get(), }); for (const [i, pack] of packedActions.entries()) { if (batchActions.length === BATCH_SIZE) { log('Batch size reached:', batchActions.length); break; } const hash = pack.action.unconstrainedHash().toString(); const count = includedActionsMap.get(hash) || 0; console.log(`hash: ${hash}, count: ${count}`); if (PulsarAction.isSettlement(pack.action).toBoolean()) { if (count <= 0) { log('Action skipped:', pack.action.toJSON()); batchActions.push(pack.action); endActionState = BigInt(pack.hash); continue; } else if (settlements === MAX_SETTLEMENT_PER_BATCH) { log('Max settlements reached for batch'); break; } settlements++; mask[i] = true; publicInput = new ValidateReducePublicInput({ ...publicInput, stateRoot: pack.action.newState, merkleListRoot: pack.action.newMerkleListRoot, blockHeight: pack.action.newBlockHeight, rewardListHash: Poseidon.hash([ publicInput.rewardListHash, pack.action.rewardListUpdateHash, ]), }); } else if (PulsarAction.isDeposit(pack.action).toBoolean()) { if (deposits === MAX_DEPOSIT_PER_BATCH) { log('Max deposits reached for batch'); break; } deposits++; mask[i] = true; publicInput = new ValidateReducePublicInput({ ...publicInput, depositListHash: Poseidon.hash([ publicInput.depositListHash, ...pack.action.account.toFields(), pack.action.amount, ]), }); } else if (PulsarAction.isWithdrawal(pack.action).toBoolean()) { if (count <= 0) { log('Action skipped:', pack.action.toJSON()); batchActions.push(pack.action); endActionState = BigInt(pack.hash); continue; } else if (withdrawals === MAX_WITHDRAWAL_PER_BATCH) { log('Max withdrawals reached for batch'); break; } withdrawals++; mask[i] = true; publicInput = new ValidateReducePublicInput({ ...publicInput, withdrawalListHash: Poseidon.hash([ publicInput.withdrawalListHash, ...pack.action.account.toFields(), pack.action.amount, ]), }); } batchActions.push(pack.action); endActionState = BigInt(pack.hash); } return { endActionState, batchActions, publicInput, mask: ReduceMask.fromArray(mask), }; } function PackActions(initialState, actions) { let packedActions = []; for (const [i, action] of actions.entries()) { packedActions.push({ action, hash: i === 0 ? initialState.toBigInt() : merkleActionsAdd(Field(packedActions[i - 1].hash), actionListAdd(emptyActionListHash, action)).toBigInt(), }); } return packedActions; } // Included actions will be fetched from the validators async function PrepareBatch(includedActions, contractInstance) { const packedActions = await fetchActions(contractInstance.address, contractInstance.actionState.get()); if (packedActions.length === 0) { log('No actions found for the contract.'); return { endActionState: 0n, batchActions: [], batch: Batch.empty(), useActionStack: Bool(false), actionStackProof: undefined, publicInput: ValidateReducePublicInput.default, mask: ReduceMask.empty(), }; } const { endActionState, batchActions, publicInput, mask } = CalculateMax(includedActions, contractInstance, packedActions); let actionStack = packedActions .slice(batchActions.length) .map((pack) => pack.action); log('Batch actions:', batchActions.map((action) => action.toJSON()), '\n', 'Action stack:', actionStack.map((action) => action.toJSON())); const batch = Batch.fromArray(batchActions); const { useActionStack, actionStackProof } = await GenerateActionStackProof(Field.from(endActionState), actionStack); log('useActionStack:', useActionStack.toBoolean(), '\n', 'actionStackProof Input:', actionStackProof.publicInput.toJSON(), 'output:', actionStackProof.publicOutput.toJSON()); return { batchActions, batch, useActionStack, actionStackProof, publicInput, mask, }; } async function PrepareBatchWithActions(includedActions, contractInstance, packedActions) { if (packedActions.length === 0) { log('No actions found for the contract.'); let proof = await ActionStackProof.dummy(Field(0), Field(0), 1, 14); return { endActionState: Field(0), batchActions: [PulsarAction.fromRawAction([])], batch: Batch.empty(), useActionStack: Bool(false), actionStackProof: proof, publicInput: ValidateReducePublicInput.default, mask: ReduceMask.empty(), }; } const { endActionState, batchActions, publicInput, mask } = CalculateMax(includedActions, contractInstance, packedActions); let actionStack = packedActions .slice(batchActions.length) .map((pack) => pack.action); log('Batch actions:', batchActions.map((action) => action.toJSON()), '\n', 'Action stack:', actionStack.map((action) => action.toJSON())); const batch = Batch.fromArray(batchActions); const { useActionStack, actionStackProof } = await GenerateActionStackProof(Field.from(endActionState), actionStack); log('useActionStack:', useActionStack.toBoolean(), '\n', 'actionStackProof Input:', actionStackProof.publicInput.toJSON(), 'output:', actionStackProof.publicOutput.toJSON()); return { endActionState: Field.from(endActionState), batchActions, batch, useActionStack, actionStackProof, publicInput, mask, }; } function validateActionQueue(rawActions, finalActionState) { if (rawActions.length === 0) { return { actions: [], isValid: false, }; } const actions = rawActions.map((action) => { return { action: PulsarAction.fromRawAction(action.actions[0]), hash: BigInt(action.hash), }; }); actions.forEach((action, index) => { if (action.action.unconstrainedHash().toBigInt() !== action.hash) { log(`Action hash mismatch at index ${index}: expected ${action.hash}, got ${action.action.unconstrainedHash().toBigInt()}`); return { actions, isValid: false }; } }); let actionListHash = emptyActionListHash; for (const action of actions) { actionListHash = merkleActionsAdd(actionListHash, Field(action.hash)); } if (actionListHash.toString() !== finalActionState) { log(`Action state mismatch: expected ${finalActionState}, got ${actionListHash.toString()}`); return { actions, isValid: false }; } return { actions, isValid: true, }; } //# sourceMappingURL=reduceWitness.js.map