Skip to content
This repository has been archived by the owner on Feb 26, 2024. It is now read-only.

fix: switch to SecureTrie #665

Merged
merged 1 commit into from
Nov 21, 2020
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 0 additions & 1 deletion src/chains/ethereum/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@
"emittery": "0.7.2",
"eth-sig-util": "2.5.3",
"ethereumjs-abi": "0.6.8",
"ethereumjs-account": "3.0.0",
"ethereumjs-block": "2.2.2",
"ethereumjs-common": "1.5.2",
"ethereumjs-tx": "2.1.2",
Expand Down
12 changes: 8 additions & 4 deletions src/chains/ethereum/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import Transaction from "./things/transaction";
import Wallet from "./wallet";
import { decode as rlpDecode } from "rlp";
import { $INLINE_JSON } from "ts-transformer-inline-file";
import keccak from "keccak";

import { PromiEvent, utils } from "@ganache/utils";
import Emittery from "emittery";
Expand Down Expand Up @@ -46,10 +45,11 @@ import { Hardfork } from "./options/chain-options";

// Read in the current ganache version from core's package.json
const { version } = $INLINE_JSON("../../../packages/ganache/package.json");
const { keccak } = utils;
//#endregion

//#region Constants
const RPCQUANTITY_ZERO = utils.RPCQUANTITY_ZERO;
const { RPCQUANTITY_ZERO } = utils;
const CLIENT_VERSION = `Ganache/v${version}/EthereumJS TestRPC/v${version}/ethereum-js`;
const PROTOCOL_VERSION = Data.from("0x3f");
const RPC_MODULES = {
Expand Down Expand Up @@ -347,10 +347,12 @@ export default class EthereumApi implements types.Api {
*
* @param address
* @param nonce
* @returns true if it worked
* @returns `true` if it worked
*/
@assertArgLength(2)
async evm_setAccountNonce(address: string, nonce: string) {
// TODO: the effect of this function could happen during a block mine operation, which would cause all sorts of
// issues. We need to figure out a good way of timing this.
return new Promise<boolean>((resolve, reject) => {
const buffer = Address.from(address).toBuffer();
const blockchain = this.#blockchain;
Expand All @@ -369,6 +371,8 @@ export default class EthereumApi implements types.Api {
return;
}

// TODO: do we need to mine a block here? The changes we're making really don't make any sense at all
// and produce an invalid trie going forward.
blockchain.mine(0).then(() => resolve(true), reject);
});
}
Expand Down Expand Up @@ -628,7 +632,7 @@ export default class EthereumApi implements types.Api {
*/
@assertArgLength(1)
async web3_sha3(data: string) {
return Data.from(keccak("keccak256").update(data).digest());
return Data.from(keccak(Buffer.from(data)));
}
//#endregion

Expand Down
60 changes: 31 additions & 29 deletions src/chains/ethereum/src/blockchain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,11 @@ import Emittery from "emittery";
import BlockManager from "./data-managers/block-manager";
import BlockLogs from "./things/blocklogs";
import TransactionManager from "./data-managers/transaction-manager";
import CheckpointTrie from "merkle-patricia-tree";
import SecureTrie from "merkle-patricia-tree/secure";
import { BN, KECCAK256_RLP } from "ethereumjs-util";
import Account from "./things/account";
import { promisify } from "util";
import { Quantity, Data } from "@ganache/utils";
import EthereumJsAccount from "ethereumjs-account";
import AccountManager from "./data-managers/account-manager";
import { utils } from "@ganache/utils";
import Transaction from "./things/transaction";
Expand Down Expand Up @@ -152,7 +151,7 @@ export default class Blockchain extends Emittery.Typed<
public transactionReceipts: Manager<TransactionReceipt>;
public accounts: AccountManager;
public vm: VM;
public trie: CheckpointTrie;
public trie: SecureTrie;

readonly #database: Database;
readonly #common: Common;
Expand Down Expand Up @@ -219,12 +218,12 @@ export default class Blockchain extends Emittery.Typed<
block: latest,
blockLogs: null
});
this.trie = new CheckpointTrie(
this.trie = new SecureTrie(
database.trie,
latest.header.stateRoot.toBuffer()
);
} else {
this.trie = new CheckpointTrie(database.trie, null);
this.trie = new SecureTrie(database.trie, null);
}

this.blockLogs = new BlockLogManager(database.blockLogs);
Expand Down Expand Up @@ -495,7 +494,7 @@ export default class Blockchain extends Emittery.Typed<
}

createVmFromStateTrie = (
stateTrie: CheckpointTrie,
stateTrie: SecureTrie,
allowUnlimitedContractSize: boolean
) => {
const blocks = this.blocks;
Expand All @@ -518,26 +517,20 @@ export default class Blockchain extends Emittery.Typed<
});
};

#commitAccounts = async (accounts: Account[]): Promise<void> => {
const stateManager = this.vm.stateManager;
const putAccount = promisify(stateManager.putAccount.bind(stateManager));
const checkpoint = promisify(stateManager.checkpoint.bind(stateManager));
const commit = promisify(stateManager.commit.bind(stateManager));
await checkpoint();
const l = accounts.length;
const pendingAccounts = Array(l);
for (let i = 0; i < l; i++) {
const account = accounts[i];
const ethereumJsAccount = new EthereumJsAccount();
(ethereumJsAccount.nonce = account.nonce.toBuffer()),
(ethereumJsAccount.balance = account.balance.toBuffer());
pendingAccounts[i] = putAccount(
account.address.toBuffer(),
ethereumJsAccount
);
}
await Promise.all(pendingAccounts);
await commit();
#commitAccounts = (accounts: Account[]) => {
return new Promise<void>((resolve, reject) => {
let length = accounts.length;
const cb = (err: Error) => {
if (err) reject(err);
else {
if (--length === 0) resolve(void 0);
}
};
for (let i = 0; i < length; i++) {
const account = accounts[i];
this.trie.put(account.address.toBuffer(), account.serialize(), cb);
}
});
};

#initializeGenesisBlock = async (
Expand Down Expand Up @@ -797,7 +790,7 @@ export default class Blockchain extends Emittery.Typed<
);

if (gasLeft >= 0) {
const stateTrie = new CheckpointTrie(
const stateTrie = new SecureTrie(
this.#database.trie,
parentBlock.header.stateRoot.toBuffer()
);
Expand Down Expand Up @@ -886,7 +879,7 @@ export default class Blockchain extends Emittery.Typed<
//
// TODO: Forking needs the forked block number passed during this step:
// https://github.com/trufflesuite/ganache-core/blob/develop/lib/blockchain_double.js#L917
const trie = new CheckpointTrie(
const trie = new SecureTrie(
this.#database.trie,
parentBlock.header.stateRoot.toBuffer()
);
Expand Down Expand Up @@ -1136,7 +1129,16 @@ export default class Blockchain extends Emittery.Typed<

// clean up listeners
this.vm.removeAllListeners();
this.transactions.transactionPool.clearListeners();

// pause processing new transactions...
await this.transactions.pause();

// then pause the miner, too.
await this.#miner.pause();

// wait for anything in the process of being saved to finish up
await this.#blockBeingSavedPromise;

await this.emit("stop");

if (this.#state === Status.started) {
Expand Down
4 changes: 2 additions & 2 deletions src/chains/ethereum/src/data-managers/account-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { LevelUp } from "levelup";
import { rlp } from "ethereumjs-util";
import { utils, Quantity } from "@ganache/utils";

const RPCQUANTITY_ZERO = utils.RPCQUANTITY_ZERO;
const { keccak, RPCQUANTITY_ZERO } = utils;

export default class AccountManager {
#blockchain: Blockchain;
Expand All @@ -25,7 +25,7 @@ export default class AccountManager {
const block = await blockchain.blocks.get(blockNumber);
const trieCopy = new Trie(this.#trie, block.header.stateRoot.toBuffer());
return new Promise((resolve, reject) => {
trieCopy.get(address.toBuffer(), (err, data) => {
trieCopy.get(keccak(address.toBuffer()), (err: Error, data: Buffer) => {
if (err) return reject(err);
resolve(data);
});
Expand Down
16 changes: 7 additions & 9 deletions src/chains/ethereum/src/things/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export default class Account {
}

public static fromBuffer(buffer: Buffer) {
const account = Object.create(Account);
const account = Object.create(Account.prototype);
const arr = (rlp.decode(buffer) as any) as [Buffer, Buffer, Buffer, Buffer];
account.nonce = Quantity.from(arr[0]);
account.balance = Quantity.from(arr[1]);
Expand All @@ -30,13 +30,11 @@ export default class Account {
}

public serialize() {
return rlp.encode(
Buffer.concat([
this.nonce.toBuffer(),
this.balance.toBuffer(),
this.stateRoot,
this.codeHash
])
);
return rlp.encode([
this.nonce.toBuffer(),
this.balance.toBuffer(),
this.stateRoot,
this.codeHash
]);
}
}
2 changes: 1 addition & 1 deletion src/chains/ethereum/tests/api/eth/eth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ describe("api", () => {
});

describe("eth_getTransactionCount", () => {
it("should get the tranasction count of the block", async () => {
it("should get the transaction count of the block", async () => {
const tx = {
from: accounts[0],
to: accounts[1],
Expand Down
6 changes: 3 additions & 3 deletions src/chains/ethereum/tests/api/eth/subscribe.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,20 +74,20 @@ describe("api", () => {
gasLimit: gasLimit,
gasUsed: "0x0",
hash:
"0xf821422e084d82d550019e555b656b9113c9af45c4c03fad670caaa9b5d8acde",
"0x7ffe37737a6a39477f0cd9eeb632a6b393c440ad17b6e82fcbe1ebf6556d768c",
logsBloom: `0x${"0".repeat(512)}`,
miner: `0x${"0".repeat(40)}`,
mixHash: `0x${"0".repeat(64)}`,
nonce: "0x0000000000000000",
number: Quantity.from(startingBlockNumber + 1).toString(),
parentHash:
"0x746144f35cfbcc1bb8ea1dcd540b674c81cc25ffec8fa1ec42d444cba9678cc2",
"0x48ba6f672feca71b75568153ce81c75d0a8074c935d4ab2a23fc7dd64dd13fe2",
receiptsRoot:
"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
sha3Uncles:
"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
stateRoot:
"0x8281cb204e0242d2d9178e392b60eaf4563ae5ffc4897c9c6cf6e99a4d35aff3",
"0xf0d9daf6f1dd86ae11b32005e9717845db40b6ddd926a37973922a957c400071",
timestamp: Quantity.from(timestamp).toString(),
transactionsRoot:
"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"
Expand Down
19 changes: 19 additions & 0 deletions src/packages/utils/npm-shrinkwrap.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion src/packages/utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@
"tooling"
],
"dependencies": {
"emittery": "0.7.2"
"emittery": "0.7.2",
"keccak": "3.0.1"
},
"devDependencies": {
"uWebSockets.js": "github:uNetworking/uWebSockets.js#v18.4.0"
Expand Down
1 change: 1 addition & 0 deletions src/packages/utils/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export * from "./has-own";
export * from "./uint-to-buffer";
export * from "./constants";
export * from "./buffer-to-key";
export * from "./keccak";
27 changes: 27 additions & 0 deletions src/packages/utils/src/utils/keccak.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import makeKeccak, { Keccak } from "keccak";

const RATE = 1088;
const CAPACITY = 512;

const instance = makeKeccak("keccak256") as {
_state: {
absorb: (buffer: Buffer) => void;
squeeze: (length: number) => Buffer;
initialize: (rate: number, capacity: number) => void;
};
_finalized: boolean;
};

/**
* keccak256, but faster if you use it a lot.
* @param buffer
*/
export function keccak(buffer: Buffer) {
instance._state.absorb(buffer);
const digest = instance._state.squeeze(32);
// reset and remove result from memory
instance._state.initialize(RATE, CAPACITY);
// make this re-usable
instance._finalized = false;
return digest;
}