-
Notifications
You must be signed in to change notification settings - Fork 790
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
Client -> Mainnet: Consensus Error on block 226522 "invalid receipt trie" #1076
Comments
This is exciting! 😄 Optimally, we would get stack traces. However, (in this case), we can quickly figure out which transaction goes wrong: to do that, we download each receipt of the block (we can do that over |
Well we don't have to do that in this case, since the block only has one transaction 😅 |
Hmm, I think the tricky part here will be to actually attribute this to a cause and e.g. do an interpretation what actually caused a wrong |
Usually the gas difference between the reported one and the actual one is a very specific number, which can be used as a clue where to look. But we should get a trace via |
Ok, this is the block (as seen by our client, should be the same though): {
"header": {
"parentHash": "0xd9b7336ac322a4fa9904dcca42f127b43b3e5829a313e661bb2142ce900fdf45",
"uncleHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"coinbase": "0x52bc44d5378309ee2abf1539bf71de1b7d7be3b5",
"stateRoot": "0xfc5c217e8a5b003ff7da5a80c39e986e212fb02bad8fe17cd27d92a2b5963fa5",
"transactionsTrie": "0x7e9c2d95a5b99c122d269f149f0804a3b1b157ec22693389cd2c5db8b1e9d94b",
"receiptTrie": "0xf1fd7ec09270db8b1f4c78fd933a93bdca77626bfc3e9484bdabfdfdf0d868a7",
"bloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"difficulty": "0x6647c8f3e9e",
"number": "0x374da",
"gasLimit": "0x2fefd8",
"gasUsed": "0x1f86b",
"timestamp": "0x55f4f963",
"extraData": "0xd783010103844765746887676f312e342e32856c696e7578",
"mixHash": "0x5d3c9f8cfbf703341816f0769d99925ee8d4fd8b432aa3609e15b772e9b2384a",
"nonce": "0xee920bf60079e087"
},
"transactions": [
{
"nonce": "0x1a",
"gasPrice": "0xba43b7400",
"gasLimit": "0x493e0",
"to": "0x9ca228250f9d8f86c23690074c2b96d5f5479f79",
"value": "0x0",
"data": "0xa9059cbb000000000000000000000000bfe465e7eb5a2928b5bf22bef93ad06089dc6179000000000000000000000000000000000000000000000000006a94d74f430000",
"v": "0x1c",
"r": "0xc04f048766bea3b20dcea6d2fcaecc92f1a615d337606ab6eec2a1e8e1f27bfe",
"s": "0x1c57e62653724560b06c95be750d958840db9fa44ffa777b6625b344bfadbbcf"
}
],
"uncleHeaders": []
} |
And this is the receipt produced by our VM: {
stateRoot: <Buffer a3 d2 8b c1 9c 14 27 e1 df a4 5f 85 bb 12 d2 4c c7 1e 8f 0d 94 eb 71 01 fa 2b 05 5c 25 62 5d 34>,
gasUsed: <Buffer 01 f8 6b>,
bitvector: <Buffer 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ... 206 more bytes>,
logs: []
} Decimal gas used is: 129131 |
Weird, that's the same amount as the block indicates. It can only be the state root, I guess. Can you check if the before/after values of the storage slots are the same as these diffs reported by etherscan? |
Here is the receipt as received by Ethers:
|
By adding some console.log statement in
|
|
Some more debugging (in
|
If I read correctly from Etherscan, the |
Ok, will stop for today. |
@holgerd77 that might be it: |
Guess it's somewhere in the |
First attempt to isolate the code run here, not sure if this is creating all the necessary context, but it at least runs through: const level = require('level')
import Common from '@ethereumjs/common'
import { Block } from '@ethereumjs/block'
import { Transaction } from '@ethereumjs/tx'
import VM from './lib'
import { SecureTrie as Trie } from '@ethereumjs/trie'
import { DefaultStateManager } from './lib/state'
const main = async () => {
const common = new Common({ chain: 'mainnet', hardfork: 'chainstart' })
const block = Block.fromBlockData(
{
header: {
// Ensures our fees get added to coinbase after we run the Tx (this is important for the state root in the receipt)
coinbase: Buffer.from('52bc44d5378309ee2abf1539bf71de1b7d7be3b5', 'hex'),
},
},
{ common }
)
const txData = {
"nonce": "0x1a",
"gasPrice": "0xba43b7400",
"gasLimit": "0x493e0",
"to": "0x9ca228250f9d8f86c23690074c2b96d5f5479f79",
"value": "0x0",
"data": "0xa9059cbb000000000000000000000000bfe465e7eb5a2928b5bf22bef93ad06089dc6179000000000000000000000000000000000000000000000000006a94d74f430000",
"v": "0x1c",
"r": "0xc04f048766bea3b20dcea6d2fcaecc92f1a615d337606ab6eec2a1e8e1f27bfe",
"s": "0x1c57e62653724560b06c95be750d958840db9fa44ffa777b6625b344bfadbbcf"
}
const tx = Transaction.fromTxData(txData, { common })
const trie = new Trie(level('/Users/hdrewes/Library/Ethereum/ethereumjs/mainnet/state/'))
const stateManager = new DefaultStateManager({ trie, common })
// Ensure we run on the right root
stateManager.setStateRoot(Buffer.from('9dceb42e227ee6a244449d1d92cc5e0c17c63b520d0ff050d943baa8055d0ca6', 'hex'))
const vm = new VM({ stateManager, common })
const result = await vm.runTx({ tx, skipBalance: true, skipNonce: true, block })
//console.log(result)
}
main() Feel free to further improve on the code. This might also be handy for future debugging sessions. We might want to add scripts like this in an extra Update: some updates on the code, a more recent example might be found along #1081 |
We won't be able to reproduce this, since we don't have access to your DB. It should be sufficient to dump all the data it accesses (so in particular, the origin account, the contract account (so code/balance)). If there are any SLOADs or SSTOREs then these values should also be made available. So do I get this right? We actually do have an internal call, which has the right call value? But somehow this does not end up in the account? I can think of two things here which might go wrong, (1) we are internally manipulating a BN/Buffer wrong (similar to #733), or, possibly, the checkpoint memory cache thing which I recently added is bugged? |
@jochem-brouwer of course you do, you just have to sync up to the respective block, this is a consensus error! 😄 Doesn't take very long, just 30-60 minutes. I guess for now we doesn't need a server for this, I assume that in the next weeks we will just go "in sync" on mainnet towards the next consensus bug or performance bottle neck blocks which we likely want to tackle together, and at the end it is more practical to have the databases locally for debugging. It is also pretty handy to do local copies of the |
Output of a run with the new debug feature from #1080, haven't done any analysis on this yet: |
Ah, the second call is taking a nonsense |
Hah yes you are right regarding that I should just sync 😅 Can you check the stack traces? (Via VMs |
The behavior of this code part seems to be wrong (the first // If enough gas and allowed code size
if (
totalGas.lte(message.gasLimit) &&
(this._vm._allowUnlimitedContractSize || allowedCodeSize)
) {
result.gasUsed = totalGas
} else {
if (this._vm._common.gteHardfork('homestead')) {
debug(`Not enough gas or code size not allowed (>= Homestead)`)
result = { ...result, ...OOGResult(message.gasLimit) }
} else {
// we are in Frontier
debug(`Not enough gas or code size not allowed (Frontier)`)
if (totalGas.sub(returnFee).lte(message.gasLimit)) {
// we cannot pay the code deposit fee (but the deposit code actually did run)
result = { ...result, ...COOGResult(totalGas.sub(returnFee)) }
} else {
result = { ...result, ...OOGResult(message.gasLimit) }
}
}
} If one is changing This seems to have to do with the following behavior from the Homestead fork, respectively the behavior which should have been in place before in Frontier described in the EIP:
My interpretation (or rather: assumption) here is that the contract call should run with this code - somewhat temporarily deployed - but at the end of the tx the contract / new account should not stay in the state? Might this be correct? |
(Just finished my exam 😄 ). So the contract creation fails on our VM? This does not seem to be the case on the chain; the contract is successfully deployed. See the parity trace and also the internal transactions on etherscan (does not show a failing internal transaction to the target address). I'm currently also syncing the chain, ready to get 🔍 this 😄 |
Just checked your reproduction code, it doesn't seem to be running the same code. It then only uses about 17k gas. If I set the trie's root to the state root of the previous block, it runs out of gas. I am running this in the VM package (and I did reproduce the failing block on client), you are also running it within VM right? |
Figured out what the problem was (w.r.t. reproduce script); VM does not run on Frontier. I have put some progress in #1081, please read my comments on there (I "reviewed" myself). The 0.03 eth now gets transfered to the right address, but it does not yet solve the problem, as there's a storage value which has an incorrect value. Will stop here. But yes, you were right regarding which rule this seems to be about 😄 |
@jochem-brouwer thanks for having a look into this, great! 😄 As a side note: I updated the example code from above with a Did you actually have an example running which was working, and did you add anything else? (I would just find it nice if we once have a working example since this might be useful for similar future situations where we need to re-create VM runs from the client. Other side note: also opened this issue here #1082 on the behavior of this first wrong example case since I have the impression the current behavior on such a case is not really fortunate for the end user (longer description in the issue). |
@jochem-brouwer ah, just answered the example question myself by having a look at your test run code from #1081 and discovering: // Ensure we run on the right root
trie._setRoot(
Buffer.from('9dceb42e227ee6a244449d1d92cc5e0c17c63b520d0ff050d943baa8055d0ca6', 'hex')
) 😄 |
Fixed by #1081 |
Yeah, we have our first consensus error - a real repeatable one and not some of this race condition stuff. 😅 I find this somewhat of a relief to now come to the "real stuff" after all this infrastructure code writing since this was one of the main reasons the client was build for. 🥳
Since this one is relatively early - on block 226522 - I would expect more to come further down the line, but we'll see.
Just pasting the stack trace here, haven't dug deeper into what's wrong:
Curious to see how the debugging of such an error goes (we'll likely need stack traces for this from another client?).
The text was updated successfully, but these errors were encountered: