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 endActionState and fromActionState to fetchActions #828

Merged
merged 7 commits into from
Apr 5, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,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
MartinMinkov marked this conversation as resolved.
Show resolved Hide resolved
- 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
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 += `, endActionHash: "${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
42 changes: 12 additions & 30 deletions src/lib/zkapp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1399,42 +1399,24 @@ Use the optional \`maxTransactionsWithActions\` argument to increase this number
}): A[][] {
let actionsForAccount: A[][] = [];
Circuit.asProver(() => {
// if the fromActionHash is the empty state, we fetch all events
fromActionHash = fromActionHash
?.equals(SequenceEvents.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[][] }) =>
let actions = Mina.getActions(
contract.address,
{
fromActionState: fromActionHash,
endActionState: endActionHash,
},
contract.self.tokenId
);

actionsForAccount = actions.map(
(event: { hash: string; actions: string[][] }) =>
// putting our string-Fields back into the original action type
event.actions.map((action: string[]) =>
(reducer.actionType as ProvablePure<A>).fromFields(
action.map((fieldAsString: string) => Field(fieldAsString))
)
)
);
);
});

return actionsForAccount;
Expand Down