Skip to content

Commit

Permalink
Merge pull request #828 from o1-labs/feat/actions-hash-recalculations
Browse files Browse the repository at this point in the history
Add endActionState and fromActionState to fetchActions
  • Loading branch information
MartinMinkov authored Apr 5, 2023
2 parents f585b64 + 4f9870a commit 52ec32b
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 72 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Improve number of constraints needed for Merkle tree hashing https://github.com/o1-labs/snarkyjs/pull/820
- This breaks deployed zkApps which use `MerkleWitness.calculateRoot()`, because the circuit is changed
- You can make your existing contracts compatible again by switching to `MerkleWitness.calculateRootSlow()`, which has the old circuit
- Renamed Function Parameters: The `getAction` function now accepts a new object structure for its parameters. https://github.com/o1-labs/snarkyjs/pull/828
- The previous object keys, `fromActionHash` and `endActionHash`, have been replaced by `fromActionState` and `endActionState`.

### Fixed

Expand All @@ -30,6 +32,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- The internal event type now includes event data and transaction information as separate objects, allowing for more accurate information about each event and its associated transaction.
- Removed multiple best tip blocks when fetching action data https://github.com/o1-labs/snarkyjs/pull/817
- Implemented a temporary fix that filters out multiple best tip blocks, if they exist, while fetching actions. This fix will be removed once the related issue in the Archive-Node-API repository (https://github.com/o1-labs/Archive-Node-API/issues/7) is resolved.
- New `fromActionState` and `endActionState` parameters for fetchActions function in SnarkyJS https://github.com/o1-labs/snarkyjs/pull/828
- Allows fetching only necessary actions to compute the latest actions state
- Eliminates the need to retrieve the entire actions history of a zkApp
- Utilizes `actionStateTwo` field returned by Archive Node API as a safe starting point for deriving the most recent action hash

## [0.9.5](https://github.com/o1-labs/snarkyjs/compare/21de489...4573252d)

Expand Down
2 changes: 1 addition & 1 deletion src/examples/zkapps/reducer/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class CounterZkapp extends SmartContract {

// compute the new counter and hash from pending actions
let pendingActions = this.reducer.getActions({
fromActionHash: actionsHash,
fromActionState: actionsHash,
});

let { state: newCounter, actionsHash: newActionsHash } =
Expand Down
2 changes: 1 addition & 1 deletion src/examples/zkapps/reducer/reducer_composite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class CounterZkapp extends SmartContract {

// compute the new counter and hash from pending actions
let pendingActions = this.reducer.getActions({
fromActionHash: actionsHash,
fromActionState: actionsHash,
});

let { state: newCounter, actionsHash: newActionsHash } =
Expand Down
4 changes: 2 additions & 2 deletions src/examples/zkapps/voting/membership.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ export class Membership_ extends SmartContract {
// checking if the member already exists within the accumulator
let { state: exists } = this.reducer.reduce(
this.reducer.getActions({
fromActionHash: accumulatedMembers,
fromActionState: accumulatedMembers,
}),
Bool,
(state: Bool, action: Member) => {
Expand Down Expand Up @@ -168,7 +168,7 @@ export class Membership_ extends SmartContract {
this.committedMembers.assertEquals(committedMembers);

let pendingActions = this.reducer.getActions({
fromActionHash: accumulatedMembers,
fromActionState: accumulatedMembers,
});

let { state: newCommittedMembers, actionsHash: newAccumulatedMembers } =
Expand Down
2 changes: 1 addition & 1 deletion src/examples/zkapps/voting/voting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ export class Voting_ extends SmartContract {

let { state: newCommittedVotes, actionsHash: newAccumulatedVotes } =
this.reducer.reduce(
this.reducer.getActions({ fromActionHash: accumulatedVotes }),
this.reducer.getActions({ fromActionState: accumulatedVotes }),
Field,
(state: Field, action: Member) => {
// apply one vote
Expand Down
74 changes: 54 additions & 20 deletions src/lib/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { SequenceEvents, TokenId } from './account_update.js';
import { PublicKey } from './signature.js';
import { NetworkValue } from './precondition.js';
import { Types } from '../provable/types.js';
import { ActionStates } from './mina.js';
import * as Encoding from './encoding.js';
import {
Account,
Expand Down Expand Up @@ -139,6 +140,9 @@ type FetchError = {
statusCode: number;
statusText: string;
};
type ActionStatesStringified = {
[K in keyof ActionStates]: string;
};
// Specify 30s as the default timeout
const defaultTimeout = 30000;

Expand Down Expand Up @@ -173,7 +177,12 @@ let accountsToFetch = {} as Record<
let networksToFetch = {} as Record<string, { graphqlEndpoint: string }>;
let actionsToFetch = {} as Record<
string,
{ publicKey: string; tokenId: string; graphqlEndpoint: string }
{
publicKey: string;
tokenId: string;
actionStates: ActionStatesStringified;
graphqlEndpoint: string;
}
>;

function markAccountToBeFetched(
Expand All @@ -195,13 +204,26 @@ function markNetworkToBeFetched(graphqlEndpoint: string) {
function markActionsToBeFetched(
publicKey: PublicKey,
tokenId: Field,
graphqlEndpoint: string
graphqlEndpoint: string,
actionStates: ActionStates = {}
) {
let publicKeyBase58 = publicKey.toBase58();
let tokenBase58 = TokenId.toBase58(tokenId);
let { fromActionState, endActionState } = actionStates;
let fromActionStateBase58 = fromActionState
? fromActionState.toString()
: undefined;
let endActionStateBase58 = endActionState
? endActionState.toString()
: undefined;

actionsToFetch[`${publicKeyBase58};${tokenBase58};${graphqlEndpoint}`] = {
publicKey: publicKeyBase58,
tokenId: tokenBase58,
actionStates: {
fromActionState: fromActionStateBase58,
endActionState: endActionStateBase58,
},
graphqlEndpoint,
};
}
Expand All @@ -220,9 +242,9 @@ async function fetchMissingData(
}
);
let actionPromises = Object.entries(actionsToFetch).map(
async ([key, { publicKey, tokenId }]) => {
async ([key, { publicKey, actionStates, tokenId }]) => {
let response = await fetchActions(
{ publicKey, tokenId },
{ publicKey, actionStates, tokenId },
archiveEndpoint
);
if (!('error' in response) || response.error === undefined)
Expand Down Expand Up @@ -558,7 +580,10 @@ type FetchedActions = {
blockInfo: {
distanceFromMaxBlockHeight: number;
};
actionState: string;
actionState: {
actionStateOne: string;
actionStateTwo: string;
};
actionData: {
accountUpdateId: string;
data: string[];
Expand Down Expand Up @@ -606,23 +631,27 @@ const getEventsQuery = (
};
const getActionsQuery = (
publicKey: string,
actionStates: ActionStatesStringified,
tokenId: string,
filterOptions?: EventActionFilterOptions
_filterOptions?: EventActionFilterOptions
) => {
const { to, from } = filterOptions ?? {};
const { fromActionState, endActionState } = actionStates ?? {};
let input = `address: "${publicKey}", tokenId: "${tokenId}"`;
if (to !== undefined) {
input += `, to: ${to}`;
if (fromActionState !== undefined) {
input += `, fromActionState: "${fromActionState}"`;
}
if (from !== undefined) {
input += `, from: ${from}`;
if (endActionState !== undefined) {
input += `, endActionState: "${endActionState}"`;
}
return `{
actions(input: { ${input} }) {
blockInfo {
distanceFromMaxBlockHeight
}
actionState
actionState {
actionStateOne
actionStateTwo
}
actionData {
accountUpdateId
data
Expand Down Expand Up @@ -711,20 +740,23 @@ async function fetchEvents(
}

async function fetchActions(
accountInfo: { publicKey: string; tokenId?: string },
graphqlEndpoint = archiveGraphqlEndpoint,
filterOptions: EventActionFilterOptions = {}
accountInfo: {
publicKey: string;
actionStates: ActionStatesStringified;
tokenId?: string;
},
graphqlEndpoint = archiveGraphqlEndpoint
) {
if (!graphqlEndpoint)
throw new Error(
'fetchEvents: Specified GraphQL endpoint is undefined. Please specify a valid endpoint.'
);
const { publicKey, tokenId } = accountInfo;
const { publicKey, actionStates, tokenId } = accountInfo;
let [response, error] = await makeGraphqlRequest(
getActionsQuery(
publicKey,
tokenId ?? TokenId.toBase58(TokenId.default),
filterOptions
actionStates,
tokenId ?? TokenId.toBase58(TokenId.default)
),
graphqlEndpoint
);
Expand Down Expand Up @@ -771,10 +803,12 @@ async function fetchActions(
// Archive Node API returns actions in the latest order, so we reverse the array to get the actions in chronological order.
fetchedActions.reverse();
let actionsList: { actions: string[][]; hash: string }[] = [];
let latestActionsHash = SequenceEvents.emptySequenceState();

fetchedActions.forEach((fetchedAction) => {
const { actionState, actionData } = fetchedAction;
const { actionData } = fetchedAction;
let latestActionsHash = Field(fetchedAction.actionState.actionStateTwo);
let actionState = Field(fetchedAction.actionState.actionStateOne);

if (actionData.length === 0)
throw new Error(
`No action data was found for the account ${publicKey} with the latest action state ${actionState}`
Expand Down
67 changes: 60 additions & 7 deletions src/lib/mina.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export {
fetchEvents,
getActions,
FeePayerSpec,
ActionStates,
faucet,
waitForFunding,
getProofsEnabled,
Expand Down Expand Up @@ -148,6 +149,11 @@ type DeprecatedFeePayerSpec =
})
| undefined;

type ActionStates = {
fromActionState?: Field;
endActionState?: Field;
};

function reportGetAccountError(publicKey: string, tokenId: string) {
if (tokenId === TokenId.toBase58(TokenId.default)) {
return `getAccount: Could not find account for public key ${publicKey}`;
Expand Down Expand Up @@ -340,6 +346,7 @@ interface Mina {
) => ReturnType<typeof Fetch.fetchEvents>;
getActions: (
publicKey: PublicKey,
actionStates?: ActionStates,
tokenId?: Field
) => { hash: string; actions: string[][] }[];
proofsEnabled: boolean;
Expand Down Expand Up @@ -581,10 +588,39 @@ function LocalBlockchain({
},
getActions(
publicKey: PublicKey,
actionStates?: ActionStates,
tokenId: Field = TokenId.default
): { hash: string; actions: string[][] }[] {
let currentActions: { hash: string; actions: string[][] }[] =
actions?.[publicKey.toBase58()]?.[Ledger.fieldToBase58(tokenId)] ?? [];
let { fromActionState, endActionState } = actionStates ?? {};

fromActionState = fromActionState
?.equals(SequenceEvents.emptySequenceState())
.toBoolean()
? undefined
: fromActionState;

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

let startIndex = start
? currentActions.findIndex((e) => e.hash === start) + 1
: 0;
let endIndex = end
? currentActions.findIndex((e) => e.hash === end) + 1
: undefined;

return (
actions?.[publicKey.toBase58()]?.[Ledger.fieldToBase58(tokenId)] ?? []
currentActions?.slice(
startIndex,
endIndex === 0 ? undefined : endIndex
) ?? []
);
},
addAccount,
Expand Down Expand Up @@ -817,9 +853,18 @@ function Network(input: { mina: string; archive: string } | string): Mina {
filterOptions
);
},
getActions(publicKey: PublicKey, tokenId: Field = TokenId.default) {
getActions(
publicKey: PublicKey,
actionStates?: ActionStates,
tokenId: Field = TokenId.default
) {
if (currentTransaction()?.fetchMode === 'test') {
Fetch.markActionsToBeFetched(publicKey, tokenId, archiveEndpoint);
Fetch.markActionsToBeFetched(
publicKey,
tokenId,
archiveEndpoint,
actionStates
);
let actions = Fetch.getCachedActions(publicKey, tokenId);
return actions ?? [];
}
Expand Down Expand Up @@ -906,10 +951,14 @@ let activeInstance: Mina = {
async transaction(sender: DeprecatedFeePayerSpec, f: () => void) {
return createTransaction(sender, f, 0);
},
fetchEvents(publicKey: PublicKey, tokenId: Field = TokenId.default) {
fetchEvents(_publicKey: PublicKey, _tokenId: Field = TokenId.default) {
throw Error('must call Mina.setActiveInstance first');
},
getActions(publicKey: PublicKey, tokenId: Field = TokenId.default) {
getActions(
_publicKey: PublicKey,
_actionStates?: ActionStates,
_tokenId: Field = TokenId.default
) {
throw Error('must call Mina.setActiveInstance first');
},
proofsEnabled: true,
Expand Down Expand Up @@ -1055,8 +1104,12 @@ async function fetchEvents(
/**
* @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 getActions(
publicKey: PublicKey,
actionStates: ActionStates,
tokenId?: Field
) {
return activeInstance.getActions(publicKey, actionStates, tokenId);
}

function getProofsEnabled() {
Expand Down
Loading

0 comments on commit 52ec32b

Please sign in to comment.