Skip to content

Commit b276092

Browse files
authored
Merge pull request ethereumjs#807 from ethereumjs/add-tangerine-whistle-support
TangerineWhistle HF Support
2 parents 4539018 + d43da03 commit b276092

File tree

11 files changed

+132
-35
lines changed

11 files changed

+132
-35
lines changed

packages/vm/lib/evm/eei.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -576,7 +576,7 @@ export default class EEI {
576576
}
577577

578578
/**
579-
* Returns true if account is empty (according to EIP-161).
579+
* Returns true if account is empty or non-existent (according to EIP-161).
580580
* @param address - Address of account
581581
*/
582582
async isAccountEmpty(address: Buffer): Promise<boolean> {

packages/vm/lib/evm/evm.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,10 @@ export default class EVM {
221221
await this._vm._emit('newContract', newContractEvent)
222222

223223
toAccount = await this._state.getAccount(message.to)
224-
toAccount.nonce = new BN(toAccount.nonce).addn(1).toArrayLike(Buffer)
224+
// EIP-161 on account creation and CREATE execution
225+
if (this._vm._common.gteHardfork('spuriousDragon')) {
226+
toAccount.nonce = new BN(toAccount.nonce).addn(1).toArrayLike(Buffer)
227+
}
225228

226229
// Add tx value to the `to` account
227230
let errorMessage

packages/vm/lib/evm/opFns.ts

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -660,9 +660,9 @@ export const handlers: { [k: string]: OpHandler } = {
660660
if (runState.eei.isStatic() && !value.isZero()) {
661661
trap(ERROR.STATIC_STATE_CHANGE)
662662
}
663-
664663
subMemUsage(runState, inOffset, inLength)
665664
subMemUsage(runState, outOffset, outLength)
665+
666666
if (!value.isZero()) {
667667
runState.eei.useGas(new BN(runState._common.param('gasPrices', 'callValueTransfer')))
668668
}
@@ -673,9 +673,17 @@ export const handlers: { [k: string]: OpHandler } = {
673673
data = runState.memory.read(inOffset.toNumber(), inLength.toNumber())
674674
}
675675

676-
const empty = await runState.eei.isAccountEmpty(toAddressBuf)
677-
if (empty) {
678-
if (!value.isZero()) {
676+
if (runState._common.gteHardfork('spuriousDragon')) {
677+
// We are at or after Spurious Dragon
678+
// Call new account gas: account is DEAD and we transfer nonzero value
679+
if ((await runState.stateManager.accountIsEmpty(toAddressBuf)) && !value.isZero()) {
680+
runState.eei.useGas(new BN(runState._common.param('gasPrices', 'callNewAccount')))
681+
}
682+
} else if (!(await runState.stateManager.accountExists(toAddressBuf))) {
683+
// We are before Spurious Dragon
684+
// Call new account gas: account does not exist (it is not in the state trie, not even as an "empty" account)
685+
const accountDoesNotExist = !(await runState.stateManager.accountExists(toAddressBuf))
686+
if (accountDoesNotExist) {
679687
runState.eei.useGas(new BN(runState._common.param('gasPrices', 'callNewAccount')))
680688
}
681689
}
@@ -789,13 +797,29 @@ export const handlers: { [k: string]: OpHandler } = {
789797
}
790798

791799
const selfdestructToAddressBuf = addressToBuffer(selfdestructToAddress)
792-
const balance = await runState.eei.getExternalBalance(runState.eei.getAddress())
793-
if (balance.gtn(0)) {
794-
const empty = await runState.eei.isAccountEmpty(selfdestructToAddressBuf)
795-
if (empty) {
796-
runState.eei.useGas(new BN(runState._common.param('gasPrices', 'callNewAccount')))
800+
let deductGas = false
801+
if (runState._common.gteHardfork('spuriousDragon')) {
802+
// EIP-161: State Trie Clearing
803+
const balance = await runState.eei.getExternalBalance(runState.eei.getAddress())
804+
if (balance.gtn(0)) {
805+
// This technically checks if account is empty or non-existent
806+
// TODO: improve on the API here (EEI and StateManager)
807+
const empty = await runState.eei.isAccountEmpty(selfdestructToAddressBuf)
808+
if (empty) {
809+
const account = await runState.stateManager.getAccount(selfdestructToAddressBuf)
810+
deductGas = true
811+
}
812+
}
813+
} else {
814+
// Pre EIP-161 (Spurious Dragon) gas semantics
815+
const exists = await runState.stateManager.accountExists(selfdestructToAddressBuf)
816+
if (!exists) {
817+
deductGas = true
797818
}
798819
}
820+
if (deductGas) {
821+
runState.eei.useGas(new BN(runState._common.param('gasPrices', 'callNewAccount')))
822+
}
799823

800824
return runState.eei.selfDestruct(selfdestructToAddressBuf)
801825
},

packages/vm/lib/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ export default class VM extends AsyncEventEmitter {
110110
const chain = opts.chain ? opts.chain : 'mainnet'
111111
const hardfork = opts.hardfork ? opts.hardfork : 'petersburg'
112112
const supportedHardforks = [
113+
'tangerineWhistle',
113114
'spuriousDragon',
114115
'byzantium',
115116
'constantinople',

packages/vm/lib/runTx.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -174,17 +174,19 @@ async function _runTx(this: VM, opts: RunTxOpts): Promise<RunTxResult> {
174174
const minerAccount = await state.getAccount(block.header.coinbase)
175175
// add the amount spent on gas to the miner's account
176176
minerAccount.balance = toBuffer(new BN(minerAccount.balance).add(results.amountSpent))
177-
if (!new BN(minerAccount.balance).isZero()) {
178-
await state.putAccount(block.header.coinbase, minerAccount)
179-
}
177+
178+
// Put the miner account into the state. If the balance of the miner account remains zero, note that
179+
// the state.putAccount function puts this into the "touched" accounts. This will thus be removed when
180+
// we clean the touched accounts below in case we are in a fork >= SpuriousDragon
181+
await state.putAccount(block.header.coinbase, minerAccount)
180182

181183
/*
182184
* Cleanup accounts
183185
*/
184186
if (results.execResult.selfdestruct) {
185187
const keys = Object.keys(results.execResult.selfdestruct)
186188
for (const k of keys) {
187-
await state.putAccount(Buffer.from(k, 'hex'), new Account())
189+
await state.deleteAccount(Buffer.from(k, 'hex'))
188190
}
189191
}
190192
await state.cleanupTouchedAccounts()

packages/vm/lib/state/cache.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,24 +54,30 @@ export default class Cache {
5454
/**
5555
* Looks up address in underlying trie.
5656
* @param address - Address of account
57+
* @param create - Create emtpy account if non-existent
5758
*/
58-
async _lookupAccount(address: Buffer): Promise<Account> {
59+
async _lookupAccount(address: Buffer, create: boolean = true): Promise<Account | undefined> {
5960
const raw = await this._trie.get(address)
60-
const account = new Account(raw)
61-
return account
61+
if (raw || create) {
62+
const account = new Account(raw)
63+
return account
64+
}
6265
}
6366

6467
/**
6568
* Looks up address in cache, if not found, looks it up
6669
* in the underlying trie.
6770
* @param key - Address of account
71+
* @param create - Create emtpy account if non-existent
6872
*/
69-
async getOrLoad(key: Buffer): Promise<Account> {
73+
async getOrLoad(key: Buffer, create: boolean = true): Promise<Account | undefined> {
7074
let account = this.lookup(key)
7175

7276
if (!account) {
73-
account = await this._lookupAccount(key)
74-
this._update(key, account, false, false)
77+
account = await this._lookupAccount(key, create)
78+
if (account) {
79+
this._update(key, account as Account, false, false)
80+
}
7581
}
7682

7783
return account
@@ -87,7 +93,7 @@ export default class Cache {
8793
if (addressHex) {
8894
const address = Buffer.from(addressHex, 'hex')
8995
const account = await this._lookupAccount(address)
90-
this._update(address, account, false, false)
96+
this._update(address, account as Account, false, false)
9197
}
9298
}
9399
}

packages/vm/lib/state/interface.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ export interface StorageDump {
1010
export interface StateManager {
1111
copy(): StateManager
1212
getAccount(address: Buffer): Promise<Account>
13-
putAccount(address: Buffer, account: Account): Promise<void>
13+
putAccount(address: Buffer, account: Account | null): Promise<void>
14+
deleteAccount(address: Buffer): Promise<void>
1415
touchAccount(address: Buffer): void
1516
putContractCode(address: Buffer, value: Buffer): Promise<void>
1617
getContractCode(address: Buffer): Promise<Buffer>
@@ -28,5 +29,6 @@ export interface StateManager {
2829
generateCanonicalGenesis(): Promise<void>
2930
generateGenesis(initState: any): Promise<void>
3031
accountIsEmpty(address: Buffer): Promise<boolean>
32+
accountExists(address: Buffer): Promise<boolean>
3133
cleanupTouchedAccounts(): Promise<void>
3234
}

packages/vm/lib/state/stateManager.ts

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ export default class DefaultStateManager implements StateManager {
7171
* @param address - Address of the `account` to get
7272
*/
7373
async getAccount(address: Buffer): Promise<Account> {
74-
const account = await this._cache.getOrLoad(address)
74+
const account = (await this._cache.getOrLoad(address)) as Account
7575
return account
7676
}
7777

@@ -87,7 +87,11 @@ export default class DefaultStateManager implements StateManager {
8787
// if they have money or a non-zero nonce or code, then write to tree
8888
this._cache.put(address, account)
8989
this.touchAccount(address)
90-
// this._trie.put(addressHex, account.serialize(), cb)
90+
}
91+
92+
async deleteAccount(address: Buffer) {
93+
this._cache.del(address)
94+
this.touchAccount(address)
9195
}
9296

9397
/**
@@ -111,7 +115,7 @@ export default class DefaultStateManager implements StateManager {
111115
const codeHash = keccak256(value)
112116

113117
if (codeHash.equals(KECCAK256_NULL)) {
114-
return
118+
//return
115119
}
116120

117121
const account = await this.getAccount(address)
@@ -468,7 +472,8 @@ export default class DefaultStateManager implements StateManager {
468472
}
469473

470474
/**
471-
* Checks if the `account` corresponding to `address` is empty as defined in
475+
* Checks if the `account` corresponding to `address`
476+
* is empty or non-existent as defined in
472477
* EIP-161 (https://eips.ethereum.org/EIPS/eip-161).
473478
* @param address - Address to check
474479
*/
@@ -477,17 +482,35 @@ export default class DefaultStateManager implements StateManager {
477482
return account.isEmpty()
478483
}
479484

485+
/**
486+
* Checks if the `account` corresponding to `address`
487+
* exists
488+
* @param address - Address of the `account` to check
489+
*/
490+
async accountExists(address: Buffer): Promise<boolean> {
491+
const account = await this._cache.lookup(address)
492+
if (account) {
493+
return true
494+
}
495+
if (await this._cache._trie.get(address)) {
496+
return true
497+
}
498+
return false
499+
}
500+
480501
/**
481502
* Removes accounts form the state trie that have been touched,
482503
* as defined in EIP-161 (https://eips.ethereum.org/EIPS/eip-161).
483504
*/
484505
async cleanupTouchedAccounts(): Promise<void> {
485-
const touchedArray = Array.from(this._touched)
486-
for (const addressHex of touchedArray) {
487-
const address = Buffer.from(addressHex, 'hex')
488-
const empty = await this.accountIsEmpty(address)
489-
if (empty) {
490-
this._cache.del(address)
506+
if (this._common.gteHardfork('spuriousDragon')) {
507+
const touchedArray = Array.from(this._touched)
508+
for (const addressHex of touchedArray) {
509+
const address = Buffer.from(addressHex, 'hex')
510+
const empty = await this.accountIsEmpty(address)
511+
if (empty) {
512+
this._cache.del(address)
513+
}
491514
}
492515
}
493516
this._touched.clear()

packages/vm/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
"coverage:test": "npm run build && tape './tests/api/**/*.js' ./tests/tester.js --state --dist",
1515
"docs:build": "typedoc --options typedoc.js",
1616
"test:state": "ts-node ./tests/tester --state",
17-
"test:state:allForks": "npm run test:state -- --fork=SpuriousDragon && npm run test:state -- --fork=Byzantium && npm run test:state -- --fork=Constantinople && npm run test:state -- --fork=Petersburg && npm run test:state -- --fork=Istanbul && npm run test:state -- --fork=MuirGlacier",
18-
"test:state:selectedForks": "npm run test:state -- --fork=Petersburg && npm run test:state -- --fork=SpuriousDragon",
17+
"test:state:allForks": "npm run test:state -- --fork=TangerineWhistle && npm run test:state -- --fork=SpuriousDragon && npm run test:state -- --fork=Byzantium && npm run test:state -- --fork=Constantinople && npm run test:state -- --fork=Petersburg && npm run test:state -- --fork=Istanbul && npm run test:state -- --fork=MuirGlacier",
18+
"test:state:selectedForks": "npm run test:state -- --fork=Petersburg && npm run test:state -- --fork=TangerineWhistle",
1919
"test:state:slow": "npm run test:state -- --runSkipped=slow",
2020
"test:buildIntegrity": "npm run test:state -- --test='stackOverflow'",
2121
"test:blockchain": "node -r ts-node/register --stack-size=1500 ./tests/tester --blockchain",

packages/vm/tests/api/state/stateManager.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,37 @@ tape('StateManager', (t) => {
113113
},
114114
)
115115

116+
t.test(
117+
'should return false for a non-existent account',
118+
async (st) => {
119+
const stateManager = new DefaultStateManager()
120+
const address = Buffer.from('a94f5374fce5edbc8e2a8697c15331677e6ebf0b', 'hex')
121+
122+
let res = await stateManager.accountExists(address)
123+
124+
st.notOk(res)
125+
126+
st.end()
127+
},
128+
)
129+
130+
t.test(
131+
'should return true for an existent account',
132+
async (st) => {
133+
const stateManager = new DefaultStateManager()
134+
const account = createAccount('0x1', '0x1')
135+
const address = Buffer.from('a94f5374fce5edbc8e2a8697c15331677e6ebf0b', 'hex')
136+
137+
await stateManager.putAccount(address, account)
138+
139+
let res = await stateManager.accountExists(address)
140+
141+
st.ok(res)
142+
143+
st.end()
144+
},
145+
)
146+
116147
t.test(
117148
'should call the callback with a false boolean representing non-emptiness when the account is not empty',
118149
async (st) => {

0 commit comments

Comments
 (0)