Skip to content

Commit

Permalink
Merge pull request #327 from o1-labs/feature/localblockchain-sequenci…
Browse files Browse the repository at this point in the history
…ngEvents
  • Loading branch information
Trivo25 committed Aug 25, 2022
2 parents 8d2ac6f + 7b46157 commit 927bbbb
Show file tree
Hide file tree
Showing 3 changed files with 173 additions and 20 deletions.
69 changes: 50 additions & 19 deletions src/examples/state_update_rollup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
Party,
isReady,
Permissions,
Circuit,
Ledger,
} from 'snarkyjs';

await isReady;
Expand Down Expand Up @@ -39,9 +41,12 @@ class CounterZkapp extends SmartContract {

// 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,
this.reducer.getActions({
fromActionHash: actionsHash,
}),
// state type
Field,
// function that says how to apply an action
Expand All @@ -60,12 +65,6 @@ class CounterZkapp extends SmartContract {
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);

Expand All @@ -77,7 +76,6 @@ let zkappKey = PrivateKey.fromBase58(
'EKEQc95PPQZnMY9d9p1vq1MWLeDJKtvKj4V75UDG3rjnf32BerWD'
);
let zkappAddress = zkappKey.toPublicKey();

let zkapp = new CounterZkapp(zkappAddress);
if (doProofs) {
console.log('compile');
Expand All @@ -100,15 +98,16 @@ let tx = await Mina.transaction(feePayer, () => {
});
tx.send();

console.log('applying actions..');

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, () => {
Expand All @@ -117,21 +116,53 @@ tx = await Mina.transaction(feePayer, () => {
});
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('action 3');
tx = await Mina.transaction(feePayer, () => {
zkapp.incrementCounter();
if (!doProofs) zkapp.sign(zkappKey);
});
if (doProofs) await tx.prove();
tx.send();

console.log('rolling up pending actions..');

console.log('state before: ' + zkapp.counter.get());

tx = await Mina.transaction(feePayer, () => {
zkapp.rollupIncrements();
if (!doProofs) zkapp.sign(zkappKey);
});
if (doProofs) await tx.prove();
tx.send();

console.log('state after rollup: ' + zkapp.counter.get());

console.log('applying more actions');

console.log('action 4');
tx = await Mina.transaction(feePayer, () => {
zkapp.incrementCounter();
if (!doProofs) zkapp.sign(zkappKey);
});
tx.send();

console.log('action 5');
tx = await Mina.transaction(feePayer, () => {
zkapp.incrementCounter();
if (!doProofs) zkapp.sign(zkappKey);
});
tx.send();

console.log('rolling up pending actions..');

console.log('state before: ' + zkapp.counter.get());

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));
console.log('state after rollup: ' + zkapp.counter.get());
66 changes: 65 additions & 1 deletion src/lib/mina.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export {
accountCreationFee,
sendTransaction,
fetchEvents,
getActions,
FeePayerSpec,
};
interface TransactionId {
Expand Down Expand Up @@ -197,6 +198,10 @@ interface Mina {
accountCreationFee(): UInt64;
sendTransaction(transaction: Transaction): TransactionId;
fetchEvents: (publicKey: PublicKey, tokenId?: Field) => any;
getActions: (
publicKey: PublicKey,
tokenId?: Field
) => { hash: string; actions: string[][] }[];
}

interface MockMina extends Mina {
Expand Down Expand Up @@ -247,6 +252,7 @@ function LocalBlockchain({
}

const events: Record<string, any> = {};
const actions: Record<string, any> = {};

return {
accountCreationFee: () => UInt64.from(accountCreationFee),
Expand Down Expand Up @@ -318,8 +324,43 @@ function LocalBlockchain({
slot: networkState.globalSlotSinceHardFork.toString(),
});
}
});

// actions/sequencing events

// gets the index of the most up to date sequence state from our sequence list
let n = actions[addr]?.[tokenId]?.length ?? 1;

// most recent sequence state
let sequenceState = actions?.[addr]?.[tokenId]?.[n - 1]?.hash;

// if there exists no hash, this means we initialize our latest hash with the empty state
let latestActionsHash =
sequenceState === undefined
? Events.emptySequenceState()
: Ledger.fieldOfBase58(sequenceState);

let actionList = p.body.sequenceEvents;
let eventsHash = Events.hash(
actionList.map((e) => e.map((f) => Field(f)))
);

if (actions[addr] === undefined) {
actions[addr] = {};
}
if (p.body.sequenceEvents.length > 0) {
latestActionsHash = Events.updateSequenceState(
latestActionsHash,
eventsHash
);
if (actions[addr][tokenId] === undefined) {
actions[addr][tokenId] = [];
}
actions[addr][tokenId].push({
actions: actionList,
hash: Ledger.fieldToBase58(latestActionsHash),
});
}
});
return { wait: async () => {} };
},
async transaction(sender: FeePayerSpec, f: () => void) {
Expand Down Expand Up @@ -350,6 +391,14 @@ function LocalBlockchain({
): Promise<any[]> {
return events?.[publicKey.toBase58()]?.[TokenId.toBase58(tokenId)] ?? [];
},
getActions(
publicKey: PublicKey,
tokenId: Field = TokenId.default
): { hash: string; actions: string[][] }[] {
return (
actions?.[publicKey.toBase58()]?.[Ledger.fieldToBase58(tokenId)] ?? []
);
},
addAccount,
testAccounts,
setTimestamp(ms: UInt64) {
Expand Down Expand Up @@ -477,6 +526,11 @@ function RemoteBlockchain(graphqlEndpoint: string): Mina {
'fetchEvents() is not implemented yet for remote blockchains.'
);
},
getActions() {
throw Error(
'fetchEvents() is not implemented yet for remote blockchains.'
);
},
};
}

Expand Down Expand Up @@ -543,6 +597,9 @@ let activeInstance: Mina = {
fetchEvents() {
throw Error('must call Mina.setActiveInstance first');
},
getActions() {
throw Error('must call Mina.setActiveInstance first');
},
};

/**
Expand Down Expand Up @@ -630,6 +687,13 @@ async function fetchEvents(publicKey: PublicKey, tokenId: Field) {
return await activeInstance.fetchEvents(publicKey, tokenId);
}

/**
* @return A list of emitted sequencing actions associated to the given public key.
*/
function getActions(publicKey: PublicKey, tokenId: Field) {
return activeInstance.getActions(publicKey, tokenId);
}

function dummyAccount(pubkey?: PublicKey): Account {
return {
balance: UInt64.zero,
Expand Down
58 changes: 58 additions & 0 deletions src/lib/zkapp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,13 @@ type ReducerReturn<Action> = {
state: State;
actionsHash: Field;
};
getActions({
fromActionHash,
endActionHash,
}: {
fromActionHash?: Field;
endActionHash?: Field;
}): Action[][];
};

function getReducer<A>(contract: SmartContract): ReducerReturn<A> {
Expand Down Expand Up @@ -826,6 +833,57 @@ Use the optional \`maxTransactionsWithActions\` argument to increase this number
contract.account.sequenceState.assertEquals(actionsHash);
return { state, actionsHash };
},
getActions({
fromActionHash,
endActionHash,
}: {
fromActionHash?: Field;
endActionHash?: Field;
}): A[][] {
let actionsForAccount: A[][] = [];

Circuit.asProver(() => {
// if the fromActionHash is the empty state, we fetch all events
fromActionHash = fromActionHash
?.equals(Events.emptySequenceState())
.toBoolean()
? undefined
: fromActionHash;

// used to determine start and end values in string
let start: string | undefined = fromActionHash
? Ledger.fieldToBase58(fromActionHash)
: undefined;
let end: string | undefined = endActionHash
? Ledger.fieldToBase58(endActionHash)
: undefined;

let actions = Mina.getActions(contract.address, contract.self.tokenId);

// gets the start/end indices of our array slice
let startIndex = start
? actions.findIndex((e) => e.hash === start) + 1
: 0;
let endIndex = end
? actions.findIndex((e) => e.hash === end) + 1
: undefined;

// slices the array so we only get the wanted range between fromActionHash and endActionHash
actionsForAccount = actions
.slice(startIndex, endIndex === 0 ? undefined : endIndex)
.map((event: { hash: string; actions: string[][] }) =>
// putting our string-Fields back into the original action type
event.actions.map((action: string[]) =>
reducer.actionType.ofFields(
action.map((fieldAsString: string) =>
Field.fromString(fieldAsString)
)
)
)
);
});
return actionsForAccount;
},
};
}

Expand Down

0 comments on commit 927bbbb

Please sign in to comment.