Adds access list sorting (#8933)

Because access lists use maps with the `StorageKey` as the key, they are
subject to inconsistent ordering in the results of the `.accessList()`
method.

To get around this, an `accessListSorted` method has been added, and
exposed with the same name. The `equal` method has also been exposed to
allow for equality checks at this level outside of this module.

Co-authored-by: 3commascapital <8562488-3commascapital@users.noreply.gitlab.com>
This commit is contained in:
3commascapital 2023-12-09 04:25:20 -06:00 committed by GitHub
parent e006db6ed9
commit 07331f900f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 70 additions and 0 deletions

View File

@ -17,6 +17,8 @@
package logger
import (
"sort"
"github.com/holiman/uint256"
libcommon "github.com/ledgerwatch/erigon-lib/common"
types2 "github.com/ledgerwatch/erigon-lib/types"
@ -94,6 +96,10 @@ func (al accessList) equal(other accessList) bool {
return true
}
func (al accessList) Equal(other accessList) bool {
return al.equal(other)
}
// accesslist converts the accesslist to a types2.AccessList.
func (al accessList) accessList() types2.AccessList {
acl := make(types2.AccessList, 0, len(al))
@ -107,6 +113,25 @@ func (al accessList) accessList() types2.AccessList {
return acl
}
// accesslist converts the accesslist to a types2.AccessList.
func (al accessList) accessListSorted() types2.AccessList {
acl := make(types2.AccessList, 0, len(al))
for addr, slots := range al {
storageKeys := make([]libcommon.Hash, 0, len(slots))
for slot := range slots {
storageKeys = append(storageKeys, slot)
}
sort.Slice(storageKeys, func(i, j int) bool {
return storageKeys[i].String() < storageKeys[j].String()
})
acl = append(acl, types2.AccessTuple{
Address: addr,
StorageKeys: storageKeys,
})
}
return acl
}
// AccessListTracer is a tracer that accumulates touched accounts and storage
// slots into an internal set.
type AccessListTracer struct {
@ -227,6 +252,11 @@ func (a *AccessListTracer) AccessList() types2.AccessList {
return a.list.accessList()
}
// AccessList returns the current accesslist maintained by the tracer.
func (a *AccessListTracer) AccessListSorted() types2.AccessList {
return a.list.accessListSorted()
}
// CreatedContracts returns the set of all addresses of contracts created during tx execution.
func (a *AccessListTracer) CreatedContracts() map[libcommon.Address]struct{} {
return a.createdContracts

View File

@ -0,0 +1,40 @@
package logger
import (
"testing"
"github.com/ledgerwatch/erigon-lib/common"
types2 "github.com/ledgerwatch/erigon-lib/types"
"github.com/stretchr/testify/require"
)
var (
addr = common.BytesToAddress([]byte{0x01, 0x71})
slot1 = common.BytesToHash([]byte{0x01})
slot2 = common.BytesToHash([]byte{0x02})
slot3 = common.BytesToHash([]byte{0x03})
slot4 = common.BytesToHash([]byte{0x04})
ordered = types2.AccessList{{
Address: addr,
StorageKeys: []common.Hash{
slot1,
slot2,
slot3,
slot4,
},
}}
)
func TestTracer_AccessList_Order(t *testing.T) {
al := newAccessList()
al.addAddress(addr)
al.addSlot(addr, slot1)
al.addSlot(addr, slot4)
al.addSlot(addr, slot3)
al.addSlot(addr, slot2)
require.NotEqual(t, ordered, al.accessList())
require.Equal(t, ordered, al.accessListSorted())
require.True(t, al.Equal(al))
}