Skip to content
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

Kk/package metadata fixes #77

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
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
5 changes: 2 additions & 3 deletions packages/chain/package.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
{
"name": "@deth/chain",
"name": "@ethereum-ts/chain",
"description": "Local ethereum chain with sane api.",
"version": "0.1.0",
"license": "MIT",
"main": "./dist/cjs/index.js",
"module": "./dist/esm/index.js",
"types": "./dist/esm/index.d.ts",
"engines": {
"node": "^11.15.0",
"yarn": ">=1.15.2"
"node": ">=8.0.0"
},
"scripts": {
"lint": "eslint --ext .ts src test",
Expand Down
2 changes: 1 addition & 1 deletion packages/chain/src/Chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ export class Chain {
): Promise<RpcBlockResponse | RpcRichBlockResponse>

async getBlock (blockTagOrHash: Quantity | Tag | Hash, includeTransactions?: boolean) {
if (blockTagOrHash === 'pending') {
if (blockTagOrHash === 'pending' || blockTagOrHash === 'earliest') {
throw unsupportedBlockTag('call', blockTagOrHash)
}

Expand Down
8 changes: 6 additions & 2 deletions packages/chain/src/model/FilterRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,16 @@ export interface FilterByRangeRequest {
fromBlock?: Quantity | Tag,
toBlock?: Quantity | Tag,
address?: Address,
topics?: unknown,
topics?: Hash[],
}

// https://github.com/ethers-io/ethers.js/blob/4ac08432b8e2c7c374dc4a0e141a39a369e2d430/src.ts/providers/base-provider.ts#L370
export interface FilterByBlockRequest {
blockHash?: Hash,
address?: Address,
topics?: unknown,
topics?: Hash[],
}

export function isByBlockRequest (filter: FilterRequest): filter is FilterByBlockRequest {
return (filter as any).blockHash
}
5 changes: 5 additions & 0 deletions packages/chain/src/model/primitives/Hash.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Opaque } from 'ts-essentials'
import { HEX_REGEX } from './common'
import { bufferToHex, toBuffer } from 'ethereumjs-util'
import { isString } from 'lodash'

/**
* A hexadecimal string representing a hash.
Expand All @@ -21,3 +22,7 @@ export function bufferToHash (buffer: Buffer): Hash {
export function hashToBuffer (hash: Hash): Buffer {
return toBuffer(hash)
}

export function isHash (value: any): value is Hash {
return isString(value) && value.length === 66
}
60 changes: 26 additions & 34 deletions packages/chain/src/vm/SaneVM.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import VM from 'ethereumts-vm'
import Block from 'ethereumjs-block'
import { BN, toBuffer } from 'ethereumjs-util'
import { Transaction } from 'ethereumjs-tx'
import { RpcTransactionReceipt, RpcTransactionResponse, RpcBlockResponse, toBlockResponse } from '../model'
import { RpcBlockResponse, toBlockResponse } from '../model'
import { ChainOptions } from '../ChainOptions'
import { Hash, Address, bufferToHash, Quantity, bufferToQuantity, HexData, bufferToHexData } from '../model'
import { initializeVM } from './initializeVM'
import { getLatestBlock } from './getLatestBlock'
import { putBlock } from './putBlock'
import { runIsolatedTransaction } from './runIsolatedTransaction'
import { DethStateManger } from './storage/DethStateManger'
Expand All @@ -18,6 +15,7 @@ import { InterpreterStep } from 'ethereumts-vm/dist/evm/interpreter'
import { BlockchainAdapter } from './storage/BlockchainAdapter'
import { StateManagerAdapter } from './storage/StateManagerAdapter'
import { Snapshot } from '../utils/Snapshot'
import { assert } from 'ts-essentials'

interface VMSnapshot {
blockchain: DethBlockchain,
Expand All @@ -32,18 +30,19 @@ export class SaneVM {
vm!: VM
state: Snapshot<{ stateManger: DethStateManger, blockchain: DethBlockchain }>
pendingTransactions: Transaction[] = []
transactions: Map<Hash, RpcTransactionResponse> = new Map()
receipts: Map<Hash, RpcTransactionReceipt> = new Map()
snapshots: VMSnapshot[] = []

constructor (private options: ChainOptions) {
this.state = new Snapshot({
stateManger: new DethStateManger(),
blockchain: new DethBlockchain(),
}, (t) => ({
blockchain: t.blockchain.copy(),
stateManger: t.stateManger.copy(),
}))
this.state = new Snapshot(
{
stateManger: new DethStateManger(),
blockchain: new DethBlockchain(),
},
t => ({
blockchain: t.blockchain.copy(),
stateManger: t.stateManger.copy(),
}),
)
}

async init () {
Expand Down Expand Up @@ -73,13 +72,17 @@ export class SaneVM {
;(this.vm as any).pStateManager = new PStateManager(stateManagerAdapter as any)
}

async getBlockNumber (): Promise<Quantity> {
const block = await getLatestBlock(this.vm)
getBlockNumber (): Quantity {
const block = this.state.value.blockchain.getLatestBlock()
assert(block, 'Blockchain is empty (no genesis block was generated)')

return bufferToQuantity(block.header.number)
}

async getLatestBlock (): Promise<RpcBlockResponse> {
const block = await getLatestBlock(this.vm)
getLatestBlock (): RpcBlockResponse {
const block = this.state.value.blockchain.getLatestBlock()
assert(block, 'Blockchain is empty (no genesis block was generated)')

return toBlockResponse(block)
}

Expand All @@ -93,22 +96,15 @@ export class SaneVM {
const transactions = this.pendingTransactions
this.pendingTransactions = []

const { receipts, responses } = await putBlock(this.vm, transactions, this.options, clockSkew)

for (const receipt of receipts) {
this.receipts.set(receipt.transactionHash, receipt)
}
for (const response of responses) {
this.transactions.set(response.hash, response)
}
await putBlock(this.vm, this.state.value.blockchain, transactions, this.options, clockSkew)
}

getTransaction (hash: Hash) {
return this.transactions.get(hash)
return this.state.value.blockchain.getTransaction(hash)
}

getTransactionReceipt (hash: Hash) {
return this.receipts.get(hash)
return this.state.value.blockchain.getTransactionReceipt(hash)
}

async getNonce (address: Address) {
Expand All @@ -131,14 +127,10 @@ export class SaneVM {
}

async runIsolatedTransaction (transaction: Transaction, clockSkew: number) {
return runIsolatedTransaction(this.vm, transaction, this.options, clockSkew)
return runIsolatedTransaction(this.vm, this.state.value.blockchain, transaction, this.options, clockSkew)
}

async getBlock (hashOrNumber: string): Promise<RpcBlockResponse> {
const query = hashOrNumber.length === 66 ? toBuffer(hashOrNumber) : new BN(hashOrNumber.substr(2), 'hex')
const block = await new Promise<Block>((resolve, reject) => {
this.vm.blockchain.getBlock(query, (err: unknown, block: Block) => (err != null ? reject(err) : resolve(block)))
})
return toBlockResponse(block)
async getBlock (hashOrNumber: Quantity | Hash): Promise<RpcBlockResponse> {
return toBlockResponse(this.state.value.blockchain.getBlock(hashOrNumber))
}
}
11 changes: 0 additions & 11 deletions packages/chain/src/vm/getLatestBlock.ts

This file was deleted.

15 changes: 9 additions & 6 deletions packages/chain/src/vm/getNextBlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,24 @@ import { Transaction } from 'ethereumjs-tx'
import BN from 'bn.js'
import { toBuffer } from 'ethereumjs-util'
import { ChainOptions } from '../ChainOptions'
import { getLatestBlock } from './getLatestBlock'
import { assert } from 'ts-essentials'
import { DethBlockchain } from './storage/DethBlockchain'

export async function getNextBlock (
vm: VM,
blockchain: DethBlockchain,
transactions: Transaction[],
options: ChainOptions,
clockSkew: number,
): Promise<Block> {
const block = await getEmptyNextBlock(vm, options, clockSkew)
const block = await getEmptyNextBlock(vm, blockchain, options, clockSkew)
await addTransactionsToBlock(block, transactions)
return block
}

async function getEmptyNextBlock (vm: VM, options: ChainOptions, clockSkew: number) {
const latestBlock = await getLatestBlock(vm)
async function getEmptyNextBlock (vm: VM, blockchain: DethBlockchain, options: ChainOptions, clockSkew: number) {
const latestBlock = blockchain.getLatestBlock()
assert(latestBlock, 'Blockchain is empty (no genesis block was generated)')

const header: BlockHeaderData = {
gasLimit: options.blockGasLimit,
Expand All @@ -29,7 +32,7 @@ async function getEmptyNextBlock (vm: VM, options: ChainOptions, clockSkew: numb
coinbase: options.coinbaseAddress,
}
const block = new Block({ header }, { common: vm._common })
block.validate = (blockchain, cb) => cb(null)
block.validate = (_blockchain, cb) => cb(null)
block.header.difficulty = toBuffer(block.header.canonicalDifficulty(latestBlock))

return block
Expand All @@ -38,7 +41,7 @@ async function getEmptyNextBlock (vm: VM, options: ChainOptions, clockSkew: numb
async function addTransactionsToBlock (block: Block, transactions: Transaction[]) {
block.transactions.push(...transactions)
await new Promise((resolve, reject) => {
block.genTxTrie(err => err != null ? reject(err) : resolve())
block.genTxTrie(err => (err != null ? reject(err) : resolve()))
})
block.header.transactionsTrie = block.txTrie.root
}
22 changes: 16 additions & 6 deletions packages/chain/src/vm/putBlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,16 @@ import { Transaction } from 'ethereumjs-tx'
import { ChainOptions } from '../ChainOptions'
import { getNextBlock } from './getNextBlock'
import { getReceiptsAndResponses } from './getReceiptsAndResponses'
import { DethBlockchain } from './storage/DethBlockchain'

export async function putBlock (vm: VM, transactions: Transaction[], options: ChainOptions, clockSkew: number) {
const block = await getNextBlock(vm, transactions, options, clockSkew)
export async function putBlock (
vm: VM,
blockchain: DethBlockchain,
transactions: Transaction[],
options: ChainOptions,
clockSkew: number,
) {
const block = await getNextBlock(vm, blockchain, transactions, options, clockSkew)

const { results } = await vm.runBlock({
block,
Expand All @@ -16,10 +23,13 @@ export async function putBlock (vm: VM, transactions: Transaction[], options: Ch
skipBalance: options.skipBalanceCheck,
})
await new Promise((resolve, reject) => {
vm.blockchain.putBlock(block, (err: unknown, block: Block) =>
err != null ? reject(err) : resolve(block),
)
vm.blockchain.putBlock(block, (err: unknown, block: Block) => (err != null ? reject(err) : resolve(block)))
})

return getReceiptsAndResponses(block, transactions, results)
const { receipts, responses } = getReceiptsAndResponses(block, transactions, results)

blockchain.addReceipts(receipts)
blockchain.addTransactions(responses)

return { receipts, responses }
}
4 changes: 3 additions & 1 deletion packages/chain/src/vm/runIsolatedTransaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import { Transaction } from 'ethereumjs-tx'
import { RunTxResult } from 'ethereumts-vm/dist/runTx'
import { getNextBlock } from './getNextBlock'
import { ChainOptions } from '../ChainOptions'
import { DethBlockchain } from './storage/DethBlockchain'

export async function runIsolatedTransaction (
vm: VM,
blockchain: DethBlockchain,
transaction: Transaction,
options: ChainOptions,
clockSkew: number,
Expand All @@ -15,7 +17,7 @@ export async function runIsolatedTransaction (
const initialStateRoot = await psm.getStateRoot()

try {
const block = await getNextBlock(vm, [transaction], options, clockSkew)
const block = await getNextBlock(vm, blockchain, [transaction], options, clockSkew)
const result = await vm.runTx({
block,
tx: transaction,
Expand Down
57 changes: 57 additions & 0 deletions packages/chain/src/vm/storage/BlockchainRepository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { range, flatten } from 'lodash'

import { Snapshot } from '../../utils/Snapshot'
import { DethStateManger } from './DethStateManger'
import { DethBlockchain } from './DethBlockchain'
import { RpcBlockResponse, numberToQuantity, toBlockResponse, RpcTransactionReceipt } from '../../model'
import { isByBlockRequest, FilterRequest, RpcLogObject } from '../..'

export class BlockchainRepository {
constructor (private readonly state: Snapshot<{ stateManger: DethStateManger, blockchain: DethBlockchain }>) {}

// @todo: super slow impl
getAllReceipts (blocks: RpcBlockResponse[]): RpcTransactionReceipt[] {
const validBlockHashes = blocks.map(b => b.hash)

const receipts: RpcTransactionReceipt[] = []
for (const receipt of this.state.value.blockchain.receipts.values()) {
if (validBlockHashes.includes(receipt.blockHash)) {
receipts.push(receipt)
}
}

return receipts
}

getLogs (filter: FilterRequest): RpcLogObject[] {
const blocks: RpcBlockResponse[] = []
if (isByBlockRequest(filter)) {
blocks.push(toBlockResponse(this.state.value.blockchain.getBlockByHash(filter.blockHash!)))
} else {
// TODO: fix handling for tags!
blocks.push(...this.getBlockByRange(filter.fromBlock as any, (filter.toBlock as any)!))
}

const receipts = this.getAllReceipts(blocks)
let logs = flatten(receipts.map(r => r.logs))

if (filter.address) {
logs = logs.filter(r => r.address === filter.address)
}

if (filter.topics) {
// @todo implement
throw new Error('unimplemented')
// logs = logs.filter(r => r.topics)
}

return logs
}

getBlockByRange (start: number, _last?: number): RpcBlockResponse[] {
const last = _last ?? this.state.value.blockchain.getBlockNumber()
return range(start, last)
.map(n => this.state.value.blockchain.getBlockByNumber(numberToQuantity(n)))
.map(toBlockResponse)
}
}
Loading