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

EIP2718/EIP2930 clean branch #1133

Closed
wants to merge 32 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
3fabbb3
common: add EIP-2718, EIP-2930
jochem-brouwer Jan 15, 2021
76af18f
tx: introduce transactionFactory
jochem-brouwer Mar 1, 2021
1847d8b
tx: add some legacy transaction methods to EIP2930
jochem-brouwer Jan 23, 2021
5f2a1c9
tx: eip2930: add more legacy methods
jochem-brouwer Mar 1, 2021
f9852ca
tx: tests: update tests so that these run
jochem-brouwer Mar 1, 2021
7a35c75
tx: add transactionFactory tests
jochem-brouwer Mar 1, 2021
64cb4c7
tx: add some EIP2930 tests
jochem-brouwer Mar 1, 2021
946e35a
tx: implement BaseTransaction
jochem-brouwer Mar 1, 2021
ac694fb
tx: make txs both support signed/unsigned transactions
jochem-brouwer Mar 1, 2021
2943360
tx: decode EIP2930 transactions from block
jochem-brouwer Jan 27, 2021
2b78645
tx: fix eip2930 hash method
jochem-brouwer Jan 28, 2021
73f4b35
tx: pack both legacy and typed transactions in TxData
jochem-brouwer Mar 1, 2021
25ec7d5
tx: factory: add fromTxData
jochem-brouwer Jan 28, 2021
ae533d2
block: integrate EIP2930 blocks
jochem-brouwer Mar 1, 2021
c5c58d0
tx: add AccessListItem type
jochem-brouwer Jan 29, 2021
cd6a58f
tx: fix EIP2930 v value of 0
jochem-brouwer Jan 30, 2021
bdc0d3b
vm: implement new tx package
jochem-brouwer Feb 20, 2021
0b8643d
tx: add transaction type getter
jochem-brouwer Feb 20, 2021
87955c8
vm: fix VM tests
jochem-brouwer Feb 24, 2021
a638d8b
tx -> EIP-2930: some variable renaming, moved tests to *.spec.ts files
holgerd77 Feb 25, 2021
cc928ee
tx -> EIP-2930: common related improvements and fixes
holgerd77 Feb 25, 2021
2385c6f
tx: fix transaction type of LegacyTransaction
jochem-brouwer Mar 1, 2021
cca3b88
tx: fix transaction type of LegacyTransaction
jochem-brouwer Feb 25, 2021
af6782f
tx, vm -> EIP-2930: smaller fixes and cleanups
holgerd77 Feb 25, 2021
3539369
block/tx: fix `raw` method to return right value
jochem-brouwer Feb 25, 2021
4a7a547
tx -> EIP-2930: set default HF from common to berlin, small fixes
holgerd77 Feb 25, 2021
9ececec
vm: fix state tests for EIP2930
jochem-brouwer Feb 25, 2021
ceeedc6
tx -> EIP-2929: started to separate dedicated legacy and base tests, …
holgerd77 Feb 26, 2021
7d12beb
tx -> EIP-2930: added generic sign(), getSenderAddress(), getSenderPu…
holgerd77 Feb 26, 2021
861d1c0
tx: increase test coverage
jochem-brouwer Feb 26, 2021
be27b71
tx: simplify getMessageToSign
jochem-brouwer Feb 26, 2021
9c6f2f6
monorepo: reset ethereum-tests to develop
jochem-brouwer Mar 1, 2021
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
2 changes: 1 addition & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[submodule "ethereum-tests"]
path = packages/ethereum-tests
url = https://github.com/ethereum/tests.git
url = https://github.com/qbzzt/tests.git
branch = develop
4 changes: 4 additions & 0 deletions packages/block/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
(modification: no type change headlines) and this project adheres to
[Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## UNRELEASED

- Integration of [EIP2718](https://eips.ethereum.org/EIPS/eip-2718) (Typed Transactions) and [EIP2930](https://eips.ethereum.org/EIPS/eip-2930) (Access List Transaction), PR [#1048](https://github.com/ethereumjs/ethereumjs-monorepo/pull/1048). It is now possible to create blocks with access list transactions.

## 3.1.0 - 2021-02-22

### Clique/PoA Support
Expand Down
10 changes: 5 additions & 5 deletions packages/block/src/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { BaseTrie as Trie } from 'merkle-patricia-tree'
import { BN, rlp, keccak256, KECCAK256_RLP } from 'ethereumjs-util'
import Common from '@ethereumjs/common'
import { Transaction, TxOptions } from '@ethereumjs/tx'
import { TransactionFactory, Transaction, TxOptions } from '@ethereumjs/tx'
import { BlockHeader } from './header'
import { BlockData, BlockOptions, JsonBlock, BlockBuffer, Blockchain } from './types'

Expand Down Expand Up @@ -31,7 +31,7 @@ export class Block {
// parse transactions
const transactions = []
for (const txData of txsData || []) {
const tx = Transaction.fromTxData(txData, {
const tx = TransactionFactory.fromTxData(txData, {
...opts,
// Use header common in case of hardforkByBlockNumber being activated
common: header._common,
Expand Down Expand Up @@ -91,7 +91,7 @@ export class Block {
const transactions = []
for (const txData of txsData || []) {
transactions.push(
Transaction.fromValuesArray(txData, {
TransactionFactory.fromBlockBodyData(txData, {
...opts,
// Use header common in case of hardforkByBlockNumber being activated
common: header._common,
Expand Down Expand Up @@ -154,7 +154,7 @@ export class Block {
raw(): BlockBuffer {
return [
this.header.raw(),
this.transactions.map((tx) => tx.raw()),
this.transactions.map((tx) => <Buffer[]>tx.raw()),
this.uncleHeaders.map((uh) => uh.raw()),
]
}
Expand Down Expand Up @@ -223,7 +223,7 @@ export class Block {
const errors: string[] = []

this.transactions.forEach(function (tx, i) {
const errs = tx.validate(true)
const errs = <string[]>tx.validate(true)
if (errs.length > 0) {
errors.push(`errors at tx ${i}: ${errs.join(', ')}`)
}
Expand Down
4 changes: 2 additions & 2 deletions packages/block/src/from-rpc.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Transaction, TxData } from '@ethereumjs/tx'
import { TransactionFactory, Transaction, TxData } from '@ethereumjs/tx'
import { toBuffer, setLengthLeft } from 'ethereumjs-util'
import { Block, BlockOptions } from './index'

Expand Down Expand Up @@ -37,7 +37,7 @@ export default function blockFromRpc(blockParams: any, uncles: any[] = [], optio
const opts = { common: header._common }
for (const _txParams of blockParams.transactions) {
const txParams = normalizeTxParams(_txParams)
const tx = Transaction.fromTxData(txParams as TxData, opts)
const tx = TransactionFactory.fromTxData(txParams as TxData, opts)
transactions.push(tx)
}
}
Expand Down
12 changes: 12 additions & 0 deletions packages/common/src/eips/2718.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "EIP-2718",
"comment": "Typed Transaction Envelope",
"url": "https://eips.ethereum.org/EIPS/eip-2718",
"status": "Draft",
"minimumHardfork": "chainstart",
"gasConfig": {},
"gasPrices": {},
"vm": {},
"pow": {}
}

22 changes: 22 additions & 0 deletions packages/common/src/eips/2930.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "EIP-2929",
"comment": "Optional access lists",
"url": "https://eips.ethereum.org/EIPS/eip-2930",
"status": "Draft",
"minimumHardfork": "istanbul",
"requiredEIPs": [2718, 2929],
"gasConfig": {},
"gasPrices": {
"accessListStorageKeyCost": {
"v": 1900,
"d": "Gas cost per storage key in an Access List transaction"
},
"accessListAddressCost": {
"v": 2400,
"d": "Gas cost per storage key in an Access List transaction"
}
},
"vm": {},
"pow": {}
}

2 changes: 2 additions & 0 deletions packages/common/src/eips/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@ export const EIPs: eipsType = {
2315: require('./2315.json'),
2537: require('./2537.json'),
2565: require('./2565.json'),
2718: require('./2718.json'),
2929: require('./2929.json'),
2930: require('./2930.json'),
}
2 changes: 1 addition & 1 deletion packages/common/src/hardforks/berlin.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
"comment": "HF targeted for July 2020 following the Muir Glacier HF",
"url": "https://eips.ethereum.org/EIPS/eip-2070",
"status": "Draft",
"eips": [ 2315, 2565, 2929 ]
"eips": [ 2315, 2565, 2929, 2718, 2930 ]
}
8 changes: 8 additions & 0 deletions packages/common/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,14 @@ export default class Common extends EventEmitter {
`${eip} cannot be activated on hardfork ${this.hardfork()}, minimumHardfork: ${minHF}`
)
}
if (EIPs[eip].requiredEIPs) {
// eslint-disable-next-line prettier/prettier
(<number[]>EIPs[eip].requiredEIPs).forEach((elem: number) => {
if (!(eips.includes(elem) || this.isActivatedEIP(elem))) {
throw new Error(`${eip} requires EIP ${elem}, but is not included in the EIP list`)
}
})
}
}
this._eips = eips
}
Expand Down
15 changes: 14 additions & 1 deletion packages/common/tests/eips.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,22 @@ import Common from '../src/'

tape('[Common]: Initialization / Chain params', function (t: tape.Test) {
t.test('Correct initialization', function (st: tape.Test) {
const eips = [2537, 2929]
let eips = [2537, 2929]
const c = new Common({ chain: 'mainnet', eips })
st.equal(c.eips(), eips, 'should initialize with supported EIP')

eips = [2718, 2929, 2930]
new Common({ chain: 'mainnet', eips, hardfork: 'istanbul' })
st.pass('Should not throw when initializing with a consistent EIP list')

eips = [2930]
const msg =
'should throw when initializing with an EIP with required EIPs not being activated along'
const f = () => {
new Common({ chain: 'mainnet', eips, hardfork: 'istanbul' })
}
st.throws(f, msg)

st.end()
})

Expand Down
2 changes: 1 addition & 1 deletion packages/ethereum-tests
10 changes: 10 additions & 0 deletions packages/tx/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
(modification: no type change headlines) and this project adheres to
[Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## UNRELEASED

- Integration of [EIP2718](https://eips.ethereum.org/EIPS/eip-2718) (Typed Transactions) and [EIP2930](https://eips.ethereum.org/EIPS/eip-2930) (Access List Transaction), PR [#1048](https://github.com/ethereumjs/ethereumjs-monorepo/pull/1048).

This PR integrates the Typed Transactions. In order to produce the right transactions, there is a new class called the `TransactionFactory`. This factory helps to create the correct transaction. When decoding directly from blocks, use the `fromBlockBodyData` method. The PR also refactors the internals of the legacy transaction and the new typed transaction: there is a base transaction class, which both transaction classes extends. It is also possible to import `EIP2930Transaction` (an access list transaction).

### How to migrate

The old `Transaction` class has been renamed to `LegacyTransaction`. This class works just how it used to work.


## 3.0.2 - 2021-02-16

Expand Down
2 changes: 1 addition & 1 deletion packages/tx/karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ module.exports = function (config) {
browserNoActivityTimeout: 60000,
frameworks: ['browserify', 'tap'],
// the official transaction's test suite is disabled for now, see https://github.com/ethereumjs/ethereumjs-testing/issues/40
files: ['./test-build/test/api.js'],
files: ['./test-build/test/legacy.spec.js'],
preprocessors: {
'./test-build/**/*.js': ['browserify'],
},
Expand Down
195 changes: 195 additions & 0 deletions packages/tx/src/baseTransaction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
import Common from '@ethereumjs/common'
import {
Address,
BN,
toBuffer,
MAX_INTEGER,
unpadBuffer,
ecsign,
publicToAddress,
} from 'ethereumjs-util'
import { BaseTransactionData, BaseTxOptions, DEFAULT_COMMON, JsonTx } from './types'

export abstract class BaseTransaction<TransactionObject> {
public readonly nonce: BN
public readonly gasLimit: BN
public readonly gasPrice: BN
public readonly to?: Address
public readonly value: BN
public readonly data: Buffer
public readonly common: Common

constructor(txData: BaseTransactionData, txOptions: BaseTxOptions = {}) {
const { nonce, gasLimit, gasPrice, to, value, data } = txData

this.nonce = new BN(toBuffer(nonce))
this.gasPrice = new BN(toBuffer(gasPrice))
this.gasLimit = new BN(toBuffer(gasLimit))
this.to = to ? new Address(toBuffer(to)) : undefined
this.value = new BN(toBuffer(value))
this.data = toBuffer(data)

const validateCannotExceedMaxInteger = {
nonce: this.nonce,
gasPrice: this.gasPrice,
gasLimit: this.gasLimit,
value: this.value,
}

this.validateExceedsMaxInteger(validateCannotExceedMaxInteger)

this.common =
(txOptions.common &&
Object.assign(Object.create(Object.getPrototypeOf(txOptions.common)), txOptions.common)) ??
DEFAULT_COMMON
}

protected validateExceedsMaxInteger(validateCannotExceedMaxInteger: { [key: string]: BN }) {
for (const [key, value] of Object.entries(validateCannotExceedMaxInteger)) {
if (value && value.gt(MAX_INTEGER)) {
throw new Error(`${key} cannot exceed MAX_INTEGER, given ${value}`)
}
}
}

/**
* If the tx's `to` is to the creation address
*/
toCreationAddress(): boolean {
return this.to === undefined || this.to.buf.length === 0
}

/**
* Computes a sha3-256 hash of the serialized unsigned tx, which is used to sign the transaction.
*/
rawTxHash(): Buffer {
return this.getMessageToSign()
}

abstract getMessageToSign(): Buffer

/**
* Returns chain ID
*/
getChainId(): number {
return this.common.chainId()
}

/**
* The amount of gas paid for the data in this tx
*/
getDataFee(): BN {
const txDataZero = this.common.param('gasPrices', 'txDataZero')
const txDataNonZero = this.common.param('gasPrices', 'txDataNonZero')

let cost = 0
for (let i = 0; i < this.data.length; i++) {
this.data[i] === 0 ? (cost += txDataZero) : (cost += txDataNonZero)
}
return new BN(cost)
}

/**
* The minimum amount of gas the tx must have (DataFee + TxFee + Creation Fee)
*/
getBaseFee(): BN {
const fee = this.getDataFee().addn(this.common.param('gasPrices', 'tx'))
if (this.common.gteHardfork('homestead') && this.toCreationAddress()) {
fee.iaddn(this.common.param('gasPrices', 'txCreation'))
}
return fee
}

/**
* The up front amount that an account must have for this transaction to be valid
*/
getUpfrontCost(): BN {
return this.gasLimit.mul(this.gasPrice).add(this.value)
}

/**
* Checks if the transaction has the minimum amount of gas required
* (DataFee + TxFee + Creation Fee).
*/
/**
* Checks if the transaction has the minimum amount of gas required
* (DataFee + TxFee + Creation Fee).
*/
validate(): boolean
/* eslint-disable-next-line no-dupe-class-members */
validate(stringError: false): boolean
/* eslint-disable-next-line no-dupe-class-members */
validate(stringError: true): string[]
/* eslint-disable-next-line no-dupe-class-members */
validate(stringError: boolean = false): boolean | string[] {
const errors = []

if (this.getBaseFee().gt(this.gasLimit)) {
errors.push(`gasLimit is too low. given ${this.gasLimit}, need at least ${this.getBaseFee()}`)
}

if (this.isSigned() && !this.verifySignature()) {
errors.push('Invalid Signature')
}

return stringError ? errors : errors.length === 0
}

/**
* Returns the encoding of the transaction.
*/
abstract serialize(): Buffer

/**
* Returns an object with the JSON representation of the transaction
*/
abstract toJSON(): JsonTx

abstract isSigned(): boolean

/**
* Determines if the signature is valid
*/
verifySignature(): boolean {
try {
// Main signature verification is done in `getSenderPublicKey()`
const publicKey = this.getSenderPublicKey()
return unpadBuffer(publicKey).length !== 0
} catch (e) {
return false
}
}

/**
* Returns the raw `Buffer[]` (LegacyTransaction) or `Buffer` (typed transaction).
* This is the data which is found in the transactions of the block body.
*/
abstract raw(): Buffer[] | Buffer
abstract hash(): Buffer

abstract getMessageToVerifySignature(): Buffer
/**
* Returns the sender's address
*/
getSenderAddress(): Address {
return new Address(publicToAddress(this.getSenderPublicKey()))
}
abstract getSenderPublicKey(): Buffer

sign(privateKey: Buffer): TransactionObject {
if (privateKey.length !== 32) {
throw new Error('Private key must be 32 bytes in length.')
}

const msgHash = this.getMessageToSign()

// Only `v` is reassigned.
/* eslint-disable-next-line prefer-const */
let { v, r, s } = ecsign(msgHash, privateKey)

return this.processSignature(v, r, s)
}

// Accept the v,r,s values from the `sign` method, and convert this into a TransactionObject
protected abstract processSignature(v: number, r: Buffer, s: Buffer): TransactionObject
}
Loading