-
Notifications
You must be signed in to change notification settings - Fork 4
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
adjust query to support ordering #107
base: main
Are you sure you want to change the base?
Changes from 6 commits
5efccc8
f01a38e
030c60f
01f0a70
5bc4124
6c1a253
e1fc23d
3c0750e
73b7158
71bc454
7f51ad5
03c0cb1
f8336ef
7020459
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -143,7 +143,7 @@ class ActionsService implements IActionsService { | |
} | ||
actions.push({ | ||
blockInfo, | ||
actionData: actionsData.flat(), | ||
actionData: this.sortActions(actionsData.flat()), | ||
actionState: { | ||
/* eslint-disable */ | ||
actionStateOne: action_state_value1!, | ||
|
@@ -157,4 +157,26 @@ class ActionsService implements IActionsService { | |
} | ||
return actions; | ||
} | ||
|
||
sortActions(actions: Action[]): Action[] { | ||
return actions.sort((a, b) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since actions are returned grouped by block, this is focused on ordering the actions within a block. I'm not sure the sort order is correct. Maybe it's reversed, but we should get it right, then we will have access to the unit test as a spec. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @deepthiskumar it's worth noting that you and gregor both mentioned a sub-order to the account update in o1-labs/o1js#1872 It is your point 4, and Gregor said "It's extremely important that when reordering the actions here, the order within the same account update is maintained". This sort only looks at sequence number and account update id. But with my JSON comment on this PR and this code, we should be able to get on the same page more easily. If there's a third sorting lever we need here, then I can add it, but it's not clear to me what it is. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, the third sorting lever is the element ID index from |
||
// Sort by sequence number | ||
if ( | ||
a.transactionInfo.sequenceNumber !== b.transactionInfo.sequenceNumber | ||
) { | ||
return ( | ||
a.transactionInfo.sequenceNumber - b.transactionInfo.sequenceNumber | ||
); | ||
} | ||
|
||
// Sort by account update index if sequence number is the same | ||
const aIndex = a.transactionInfo.zkappAccountUpdateIds.indexOf( | ||
Number(a.accountUpdateId) | ||
); | ||
const bIndex = b.transactionInfo.zkappAccountUpdateIds.indexOf( | ||
Number(b.accountUpdateId) | ||
); | ||
return aIndex - bIndex; | ||
}); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import { createYoga, createSchema } from 'graphql-yoga'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This whole file may be better to remove. It's just a script to make a graphql request against the local API. |
||
import { loadSchemaSync } from '@graphql-tools/load'; | ||
import { GraphQLFileLoader } from '@graphql-tools/graphql-file-loader'; | ||
import { buildHTTPExecutor } from '@graphql-tools/executor-http'; | ||
import { parse } from 'graphql'; | ||
import { resolvers } from '../src/resolvers.js'; | ||
import { buildContext, GraphQLContext } from '../src/context.js'; | ||
|
||
const PG_CONN = 'postgresql://postgres:postgres@localhost:5432/archive '; | ||
const zkappAccount = 'B62qmBrPiukbHj4VnXdgMzjj2zpoQZeSBxZA6JDYMeeShApRAKaorto'; | ||
|
||
const actionsQuery = ` | ||
query getActions($input: ActionFilterOptionsInput!) { | ||
actions(input: $input) { | ||
blockInfo { | ||
stateHash | ||
timestamp | ||
height | ||
parentHash | ||
chainStatus | ||
distanceFromMaxBlockHeight | ||
globalSlotSinceGenesis | ||
} | ||
actionState { | ||
actionStateOne | ||
actionStateTwo | ||
actionStateThree | ||
actionStateFour | ||
actionStateFive | ||
} | ||
actionData { | ||
data | ||
accountUpdateId | ||
transactionInfo { | ||
status | ||
hash | ||
memo | ||
sequenceNumber | ||
zkappAccountUpdateIds | ||
zkappEventElementIds | ||
} | ||
} | ||
} | ||
} | ||
`; | ||
|
||
const schema = createSchema({ | ||
typeDefs: loadSchemaSync('./schema.graphql', { | ||
loaders: [new GraphQLFileLoader()], | ||
}), | ||
resolvers, | ||
}); | ||
const context = await buildContext(PG_CONN); | ||
const yoga = createYoga<GraphQLContext>({ schema, context }); | ||
const executor = buildHTTPExecutor({ | ||
fetch: yoga.fetch, | ||
}); | ||
|
||
const results = await executor({ | ||
variables: { | ||
input: { address: zkappAccount }, | ||
}, | ||
document: parse(`${actionsQuery}`), | ||
}); | ||
|
||
console.log(JSON.stringify(results)); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,7 @@ import { loadSchemaSync } from '@graphql-tools/load'; | |
import { GraphQLFileLoader } from '@graphql-tools/graphql-file-loader'; | ||
import { buildHTTPExecutor } from '@graphql-tools/executor-http'; | ||
import { parse } from 'graphql'; | ||
import { PrivateKey, Lightnet } from 'o1js'; | ||
import { PrivateKey, Lightnet, Mina } from 'o1js'; | ||
import { resolvers } from '../src/resolvers.js'; | ||
import { buildContext, GraphQLContext } from '../src/context.js'; | ||
import { | ||
|
@@ -15,8 +15,10 @@ import { | |
emitSingleEvent, | ||
setNetworkConfig, | ||
Keypair, | ||
emitActionsFromMultipleSenders, | ||
} from '../zkapp/utils.js'; | ||
import { HelloWorld } from '../zkapp/contract.js'; | ||
import { Actions } from 'src/blockchain/types.js'; | ||
|
||
const eventsQuery = ` | ||
query getEvents($input: EventFilterOptionsInput!) { | ||
|
@@ -69,6 +71,8 @@ query getActions($input: ActionFilterOptionsInput!) { | |
status | ||
hash | ||
memo | ||
sequenceNumber | ||
zkappAccountUpdateIds | ||
} | ||
} | ||
} | ||
|
@@ -85,27 +89,31 @@ describe('Query Resolvers', async () => { | |
let zkApp: HelloWorld; | ||
|
||
before(async () => { | ||
setNetworkConfig(); | ||
try { | ||
setNetworkConfig(); | ||
|
||
const schema = createSchema({ | ||
typeDefs: loadSchemaSync('./schema.graphql', { | ||
loaders: [new GraphQLFileLoader()], | ||
}), | ||
resolvers, | ||
}); | ||
const context = await buildContext(PG_CONN); | ||
const yoga = createYoga<GraphQLContext>({ schema, context }); | ||
executor = buildHTTPExecutor({ | ||
fetch: yoga.fetch, | ||
}); | ||
const schema = createSchema({ | ||
typeDefs: loadSchemaSync('./schema.graphql', { | ||
loaders: [new GraphQLFileLoader()], | ||
}), | ||
resolvers, | ||
}); | ||
const context = await buildContext(PG_CONN); | ||
const yoga = createYoga<GraphQLContext>({ schema, context }); | ||
executor = buildHTTPExecutor({ | ||
fetch: yoga.fetch, | ||
}); | ||
|
||
zkAppKeypair = await Lightnet.acquireKeyPair(); | ||
senderKeypair = await Lightnet.acquireKeyPair(); | ||
zkApp = await deployContract( | ||
zkAppKeypair, | ||
senderKeypair, | ||
/* fundNewAccount = */ false | ||
); | ||
zkAppKeypair = await Lightnet.acquireKeyPair(); | ||
senderKeypair = await Lightnet.acquireKeyPair(); | ||
zkApp = await deployContract( | ||
zkAppKeypair, | ||
senderKeypair, | ||
/* fundNewAccount = */ false | ||
); | ||
} catch (error) { | ||
console.error(error); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This console was helpful in debugging. Errors were swallowed otherwise. |
||
} | ||
}); | ||
|
||
after(async () => { | ||
|
@@ -253,5 +261,80 @@ describe('Query Resolvers', async () => { | |
const lastAction = actions[actions.length - 1]; | ||
assert.strictEqual(lastAction.actionData.length, 3); | ||
}); | ||
|
||
describe('Actions from different accounts', async () => { | ||
const sendersCount = 5; | ||
const actionsCount = 3; | ||
const senders: Keypair[] = []; | ||
|
||
before(async () => { | ||
for (let i = 0; i < sendersCount; i++) { | ||
senders.push(await Lightnet.acquireKeyPair()); | ||
} | ||
|
||
await emitActionsFromMultipleSenders(zkApp, senders, { | ||
numberOfEmits: actionsCount, | ||
}); | ||
}); | ||
|
||
test('Emitting actions from many accounts should be fetchable in o1js', async () => { | ||
await Mina.fetchActions(zkApp.address); // This line will throw if actions do not reproduce the correct action hash | ||
assert(true); | ||
}); | ||
|
||
test('Fetched actions have order metadata', async () => { | ||
const results = await executor({ | ||
variables: { | ||
input: { | ||
address: zkApp.address, | ||
}, | ||
}, | ||
document: parse(`${actionsQuery}`), | ||
}); | ||
const actions: Actions = results.data.actions; | ||
for (const block of actions) { | ||
const actionData = block.actionData; | ||
for (const action of actionData) { | ||
assert.ok(action.transactionInfo.sequenceNumber); | ||
assert(action.transactionInfo.zkappAccountUpdateIds.length > 0); | ||
} | ||
} | ||
}); | ||
|
||
test('Fetched actions have correct order', async () => { | ||
const results = await executor({ | ||
variables: { | ||
input: { | ||
address: zkApp.address, | ||
}, | ||
}, | ||
document: parse(`${actionsQuery}`), | ||
}); | ||
const actions: Actions = results.data.actions; | ||
|
||
let testedAccountUpdateOrder = false; | ||
for (const block of actions) { | ||
const actionData = block.actionData; | ||
for (let i = 1; i < actionData.length; i++) { | ||
const previousAction = actionData[i - 1]; | ||
const currentAction = actionData[i]; | ||
assert.ok( | ||
previousAction.transactionInfo.sequenceNumber <= | ||
currentAction.transactionInfo.sequenceNumber | ||
); | ||
if ( | ||
previousAction.transactionInfo.sequenceNumber === | ||
currentAction.transactionInfo.sequenceNumber | ||
) { | ||
testedAccountUpdateOrder = true; | ||
assert.ok( | ||
previousAction.accountUpdateId < currentAction.accountUpdateId | ||
); | ||
} | ||
} | ||
} | ||
assert.ok(testedAccountUpdateOrder); | ||
}); | ||
}); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You'll want another similar field for Events (this is for Actions) to include as part of the Events data, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need that, assuming events will not be sorted?