Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
30 changes: 4 additions & 26 deletions db/state/execctx/domain_shared.go
Original file line number Diff line number Diff line change
Expand Up @@ -1415,11 +1415,11 @@ func (sd *SharedDomains) codeHashForAddr(tx kv.TemporalTx, addr []byte, txNum ui
// on flush. Route mem-first; the LRU is a committed-state layer that may only
// answer once mem has missed.
if v, _, ok := sd.mem.GetLatest(kv.AccountsDomain, addr); ok {
return decodeAccountCodeHash(v)
return accounts.DeserialiseV3CodeHash(v)
}
if sd.parent != nil {
if v, _, ok := sd.parent.mem.GetLatest(kv.AccountsDomain, addr); ok {
return decodeAccountCodeHash(v)
return accounts.DeserialiseV3CodeHash(v)
}
}

Expand All @@ -1440,14 +1440,14 @@ func (sd *SharedDomains) codeHashForAddr(tx kv.TemporalTx, addr []byte, txNum ui
resolve := func() []byte {
if sd.stateCache != nil {
if v, ok := sd.stateCache.Get(kv.AccountsDomain, addr); ok {
return decodeAccountCodeHash(v)
return accounts.DeserialiseV3CodeHash(v)
}
}
v, _, err := tx.GetLatest(kv.AccountsDomain, addr)
if err != nil || len(v) == 0 {
return nil
}
return decodeAccountCodeHash(v)
return accounts.DeserialiseV3CodeHash(v)
}

h := resolve()
Expand All @@ -1465,28 +1465,6 @@ func (sd *SharedDomains) codeHashForAddr(tx kv.TemporalTx, addr []byte, txNum ui
return h
}

// decodeAccountCodeHash extracts the codeHash from an account's encoded
// (DecodeForStorage) bytes. Returns nil on decode error or when the account
// has no code (empty codeHash).
func decodeAccountCodeHash(enc []byte) []byte {
if len(enc) == 0 {
return nil
}
var acc accounts.Account
// AccountsDomain values are SerialiseV3-encoded, so they must be decoded
// with DeserialiseV3. DecodeForStorage is the legacy MDBX bitmask format
// with an incompatible binary layout; applied to V3 bytes it silently
// misparses and leaves CodeHash empty.
if err := accounts.DeserialiseV3(&acc, enc); err != nil {
return nil
}
if acc.CodeHash.IsEmpty() {
return nil
}
h := acc.CodeHash.Value()
return h[:]
}

func (sd *SharedDomains) Metrics() *kvmetrics.DomainMetrics {
return &sd.metrics
}
Expand Down
32 changes: 32 additions & 0 deletions execution/types/accounts/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (

"github.com/erigontech/erigon/common"
"github.com/erigontech/erigon/common/empty"
"github.com/erigontech/erigon/common/length"
"github.com/erigontech/erigon/execution/rlp"
)

Expand Down Expand Up @@ -641,6 +642,37 @@ func DeserialiseV3(a *Account, enc []byte) error {
return nil
}

// DeserialiseV3CodeHash extracts just the codeHash field from a
// SerialiseV3-encoded account, skipping the full decode (balance parse,
// codeHash interning) that DeserialiseV3 pays. It parses only up to and
// including the codeHash field — later fields are not validated. Returns a
// subslice of enc — valid only while enc is — or nil when the record is
// malformed up to that field or the account has no code (including
// non-canonical spellings of the empty or zero sentinel, which
// CodeHash.IsEmpty treats as no-code).
func DeserialiseV3CodeHash(enc []byte) []byte {
pos := 0
for range 2 { // skip the length-prefixed nonce and balance fields
if pos >= len(enc) {
return nil
}
pos += 1 + int(enc[pos])
}
if pos >= len(enc) {
return nil
}
codeHashBytes := int(enc[pos])
pos++
if codeHashBytes != length.Hash || pos+codeHashBytes > len(enc) {
return nil
}
h := enc[pos : pos+codeHashBytes]
if ch := common.Hash(h); ch == (common.Hash{}) || ch == empty.CodeHash {
return nil
}
return h
}

func SerialiseV3(a *Account) []byte {
var l int
l++
Expand Down
80 changes: 80 additions & 0 deletions execution/types/accounts/account_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package accounts

import (
"bytes"
"testing"

"github.com/holiman/uint256"
Expand Down Expand Up @@ -76,6 +77,85 @@ func TestEmptyAccount_BufferStrangeBehaviour(t *testing.T) {
isIncarnationEqual(t, a.Incarnation, decodedAcc.Incarnation)
}

func TestDeserialiseV3CodeHash(t *testing.T) {
t.Parallel()
balances := []uint256.Int{{}, *uint256.NewInt(1), *uint256.NewInt(1e18), *new(uint256.Int).Lsh(uint256.NewInt(1), 200)}
nonces := []uint64{0, 1, 255, 1 << 40}
codeHashes := []CodeHash{EmptyCodeHash, InternCodeHash(common.BytesToHash(crypto.Keccak256([]byte{1, 2, 3})))}
incarnations := []uint64{0, 7}

for _, nonce := range nonces {
for i := range balances {
for _, ch := range codeHashes {
for _, inc := range incarnations {
a := Account{Nonce: nonce, Balance: balances[i], CodeHash: ch, Incarnation: inc}
enc := SerialiseV3(&a)

var full Account
if err := DeserialiseV3(&full, enc); err != nil {
t.Fatal(err)
}
got := DeserialiseV3CodeHash(enc)
if full.CodeHash.IsEmpty() {
if got != nil {
t.Fatalf("empty codeHash must extract as nil, got %x (acc %+v)", got, a)
}
} else {
want := full.CodeHash.Value()
if !bytes.Equal(got, want[:]) {
t.Fatalf("extracted %x, want %x (acc %+v)", got, want, a)
}
}
}
}
}
}
}

func TestDeserialiseV3CodeHashMalformed(t *testing.T) {
t.Parallel()
a := Account{
Nonce: 255,
Balance: *uint256.NewInt(1e18),
CodeHash: InternCodeHash(common.BytesToHash(crypto.Keccak256([]byte{1, 2, 3}))),
Incarnation: 4,
}
enc := SerialiseV3(&a)
// [1+nonce][1+balance][1+codeHash]... — the codeHash field is complete at:
codeHashEnd := 1 + int(enc[0]) + 1
codeHashEnd += int(enc[codeHashEnd-1]) + 1
codeHashEnd += int(enc[codeHashEnd-1])
// Any truncation cutting into (or before) the codeHash must yield nil,
// never an out-of-bounds read; beyond it the codeHash is extractable.
for cut := 0; cut <= len(enc); cut++ {
got := DeserialiseV3CodeHash(enc[:cut])
if cut < codeHashEnd && got != nil {
t.Fatalf("cut=%d (codeHash complete at %d): expected nil, got %x", cut, codeHashEnd, got)
}
if cut >= codeHashEnd && got == nil {
t.Fatalf("cut=%d (codeHash complete at %d): expected hash, got nil", cut, codeHashEnd)
}
}
if got := DeserialiseV3CodeHash(nil); got != nil {
t.Fatalf("nil input: expected nil, got %x", got)
}
// A record claiming a non-32-byte codeHash is malformed for extraction.
odd := append([]byte{0, 0, 31}, make([]byte, 31)...)
if got := DeserialiseV3CodeHash(odd); got != nil {
t.Fatalf("non-32-byte codeHash field: expected nil, got %x", got)
}
// Non-canonical records spelling out the no-code sentinels (canonical
// SerialiseV3 writes length 0 instead) must extract as nil, matching
// CodeHash.IsEmpty.
for _, sentinel := range [][]byte{make([]byte, 32), empty.CodeHash[:]} {
rec := append([]byte{0, 0, 32}, sentinel...)
rec = append(rec, 0)
if got := DeserialiseV3CodeHash(rec); got != nil {
t.Fatalf("sentinel codeHash %x: expected nil, got %x", sentinel, got)
}
}
}

func TestAccountEncodeWithCode(t *testing.T) {
t.Parallel()
a := Account{
Expand Down
Loading