Skip to content

Commit

Permalink
feat(evm): add historical account data (#878)
Browse files Browse the repository at this point in the history
* take opts struct

* add history writer

* use history writer

* rename

* perform history lookup

* fix opts

* update rpc calls

* fix test

* style: resolve style guide violations
  • Loading branch information
oXtxNt9U authored Mar 10, 2025
1 parent d8ab7d6 commit e7d129b
Show file tree
Hide file tree
Showing 13 changed files with 861 additions and 109 deletions.
8 changes: 6 additions & 2 deletions packages/api-evm/source/actions/eth-get-balance.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { inject, injectable, tagged } from "@mainsail/container";
import { Contracts, Identifiers } from "@mainsail/contracts";

import { getHistoryHeightFromBlockTag } from "../utils/resolve-block-tag.js";

@injectable()
export class EthGetBalanceAction implements Contracts.Api.RPC.Action {
@inject(Identifiers.Evm.Instance)
Expand All @@ -20,8 +22,10 @@ export class EthGetBalanceAction implements Contracts.Api.RPC.Action {
};

public async handle(parameters: [string, string]): Promise<string> {
const [address] = parameters;
const accountInfo = await this.evm.getAccountInfo(address);
const [address, blockTag] = parameters;
const height = await getHistoryHeightFromBlockTag(blockTag);

const accountInfo = await this.evm.getAccountInfo(address, height);

return `0x${accountInfo.balance.toString(16)}`;
}
Expand Down
8 changes: 6 additions & 2 deletions packages/api-evm/source/actions/eth-get-code.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { inject, injectable, tagged } from "@mainsail/container";
import { Contracts, Identifiers } from "@mainsail/contracts";

import { getHistoryHeightFromBlockTag } from "../utils/resolve-block-tag.js";

@injectable()
export class EthGetCodeAction implements Contracts.Api.RPC.Action {
@inject(Identifiers.Evm.Instance)
Expand All @@ -20,7 +22,9 @@ export class EthGetCodeAction implements Contracts.Api.RPC.Action {
};

public async handle(parameters: [string, string]): Promise<any> {
const [address] = parameters;
return await this.evm.codeAt(address);
const [address, blockTag] = parameters;
const height = await getHistoryHeightFromBlockTag(blockTag);

return await this.evm.codeAt(address, height);
}
}
8 changes: 6 additions & 2 deletions packages/api-evm/source/actions/eth-get-transaction-count.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { inject, injectable, tagged } from "@mainsail/container";
import { Contracts, Identifiers } from "@mainsail/contracts";

import { getHistoryHeightFromBlockTag } from "../utils/resolve-block-tag.js";

@injectable()
export class EthGetTransactionCount implements Contracts.Api.RPC.Action {
@inject(Identifiers.Evm.Instance)
Expand All @@ -20,8 +22,10 @@ export class EthGetTransactionCount implements Contracts.Api.RPC.Action {
};

public async handle(parameters: [string, string]): Promise<string> {
const [address] = parameters;
const accountInfo = await this.evm.getAccountInfo(address);
const [address, blockTag] = parameters;
const height = await getHistoryHeightFromBlockTag(blockTag);

const accountInfo = await this.evm.getAccountInfo(address, height);

return `0x${accountInfo.nonce.toString(16)}`;
}
Expand Down
19 changes: 19 additions & 0 deletions packages/api-evm/source/utils/resolve-block-tag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,22 @@ export const resolveBlockTag = async (
}
}
};

export const getHistoryHeightFromBlockTag = async (
tag: string | Contracts.Crypto.BlockTag,
): Promise<bigint | undefined> => {
if (tag.startsWith("0x")) {
return BigInt(Number.parseInt(tag));
}

switch (tag as Contracts.Crypto.BlockTag) {
case "finalized":
case "latest":
case "safe": {
return undefined; // do not use history
}
default: {
throw new Error("invalid blockTag:" + tag);
}
}
};
4 changes: 2 additions & 2 deletions packages/contracts/source/contracts/evm/evm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export interface Instance extends CommitHandler {
process(txContext: TransactionContext): Promise<ProcessResult>;
view(viewContext: TransactionViewContext): Promise<ViewResult>;
initializeGenesis(commit: GenesisInfo): Promise<void>;
getAccountInfo(address: string): Promise<AccountInfo>;
getAccountInfo(address: string, height?: bigint): Promise<AccountInfo>;
getAccountInfoExtended(address: string, legacyAddress?: string): Promise<AccountInfoExtended>;
importAccountInfo(info: AccountInfoExtended): Promise<void>;
importLegacyColdWallet(wallet: ImportLegacyColdWallet): Promise<void>;
Expand All @@ -31,7 +31,7 @@ export interface Instance extends CommitHandler {
updateRewardsAndVotes(context: UpdateRewardsAndVotesContext): Promise<void>;
logsBloom(commitKey: CommitKey): Promise<string>;
stateHash(commitKey: CommitKey, currentHash: string): Promise<string>;
codeAt(address: string): Promise<string>;
codeAt(address: string, height?: bigint): Promise<string>;
storageAt(address: string, slot: bigint): Promise<string>;
mode(): EvmMode;
dispose(): Promise<void>;
Expand Down
9 changes: 6 additions & 3 deletions packages/evm-service/source/instances/evm.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,12 @@ describe<{
it("should call log hook", async ({ sandbox, instance }) => {
let hookCalled = 0;

const evm = new Evm(sandbox.app.dataPath("loghook"), (level, message) => {
//console.log("CALLED HOOK", { level, message, hookCalled });
hookCalled++;
const evm = new Evm({
path: sandbox.app.dataPath("loghook"),
logger: (level, message) => {
//console.log("CALLED HOOK", { level, message, hookCalled });
hookCalled++;
},
});

assert.equal(hookCalled, 0);
Expand Down
76 changes: 40 additions & 36 deletions packages/evm-service/source/instances/evm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,39 +14,43 @@ export class EvmInstance implements Contracts.Evm.Instance {

@postConstruct()
public initialize() {
this.#evm = new Evm(this.app.dataPath(), (level: LogLevel, message: string) => {
try {
switch (level) {
case LogLevel.Info: {
this.logger.info(message);
break;
this.#evm = new Evm({
historySize: 256n,
logger: (level: LogLevel, message: string) => {
try {
switch (level) {
case LogLevel.Info: {
this.logger.info(message);
break;
}
case LogLevel.Debug: {
this.logger.debug(message);
break;
}
case LogLevel.Notice: {
this.logger.notice(message);
break;
}
case LogLevel.Emergency: {
this.logger.emergency(message);
break;
}
case LogLevel.Alert: {
this.logger.alert(message);
break;
}
case LogLevel.Critical: {
this.logger.critical(message);
break;
}
case LogLevel.Warning: {
this.logger.warning(message);
break;
}
}
case LogLevel.Debug: {
this.logger.debug(message);
break;
}
case LogLevel.Notice: {
this.logger.notice(message);
break;
}
case LogLevel.Emergency: {
this.logger.emergency(message);
break;
}
case LogLevel.Alert: {
this.logger.alert(message);
break;
}
case LogLevel.Critical: {
this.logger.critical(message);
break;
}
case LogLevel.Warning: {
this.logger.warning(message);
break;
}
}
} catch {}
} catch {}
},
path: this.app.dataPath(),
});
}

Expand Down Expand Up @@ -82,8 +86,8 @@ export class EvmInstance implements Contracts.Evm.Instance {
});
}

public async getAccountInfo(address: string): Promise<Contracts.Evm.AccountInfo> {
return this.#evm.getAccountInfo(address);
public async getAccountInfo(address: string, height?: bigint): Promise<Contracts.Evm.AccountInfo> {
return this.#evm.getAccountInfo(address, height);
}

public async getAccountInfoExtended(
Expand Down Expand Up @@ -136,8 +140,8 @@ export class EvmInstance implements Contracts.Evm.Instance {
unit.setAccountUpdates(result.dirtyAccounts);
}

public async codeAt(address: string): Promise<string> {
return this.#evm.codeAt(address);
public async codeAt(address: string, height?: bigint): Promise<string> {
return this.#evm.codeAt(address, height);
}

public async storageAt(address: string, slot: bigint): Promise<string> {
Expand Down
35 changes: 33 additions & 2 deletions packages/evm/bindings/src/ctx.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
use std::str::FromStr;
use std::{path::PathBuf, str::FromStr};

use mainsail_evm_core::{db::CommitKey, legacy::LegacyAddress};
use napi::{JsBigInt, JsBuffer, JsString};
use napi::{JsBigInt, JsBuffer, JsFunction, JsString};
use napi_derive::napi;
use revm::primitives::{Address, Bytes, SpecId, B256, U256};

use crate::utils;

#[napi(object)]
pub struct JsEvmOptions {
pub path: JsString,
pub logger: Option<JsFunction>,
pub history_size: Option<JsBigInt>,
}

#[napi(object)]
pub struct JsTransactionContext {
pub caller: JsString,
Expand Down Expand Up @@ -175,6 +182,12 @@ pub struct UpdateRewardsAndVotesContext {
pub spec_id: SpecId,
}

pub struct EvmOptions {
pub path: PathBuf,
pub logger_callback: Option<JsFunction>,
pub history_size: Option<u64>,
}

#[derive(Debug)]
pub struct ExecutionContext {
pub caller: Address,
Expand Down Expand Up @@ -371,6 +384,24 @@ impl TryFrom<JsGenesisContext> for GenesisContext {
}
}

impl TryFrom<JsEvmOptions> for EvmOptions {
type Error = anyhow::Error;

fn try_from(value: JsEvmOptions) -> Result<Self, Self::Error> {
let history_size = if let Some(history_size) = value.history_size {
Some(history_size.get_u64()?.0)
} else {
None
};

Ok(EvmOptions {
path: value.path.into_utf8()?.into_owned()?.into(),
logger_callback: value.logger,
history_size,
})
}
}

impl TryFrom<JsCalculateActiveValidatorsContext> for CalculateActiveValidatorsContext {
type Error = anyhow::Error;

Expand Down
Loading

0 comments on commit e7d129b

Please sign in to comment.