Skip to content

Commit

Permalink
Implement function calls and blockDate
Browse files Browse the repository at this point in the history
  • Loading branch information
pkudinov committed Jun 27, 2024
1 parent d33ca61 commit 5e6614f
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 56 deletions.
28 changes: 28 additions & 0 deletions packages/near-lake-primitives/src/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { ExecutionStatus, ReceiptStatusFilter } from "./types/core/types";

function isMatchingReceiverSingle(receiverId: string, wildcardFilter: string) {
if (wildcardFilter === '*') {
return true;
}
const regExp = new RegExp(wildcardFilter
.replace(/\*/g, '[\\w\\d]+')
.replace(/./g, '\.')
);
return regExp.test(receiverId);
}

export function isMatchingReceiver(receiverId: string, contractFilter: string): boolean {
const filters = contractFilter.split(',').map(f => f.trim());
return filters.some(f => isMatchingReceiverSingle(receiverId, f));
}

export function isMatchingReceiptStatus(receiptStatus: ExecutionStatus, statusFilter: ReceiptStatusFilter): boolean {
switch (statusFilter) {
case "all": return true;
case "onlySuccessful":
return receiptStatus.hasOwnProperty('SuccessValue')
|| receiptStatus.hasOwnProperty('SuccessReceiptId');
case "onlyFailed":
return receiptStatus.hasOwnProperty('Failure');
}
}
52 changes: 35 additions & 17 deletions packages/near-lake-primitives/src/types/block.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Action, Receipt } from './receipts';
import { StreamerMessage, ValidatorStakeView } from './core/types';
import { Transaction } from './transactions';
import { Event, RawEvent, Log } from './events';
import { StateChange } from './stateChanges';
import { Action, FunctionCall, Receipt } from "./receipts";
import { FunctionCallView, ReceiptStatusFilter, StreamerMessage, ValidatorStakeView } from "./core/types";
import { Transaction } from "./transactions";
import { Event, Log } from "./events";
import { StateChange } from "./stateChanges";
import { isMatchingReceiptStatus, isMatchingReceiver } from "../helpers";

/**
* The `Block` type is used to represent a block in the NEAR Lake Framework.
Expand Down Expand Up @@ -55,6 +56,15 @@ export class Block {
return this.header().height;
}

/**
* Returns the block date in ISO format, e.g. 2022-01-01.
*/
get blockDate(): string {
return new Date(this.streamerMessage.block.header.timestamp / 1000000)
.toISOString()
.substring(0, 10);
}

/**
* Returns a `BlockHeader` structure of the block
* See `BlockHeader` structure sections for details.
Expand All @@ -80,24 +90,32 @@ export class Block {
* Returns an Array of `Actions` executed in the block.
*/
actions(): Action[] {
const actions: Action[] = this.streamerMessage.shards
.flatMap((shard) => shard.receiptExecutionOutcomes)
.filter((exeuctionOutcomeWithReceipt) => Action.isActionReceipt(exeuctionOutcomeWithReceipt.receipt))
.map((exeuctionOutcomeWithReceipt) => Action.fromReceiptView(exeuctionOutcomeWithReceipt.receipt))
.filter((action): action is Action => action !== null)
.map(action => action)
return actions
return this.streamerMessage.shards
.flatMap((shard) => shard.receiptExecutionOutcomes)
.filter((executionOutcomeWithReceipt) => Action.isActionReceipt(executionOutcomeWithReceipt.receipt))
.map((executionOutcomeWithReceipt) => Action.fromOutcomeWithReceipt(executionOutcomeWithReceipt))
.filter((action): action is Action => action !== null)
}

/**
* Returns an Array of function calls executed in the block.
*/
functionCalls(contractFilter="*", statusFilter: ReceiptStatusFilter="onlySuccessful"): FunctionCallView[] {
return this.actions()
.filter(action =>
isMatchingReceiver(action.receiverId, contractFilter)
&& isMatchingReceiptStatus(action.receiptStatus, statusFilter))
.flatMap(a =>
a.operations
.filter(op => op instanceof FunctionCall)
.map(op => FunctionCallView.fromFunctionCall(op as FunctionCall)));
}

/**
* Returns `Events` emitted in the block.
*/
events(): Event[] {
const events = this.receipts().flatMap((executedReceipt) => executedReceipt.logs.filter(RawEvent.isEvent).map(RawEvent.fromLog).map((rawEvent) => {
let event: Event = { relatedReceiptId: executedReceipt.receiptId, rawEvent: rawEvent }
return event
}))
return events
return this.receipts().flatMap((receipt) => receipt.events);
}

/**
Expand Down
48 changes: 36 additions & 12 deletions packages/near-lake-primitives/src/types/core/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { FunctionCall } from "../receipts";

export type BlockHeight = number;

export interface StreamerMessage {
Expand Down Expand Up @@ -162,22 +164,24 @@ type ExecutionProof = {
hash: string;
};

export type ReceiptExecutionOutcome = {
executorId: string;
gasBurnt: number;
logs: string[];
metadata: {
gasProfile: string | null;
version: number;
};
receiptIds: string[];
status: ExecutionStatus;
tokensBurnt: string;
}

export type ExecutionOutcomeWithReceipt = {
executionOutcome: {
blockHash: string;
id: string;
outcome: {
executorId: string;
gasBurnt: number;
logs: string[];
metadata: {
gasProfile: string | null;
version: number;
};
receiptIds: string[];
status: ExecutionStatus;
tokensBurnt: string;
};
outcome: ReceiptExecutionOutcome;
proof: ExecutionProof[];
};
receipt: ReceiptView;
Expand Down Expand Up @@ -311,3 +315,23 @@ export type StateChangeWithCauseView = {
};
type: string;
};

export type ReceiptStatusFilter = "all"|"onlySuccessful"|"onlyFailed";

export class FunctionCallView {
constructor(
readonly methodName: string,
readonly args: JSON,
readonly gas: number,
readonly deposit: string
) {}

static fromFunctionCall(functionCall: FunctionCall): FunctionCallView {
return new FunctionCallView(
functionCall.methodName,
JSON.parse(Buffer.from(functionCall.args).toString('ascii')),
functionCall.gas,
functionCall.deposit
)
}
}
4 changes: 2 additions & 2 deletions packages/near-lake-primitives/src/types/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ export type Log = {
export class Event {
constructor(readonly relatedReceiptId: string, readonly rawEvent: RawEvent) { }

static fromLog = (log: string): Event => {
static fromLog = (log: string, relatedReceiptId=''): Event => {
const rawEvent = RawEvent.fromLog(log);
return new Event('', rawEvent);
return new Event(relatedReceiptId, rawEvent);
}
}

Expand Down
75 changes: 50 additions & 25 deletions packages/near-lake-primitives/src/types/receipts.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

import { ExecutionOutcomeWithReceipt, ExecutionStatus, ReceiptView, ActionReceipt } from './core/types';
import { Events, Event } from './events';
import { Events, Event, RawEvent } from "./events";

/**
* This field is a simplified representation of the `ReceiptView` structure from [near-primitives](https://github.com/near/nearcore/tree/master/core/primitives).
Expand Down Expand Up @@ -40,15 +40,17 @@ export class Receipt implements Events {
/**
* The original logs of the corresponding `ExecutionOutcome` of the `Receipt`.
*
* **Note:** not all of the logs might be parsed as JSON Events (`Events`).
* **Note:** not all the logs might be parsed as JSON Events (`Events`).
*/
readonly logs: string[] = []) { }

/**
* Returns an Array of `Events` for the `Receipt`, if any. This might be empty if the `logs` field is empty or doesn’t contain JSON Events compatible log records.
*/
get events(): Event[] {
return this.logs.map(Event.fromLog).filter((e): e is Event => e !== undefined);
return this.logs
.filter(log => RawEvent.isEvent(log))
.map(log => Event.fromLog(log, this.receiptId));
}

static fromOutcomeWithReceipt = (outcomeWithReceipt: ExecutionOutcomeWithReceipt): Receipt => {
Expand Down Expand Up @@ -85,7 +87,12 @@ export class Action {
/**
* The id of the corresponding `Receipt`
*/
readonly receiptId: string,
readonly receiptId: string,

/**
* The status of the corresponding `Receipt`
*/
readonly receiptStatus: ExecutionStatus,

/**
* The predecessor account id of the corresponding `Receipt`.
Expand All @@ -112,52 +119,70 @@ export class Action {
/**
* An array of `Operation` for this `ActionReceipt`
*/
readonly operations: Operation[]) { }
readonly operations: Operation[],

/**
* An array of log messages for this `ActionReceipt`
*/
readonly logs: string[] = []

) { }

/**
* An array of events complying to NEP-0297 standard for this `ActionReceipt`
*/
get event(): Event[] {
return this.logs
.filter(RawEvent.isEvent)
.map(log => Event.fromLog(log, this.receiptId))
}

static isActionReceipt = (receipt: ReceiptView) => {
if (typeof receipt.receipt === "object" && receipt.receipt.constructor.name === "ActionReceipt") return true
return true
return typeof receipt.receipt === "object" && receipt.receipt.constructor.name === "ActionReceipt";
}

static fromReceiptView = (receipt: ReceiptView): Action | null => {
if (!this.isActionReceipt(receipt)) return null
const { Action } = receipt.receipt as ActionReceipt;
return {
receiptId: receipt.receiptId,
predecessorId: receipt.predecessorId,
receiverId: receipt.receiverId,
signerId: Action.signerId,
signerPublicKey: Action.signerPublicKey,
operations: Action.actions.map((a) => a) as Operation[],
};
static fromOutcomeWithReceipt = (outcomeWithReceipt: ExecutionOutcomeWithReceipt): Action | null => {
if (!this.isActionReceipt(outcomeWithReceipt.receipt)) return null
const receiptView = outcomeWithReceipt.receipt;
const { Action: action } = receiptView.receipt as ActionReceipt;
return new Action(
receiptView.receiptId,
outcomeWithReceipt.executionOutcome.outcome.status,
receiptView.predecessorId,
receiptView.receiverId,
action.signerId,
action.signerPublicKey,
action.actions.map((a) => a) as Operation[],
outcomeWithReceipt.executionOutcome.outcome.logs
);
}
};

class DeployContract {
export class DeployContract {
constructor(readonly code: Uint8Array) { }
};

class FunctionCall {
export class FunctionCall {
constructor(readonly methodName: string, readonly args: Uint8Array, readonly gas: number, readonly deposit: string) { }
};

class Transfer {
export class Transfer {
constructor(readonly deposit: string) { }
};

class Stake {
export class Stake {
constructor(readonly stake: number, readonly publicKey: string) { }
};

class AddKey {
export class AddKey {
constructor(readonly publicKey: string, readonly accessKey: AccessKey) { }
};

class DeleteKey {
export class DeleteKey {
constructor(readonly publicKey: string) { }
};

class DeleteAccount {
export class DeleteAccount {
constructor(readonly beneficiaryId: string) { }
};

Expand Down
12 changes: 12 additions & 0 deletions packages/near-lake-primitives/test/functionCalls.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { getBlock } from "./helpers";

describe("Block", () => {
describe("functionCalls", () => {
it("gets successful function calls", async () => {
let block = await getBlock(105793821);
const functionCalls = block.functionCalls();
console.log(JSON.stringify(functionCalls, null, 2));

});
});
});
10 changes: 10 additions & 0 deletions packages/near-lake-primitives/test/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { readFile } from "fs/promises";
import { Block } from "../src";

export async function getBlock(blockHeight: number) {
const streamerMessageBuffer = await readFile(
`${__dirname}/../../../blocks/${blockHeight}.json`
);
const streamerMessage = JSON.parse(streamerMessageBuffer.toString());
return Block.fromStreamerMessage(streamerMessage);
}

0 comments on commit 5e6614f

Please sign in to comment.