There are currently a number of bugs in the flat DB trie hash
computation. These bugs are improbable in 'real' scenarios (hence the
ability to sync the assorted large chains), and, the repercussions of
encountering them are low (generally re-computing the hash at a later
block will succeed). Still, they can cause the process to crash, or
deadlock, and a clever adversary could feasibly construct a block or
storage update designed to trigger these bugs. (Or, an unlucky block may
trigger them inadvertently). Based on the tracing in the code, it seems
that some of these bugs may have been witnessed in the wild, but not
reliably reproduced (for instance when `maxlen >= len(curr)`).
1. There is an infinite loop that can occur in
_nextSiblingOfParentInMem. This occurs in the account path when the
c.k[1] entry is 'nil' and the code will continuously retry resolving
this entry indefinitely.
2. When the next trie node is deeper in the trie than the previous, but
is not a descendent of the previous trie node, then the old trie node is
inadvertently left in memory instead of nil-ed. This results in an
incorrect hash being computed.
3. When the last trie node being processed is comprised entirely of 'f'
nibbles, the 'FirstNotCoveredPrefix' returns an empty byte array,
because there is no next nibble sub-tree. This causes keys to
inappropriately be reprocessed triggering either an index out of bounds
panic or incorrect hash results.
4. When the _nextSiblingInDB path is triggered, if the next nibble
subtree contains no trie table entries, then any keys with a prefix in
that subtree will be skipped resulting in incorrect hash results.
The fuzzing seeds included cover all four of these cases (for both
accounts and storage where appropriate). As fuzzing is baked into the
native go 1.18 toolchain, running 'go test' (as the Makefile currently
does) is adequate to cover these failures. To run additional fuzzing the
`-fuzz` flag may be provided as documented in the go test doc.
Co-authored-by: Jason Yellick <jason@enya.ai>
This PR completes the implementation of `eth_getProof` by adding support
for storage proofs.
Because storage proofs are potentially overlapping, the existing
strategy of simply aggregating all proofs together into a single result
was challenging. Instead, this commit rewires things to introduce a
ProofRetainer, which aggregates proofs and their corresponding nibble
encoded paths in the trie. Once all of the proofs have been aggregated,
the caller requests the proof result, which then iterates over the
aggregated proofs, placing each into the relevant proof array into the
result.
Although there are tests for `eth_getProof` as an RPC and for the new
`ProofRetainer` code, the code coverage for the proof generation over
complex tries is lacking. But, since this is not a new problem I'll plan
to follow up this PR with an additional one adding more coverage into
`turbo/trie`.
---------
Co-authored-by: Jason Yellick <jason@enya.ai>
This PR starts with a few small commits of code cleanup. Reviewed
separately they should hopefully obviously be functionally no-ops. I'm
happy to strip these out and submit them separately if desired.
The final commit is to add support for older blocks as a parameter to
eth_getProof. In order to compute proofs, the function leverages the
staged sync unwinding code to bring the hashed state table back to its
historic state, as well as to build a list of trie nodes which need to
be invalidated/re-computed in the trie computation. Because these
operations could be expensive for very old blocks, it limits the
distance proofs are allowed from the head. It also adds some additional
checks for correctness, as well as tests which verify the
implementation.
This was discussed a bit on Discord in the db-format topic.
---------
Co-authored-by: Jason Yellick <jason@enya.ai>
This is a partial implementation of eth_getProof (see issue #1349),
supporting only a request for the latest block and an empty list of
storage keys (i.e. Account proof only). I don't know if there's a better
way of implementing this, but this was what I could come up with.
Posting it here in case it's useful.
Example output:
```
> eth.getProof("0x67b1d87101671b127f5f8714789C7192f7ad340e", [], 'latest')
{
accountProof: ["0xf90131a0252c9d4ed347b4cf3fdccaea3ccef0a507e6bd4dbe4dcd98609b7195347c4062a0ab8cdb808c8303bb61fb48e276217be9770fa83ecf3f90f2234d558885f5abf18080a01a697e814758281972fcd13bc9707dbcd2f195986b05463d7b78426508445a04a0b5d7a91be5ee273cce27e2ad9a160d2faadd5a6ba518d384019b68728a4f62f4a0c2c799b60a0cd6acd42c1015512872e86c186bcf196e85061e76842f3b7cf86080a0e73919d9f472eec11f6da95518503f5527a98b9428f7a02c4f55bf51854214e480a06301b39b2ea8a44df8b0356120db64b788e71f52e1d7a6309d0d2e5b86fee7cb8080a01b7779e149cadf24d4ffb77ca7e11314b8db7097e4d70b2a173493153ca2e5a0a066a7662811491b3d352e969506b420d269e8b51a224f574b3b38b3463f43f0098080", "0xf8518080808080a0a00135c9ec2655cb6a47ab7ad27d6fc150d9cba8b3d4a702e879179116a68a60808080808080a02fb46956347985b9870156b5747712899d213b1636ad4fe553c63e33521d567a80808080", "0xf873a02056274a27dd7524955417c11ecd917251cc7c4c8310f4c7e4bd3c304d3d9a79b850f84e808a021e19e0c9bab2400000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"],
address: "0x67b1d87101671b127f5f8714789c7192f7ad340e",
balance: "0x21e19e0c9bab2400000",
codeHash: "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
nonce: "0x0",
storageHash: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
storageProof: []
}
> eth.getBlock('latest').stateRoot
"0x6a0673c691edfa4c4528323986bb43c579316f436ff6f8b4ac70854bbd95340b"
```
* Test case that reproduces wrong trie root in Hive
* Do not hardcode roots
* Fix IH when state contains addresses smaller than the first key in AccTrie
* Move ETL to erigon-lib
* Update link in the readme
* go mod tidy
* Use common/chan.go from erigon-lib
* Clean up
* Fix lint
* Fix test
* Fix compilation
Co-authored-by: Alex Sharp <alexsharp@Alexs-MacBook-Pro.local>
* squash
* squash
* squash
* squash
* squash
* squash
* squash
* squash
* squash
* squash
* squash
* squash
* squash
* squash
* squash
* squash
* squash
* squash
* squash
* squash
* squash
* SE
* SE
* SE
* SE
* SE
* SE
* SE
* SE
* SE
* SE
* SE
* SE
* SE
* SE
* SE