-
Notifications
You must be signed in to change notification settings - Fork 14
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
Block helper functions for successful function calls #60
Changes from all commits
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 |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@near-lake/framework": patch | ||
--- | ||
|
||
Upgrade near-primitives to 0.5.0 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@near-lake/primitives": minor | ||
--- | ||
|
||
Block helper methods for successfulFunctionCalls and socialOperations |
Large diffs are not rendered by default.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
{ | ||
"name": "@near-lake/framework", | ||
"version": "0.1.3", | ||
"version": "0.1.4", | ||
"description": "JS Library to connect to the NEAR Lake S3 and stream the data", | ||
"author": "NEAR Inc <[email protected]>", | ||
"keywords": [ | ||
|
@@ -27,6 +27,6 @@ | |
}, | ||
"dependencies": { | ||
"@aws-sdk/client-s3": "^3.32.0", | ||
"@near-lake/primitives": "^0.4.0" | ||
"@near-lake/primitives": "^0.5.0" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,9 +1,15 @@ | ||||||||||||||||||||||||||||||||||||
import { Action, Receipt } from './receipts'; | ||||||||||||||||||||||||||||||||||||
import { StreamerMessage, ValidatorStakeView } from './core/types'; | ||||||||||||||||||||||||||||||||||||
import { Action, Receipt, Operation, FunctionCall, FunctionCallWrapper } from './receipts'; | ||||||||||||||||||||||||||||||||||||
import { StreamerMessage, ValidatorStakeView } from "./core/types"; | ||||||||||||||||||||||||||||||||||||
import { Transaction } from './transactions'; | ||||||||||||||||||||||||||||||||||||
import { Event, RawEvent, Log } from './events'; | ||||||||||||||||||||||||||||||||||||
import { StateChange } from './stateChanges'; | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
export type DecodedFunctionCall = { | ||||||||||||||||||||||||||||||||||||
methodName: string; | ||||||||||||||||||||||||||||||||||||
args: Record<string, any>; | ||||||||||||||||||||||||||||||||||||
receiptId: string; | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||||||
* The `Block` type is used to represent a block in the NEAR Lake Framework. | ||||||||||||||||||||||||||||||||||||
* | ||||||||||||||||||||||||||||||||||||
|
@@ -79,11 +85,14 @@ export class Block { | |||||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||||||
* Returns an Array of `Actions` executed in the block. | ||||||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||||||
actions(): Action[] { | ||||||||||||||||||||||||||||||||||||
actions(successfulOnly?: boolean): Action[] { | ||||||||||||||||||||||||||||||||||||
const actions: Action[] = this.streamerMessage.shards | ||||||||||||||||||||||||||||||||||||
.flatMap((shard) => shard.receiptExecutionOutcomes) | ||||||||||||||||||||||||||||||||||||
.filter((exeuctionOutcomeWithReceipt) => Action.isActionReceipt(exeuctionOutcomeWithReceipt.receipt)) | ||||||||||||||||||||||||||||||||||||
.map((exeuctionOutcomeWithReceipt) => Action.fromReceiptView(exeuctionOutcomeWithReceipt.receipt)) | ||||||||||||||||||||||||||||||||||||
.filter((executionOutcomeWithReceipt) => | ||||||||||||||||||||||||||||||||||||
Action.isActionReceipt(executionOutcomeWithReceipt.receipt) && | ||||||||||||||||||||||||||||||||||||
(!successfulOnly || (executionOutcomeWithReceipt.executionOutcome.outcome.status as {SuccessValue: any})) | ||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||
.map((executionOutcomeWithReceipt) => Action.fromReceiptView(executionOutcomeWithReceipt.receipt)) | ||||||||||||||||||||||||||||||||||||
.filter((action): action is Action => action !== null) | ||||||||||||||||||||||||||||||||||||
.map(action => action) | ||||||||||||||||||||||||||||||||||||
return actions | ||||||||||||||||||||||||||||||||||||
|
@@ -157,6 +166,90 @@ export class Block { | |||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||||||
* Decodes base64 encoded JSON data. Returns `undefined` if the data is not in the expected format. | ||||||||||||||||||||||||||||||||||||
* @param encodedValue | ||||||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||||||
base64decode(encodedValue: any) { | ||||||||||||||||||||||||||||||||||||
try { | ||||||||||||||||||||||||||||||||||||
const buff = Buffer.from(encodedValue, "base64"); | ||||||||||||||||||||||||||||||||||||
const str = buff.toString("utf-8").replace(/\\xa0/g, " "); | ||||||||||||||||||||||||||||||||||||
return JSON.parse(str); | ||||||||||||||||||||||||||||||||||||
} catch (error) { | ||||||||||||||||||||||||||||||||||||
console.error( | ||||||||||||||||||||||||||||||||||||
'Error parsing base64 JSON - skipping data', | ||||||||||||||||||||||||||||||||||||
error | ||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
Comment on lines
+179
to
+182
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. I don't really like the idea of libraries logging things, because they can't be suppressed. Let's just |
||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||||||
* Returns an Array of `DecodedFunctionCall` for the given `contract` and `method` if provided. | ||||||||||||||||||||||||||||||||||||
* If the `method` is not provided, it returns all the `DecodedFunctionCall`s for the given `contract`. | ||||||||||||||||||||||||||||||||||||
* Arguments to the function call are decoded from base64 to JSON. | ||||||||||||||||||||||||||||||||||||
* @param contract | ||||||||||||||||||||||||||||||||||||
* @param method | ||||||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||||||
successfulFunctionCalls(contract: string, method?: string) : DecodedFunctionCall[] { | ||||||||||||||||||||||||||||||||||||
return this | ||||||||||||||||||||||||||||||||||||
.actions(true) | ||||||||||||||||||||||||||||||||||||
.filter((action) => action.receiverId === contract) | ||||||||||||||||||||||||||||||||||||
.flatMap((action: Action) => | ||||||||||||||||||||||||||||||||||||
action.operations | ||||||||||||||||||||||||||||||||||||
.map((operation: Operation): FunctionCall => (operation as FunctionCallWrapper)?.FunctionCall) | ||||||||||||||||||||||||||||||||||||
.filter((functionCallOperation) => functionCallOperation && (!method || functionCallOperation?.methodName === method)) | ||||||||||||||||||||||||||||||||||||
.map((functionCallOperation) => ({ | ||||||||||||||||||||||||||||||||||||
...functionCallOperation, | ||||||||||||||||||||||||||||||||||||
args: this.base64decode(functionCallOperation.args), | ||||||||||||||||||||||||||||||||||||
receiptId: action.receiptId, | ||||||||||||||||||||||||||||||||||||
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.
Suggested change
It may be better to expose this as |
||||||||||||||||||||||||||||||||||||
})) | ||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||||||
* Returns data that follows the social.near contract template for key value data. | ||||||||||||||||||||||||||||||||||||
* @param operation The top level key to search for in each user's account space | ||||||||||||||||||||||||||||||||||||
* @param contract Defaults to 'social.near', pass in a different contract if needed | ||||||||||||||||||||||||||||||||||||
* @param method Defaults to 'set', pass in a different method if needed | ||||||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||||||
socialOperations(operation: string, contract: string = "social.near", method: string = "set") { | ||||||||||||||||||||||||||||||||||||
return this.successfulFunctionCalls(contract, method) | ||||||||||||||||||||||||||||||||||||
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. Can we add a return type here please? |
||||||||||||||||||||||||||||||||||||
.filter((functionCall) => { | ||||||||||||||||||||||||||||||||||||
if ( | ||||||||||||||||||||||||||||||||||||
!functionCall || | ||||||||||||||||||||||||||||||||||||
!functionCall.args || | ||||||||||||||||||||||||||||||||||||
!functionCall.args.data || | ||||||||||||||||||||||||||||||||||||
!Object.keys(functionCall.args.data) || | ||||||||||||||||||||||||||||||||||||
!Object.keys(functionCall.args.data)[0] | ||||||||||||||||||||||||||||||||||||
) { | ||||||||||||||||||||||||||||||||||||
console.error( | ||||||||||||||||||||||||||||||||||||
"Set operation did not have arg data in expected format" | ||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||
return; | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
const accountId = Object.keys(functionCall.args.data)[0]; | ||||||||||||||||||||||||||||||||||||
if (!functionCall.args.data[accountId]) { | ||||||||||||||||||||||||||||||||||||
Comment on lines
+218
to
+230
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.
Suggested change
I believe this is the same? Same comment around logging, can we please remove? Also we should be returning |
||||||||||||||||||||||||||||||||||||
console.error("Set operation did not have arg data for accountId"); | ||||||||||||||||||||||||||||||||||||
return; | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
const accountData = functionCall.args.data[accountId]; | ||||||||||||||||||||||||||||||||||||
if (!accountData) { | ||||||||||||||||||||||||||||||||||||
console.error( | ||||||||||||||||||||||||||||||||||||
"Set operation did not have arg data for accountId in expected format" | ||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||
return; | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
return accountData[operation]; | ||||||||||||||||||||||||||||||||||||
}) | ||||||||||||||||||||||||||||||||||||
.map((functionCall) => { | ||||||||||||||||||||||||||||||||||||
const accountId = Object.keys(functionCall.args.data)[0]; | ||||||||||||||||||||||||||||||||||||
return { | ||||||||||||||||||||||||||||||||||||
accountId, | ||||||||||||||||||||||||||||||||||||
data: functionCall.args.data[accountId][operation], | ||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
private buildActionsHashmap() { | ||||||||||||||||||||||||||||||||||||
const actions = new Map<string, Action>(); | ||||||||||||||||||||||||||||||||||||
this.actions().forEach(action => { | ||||||||||||||||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -137,7 +137,9 @@ class DeployContract { | |
constructor(readonly code: Uint8Array) { } | ||
}; | ||
|
||
class FunctionCall { | ||
export type FunctionCallWrapper = {FunctionCall: FunctionCall}; | ||
|
||
export class FunctionCall { | ||
constructor(readonly methodName: string, readonly args: Uint8Array, readonly gas: number, readonly deposit: string) { } | ||
}; | ||
|
||
|
@@ -167,7 +169,7 @@ class DeleteAccount { | |
export type Operation = | ||
| 'CreateAccount' | ||
| DeployContract | ||
| FunctionCall | ||
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. Was this incorrect? Is this a breaking change? 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. @pkudinov This file is where I ran into the Operations types not matching the data. I only changed FunctionCall but they should probably all be corrected. |
||
| FunctionCallWrapper | ||
| Transfer | ||
| Stake | ||
| AddKey | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -75,4 +75,24 @@ describe("Block", () => { | |
); | ||
expect(authorToPostId).toMatchSnapshot(); | ||
}); | ||
|
||
it("parses successful function calls", async () => { | ||
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. Can we add a test for |
||
let streamerMessageBuffer = await readFile( | ||
`${__dirname}/../../../blocks/117969098.json` | ||
); | ||
let streamerMessage = JSON.parse(streamerMessageBuffer.toString()); | ||
let block = Block.fromStreamerMessage(streamerMessage); | ||
|
||
expect(block.successfulFunctionCalls("social.near")).toMatchSnapshot(); | ||
}); | ||
|
||
it("parses social operations", async () => { | ||
let streamerMessageBuffer = await readFile( | ||
`${__dirname}/../../../blocks/117969098.json` | ||
); | ||
let streamerMessage = JSON.parse(streamerMessageBuffer.toString()); | ||
let block = Block.fromStreamerMessage(streamerMessage); | ||
|
||
expect(block.socialOperations("entities")).toMatchSnapshot(); | ||
}); | ||
}); |
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.
This assertion doesn't actually check if the action was only successful - it only checks if
status
exists, which it does for both successful/failure.as
is just a type cast, and won't affect the outcome of this.We need to check for the existence of
status.SuccessValue
, and perhapsstatus.SuccessReceiptId
too, instead.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.
@pkudinov hopefully your function is better than mine here.