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

add getActions to LocalBlockchain #327

Merged
merged 19 commits into from
Aug 25, 2022
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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());
60 changes: 59 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,37 @@ function LocalBlockchain({
slot: networkState.globalSlotSinceHardFork.toString(),
});
}
});

// actions/sequencing events
let latestActionsHash =
ledger.getAccount(
PublicKey.fromBase58(addr),
Ledger.fieldOfBase58(tokenId)
)?.zkapp?.sequenceState[0] ?? Events.emptySequenceState();
Trivo25 marked this conversation as resolved.
Show resolved Hide resolved

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

latestActionsHash = Events.updateSequenceState(
latestActionsHash,
Trivo25 marked this conversation as resolved.
Show resolved Hide resolved
eventsHash
);

if (actions[addr] === undefined) {
actions[addr] = {};
}
if (p.body.sequenceEvents.length > 0) {
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 +385,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 +520,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 +591,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 +681,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
57 changes: 57 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,56 @@ 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(() => {
Trivo25 marked this conversation as resolved.
Show resolved Hide resolved
// 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) + 2
Trivo25 marked this conversation as resolved.
Show resolved Hide resolved
: 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[][] }) =>
event.actions.map((action: string[]) =>
reducer.actionType.ofFields(
action.map((fieldAsString: string) =>
Field.fromString(fieldAsString)
)
)
)
);
});
return actionsForAccount;
},
};
}

Expand Down