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

Block library refactoring #883

Merged
merged 29 commits into from
Oct 12, 2020
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
c053761
block -> refactor: reworked header class with static factory instanti…
holgerd77 Sep 21, 2020
ff46c55
block -> refactoring: added new static factory helpers to block class
holgerd77 Sep 21, 2020
dd228bc
block -> refactor: fix build errors, remove unused imports, unpad num…
jochem-brouwer Sep 24, 2020
608d804
block -> rename Header to BlockHeader
jochem-brouwer Sep 28, 2020
17a33a8
block/tx -> fix block tests
jochem-brouwer Sep 28, 2020
72b5b78
block -> enforce BNs on fields which are interpreted as numbers
jochem-brouwer Sep 28, 2020
8adcdf9
block -> edge case in toBN
jochem-brouwer Sep 29, 2020
8987003
ethash -> make ethash compatible with block
jochem-brouwer Sep 29, 2020
5f5e9f2
Merge branch 'master' into refactor-block-library
ryanio Oct 7, 2020
5351fb8
have validateTransactions return a string[] (https://github.com/ether…
ryanio Oct 7, 2020
ce1dac1
let => const
ryanio Oct 7, 2020
526f986
set default param to resolve js runtime check
ryanio Oct 7, 2020
75689e6
continue refactoring and simplifying methods
ryanio Oct 8, 2020
8923f71
api updates
ryanio Oct 8, 2020
6a2c193
continuing work
ryanio Oct 8, 2020
381f5e1
inline buffer validations. add checks for extraData, mixHash and nonce
ryanio Oct 8, 2020
7eecf80
various fixups
ryanio Oct 8, 2020
395c6f8
continuing various work
ryanio Oct 9, 2020
be5c8d2
continuing work and refactoring
ryanio Oct 9, 2020
7fa486d
Merge branch 'master' into refactor-block-library
ryanio Oct 9, 2020
91d45d7
re-add timestamp to genesis (for rinkeby)
ryanio Oct 9, 2020
7e788c3
last fixups
ryanio Oct 9, 2020
bc459e8
update readme, benchmarks
ryanio Oct 9, 2020
ea8a401
update vm readme, simplify validate
ryanio Oct 10, 2020
b694010
fix timestamp validation
ryanio Oct 10, 2020
1f66378
use native eq
ryanio Oct 10, 2020
9f9bab0
make blockchain optional in block.validate()
ryanio Oct 10, 2020
7ce9132
fixups
ryanio Oct 10, 2020
a5d3d14
remove BLOCK_difficulty_GivenAsList from skip list (https://github.co…
ryanio Oct 12, 2020
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
133 changes: 83 additions & 50 deletions packages/block/src/block.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { BaseTrie as Trie } from 'merkle-patricia-tree'
import { BN, rlp, keccak256, KECCAK256_RLP, baToJSON } from 'ethereumjs-util'
import Common from '@ethereumjs/common'
import { Transaction } from '@ethereumjs/tx'
import { Transaction, TxOptions } from '@ethereumjs/tx'
import { BlockHeader } from './header'
import { Blockchain, BlockData, BlockOptions } from './types'

Expand All @@ -16,6 +16,73 @@ export class Block {

private readonly _common: Common

public static fromBlockData(blockData: BlockData, opts: BlockOptions = {}) {
// Checking at runtime, to prevent errors down the path for JavaScript consumers.
if (blockData === null) {
blockData = {}
}

const headerData = blockData.header || {}
const txsData = blockData.transactions || []
const uncleHeadersData = blockData.uncleHeaders || []

const header = BlockHeader.fromHeaderData(headerData, opts)

// parse transactions
let transactions = []
for (const txData of txsData) {
transactions.push(Transaction.fromTxData(txData, opts as TxOptions))
}

// parse uncle headers
let uncleHeaders = []
for (const uncleHeaderData of uncleHeadersData) {
uncleHeaders.push(BlockHeader.fromHeaderData(uncleHeaderData, opts))
}

return new Block(header, transactions, uncleHeaders, opts)
}

public static fromRLPSerializedBlock(serialized: Buffer, opts: BlockOptions = {}) {
// We do this to silence a TS error. We know that after this statement, data is
// a [Buffer[], Buffer[][], Buffer[][]]
let values = (rlp.decode(serialized) as any) as [Buffer[], Buffer[][], Buffer[][]]
if (!Array.isArray(values)) {
throw new Error('Invalid serialized block input. Must be array')
}

return Block.fromValuesArray(values, opts)
}

public static fromValuesArray(
values: [Buffer[], Buffer[][], Buffer[][]],
opts: BlockOptions = {},
) {
if (values.length > 3) {
throw new Error('invalid block. More values than expected were received')
}

const headerArray = values[0] || []
const txsData = values[1] || []
const uncleHeadersData = values[2] || []

const header = BlockHeader.fromValuesArray(headerArray, opts)

// parse transactions
let transactions = []
for (const txData of txsData) {
transactions.push(Transaction.fromValuesArray(txData as Buffer[], opts as TxOptions))
holgerd77 marked this conversation as resolved.
Show resolved Hide resolved
}

// parse uncle headers
let uncleHeaders = []
for (const uncleHeaderData of uncleHeadersData) {
uncleHeaders.push(BlockHeader.fromValuesArray(uncleHeaderData as Buffer[], opts))
}

return new Block(header, transactions, uncleHeaders, opts)
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, thought we can really keep this as simple as you proposed (or more or less), so optimally Block.genesis(common), and everyone needing something adjusted can just use the Block.fromBlockData() variant?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I wanted to too but in practice (in our test suite) it was helpful to accept custom overrides as well.


/**
* Creates a new block object
*
Expand All @@ -27,50 +94,16 @@ export class Block {
* @param options - The options for this block (like the chain setup)
*/
constructor(
data: Buffer | [Buffer[], Buffer[], Buffer[]] | BlockData = {},
options: BlockOptions = {},
header: BlockHeader,
transactions: Transaction[],
uncleHeaders: BlockHeader[],
//data: Buffer | [Buffer[], Buffer[], Buffer[]] | BlockData = {},
opts: BlockOptions = {},
) {
// Checking at runtime, to prevent errors down the path for JavaScript consumers.
if (data === null) {
data = {}
}

let rawTransactions
let rawUncleHeaders

if (Buffer.isBuffer(data)) {
// We do this to silence a TS error. We know that after this statement, data is
// a [Buffer[], Buffer[], Buffer[]]
const dataAsAny = rlp.decode(data) as any
data = dataAsAny as [Buffer[], Buffer[], Buffer[]]
}

// Initialize the block header
if (Array.isArray(data)) {
this.header = new BlockHeader(data[0], options)
rawTransactions = data[1]
rawUncleHeaders = data[2]
} else {
this.header = new BlockHeader(data.header, options)
rawTransactions = data.transactions || []
rawUncleHeaders = data.uncleHeaders || []
}
this.header = header
this.transactions = transactions
this.uncleHeaders = uncleHeaders
this._common = this.header._common

// parse uncle headers
for (let i = 0; i < rawUncleHeaders.length; i++) {
this.uncleHeaders.push(new BlockHeader(rawUncleHeaders[i], options))
}

// parse transactions
const txOpts = { common: this._common }
for (let i = 0; i < rawTransactions.length; i++) {
const txData = rawTransactions[i]
const tx = Array.isArray(txData)
? Transaction.fromValuesArray(txData as Buffer[], txOpts)
: Transaction.fromRlpSerializedTx(txData as Buffer, txOpts)
this.transactions.push(tx)
}
}

get raw(): [Buffer[], Buffer[], Buffer[]] {
Expand Down Expand Up @@ -103,9 +136,9 @@ export class Block {
serialize(rlpEncode: false): [Buffer[], Buffer[], Buffer[]]
serialize(rlpEncode = true) {
const raw = [
this.header.raw,
this.header.raw(),
this.transactions.map((tx) => tx.serialize()),
this.uncleHeaders.map((uh) => uh.raw),
this.uncleHeaders.map((uh) => uh.raw()),
]

return rlpEncode ? rlp.encode(raw) : raw
Expand Down Expand Up @@ -140,7 +173,7 @@ export class Block {
*/
validateTransactions(): boolean
validateTransactions(stringError: false): boolean
validateTransactions(stringError: true): string
validateTransactions(stringError: true): string[]
validateTransactions(stringError = false) {
const errors: string[] = []

Expand All @@ -151,7 +184,7 @@ export class Block {
}
})

return stringError ? errors.join(' ') : errors.length === 0
return stringError ? errors : errors.length === 0
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, just re-read the discussion with @alcuadrado, this really makes a lot of sense to skip the join here.

}

/**
Expand All @@ -171,8 +204,8 @@ export class Block {
}

const txErrors = this.validateTransactions(true)
if (txErrors !== '') {
throw new Error(txErrors)
if (txErrors.length > 0) {
throw new Error(`invalid transactions: ${txErrors.join(' ')}`)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

}

if (!this.validateUnclesHash()) {
Expand All @@ -184,7 +217,7 @@ export class Block {
* Validates the uncle's hash
*/
validateUnclesHash(): boolean {
const raw = rlp.encode(this.uncleHeaders.map((uh) => uh.raw))
const raw = rlp.encode(this.uncleHeaders.map((uh) => uh.raw()))

return keccak256(raw).equals(this.header.uncleHash)
}
Expand Down
19 changes: 9 additions & 10 deletions packages/block/src/from-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,10 @@ export default function blockFromRpc(blockParams: any, uncles?: any[], options?:

const header = blockHeaderFromRpc(blockParams, options)

const block = new Block(
{
header: header.toJSON(true),
transactions: [],
uncleHeaders: uncles.map((uh) => blockHeaderFromRpc(uh, options).toJSON(true)),
},
options,
)
const transactions: TxData[] = []

if (blockParams.transactions) {
const txOpts = { common: (<any>block)._common }
const txOpts = { common: header._common }

for (const _txParams of blockParams.transactions) {
const txParams = normalizeTxParams(_txParams)
Expand All @@ -47,10 +40,16 @@ export default function blockFromRpc(blockParams: any, uncles?: any[], options?:
return toBuffer(txParams.hash)
}

block.transactions.push(fakeTx)
transactions.push(fakeTx)
}
}

const block = Block.fromBlockData({
header,
transactions,
uncleHeaders: uncles.map((uh) => blockHeaderFromRpc(uh, options)),
})

return block
}

Expand Down
10 changes: 5 additions & 5 deletions packages/block/src/header-from-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { BlockOptions } from './types'
* @param chainOptions - An object describing the blockchain
*/
export default function blockHeaderFromRpc(blockParams: any, options?: BlockOptions) {
const blockHeader = new BlockHeader(
const blockHeader = BlockHeader.fromHeaderData(
{
parentHash: blockParams.parentHash,
uncleHash: blockParams.sha3Uncles,
Expand All @@ -30,10 +30,10 @@ export default function blockHeaderFromRpc(blockParams: any, options?: BlockOpti
options,
)

// override hash in case something was missing
blockHeader.hash = function () {
return toBuffer(blockParams.hash)
}
// override hash in case something was missing TODO: why do we need this?
//blockHeader.hash = function () {
// return toBuffer(blockParams.hash)
//}
ryanio marked this conversation as resolved.
Show resolved Hide resolved

return blockHeader
}
Loading