mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-24 21:56:43 +00:00
Merge branch 'master' into upgrade_hasher_pool
This commit is contained in:
commit
75e16ba615
193 changed files with 5291 additions and 4402 deletions
22
.gitea/workflows/release-azure-cleanup.yml
Normal file
22
.gitea/workflows/release-azure-cleanup.yml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
on:
|
||||
schedule:
|
||||
- cron: '0 14 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
azure-cleanup:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.24
|
||||
cache: false
|
||||
|
||||
- name: Run cleanup script
|
||||
run: |
|
||||
go run build/ci.go purge -store gethstore/builds -days 14
|
||||
env:
|
||||
AZURE_BLOBSTORE_TOKEN: ${{ secrets.AZURE_BLOBSTORE_TOKEN }}
|
||||
36
.gitea/workflows/release-ppa.yml
Normal file
36
.gitea/workflows/release-ppa.yml
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
on:
|
||||
schedule:
|
||||
- cron: '0 15 * * *'
|
||||
push:
|
||||
branches:
|
||||
- "release/*"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
ppa:
|
||||
name: PPA Upload
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.24
|
||||
cache: false
|
||||
|
||||
- name: Install deb toolchain
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get -yq --no-install-suggests --no-install-recommends install devscripts debhelper dput fakeroot
|
||||
|
||||
- name: Add launchpad to known_hosts
|
||||
run: |
|
||||
echo '|1|7SiYPr9xl3uctzovOTj4gMwAC1M=|t6ReES75Bo/PxlOPJ6/GsGbTrM0= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA0aKz5UTUndYgIGG7dQBV+HaeuEZJ2xPHo2DS2iSKvUL4xNMSAY4UguNW+pX56nAQmZKIZZ8MaEvSj6zMEDiq6HFfn5JcTlM80UwlnyKe8B8p7Nk06PPQLrnmQt5fh0HmEcZx+JU9TZsfCHPnX7MNz4ELfZE6cFsclClrKim3BHUIGq//t93DllB+h4O9LHjEUsQ1Sr63irDLSutkLJD6RXchjROXkNirlcNVHH/jwLWR5RcYilNX7S5bIkK8NlWPjsn/8Ua5O7I9/YoE97PpO6i73DTGLh5H9JN/SITwCKBkgSDWUt61uPK3Y11Gty7o2lWsBjhBUm2Y38CBsoGmBw==' >> ~/.ssh/known_hosts
|
||||
|
||||
- name: Run ci.go
|
||||
run: |
|
||||
go run build/ci.go debsrc -upload ethereum/ethereum -sftp-user geth-ci -signer "Go Ethereum Linux Builder <geth-ci@ethereum.org>"
|
||||
env:
|
||||
PPA_SIGNING_KEY: ${{ secrets.PPA_SIGNING_KEY }}
|
||||
PPA_SSH_KEY: ${{ secrets.PPA_SSH_KEY }}
|
||||
147
.gitea/workflows/release.yml
Normal file
147
.gitea/workflows/release.yml
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
on:
|
||||
push:
|
||||
branches:
|
||||
- "master"
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
jobs:
|
||||
linux-intel:
|
||||
name: Linux Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.24
|
||||
cache: false
|
||||
|
||||
- name: Install cross toolchain
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get -yq --no-install-suggests --no-install-recommends install gcc-multilib
|
||||
|
||||
- name: Build (amd64)
|
||||
run: |
|
||||
go run build/ci.go install -arch amd64 -dlgo
|
||||
|
||||
- name: Create/upload archive (amd64)
|
||||
run: |
|
||||
go run build/ci.go archive -arch amd64 -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
||||
rm -f build/bin/*
|
||||
env:
|
||||
LINUX_SIGNING_KEY: ${{ secrets.LINUX_SIGNING_KEY }}
|
||||
AZURE_BLOBSTORE_TOKEN: ${{ secrets.AZURE_BLOBSTORE_TOKEN }}
|
||||
|
||||
- name: Build (386)
|
||||
run: |
|
||||
go run build/ci.go install -arch 386 -dlgo
|
||||
|
||||
- name: Create/upload archive (386)
|
||||
run: |
|
||||
go run build/ci.go archive -arch 386 -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
||||
rm -f build/bin/*
|
||||
env:
|
||||
LINUX_SIGNING_KEY: ${{ secrets.LINUX_SIGNING_KEY }}
|
||||
AZURE_BLOBSTORE_TOKEN: ${{ secrets.AZURE_BLOBSTORE_TOKEN }}
|
||||
|
||||
linux-arm:
|
||||
name: Linux Build (arm)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.24
|
||||
cache: false
|
||||
|
||||
- name: Install cross toolchain
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get -yq --no-install-suggests --no-install-recommends install gcc-arm-linux-gnueabi libc6-dev-armel-cross gcc-arm-linux-gnueabihf libc6-dev-armhf-cross gcc-aarch64-linux-gnu libc6-dev-arm64-cross
|
||||
ln -s /usr/include/asm-generic /usr/include/asm
|
||||
|
||||
- name: Build (arm64)
|
||||
run: |
|
||||
go run build/ci.go install -dlgo -arch arm64 -cc aarch64-linux-gnu-gcc
|
||||
|
||||
- name: Create/upload archive (arm64)
|
||||
run: |
|
||||
go run build/ci.go archive -arch arm64 -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
||||
rm -fr build/bin/*
|
||||
env:
|
||||
LINUX_SIGNING_KEY: ${{ secrets.LINUX_SIGNING_KEY }}
|
||||
AZURE_BLOBSTORE_TOKEN: ${{ secrets.AZURE_BLOBSTORE_TOKEN }}
|
||||
|
||||
- name: Run build (arm5)
|
||||
run: |
|
||||
go run build/ci.go install -dlgo -arch arm -cc arm-linux-gnueabi-gcc
|
||||
env:
|
||||
GOARM: "5"
|
||||
|
||||
- name: Create/upload archive (arm5)
|
||||
run: |
|
||||
go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
||||
env:
|
||||
GOARM: "5"
|
||||
LINUX_SIGNING_KEY: ${{ secrets.LINUX_SIGNING_KEY }}
|
||||
AZURE_BLOBSTORE_TOKEN: ${{ secrets.AZURE_BLOBSTORE_TOKEN }}
|
||||
|
||||
- name: Run build (arm6)
|
||||
run: |
|
||||
go run build/ci.go install -dlgo -arch arm -cc arm-linux-gnueabi-gcc
|
||||
env:
|
||||
GOARM: "6"
|
||||
|
||||
- name: Create/upload archive (arm6)
|
||||
run: |
|
||||
go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
||||
rm -fr build/bin/*
|
||||
env:
|
||||
GOARM: "6"
|
||||
LINUX_SIGNING_KEY: ${{ secrets.LINUX_SIGNING_KEY }}
|
||||
AZURE_BLOBSTORE_TOKEN: ${{ secrets.AZURE_BLOBSTORE_TOKEN }}
|
||||
|
||||
- name: Run build (arm7)
|
||||
run: |
|
||||
go run build/ci.go install -dlgo -arch arm -cc arm-linux-gnueabi-gcc
|
||||
env:
|
||||
GOARM: "7"
|
||||
|
||||
- name: Create/upload archive (arm7)
|
||||
run: |
|
||||
go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
||||
rm -fr build/bin/*
|
||||
env:
|
||||
GOARM: "7"
|
||||
LINUX_SIGNING_KEY: ${{ secrets.LINUX_SIGNING_KEY }}
|
||||
AZURE_BLOBSTORE_TOKEN: ${{ secrets.AZURE_BLOBSTORE_TOKEN }}
|
||||
|
||||
docker:
|
||||
name: Docker Image
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.24
|
||||
cache: false
|
||||
|
||||
- name: Run docker build
|
||||
env:
|
||||
DOCKER_HUB_USERNAME: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||
DOCKER_HUB_PASSWORD: ${{ secrets.DOCKER_HUB_PASSWORD }}
|
||||
run: |
|
||||
go run build/ci.go dockerx -platform linux/amd64,linux/arm64,linux/riscv64 -upload
|
||||
65
.travis.yml
65
.travis.yml
|
|
@ -5,7 +5,7 @@ jobs:
|
|||
include:
|
||||
# This builder create and push the Docker images for all architectures
|
||||
- stage: build
|
||||
if: type = push
|
||||
if: type = push && tag ~= /^v[0-9]/
|
||||
os: linux
|
||||
arch: amd64
|
||||
dist: focal
|
||||
|
|
@ -21,9 +21,25 @@ jobs:
|
|||
script:
|
||||
- go run build/ci.go dockerx -platform "linux/amd64,linux/arm64,linux/riscv64" -hub ethereum/client-go -upload
|
||||
|
||||
# This builder does the Ubuntu PPA nightly uploads
|
||||
- stage: build
|
||||
if: type = push && tag ~= /^v[0-9]/
|
||||
os: linux
|
||||
dist: focal
|
||||
go: 1.24.x
|
||||
env:
|
||||
- ubuntu-ppa
|
||||
git:
|
||||
submodules: false # avoid cloning ethereum/tests
|
||||
before_install:
|
||||
- sudo -E apt-get -yq --no-install-suggests --no-install-recommends install devscripts debhelper dput fakeroot
|
||||
script:
|
||||
- echo '|1|7SiYPr9xl3uctzovOTj4gMwAC1M=|t6ReES75Bo/PxlOPJ6/GsGbTrM0= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA0aKz5UTUndYgIGG7dQBV+HaeuEZJ2xPHo2DS2iSKvUL4xNMSAY4UguNW+pX56nAQmZKIZZ8MaEvSj6zMEDiq6HFfn5JcTlM80UwlnyKe8B8p7Nk06PPQLrnmQt5fh0HmEcZx+JU9TZsfCHPnX7MNz4ELfZE6cFsclClrKim3BHUIGq//t93DllB+h4O9LHjEUsQ1Sr63irDLSutkLJD6RXchjROXkNirlcNVHH/jwLWR5RcYilNX7S5bIkK8NlWPjsn/8Ua5O7I9/YoE97PpO6i73DTGLh5H9JN/SITwCKBkgSDWUt61uPK3Y11Gty7o2lWsBjhBUm2Y38CBsoGmBw==' >> ~/.ssh/known_hosts
|
||||
- go run build/ci.go debsrc -upload ethereum/ethereum -sftp-user geth-ci -signer "Go Ethereum Linux Builder <geth-ci@ethereum.org>"
|
||||
|
||||
# This builder does the Linux Azure uploads
|
||||
- stage: build
|
||||
if: type = push
|
||||
if: type = push && tag ~= /^v[0-9]/
|
||||
os: linux
|
||||
dist: focal
|
||||
sudo: required
|
||||
|
|
@ -56,40 +72,6 @@ jobs:
|
|||
- go run build/ci.go install -dlgo -arch arm64 -cc aarch64-linux-gnu-gcc
|
||||
- go run build/ci.go archive -arch arm64 -type tar -signer LINUX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds
|
||||
|
||||
# These builders run the tests
|
||||
- stage: build
|
||||
if: type = push
|
||||
os: linux
|
||||
arch: amd64
|
||||
dist: focal
|
||||
go: 1.24.x
|
||||
script:
|
||||
- travis_wait 45 go run build/ci.go test $TEST_PACKAGES
|
||||
|
||||
- stage: build
|
||||
if: type = push
|
||||
os: linux
|
||||
dist: focal
|
||||
go: 1.23.x
|
||||
script:
|
||||
- travis_wait 45 go run build/ci.go test $TEST_PACKAGES
|
||||
|
||||
# This builder does the Ubuntu PPA nightly uploads
|
||||
- stage: build
|
||||
if: type = cron || (type = push && tag ~= /^v[0-9]/)
|
||||
os: linux
|
||||
dist: focal
|
||||
go: 1.24.x
|
||||
env:
|
||||
- ubuntu-ppa
|
||||
git:
|
||||
submodules: false # avoid cloning ethereum/tests
|
||||
before_install:
|
||||
- sudo -E apt-get -yq --no-install-suggests --no-install-recommends install devscripts debhelper dput fakeroot
|
||||
script:
|
||||
- echo '|1|7SiYPr9xl3uctzovOTj4gMwAC1M=|t6ReES75Bo/PxlOPJ6/GsGbTrM0= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA0aKz5UTUndYgIGG7dQBV+HaeuEZJ2xPHo2DS2iSKvUL4xNMSAY4UguNW+pX56nAQmZKIZZ8MaEvSj6zMEDiq6HFfn5JcTlM80UwlnyKe8B8p7Nk06PPQLrnmQt5fh0HmEcZx+JU9TZsfCHPnX7MNz4ELfZE6cFsclClrKim3BHUIGq//t93DllB+h4O9LHjEUsQ1Sr63irDLSutkLJD6RXchjROXkNirlcNVHH/jwLWR5RcYilNX7S5bIkK8NlWPjsn/8Ua5O7I9/YoE97PpO6i73DTGLh5H9JN/SITwCKBkgSDWUt61uPK3Y11Gty7o2lWsBjhBUm2Y38CBsoGmBw==' >> ~/.ssh/known_hosts
|
||||
- go run build/ci.go debsrc -upload ethereum/ethereum -sftp-user geth-ci -signer "Go Ethereum Linux Builder <geth-ci@ethereum.org>"
|
||||
|
||||
# This builder does the Azure archive purges to avoid accumulating junk
|
||||
- stage: build
|
||||
if: type = cron
|
||||
|
|
@ -102,14 +84,3 @@ jobs:
|
|||
submodules: false # avoid cloning ethereum/tests
|
||||
script:
|
||||
- go run build/ci.go purge -store gethstore/builds -days 14
|
||||
|
||||
# This builder executes race tests
|
||||
- stage: build
|
||||
if: type = cron
|
||||
os: linux
|
||||
dist: focal
|
||||
go: 1.24.x
|
||||
env:
|
||||
- racetests
|
||||
script:
|
||||
- travis_wait 60 go run build/ci.go test -race $TEST_PACKAGES
|
||||
|
|
|
|||
8
Makefile
8
Makefile
|
|
@ -2,7 +2,7 @@
|
|||
# with Go source code. If you know what GOPATH is then you probably
|
||||
# don't need to bother with make.
|
||||
|
||||
.PHONY: geth all test lint fmt clean devtools help
|
||||
.PHONY: geth evm all test lint fmt clean devtools help
|
||||
|
||||
GOBIN = ./build/bin
|
||||
GO ?= latest
|
||||
|
|
@ -14,6 +14,12 @@ geth:
|
|||
@echo "Done building."
|
||||
@echo "Run \"$(GOBIN)/geth\" to launch geth."
|
||||
|
||||
#? evm: Build evm.
|
||||
evm:
|
||||
$(GORUN) build/ci.go install ./cmd/evm
|
||||
@echo "Done building."
|
||||
@echo "Run \"$(GOBIN)/evm\" to launch evm."
|
||||
|
||||
#? all: Build all packages and executables.
|
||||
all:
|
||||
$(GORUN) build/ci.go install
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ var (
|
|||
errBadInt16 = errors.New("abi: improperly encoded int16 value")
|
||||
errBadInt32 = errors.New("abi: improperly encoded int32 value")
|
||||
errBadInt64 = errors.New("abi: improperly encoded int64 value")
|
||||
errInvalidSign = errors.New("abi: negatively-signed value cannot be packed into uint parameter")
|
||||
)
|
||||
|
||||
// formatSliceString formats the reflection kind with the given slice size
|
||||
|
|
|
|||
|
|
@ -37,7 +37,16 @@ func packBytesSlice(bytes []byte, l int) []byte {
|
|||
// t.
|
||||
func packElement(t Type, reflectValue reflect.Value) ([]byte, error) {
|
||||
switch t.T {
|
||||
case IntTy, UintTy:
|
||||
case UintTy:
|
||||
// make sure to not pack a negative value into a uint type.
|
||||
if reflectValue.Kind() == reflect.Ptr {
|
||||
val := new(big.Int).Set(reflectValue.Interface().(*big.Int))
|
||||
if val.Sign() == -1 {
|
||||
return nil, errInvalidSign
|
||||
}
|
||||
}
|
||||
return packNum(reflectValue), nil
|
||||
case IntTy:
|
||||
return packNum(reflectValue), nil
|
||||
case StringTy:
|
||||
return packBytesSlice([]byte(reflectValue.String()), reflectValue.Len()), nil
|
||||
|
|
|
|||
|
|
@ -177,6 +177,11 @@ func TestMethodPack(t *testing.T) {
|
|||
if !bytes.Equal(packed, sig) {
|
||||
t.Errorf("expected %x got %x", sig, packed)
|
||||
}
|
||||
|
||||
// test that we can't pack a negative value for a parameter that is specified as a uint
|
||||
if _, err := abi.Pack("send", big.NewInt(-1)); err == nil {
|
||||
t.Fatal("expected error when trying to pack negative big.Int into uint256 value")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPackNumber(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -1014,128 +1014,134 @@ func TestPackAndUnpackIncompatibleNumber(t *testing.T) {
|
|||
cases := []struct {
|
||||
decodeType string
|
||||
inputValue *big.Int
|
||||
err error
|
||||
unpackErr error
|
||||
packErr error
|
||||
expectValue interface{}
|
||||
}{
|
||||
{
|
||||
decodeType: "uint8",
|
||||
inputValue: big.NewInt(math.MaxUint8 + 1),
|
||||
err: errBadUint8,
|
||||
unpackErr: errBadUint8,
|
||||
},
|
||||
{
|
||||
decodeType: "uint8",
|
||||
inputValue: big.NewInt(math.MaxUint8),
|
||||
err: nil,
|
||||
unpackErr: nil,
|
||||
expectValue: uint8(math.MaxUint8),
|
||||
},
|
||||
{
|
||||
decodeType: "uint16",
|
||||
inputValue: big.NewInt(math.MaxUint16 + 1),
|
||||
err: errBadUint16,
|
||||
unpackErr: errBadUint16,
|
||||
},
|
||||
{
|
||||
decodeType: "uint16",
|
||||
inputValue: big.NewInt(math.MaxUint16),
|
||||
err: nil,
|
||||
unpackErr: nil,
|
||||
expectValue: uint16(math.MaxUint16),
|
||||
},
|
||||
{
|
||||
decodeType: "uint32",
|
||||
inputValue: big.NewInt(math.MaxUint32 + 1),
|
||||
err: errBadUint32,
|
||||
unpackErr: errBadUint32,
|
||||
},
|
||||
{
|
||||
decodeType: "uint32",
|
||||
inputValue: big.NewInt(math.MaxUint32),
|
||||
err: nil,
|
||||
unpackErr: nil,
|
||||
expectValue: uint32(math.MaxUint32),
|
||||
},
|
||||
{
|
||||
decodeType: "uint64",
|
||||
inputValue: maxU64Plus1,
|
||||
err: errBadUint64,
|
||||
unpackErr: errBadUint64,
|
||||
},
|
||||
{
|
||||
decodeType: "uint64",
|
||||
inputValue: maxU64,
|
||||
err: nil,
|
||||
unpackErr: nil,
|
||||
expectValue: uint64(math.MaxUint64),
|
||||
},
|
||||
{
|
||||
decodeType: "uint256",
|
||||
inputValue: maxU64Plus1,
|
||||
err: nil,
|
||||
unpackErr: nil,
|
||||
expectValue: maxU64Plus1,
|
||||
},
|
||||
{
|
||||
decodeType: "int8",
|
||||
inputValue: big.NewInt(math.MaxInt8 + 1),
|
||||
err: errBadInt8,
|
||||
unpackErr: errBadInt8,
|
||||
},
|
||||
{
|
||||
decodeType: "int8",
|
||||
inputValue: big.NewInt(math.MinInt8 - 1),
|
||||
err: errBadInt8,
|
||||
packErr: errInvalidSign,
|
||||
},
|
||||
{
|
||||
decodeType: "int8",
|
||||
inputValue: big.NewInt(math.MaxInt8),
|
||||
err: nil,
|
||||
unpackErr: nil,
|
||||
expectValue: int8(math.MaxInt8),
|
||||
},
|
||||
{
|
||||
decodeType: "int16",
|
||||
inputValue: big.NewInt(math.MaxInt16 + 1),
|
||||
err: errBadInt16,
|
||||
unpackErr: errBadInt16,
|
||||
},
|
||||
{
|
||||
decodeType: "int16",
|
||||
inputValue: big.NewInt(math.MinInt16 - 1),
|
||||
err: errBadInt16,
|
||||
packErr: errInvalidSign,
|
||||
},
|
||||
{
|
||||
decodeType: "int16",
|
||||
inputValue: big.NewInt(math.MaxInt16),
|
||||
err: nil,
|
||||
unpackErr: nil,
|
||||
expectValue: int16(math.MaxInt16),
|
||||
},
|
||||
{
|
||||
decodeType: "int32",
|
||||
inputValue: big.NewInt(math.MaxInt32 + 1),
|
||||
err: errBadInt32,
|
||||
unpackErr: errBadInt32,
|
||||
},
|
||||
{
|
||||
decodeType: "int32",
|
||||
inputValue: big.NewInt(math.MinInt32 - 1),
|
||||
err: errBadInt32,
|
||||
packErr: errInvalidSign,
|
||||
},
|
||||
{
|
||||
decodeType: "int32",
|
||||
inputValue: big.NewInt(math.MaxInt32),
|
||||
err: nil,
|
||||
unpackErr: nil,
|
||||
expectValue: int32(math.MaxInt32),
|
||||
},
|
||||
{
|
||||
decodeType: "int64",
|
||||
inputValue: new(big.Int).Add(big.NewInt(math.MaxInt64), big.NewInt(1)),
|
||||
err: errBadInt64,
|
||||
unpackErr: errBadInt64,
|
||||
},
|
||||
{
|
||||
decodeType: "int64",
|
||||
inputValue: new(big.Int).Sub(big.NewInt(math.MinInt64), big.NewInt(1)),
|
||||
err: errBadInt64,
|
||||
packErr: errInvalidSign,
|
||||
},
|
||||
{
|
||||
decodeType: "int64",
|
||||
inputValue: big.NewInt(math.MaxInt64),
|
||||
err: nil,
|
||||
unpackErr: nil,
|
||||
expectValue: int64(math.MaxInt64),
|
||||
},
|
||||
}
|
||||
for i, testCase := range cases {
|
||||
packed, err := encodeABI.Pack(testCase.inputValue)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
if testCase.packErr != nil {
|
||||
if err == nil {
|
||||
t.Fatalf("expected packing of testcase input value to fail")
|
||||
}
|
||||
if err != testCase.packErr {
|
||||
t.Fatalf("expected error '%v', got '%v'", testCase.packErr, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err != nil && err != testCase.packErr {
|
||||
panic(fmt.Errorf("unexpected error packing test-case input: %v", err))
|
||||
}
|
||||
ty, err := NewType(testCase.decodeType, "", nil)
|
||||
if err != nil {
|
||||
|
|
@ -1145,8 +1151,8 @@ func TestPackAndUnpackIncompatibleNumber(t *testing.T) {
|
|||
{Type: ty},
|
||||
}
|
||||
decoded, err := decodeABI.Unpack(packed)
|
||||
if err != testCase.err {
|
||||
t.Fatalf("Expected error %v, actual error %v. case %d", testCase.err, err, i)
|
||||
if err != testCase.unpackErr {
|
||||
t.Fatalf("Expected error %v, actual error %v. case %d", testCase.unpackErr, err, i)
|
||||
}
|
||||
if err != nil {
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -93,9 +93,6 @@ func NewManager(config *Config, backends ...Backend) *Manager {
|
|||
|
||||
// Close terminates the account manager's internal notification processes.
|
||||
func (am *Manager) Close() error {
|
||||
for _, w := range am.wallets {
|
||||
w.Close()
|
||||
}
|
||||
errc := make(chan error)
|
||||
am.quit <- errc
|
||||
return <-errc
|
||||
|
|
@ -149,6 +146,10 @@ func (am *Manager) update() {
|
|||
am.lock.Unlock()
|
||||
close(event.processed)
|
||||
case errc := <-am.quit:
|
||||
// Close all owned wallets
|
||||
for _, w := range am.wallets {
|
||||
w.Close()
|
||||
}
|
||||
// Manager terminating, return
|
||||
errc <- nil
|
||||
// Signals event emitters the loop is not receiving values
|
||||
|
|
|
|||
|
|
@ -123,6 +123,11 @@ type BlobAndProofV1 struct {
|
|||
Proof hexutil.Bytes `json:"proof"`
|
||||
}
|
||||
|
||||
type BlobAndProofV2 struct {
|
||||
Blob hexutil.Bytes `json:"blob"`
|
||||
CellProofs []hexutil.Bytes `json:"proofs"`
|
||||
}
|
||||
|
||||
// JSON type overrides for ExecutionPayloadEnvelope.
|
||||
type executionPayloadEnvelopeMarshaling struct {
|
||||
BlockValue *hexutil.Big
|
||||
|
|
@ -331,7 +336,9 @@ func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types.
|
|||
for j := range sidecar.Blobs {
|
||||
bundle.Blobs = append(bundle.Blobs, hexutil.Bytes(sidecar.Blobs[j][:]))
|
||||
bundle.Commitments = append(bundle.Commitments, hexutil.Bytes(sidecar.Commitments[j][:]))
|
||||
bundle.Proofs = append(bundle.Proofs, hexutil.Bytes(sidecar.Proofs[j][:]))
|
||||
}
|
||||
for _, proof := range sidecar.Proofs {
|
||||
bundle.Proofs = append(bundle.Proofs, hexutil.Bytes(proof[:]))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
56
beacon/engine/types_test.go
Normal file
56
beacon/engine/types_test.go
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
// Copyright 2025 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package engine
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
||||
)
|
||||
|
||||
func TestBlobs(t *testing.T) {
|
||||
var (
|
||||
emptyBlob = new(kzg4844.Blob)
|
||||
emptyBlobCommit, _ = kzg4844.BlobToCommitment(emptyBlob)
|
||||
emptyBlobProof, _ = kzg4844.ComputeBlobProof(emptyBlob, emptyBlobCommit)
|
||||
emptyCellProof, _ = kzg4844.ComputeCellProofs(emptyBlob)
|
||||
)
|
||||
header := types.Header{}
|
||||
block := types.NewBlock(&header, &types.Body{}, nil, nil)
|
||||
|
||||
sidecarWithoutCellProofs := &types.BlobTxSidecar{
|
||||
Blobs: []kzg4844.Blob{*emptyBlob},
|
||||
Commitments: []kzg4844.Commitment{emptyBlobCommit},
|
||||
Proofs: []kzg4844.Proof{emptyBlobProof},
|
||||
}
|
||||
env := BlockToExecutableData(block, common.Big0, []*types.BlobTxSidecar{sidecarWithoutCellProofs}, nil)
|
||||
if len(env.BlobsBundle.Proofs) != 1 {
|
||||
t.Fatalf("Expect 1 proof in blobs bundle, got %v", len(env.BlobsBundle.Proofs))
|
||||
}
|
||||
|
||||
sidecarWithCellProofs := &types.BlobTxSidecar{
|
||||
Blobs: []kzg4844.Blob{*emptyBlob},
|
||||
Commitments: []kzg4844.Commitment{emptyBlobCommit},
|
||||
Proofs: emptyCellProof,
|
||||
}
|
||||
env = BlockToExecutableData(block, common.Big0, []*types.BlobTxSidecar{sidecarWithCellProofs}, nil)
|
||||
if len(env.BlobsBundle.Proofs) != 128 {
|
||||
t.Fatalf("Expect 128 proofs in blobs bundle, got %v", len(env.BlobsBundle.Proofs))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,58 +1,58 @@
|
|||
# This file contains sha256 checksums of optional build dependencies.
|
||||
|
||||
# version:spec-tests pectra-devnet-6@v1.0.0
|
||||
# version:spec-tests v4.5.0
|
||||
# https://github.com/ethereum/execution-spec-tests/releases
|
||||
# https://github.com/ethereum/execution-spec-tests/releases/download/pectra-devnet-6%40v1.0.0/
|
||||
b69211752a3029083c020dc635fe12156ca1a6725a08559da540a0337586a77e fixtures_pectra-devnet-6.tar.gz
|
||||
# https://github.com/ethereum/execution-spec-tests/releases/download/v4.5.0/
|
||||
58afb92a0075a2cb7c4dec1281f7cb88b21b02afbedad096b580f3f8cc14c54c fixtures_develop.tar.gz
|
||||
|
||||
# version:golang 1.24.3
|
||||
# version:golang 1.24.4
|
||||
# https://go.dev/dl/
|
||||
229c08b600b1446798109fae1f569228102c8473caba8104b6418cb5bc032878 go1.24.3.src.tar.gz
|
||||
6f6901497547db3b77c14f7f953fbcef9fa5fb84199ee2ee14a5686e66bed5a6 go1.24.3.aix-ppc64.tar.gz
|
||||
a05fa7e4043a4fec66897135219e3b8ab2202b5ef351c60c2fbb531dfb8f2900 go1.24.3.darwin-amd64.pkg
|
||||
13e6fe3fcf65689d77d40e633de1e31c6febbdbcb846eb05fc2434ed2213e92b go1.24.3.darwin-amd64.tar.gz
|
||||
97055ff4214043b39dc32e043fdd5c565df7c0a4e2fc0174e779a134c347ae0e go1.24.3.darwin-arm64.pkg
|
||||
64a3fa22142f627e78fac3018ce3d4aeace68b743eff0afda8aae0411df5e4fb go1.24.3.darwin-arm64.tar.gz
|
||||
32de3fd44d5055973978436a7f1f0ffbaae85c1b603ec6105e5c38d8a674c721 go1.24.3.dragonfly-amd64.tar.gz
|
||||
9fe6101b3797919bd7337ee5ce591954f85d59db7ae88983904db29fd64c3dd1 go1.24.3.freebsd-386.tar.gz
|
||||
6ccf4cca287e90cc28cd7954b6172f5d177a17e20b072b65f7f39636c325e2fb go1.24.3.freebsd-amd64.tar.gz
|
||||
ce45ebf389066f82a7b056b66dd650efb51fde6f8bf92a2a3ab6990f02788ebf go1.24.3.freebsd-arm.tar.gz
|
||||
8f6494a12a874d0ea57c67987829359e016960ce3ba0673273609d6ac2af589a go1.24.3.freebsd-arm64.tar.gz
|
||||
f9db392560cf0851f0bc8f2190e1978e01b4603038c27fecfc8658a695b71616 go1.24.3.freebsd-riscv64.tar.gz
|
||||
01717fff64c5d98457272002fa825d0a15e307bf6e189f2b0c23817fa033b61c go1.24.3.illumos-amd64.tar.gz
|
||||
41b1051063e68cbd2b919bf12326764fe33937cf1d32b5c529dd1a4f43dce578 go1.24.3.linux-386.tar.gz
|
||||
3333f6ea53afa971e9078895eaa4ac7204a8c6b5c68c10e6bc9a33e8e391bdd8 go1.24.3.linux-amd64.tar.gz
|
||||
a463cb59382bd7ae7d8f4c68846e73c4d589f223c589ac76871b66811ded7836 go1.24.3.linux-arm64.tar.gz
|
||||
17a392d7e826625dd12a32099df0b00b85c32d8132ed86fe917183ee5c3f88ed go1.24.3.linux-armv6l.tar.gz
|
||||
e4b003c04c902edc140153d279b42167f1ad7c229f48f1f729bbef5e65e88d1f go1.24.3.linux-loong64.tar.gz
|
||||
1c79d89edf835edf9d4336ccea7cb89bc5c0ca82b12b36b218d599a5400d60fe go1.24.3.linux-mips.tar.gz
|
||||
0b64fe147d69f4d681d8e8a035c760477531432f83d831f18d37cb9bf3652488 go1.24.3.linux-mips64.tar.gz
|
||||
396b784c255b64512dc00c302c053e43a3cbfc77518664c6ac5569aafad4d1e6 go1.24.3.linux-mips64le.tar.gz
|
||||
93898313887f14e8efbe9d7386d5da4792b2d6c492bee562993fd4c9daa75c6d go1.24.3.linux-mipsle.tar.gz
|
||||
873ae3a6a6655a7b6f820e095d9965507e8dfd3cf76bc92d75c564ecbca385f6 go1.24.3.linux-ppc64.tar.gz
|
||||
341a749d168f47b1d4dad25e32cae70849b7ceed7c290823b853c9e6b0df0856 go1.24.3.linux-ppc64le.tar.gz
|
||||
fa482f53ccb4ba280316b8c5751ea67291507280d9166f2a38fe4d9b5d5fb64b go1.24.3.linux-riscv64.tar.gz
|
||||
a87b0c2a079a0bece1620fb29a00e02b4dba17507850f837e754af7d57cda282 go1.24.3.linux-s390x.tar.gz
|
||||
63155382308db1306200aff7821aa26bf2a2dda23537dd637a9704b485b6ddf0 go1.24.3.netbsd-386.tar.gz
|
||||
fe2c5c79482958b867c08a4fc2a10a998de9c0206b08d5b3ebcb2232e8d2777c go1.24.3.netbsd-amd64.tar.gz
|
||||
e8ff77aef21521b5dd94e44282a3243309b80717414cf12f72835a45886a049f go1.24.3.netbsd-arm.tar.gz
|
||||
b337fbaf82822685940ffaa76fbcf4be5d2f0258bc819cd80bc408b491f45c04 go1.24.3.netbsd-arm64.tar.gz
|
||||
c1bb9dd8418480aa7f65452b08de3759da3bf89702be71b5a9fc084836b24ad5 go1.24.3.openbsd-386.tar.gz
|
||||
531218de748b0caaf6d1ad18921206fc12baaa89bf483a0a5e60a571c206fe6f go1.24.3.openbsd-amd64.tar.gz
|
||||
bcd0dc959986fc346969b5d4111c3c8031882d8bf8d87a2c2ecf1328962a91f2 go1.24.3.openbsd-arm.tar.gz
|
||||
00ee6f8f1c41fd2e28ad386bd7e39acce7cab84af6de835855b29d1c597335c4 go1.24.3.openbsd-arm64.tar.gz
|
||||
9f4ec0a9203ed3c54ce1a2a390ad3d45838cdb7efd85baeff857e37dfde04edd go1.24.3.openbsd-ppc64.tar.gz
|
||||
da4d6f80e2373250d8c31c32dcd1e08775c327c0d610923604660cc0e07e8cba go1.24.3.openbsd-riscv64.tar.gz
|
||||
f5d02149132eedda6c2d46b360d7da462b8a5f9e3f8567db100c2d7bff0ddcd7 go1.24.3.plan9-386.tar.gz
|
||||
175f3d79f4762a3c545d2c6393bf6b8bac24e838026869dafab06b930735c94f go1.24.3.plan9-amd64.tar.gz
|
||||
d1e4ac15095da1611659261c2228c2058756cf87d61d9fad262f76755ef26849 go1.24.3.plan9-arm.tar.gz
|
||||
e644220a6ced3c07a7acc1364193cb709a97737dd8b6792a07a8ec6d9996713e go1.24.3.solaris-amd64.tar.gz
|
||||
0d7e7dc0a31ba0cdd487415709d03b02fc9490ef111e8dfd22788a6d63316f37 go1.24.3.windows-386.msi
|
||||
c27c463a61ab849266baa0c17a6c5c4256a574ab642f609ba25c96ec965dc184 go1.24.3.windows-386.zip
|
||||
d5b7637e7e138be877d96a4501709d480e050d86a8f402bc950e72112b5aedc5 go1.24.3.windows-amd64.msi
|
||||
be9787cb08998b1860fe3513e48a5fe5b96302d358a321b58e651184fa9638b3 go1.24.3.windows-amd64.zip
|
||||
7efde2e5e8468e9caf2c7fc94f4da78a726a5031a1ed63acff7899527cdddff6 go1.24.3.windows-arm64.msi
|
||||
eec9fa736056b54dd88ecb669db2bfad39b0c48f6f9080f036dfa1ca42dc4bb5 go1.24.3.windows-arm64.zip
|
||||
5a86a83a31f9fa81490b8c5420ac384fd3d95a3e71fba665c7b3f95d1dfef2b4 go1.24.4.src.tar.gz
|
||||
0d2af78e3b6e08f8013dbbdb26ae33052697b6b72e03ec17d496739c2a1aed68 go1.24.4.aix-ppc64.tar.gz
|
||||
69bef555e114b4a2252452b6e7049afc31fbdf2d39790b669165e89525cd3f5c go1.24.4.darwin-amd64.tar.gz
|
||||
c4d74453a26f488bdb4b0294da4840d9020806de4661785334eb6d1803ee5c27 go1.24.4.darwin-amd64.pkg
|
||||
27973684b515eaf461065054e6b572d9390c05e69ba4a423076c160165336470 go1.24.4.darwin-arm64.tar.gz
|
||||
2fe1f8746745c4bfebd494583aaef24cad42594f6d25ed67856879d567ee66e7 go1.24.4.darwin-arm64.pkg
|
||||
70b2de9c1cafe5af7be3eb8f80753cce0501ef300db3f3bd59be7ccc464234e1 go1.24.4.dragonfly-amd64.tar.gz
|
||||
8d529839db29ee171505b89dc9c3de76003a4ab56202d84bddbbecacbfb6d7c9 go1.24.4.freebsd-386.tar.gz
|
||||
6cbc3ad6cc21bdcc7283824d3ac0e85512c02022f6a35eb2e844882ea6e8448c go1.24.4.freebsd-amd64.tar.gz
|
||||
d49ae050c20aff646a7641dd903f03eb674570790b90ffb298076c4d41e36655 go1.24.4.freebsd-arm.tar.gz
|
||||
e31924abef2a28456b7103c0a5d333dcc11ecf19e76d5de1a383ad5fe0b42457 go1.24.4.freebsd-arm64.tar.gz
|
||||
b5bca135eae8ebddf22972611ac1c58ae9fbb5979fd953cc5245c5b1b2517546 go1.24.4.freebsd-riscv64.tar.gz
|
||||
7d5efda511ff7e3114b130acee5d0bffbb078fedbfa9b2c1b6a807107e1ca23a go1.24.4.illumos-amd64.tar.gz
|
||||
130c9b061082eca15513e595e9952a2ded32e737e609dd0e49f7dfa74eba026d go1.24.4.linux-386.tar.gz
|
||||
77e5da33bb72aeaef1ba4418b6fe511bc4d041873cbf82e5aa6318740df98717 go1.24.4.linux-amd64.tar.gz
|
||||
d5501ee5aca0f258d5fe9bfaed401958445014495dc115f202d43d5210b45241 go1.24.4.linux-arm64.tar.gz
|
||||
6a554e32301cecae3162677e66d4264b81b3b1a89592dd1b7b5c552c7a49fe37 go1.24.4.linux-armv6l.tar.gz
|
||||
b208eb25fe244408cbe269ed426454bc46e59d0e0a749b6240d39e884e969875 go1.24.4.linux-loong64.tar.gz
|
||||
fddfcb28fd36fe63d2ae181026798f86f3bbd3a7bb0f1e1f617dd3d604bf3fe4 go1.24.4.linux-mips.tar.gz
|
||||
7934b924d5ab8c8ae3134a09a6ae74d3c39f63f6c4322ec289364dbbf0bac3ca go1.24.4.linux-mips64.tar.gz
|
||||
fa763d8673f94d6e534bb72c3cf675d4c2b8da4a6da42a89f08c5586106db39c go1.24.4.linux-mips64le.tar.gz
|
||||
84363dbfe49b41d43df84420a09bd53a4770053d63bfa509868c46a5f8eb3ff7 go1.24.4.linux-mipsle.tar.gz
|
||||
28fcbd5d3b56493606873c33f2b4bdd84ba93c633f37313613b5a1e6495c6fe5 go1.24.4.linux-ppc64.tar.gz
|
||||
9ca4afef813a2578c23843b640ae0290aa54b2e3c950a6cc4c99e16a57dec2ec go1.24.4.linux-ppc64le.tar.gz
|
||||
1d7034f98662d8f2c8abd7c700ada4093acb4f9c00e0e51a30344821d0785c77 go1.24.4.linux-riscv64.tar.gz
|
||||
0449f3203c39703ab27684be763e9bb78ca9a051e0e4176727aead9461b6deb5 go1.24.4.linux-s390x.tar.gz
|
||||
954b49ccc2cfcf4b5f7cd33ff662295e0d3b74e7590c8e25fc2abb30bce120ba go1.24.4.netbsd-386.tar.gz
|
||||
370fabcdfee7c18857c96fdd5b706e025d4fb86a208da88ba56b1493b35498e9 go1.24.4.netbsd-amd64.tar.gz
|
||||
7935ef95d4d1acc48587b1eb4acab98b0a7d9569736a32398b9c1d2e89026865 go1.24.4.netbsd-arm.tar.gz
|
||||
ead78fd0fa29fbb176cc83f1caa54032e1a44f842affa56a682c647e0759f237 go1.24.4.netbsd-arm64.tar.gz
|
||||
913e217394b851a636b99de175f0c2f9ab9938b41c557f047168f77ee485d776 go1.24.4.openbsd-386.tar.gz
|
||||
24568da3dcbcdb24ec18b631f072faf0f3763e3d04f79032dc56ad9ec35379c4 go1.24.4.openbsd-amd64.tar.gz
|
||||
45abf523f870632417ab007de3841f64dd906bde546ffc8c6380ccbe91c7fb73 go1.24.4.openbsd-arm.tar.gz
|
||||
7c57c69b5dd1e946b28a3034c285240a48e2861bdcb50b7d9c0ed61bcf89c879 go1.24.4.openbsd-arm64.tar.gz
|
||||
91ed711f704829372d6931e1897631ef40288b8f9e3cd6ef4a24df7126d1066a go1.24.4.openbsd-ppc64.tar.gz
|
||||
de5e270d971c8790e8880168d56a2ea103979927c10ded136d792bbdf9bce3d3 go1.24.4.openbsd-riscv64.tar.gz
|
||||
ff429d03f00bcd32a50f445320b8329d0fadb2a2fff899c11e95e0922a82c543 go1.24.4.plan9-386.tar.gz
|
||||
39d6363a43fd16b60ae9ad7346a264e982e4fa653dee3b45f83e03cd2f7a6647 go1.24.4.plan9-amd64.tar.gz
|
||||
1964ae2571259de77b930e97f2891aa92706ff81aac9909d45bb107b0fab16c8 go1.24.4.plan9-arm.tar.gz
|
||||
a7f9af424e8fb87886664754badca459513f64f6a321d17f1d219b8edf519821 go1.24.4.solaris-amd64.tar.gz
|
||||
d454d3cb144432f1726bf00e28c6017e78ccb256a8d01b8e3fb1b2e6b5650f28 go1.24.4.windows-386.zip
|
||||
966ecace1cdbb3497a2b930bdb0f90c3ad32922fa1a7c655b2d4bbeb7e4ac308 go1.24.4.windows-386.msi
|
||||
b751a1136cb9d8a2e7ebb22c538c4f02c09b98138c7c8bfb78a54a4566c013b1 go1.24.4.windows-amd64.zip
|
||||
0cbb6e83865747dbe69b3d4155f92e88fcf336ff5d70182dba145e9d7bd3d8f6 go1.24.4.windows-amd64.msi
|
||||
d17da51bc85bd010754a4063215d15d2c033cc289d67ca9201a03c9041b2969d go1.24.4.windows-arm64.zip
|
||||
47dbe734b6a829de45654648a7abcf05bdceef5c80e03ea0b208eeebef75a852 go1.24.4.windows-arm64.msi
|
||||
|
||||
# version:golangci 2.0.2
|
||||
# https://github.com/golangci/golangci-lint/releases/
|
||||
|
|
|
|||
|
|
@ -332,7 +332,7 @@ func doTest(cmdline []string) {
|
|||
// downloadSpecTestFixtures downloads and extracts the execution-spec-tests fixtures.
|
||||
func downloadSpecTestFixtures(csdb *download.ChecksumDB, cachedir string) string {
|
||||
ext := ".tar.gz"
|
||||
base := "fixtures_pectra-devnet-6" // TODO(s1na) rename once the version becomes part of the filename
|
||||
base := "fixtures_develop"
|
||||
archivePath := filepath.Join(cachedir, base+ext)
|
||||
if err := csdb.DownloadFileFromKnownURL(archivePath); err != nil {
|
||||
log.Fatal(err)
|
||||
|
|
|
|||
|
|
@ -1,228 +0,0 @@
|
|||
// Copyright 2024 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var jt vm.JumpTable
|
||||
|
||||
const initcode = "INITCODE"
|
||||
|
||||
func init() {
|
||||
jt = vm.NewEOFInstructionSetForTesting()
|
||||
}
|
||||
|
||||
var (
|
||||
hexFlag = &cli.StringFlag{
|
||||
Name: "hex",
|
||||
Usage: "Single container data parse and validation",
|
||||
}
|
||||
refTestFlag = &cli.StringFlag{
|
||||
Name: "test",
|
||||
Usage: "Path to EOF validation reference test.",
|
||||
}
|
||||
eofParseCommand = &cli.Command{
|
||||
Name: "eofparse",
|
||||
Aliases: []string{"eof"},
|
||||
Usage: "Parses hex eof container and returns validation errors (if any)",
|
||||
Action: eofParseAction,
|
||||
Flags: []cli.Flag{
|
||||
hexFlag,
|
||||
refTestFlag,
|
||||
},
|
||||
}
|
||||
eofDumpCommand = &cli.Command{
|
||||
Name: "eofdump",
|
||||
Usage: "Parses hex eof container and prints out human-readable representation of the container.",
|
||||
Action: eofDumpAction,
|
||||
Flags: []cli.Flag{
|
||||
hexFlag,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func eofParseAction(ctx *cli.Context) error {
|
||||
// If `--test` is set, parse and validate the reference test at the provided path.
|
||||
if ctx.IsSet(refTestFlag.Name) {
|
||||
var (
|
||||
file = ctx.String(refTestFlag.Name)
|
||||
executedTests int
|
||||
passedTests int
|
||||
)
|
||||
err := filepath.Walk(file, func(path string, info fs.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
log.Debug("Executing test", "name", info.Name())
|
||||
passed, tot, err := executeTest(path)
|
||||
passedTests += passed
|
||||
executedTests += tot
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info("Executed tests", "passed", passedTests, "total executed", executedTests)
|
||||
return nil
|
||||
}
|
||||
// If `--hex` is set, parse and validate the hex string argument.
|
||||
if ctx.IsSet(hexFlag.Name) {
|
||||
if _, err := parseAndValidate(ctx.String(hexFlag.Name), false); err != nil {
|
||||
return fmt.Errorf("err: %w", err)
|
||||
}
|
||||
fmt.Println("OK")
|
||||
return nil
|
||||
}
|
||||
// If neither are passed in, read input from stdin.
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
scanner.Buffer(make([]byte, 1024*1024), 10*1024*1024)
|
||||
for scanner.Scan() {
|
||||
l := strings.TrimSpace(scanner.Text())
|
||||
if strings.HasPrefix(l, "#") || l == "" {
|
||||
continue
|
||||
}
|
||||
if _, err := parseAndValidate(l, false); err != nil {
|
||||
fmt.Printf("err: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("OK")
|
||||
}
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
fmt.Println(err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type refTests struct {
|
||||
Vectors map[string]eOFTest `json:"vectors"`
|
||||
}
|
||||
|
||||
type eOFTest struct {
|
||||
Code string `json:"code"`
|
||||
Results map[string]etResult `json:"results"`
|
||||
ContainerKind string `json:"containerKind"`
|
||||
}
|
||||
|
||||
type etResult struct {
|
||||
Result bool `json:"result"`
|
||||
Exception string `json:"exception,omitempty"`
|
||||
}
|
||||
|
||||
func executeTest(path string) (int, int, error) {
|
||||
src, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
var testsByName map[string]refTests
|
||||
if err := json.Unmarshal(src, &testsByName); err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
passed, total := 0, 0
|
||||
for testsName, tests := range testsByName {
|
||||
for name, tt := range tests.Vectors {
|
||||
for fork, r := range tt.Results {
|
||||
total++
|
||||
_, err := parseAndValidate(tt.Code, tt.ContainerKind == initcode)
|
||||
if r.Result && err != nil {
|
||||
log.Error("Test failure, expected validation success", "name", testsName, "idx", name, "fork", fork, "err", err)
|
||||
continue
|
||||
}
|
||||
if !r.Result && err == nil {
|
||||
log.Error("Test failure, expected validation error", "name", testsName, "idx", name, "fork", fork, "have err", r.Exception, "err", err)
|
||||
continue
|
||||
}
|
||||
passed++
|
||||
}
|
||||
}
|
||||
}
|
||||
return passed, total, nil
|
||||
}
|
||||
|
||||
func parseAndValidate(s string, isInitCode bool) (*vm.Container, error) {
|
||||
if len(s) >= 2 && strings.HasPrefix(s, "0x") {
|
||||
s = s[2:]
|
||||
}
|
||||
b, err := hex.DecodeString(s)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to decode data: %w", err)
|
||||
}
|
||||
return parse(b, isInitCode)
|
||||
}
|
||||
|
||||
func parse(b []byte, isInitCode bool) (*vm.Container, error) {
|
||||
var c vm.Container
|
||||
if err := c.UnmarshalBinary(b, isInitCode); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := c.ValidateCode(&jt, isInitCode); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
func eofDumpAction(ctx *cli.Context) error {
|
||||
// If `--hex` is set, parse and validate the hex string argument.
|
||||
if ctx.IsSet(hexFlag.Name) {
|
||||
return eofDump(ctx.String(hexFlag.Name))
|
||||
}
|
||||
// Otherwise read from stdin
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
scanner.Buffer(make([]byte, 1024*1024), 10*1024*1024)
|
||||
for scanner.Scan() {
|
||||
l := strings.TrimSpace(scanner.Text())
|
||||
if strings.HasPrefix(l, "#") || l == "" {
|
||||
continue
|
||||
}
|
||||
if err := eofDump(l); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("")
|
||||
}
|
||||
return scanner.Err()
|
||||
}
|
||||
|
||||
func eofDump(hexdata string) error {
|
||||
if len(hexdata) >= 2 && strings.HasPrefix(hexdata, "0x") {
|
||||
hexdata = hexdata[2:]
|
||||
}
|
||||
b, err := hex.DecodeString(hexdata)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to decode data: %w", err)
|
||||
}
|
||||
var c vm.Container
|
||||
if err := c.UnmarshalBinary(b, false); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(c.String())
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,182 +0,0 @@
|
|||
// Copyright 2024 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
)
|
||||
|
||||
func FuzzEofParsing(f *testing.F) {
|
||||
// Seed with corpus from execution-spec-tests
|
||||
for i := 0; ; i++ {
|
||||
fname := fmt.Sprintf("testdata/eof/eof_corpus_%d.txt", i)
|
||||
corpus, err := os.Open(fname)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
f.Logf("Reading seed data from %v", fname)
|
||||
scanner := bufio.NewScanner(corpus)
|
||||
scanner.Buffer(make([]byte, 1024), 10*1024*1024)
|
||||
for scanner.Scan() {
|
||||
s := scanner.Text()
|
||||
if len(s) >= 2 && strings.HasPrefix(s, "0x") {
|
||||
s = s[2:]
|
||||
}
|
||||
b, err := hex.DecodeString(s)
|
||||
if err != nil {
|
||||
panic(err) // rotten corpus
|
||||
}
|
||||
f.Add(b)
|
||||
}
|
||||
corpus.Close()
|
||||
if err := scanner.Err(); err != nil {
|
||||
panic(err) // rotten corpus
|
||||
}
|
||||
}
|
||||
// And do the fuzzing
|
||||
f.Fuzz(func(t *testing.T, data []byte) {
|
||||
var (
|
||||
jt = vm.NewEOFInstructionSetForTesting()
|
||||
c vm.Container
|
||||
)
|
||||
cpy := common.CopyBytes(data)
|
||||
if err := c.UnmarshalBinary(data, true); err == nil {
|
||||
c.ValidateCode(&jt, true)
|
||||
if have := c.MarshalBinary(); !bytes.Equal(have, data) {
|
||||
t.Fatal("Unmarshal-> Marshal failure!")
|
||||
}
|
||||
}
|
||||
if err := c.UnmarshalBinary(data, false); err == nil {
|
||||
c.ValidateCode(&jt, false)
|
||||
if have := c.MarshalBinary(); !bytes.Equal(have, data) {
|
||||
t.Fatal("Unmarshal-> Marshal failure!")
|
||||
}
|
||||
}
|
||||
if !bytes.Equal(cpy, data) {
|
||||
panic("data modified during unmarshalling")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestEofParseInitcode(t *testing.T) {
|
||||
testEofParse(t, true, "testdata/eof/results.initcode.txt")
|
||||
}
|
||||
|
||||
func TestEofParseRegular(t *testing.T) {
|
||||
testEofParse(t, false, "testdata/eof/results.regular.txt")
|
||||
}
|
||||
|
||||
func testEofParse(t *testing.T, isInitCode bool, wantFile string) {
|
||||
var wantFn func() string
|
||||
var wantLoc = 0
|
||||
{ // Configure the want-reader
|
||||
wants, err := os.Open(wantFile)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
scanner := bufio.NewScanner(wants)
|
||||
scanner.Buffer(make([]byte, 1024), 10*1024*1024)
|
||||
wantFn = func() string {
|
||||
if scanner.Scan() {
|
||||
wantLoc++
|
||||
return scanner.Text()
|
||||
}
|
||||
return "end of file reached"
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; ; i++ {
|
||||
fname := fmt.Sprintf("testdata/eof/eof_corpus_%d.txt", i)
|
||||
corpus, err := os.Open(fname)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
t.Logf("# Reading seed data from %v", fname)
|
||||
scanner := bufio.NewScanner(corpus)
|
||||
scanner.Buffer(make([]byte, 1024), 10*1024*1024)
|
||||
line := 1
|
||||
for scanner.Scan() {
|
||||
s := scanner.Text()
|
||||
if len(s) >= 2 && strings.HasPrefix(s, "0x") {
|
||||
s = s[2:]
|
||||
}
|
||||
b, err := hex.DecodeString(s)
|
||||
if err != nil {
|
||||
panic(err) // rotten corpus
|
||||
}
|
||||
have := "OK"
|
||||
if _, err := parse(b, isInitCode); err != nil {
|
||||
have = fmt.Sprintf("ERR: %v", err)
|
||||
}
|
||||
if false { // Change this to generate the want-output
|
||||
fmt.Printf("%v\n", have)
|
||||
} else {
|
||||
want := wantFn()
|
||||
if have != want {
|
||||
if len(want) > 100 {
|
||||
want = want[:100]
|
||||
}
|
||||
if len(b) > 100 {
|
||||
b = b[:100]
|
||||
}
|
||||
t.Errorf("%v:%d\n%v\ninput %x\nisInit: %v\nhave: %q\nwant: %q\n",
|
||||
fname, line, fmt.Sprintf("%v:%d", wantFile, wantLoc), b, isInitCode, have, want)
|
||||
}
|
||||
}
|
||||
line++
|
||||
}
|
||||
corpus.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEofParse(b *testing.B) {
|
||||
corpus, err := os.Open("testdata/eof/eof_benches.txt")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer corpus.Close()
|
||||
scanner := bufio.NewScanner(corpus)
|
||||
scanner.Buffer(make([]byte, 1024), 10*1024*1024)
|
||||
line := 1
|
||||
for scanner.Scan() {
|
||||
s := scanner.Text()
|
||||
if len(s) >= 2 && strings.HasPrefix(s, "0x") {
|
||||
s = s[2:]
|
||||
}
|
||||
data, err := hex.DecodeString(s)
|
||||
if err != nil {
|
||||
b.Fatal(err) // rotten corpus
|
||||
}
|
||||
b.Run(fmt.Sprintf("test-%d", line), func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
b.SetBytes(int64(len(data)))
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = parse(data, false)
|
||||
}
|
||||
})
|
||||
line++
|
||||
}
|
||||
}
|
||||
|
|
@ -302,7 +302,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
|||
}
|
||||
|
||||
// Set the receipt logs and create the bloom filter.
|
||||
receipt.Logs = statedb.GetLogs(tx.Hash(), vmContext.BlockNumber.Uint64(), blockHash)
|
||||
receipt.Logs = statedb.GetLogs(tx.Hash(), vmContext.BlockNumber.Uint64(), blockHash, vmContext.Time)
|
||||
receipt.Bloom = types.CreateBloom(receipt)
|
||||
|
||||
// These three are non-consensus fields:
|
||||
|
|
|
|||
|
|
@ -210,8 +210,6 @@ func init() {
|
|||
stateTransitionCommand,
|
||||
transactionCommand,
|
||||
blockBuilderCommand,
|
||||
eofParseCommand,
|
||||
eofDumpCommand,
|
||||
}
|
||||
app.Before = func(ctx *cli.Context) error {
|
||||
flags.MigrateGlobalFlags(ctx)
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/internal/era/eradl"
|
||||
"github.com/ethereum/go-ethereum/internal/flags"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
|
@ -277,10 +278,7 @@ func initGenesis(ctx *cli.Context) error {
|
|||
overrides.OverrideVerkle = &v
|
||||
}
|
||||
|
||||
chaindb, err := stack.OpenDatabaseWithFreezer("chaindata", 0, 0, ctx.String(utils.AncientFlag.Name), "", false)
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to open database: %v", err)
|
||||
}
|
||||
chaindb := utils.MakeChainDatabase(ctx, stack, false)
|
||||
defer chaindb.Close()
|
||||
|
||||
triedb := utils.MakeTrieDatabase(ctx, chaindb, ctx.Bool(utils.CachePreimagesFlag.Name), false, genesis.IsVerkle())
|
||||
|
|
@ -317,7 +315,7 @@ func dumpGenesis(ctx *cli.Context) error {
|
|||
// dump whatever already exists in the datadir
|
||||
stack, _ := makeConfigNode(ctx)
|
||||
|
||||
db, err := stack.OpenDatabase("chaindata", 0, 0, "", true)
|
||||
db, err := stack.OpenDatabaseWithOptions("chaindata", node.DatabaseOptions{ReadOnly: true})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -725,8 +723,12 @@ func downloadEra(ctx *cli.Context) error {
|
|||
// Resolve the destination directory.
|
||||
stack, _ := makeConfigNode(ctx)
|
||||
defer stack.Close()
|
||||
|
||||
ancients := stack.ResolveAncient("chaindata", "")
|
||||
dir := filepath.Join(ancients, "era")
|
||||
dir := filepath.Join(ancients, rawdb.ChainFreezerName, "era")
|
||||
if ctx.IsSet(utils.EraFlag.Name) {
|
||||
dir = filepath.Join(ancients, ctx.String(utils.EraFlag.Name))
|
||||
}
|
||||
|
||||
baseURL := ctx.String(eraServerFlag.Name)
|
||||
if baseURL == "" {
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/eth/catalyst"
|
||||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
||||
"github.com/ethereum/go-ethereum/internal/flags"
|
||||
|
|
@ -180,6 +181,45 @@ func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) {
|
|||
return stack, cfg
|
||||
}
|
||||
|
||||
// constructs the disclaimer text block which will be printed in the logs upon
|
||||
// startup when Geth is running in dev mode.
|
||||
func constructDevModeBanner(ctx *cli.Context, cfg gethConfig) string {
|
||||
devModeBanner := `You are running Geth in --dev mode. Please note the following:
|
||||
|
||||
1. This mode is only intended for fast, iterative development without assumptions on
|
||||
security or persistence.
|
||||
2. The database is created in memory unless specified otherwise. Therefore, shutting down
|
||||
your computer or losing power will wipe your entire block data and chain state for
|
||||
your dev environment.
|
||||
3. A random, pre-allocated developer account will be available and unlocked as
|
||||
eth.coinbase, which can be used for testing. The random dev account is temporary,
|
||||
stored on a ramdisk, and will be lost if your machine is restarted.
|
||||
4. Mining is enabled by default. However, the client will only seal blocks if transactions
|
||||
are pending in the mempool. The miner's minimum accepted gas price is 1.
|
||||
5. Networking is disabled; there is no listen-address, the maximum number of peers is set
|
||||
to 0, and discovery is disabled.
|
||||
`
|
||||
if !ctx.IsSet(utils.DataDirFlag.Name) {
|
||||
devModeBanner += fmt.Sprintf(`
|
||||
|
||||
Running in ephemeral mode. The following account has been prefunded in the genesis:
|
||||
|
||||
Account
|
||||
------------------
|
||||
0x%x (10^49 ETH)
|
||||
`, cfg.Eth.Miner.PendingFeeRecipient)
|
||||
if cfg.Eth.Miner.PendingFeeRecipient == utils.DeveloperAddr {
|
||||
devModeBanner += fmt.Sprintf(`
|
||||
Private Key
|
||||
------------------
|
||||
0x%x
|
||||
`, crypto.FromECDSA(utils.DeveloperKey))
|
||||
}
|
||||
}
|
||||
|
||||
return devModeBanner
|
||||
}
|
||||
|
||||
// makeFullNode loads geth configuration and creates the Ethereum backend.
|
||||
func makeFullNode(ctx *cli.Context) *node.Node {
|
||||
stack, cfg := makeConfigNode(ctx)
|
||||
|
|
@ -239,6 +279,11 @@ func makeFullNode(ctx *cli.Context) *node.Node {
|
|||
}
|
||||
catalyst.RegisterSimulatedBeaconAPIs(stack, simBeacon)
|
||||
stack.RegisterLifecycle(simBeacon)
|
||||
|
||||
banner := constructDevModeBanner(ctx, cfg)
|
||||
for _, line := range strings.Split(banner, "\n") {
|
||||
log.Warn(line)
|
||||
}
|
||||
} else if ctx.IsSet(utils.BeaconApiFlag.Name) {
|
||||
// Start blsync mode.
|
||||
srv := rpc.NewServer()
|
||||
|
|
|
|||
|
|
@ -294,24 +294,6 @@ func prepare(ctx *cli.Context) {
|
|||
case ctx.IsSet(utils.HoodiFlag.Name):
|
||||
log.Info("Starting Geth on Hoodi testnet...")
|
||||
|
||||
case ctx.IsSet(utils.DeveloperFlag.Name):
|
||||
log.Info("Starting Geth in ephemeral dev mode...")
|
||||
log.Warn(`You are running Geth in --dev mode. Please note the following:
|
||||
|
||||
1. This mode is only intended for fast, iterative development without assumptions on
|
||||
security or persistence.
|
||||
2. The database is created in memory unless specified otherwise. Therefore, shutting down
|
||||
your computer or losing power will wipe your entire block data and chain state for
|
||||
your dev environment.
|
||||
3. A random, pre-allocated developer account will be available and unlocked as
|
||||
eth.coinbase, which can be used for testing. The random dev account is temporary,
|
||||
stored on a ramdisk, and will be lost if your machine is restarted.
|
||||
4. Mining is enabled by default. However, the client will only seal blocks if transactions
|
||||
are pending in the mempool. The miner's minimum accepted gas price is 1.
|
||||
5. Networking is disabled; there is no listen-address, the maximum number of peers is set
|
||||
to 0, and discovery is disabled.
|
||||
`)
|
||||
|
||||
case !ctx.IsSet(utils.NetworkIdFlag.Name):
|
||||
log.Info("Starting Geth on Ethereum mainnet...")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -111,6 +111,11 @@ var (
|
|||
Usage: "Root directory for ancient data (default = inside chaindata)",
|
||||
Category: flags.EthCategory,
|
||||
}
|
||||
EraFlag = &flags.DirectoryFlag{
|
||||
Name: "datadir.era",
|
||||
Usage: "Root directory for era1 history (default = inside ancient/chain)",
|
||||
Category: flags.EthCategory,
|
||||
}
|
||||
MinFreeDiskSpaceFlag = &flags.DirectoryFlag{
|
||||
Name: "datadir.minfreedisk",
|
||||
Usage: "Minimum free disk space in MB, once reached triggers auto shut down (default = --cache.gc converted to MB, 0 = disabled)",
|
||||
|
|
@ -145,7 +150,7 @@ var (
|
|||
}
|
||||
SepoliaFlag = &cli.BoolFlag{
|
||||
Name: "sepolia",
|
||||
Usage: "Sepolia network: pre-configured proof-of-work test network",
|
||||
Usage: "Sepolia network: pre-configured proof-of-stake test network",
|
||||
Category: flags.EthCategory,
|
||||
}
|
||||
HoleskyFlag = &cli.BoolFlag{
|
||||
|
|
@ -977,6 +982,7 @@ var (
|
|||
DatabaseFlags = []cli.Flag{
|
||||
DataDirFlag,
|
||||
AncientFlag,
|
||||
EraFlag,
|
||||
RemoteDBFlag,
|
||||
DBEngineFlag,
|
||||
StateSchemeFlag,
|
||||
|
|
@ -984,6 +990,12 @@ var (
|
|||
}
|
||||
)
|
||||
|
||||
// default account to prefund when running Geth in dev mode
|
||||
var (
|
||||
DeveloperKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
DeveloperAddr = crypto.PubkeyToAddress(DeveloperKey.PublicKey)
|
||||
)
|
||||
|
||||
// MakeDataDir retrieves the currently requested data directory, terminating
|
||||
// if none (or the empty string) is specified. If the node is starting a testnet,
|
||||
// then a subdirectory of the specified datadir will be used.
|
||||
|
|
@ -1607,6 +1619,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
|
|||
if ctx.IsSet(AncientFlag.Name) {
|
||||
cfg.DatabaseFreezer = ctx.String(AncientFlag.Name)
|
||||
}
|
||||
if ctx.IsSet(EraFlag.Name) {
|
||||
cfg.DatabaseEra = ctx.String(EraFlag.Name)
|
||||
}
|
||||
|
||||
if gcmode := ctx.String(GCModeFlag.Name); gcmode != "full" && gcmode != "archive" {
|
||||
Fatalf("--%s must be either 'full' or 'archive'", GCModeFlag.Name)
|
||||
|
|
@ -1769,9 +1784,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
|
|||
} else if accs := ks.Accounts(); len(accs) > 0 {
|
||||
developer = ks.Accounts()[0]
|
||||
} else {
|
||||
developer, err = ks.NewAccount(passphrase)
|
||||
developer, err = ks.ImportECDSA(DeveloperKey, passphrase)
|
||||
if err != nil {
|
||||
Fatalf("Failed to create developer account: %v", err)
|
||||
Fatalf("Failed to import developer account: %v", err)
|
||||
}
|
||||
}
|
||||
// Make sure the address is configured as fee recipient, otherwise
|
||||
|
|
@ -1786,14 +1801,18 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
|
|||
}
|
||||
log.Info("Using developer account", "address", developer.Address)
|
||||
|
||||
// Create a new developer genesis block or reuse existing one
|
||||
// configure default developer genesis which will be used unless a
|
||||
// datadir is specified and a chain is preexisting at that location.
|
||||
cfg.Genesis = core.DeveloperGenesisBlock(ctx.Uint64(DeveloperGasLimitFlag.Name), &developer.Address)
|
||||
|
||||
// If a datadir is specified, ensure that any preexisting chain in that location
|
||||
// has a configuration that is compatible with dev mode: it must be merged at genesis.
|
||||
if ctx.IsSet(DataDirFlag.Name) {
|
||||
chaindb := tryMakeReadOnlyDatabase(ctx, stack)
|
||||
if rawdb.ReadCanonicalHash(chaindb, 0) != (common.Hash{}) {
|
||||
cfg.Genesis = nil // fallback to db content
|
||||
// signal fallback to preexisting chain on disk
|
||||
cfg.Genesis = nil
|
||||
|
||||
//validate genesis has PoS enabled in block 0
|
||||
genesis, err := core.ReadGenesis(chaindb)
|
||||
if err != nil {
|
||||
Fatalf("Could not read genesis from database: %v", err)
|
||||
|
|
@ -2072,7 +2091,15 @@ func MakeChainDatabase(ctx *cli.Context, stack *node.Node, readonly bool) ethdb.
|
|||
}
|
||||
chainDb = remotedb.New(client)
|
||||
default:
|
||||
chainDb, err = stack.OpenDatabaseWithFreezer("chaindata", cache, handles, ctx.String(AncientFlag.Name), "eth/db/chaindata/", readonly)
|
||||
options := node.DatabaseOptions{
|
||||
ReadOnly: readonly,
|
||||
Cache: cache,
|
||||
Handles: handles,
|
||||
AncientsDirectory: ctx.String(AncientFlag.Name),
|
||||
MetricsNamespace: "eth/db/chaindata/",
|
||||
EraDirectory: ctx.String(EraFlag.Name),
|
||||
}
|
||||
chainDb, err = stack.OpenDatabaseWithOptions("chaindata", options)
|
||||
}
|
||||
if err != nil {
|
||||
Fatalf("Could not open database: %v", err)
|
||||
|
|
|
|||
|
|
@ -158,7 +158,7 @@ func TestHistoryImportAndExport(t *testing.T) {
|
|||
}
|
||||
|
||||
// Now import Era.
|
||||
db2, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
|
||||
db2, err := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,4 +26,5 @@ the following commands (in this directory) against a synced mainnet node:
|
|||
```shell
|
||||
> go run . filtergen --queries queries/filter_queries_mainnet.json http://host:8545
|
||||
> go run . historygen --history-tests queries/history_mainnet.json http://host:8545
|
||||
> go run . tracegen --trace-tests queries/trace_mainnet.json http://host:8545
|
||||
```
|
||||
|
|
|
|||
104
cmd/workload/client.go
Normal file
104
cmd/workload/client.go
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
// Copyright 2025 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
"github.com/ethereum/go-ethereum/ethclient/gethclient"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
type client struct {
|
||||
Eth *ethclient.Client
|
||||
Geth *gethclient.Client
|
||||
RPC *rpc.Client
|
||||
}
|
||||
|
||||
func makeClient(ctx *cli.Context) *client {
|
||||
if ctx.NArg() < 1 {
|
||||
exit("missing RPC endpoint URL as command-line argument")
|
||||
}
|
||||
url := ctx.Args().First()
|
||||
cl, err := rpc.Dial(url)
|
||||
if err != nil {
|
||||
exit(fmt.Errorf("could not create RPC client at %s: %v", url, err))
|
||||
}
|
||||
return &client{
|
||||
RPC: cl,
|
||||
Eth: ethclient.NewClient(cl),
|
||||
Geth: gethclient.New(cl),
|
||||
}
|
||||
}
|
||||
|
||||
type simpleBlock struct {
|
||||
Number hexutil.Uint64 `json:"number"`
|
||||
Hash common.Hash `json:"hash"`
|
||||
}
|
||||
|
||||
type simpleTransaction struct {
|
||||
Hash common.Hash `json:"hash"`
|
||||
TransactionIndex hexutil.Uint64 `json:"transactionIndex"`
|
||||
}
|
||||
|
||||
func (c *client) getBlockByHash(ctx context.Context, arg common.Hash, fullTx bool) (*simpleBlock, error) {
|
||||
var r *simpleBlock
|
||||
err := c.RPC.CallContext(ctx, &r, "eth_getBlockByHash", arg, fullTx)
|
||||
return r, err
|
||||
}
|
||||
|
||||
func (c *client) getBlockByNumber(ctx context.Context, arg uint64, fullTx bool) (*simpleBlock, error) {
|
||||
var r *simpleBlock
|
||||
err := c.RPC.CallContext(ctx, &r, "eth_getBlockByNumber", hexutil.Uint64(arg), fullTx)
|
||||
return r, err
|
||||
}
|
||||
|
||||
func (c *client) getTransactionByBlockHashAndIndex(ctx context.Context, block common.Hash, index uint64) (*simpleTransaction, error) {
|
||||
var r *simpleTransaction
|
||||
err := c.RPC.CallContext(ctx, &r, "eth_getTransactionByBlockHashAndIndex", block, hexutil.Uint64(index))
|
||||
return r, err
|
||||
}
|
||||
|
||||
func (c *client) getTransactionByBlockNumberAndIndex(ctx context.Context, block uint64, index uint64) (*simpleTransaction, error) {
|
||||
var r *simpleTransaction
|
||||
err := c.RPC.CallContext(ctx, &r, "eth_getTransactionByBlockNumberAndIndex", hexutil.Uint64(block), hexutil.Uint64(index))
|
||||
return r, err
|
||||
}
|
||||
|
||||
func (c *client) getBlockTransactionCountByHash(ctx context.Context, block common.Hash) (uint64, error) {
|
||||
var r hexutil.Uint64
|
||||
err := c.RPC.CallContext(ctx, &r, "eth_getBlockTransactionCountByHash", block)
|
||||
return uint64(r), err
|
||||
}
|
||||
|
||||
func (c *client) getBlockTransactionCountByNumber(ctx context.Context, block uint64) (uint64, error) {
|
||||
var r hexutil.Uint64
|
||||
err := c.RPC.CallContext(ctx, &r, "eth_getBlockTransactionCountByNumber", hexutil.Uint64(block))
|
||||
return uint64(r), err
|
||||
}
|
||||
|
||||
func (c *client) getBlockReceipts(ctx context.Context, arg any) ([]*types.Receipt, error) {
|
||||
var result []*types.Receipt
|
||||
err := c.RPC.CallContext(ctx, &result, "eth_getBlockReceipts", arg)
|
||||
return result, err
|
||||
}
|
||||
|
|
@ -45,11 +45,11 @@ func newFilterTestSuite(cfg testConfig) *filterTestSuite {
|
|||
return s
|
||||
}
|
||||
|
||||
func (s *filterTestSuite) allTests() []utesting.Test {
|
||||
return []utesting.Test{
|
||||
{Name: "Filter/ShortRange", Fn: s.filterShortRange},
|
||||
{Name: "Filter/LongRange", Fn: s.filterLongRange, Slow: true},
|
||||
{Name: "Filter/FullRange", Fn: s.filterFullRange, Slow: true},
|
||||
func (s *filterTestSuite) allTests() []workloadTest {
|
||||
return []workloadTest{
|
||||
newWorkLoadTest("Filter/ShortRange", s.filterShortRange),
|
||||
newSlowWorkloadTest("Filter/LongRange", s.filterLongRange),
|
||||
newSlowWorkloadTest("Filter/FullRange", s.filterFullRange),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import (
|
|||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/internal/utesting"
|
||||
)
|
||||
|
||||
|
|
@ -65,40 +64,16 @@ func (s *historyTestSuite) loadTests() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *historyTestSuite) allTests() []utesting.Test {
|
||||
return []utesting.Test{
|
||||
{
|
||||
Name: "History/getBlockByHash",
|
||||
Fn: s.testGetBlockByHash,
|
||||
},
|
||||
{
|
||||
Name: "History/getBlockByNumber",
|
||||
Fn: s.testGetBlockByNumber,
|
||||
},
|
||||
{
|
||||
Name: "History/getBlockReceiptsByHash",
|
||||
Fn: s.testGetBlockReceiptsByHash,
|
||||
},
|
||||
{
|
||||
Name: "History/getBlockReceiptsByNumber",
|
||||
Fn: s.testGetBlockReceiptsByNumber,
|
||||
},
|
||||
{
|
||||
Name: "History/getBlockTransactionCountByHash",
|
||||
Fn: s.testGetBlockTransactionCountByHash,
|
||||
},
|
||||
{
|
||||
Name: "History/getBlockTransactionCountByNumber",
|
||||
Fn: s.testGetBlockTransactionCountByNumber,
|
||||
},
|
||||
{
|
||||
Name: "History/getTransactionByBlockHashAndIndex",
|
||||
Fn: s.testGetTransactionByBlockHashAndIndex,
|
||||
},
|
||||
{
|
||||
Name: "History/getTransactionByBlockNumberAndIndex",
|
||||
Fn: s.testGetTransactionByBlockNumberAndIndex,
|
||||
},
|
||||
func (s *historyTestSuite) allTests() []workloadTest {
|
||||
return []workloadTest{
|
||||
newWorkLoadTest("History/getBlockByHash", s.testGetBlockByHash),
|
||||
newWorkLoadTest("History/getBlockByNumber", s.testGetBlockByNumber),
|
||||
newWorkLoadTest("History/getBlockReceiptsByHash", s.testGetBlockReceiptsByHash),
|
||||
newWorkLoadTest("History/getBlockReceiptsByNumber", s.testGetBlockReceiptsByNumber),
|
||||
newWorkLoadTest("History/getBlockTransactionCountByHash", s.testGetBlockTransactionCountByHash),
|
||||
newWorkLoadTest("History/getBlockTransactionCountByNumber", s.testGetBlockTransactionCountByNumber),
|
||||
newWorkLoadTest("History/getTransactionByBlockHashAndIndex", s.testGetTransactionByBlockHashAndIndex),
|
||||
newWorkLoadTest("History/getTransactionByBlockNumberAndIndex", s.testGetTransactionByBlockNumberAndIndex),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -279,55 +254,3 @@ func (s *historyTestSuite) testGetTransactionByBlockNumberAndIndex(t *utesting.T
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
type simpleBlock struct {
|
||||
Number hexutil.Uint64 `json:"number"`
|
||||
Hash common.Hash `json:"hash"`
|
||||
}
|
||||
|
||||
type simpleTransaction struct {
|
||||
Hash common.Hash `json:"hash"`
|
||||
TransactionIndex hexutil.Uint64 `json:"transactionIndex"`
|
||||
}
|
||||
|
||||
func (c *client) getBlockByHash(ctx context.Context, arg common.Hash, fullTx bool) (*simpleBlock, error) {
|
||||
var r *simpleBlock
|
||||
err := c.RPC.CallContext(ctx, &r, "eth_getBlockByHash", arg, fullTx)
|
||||
return r, err
|
||||
}
|
||||
|
||||
func (c *client) getBlockByNumber(ctx context.Context, arg uint64, fullTx bool) (*simpleBlock, error) {
|
||||
var r *simpleBlock
|
||||
err := c.RPC.CallContext(ctx, &r, "eth_getBlockByNumber", hexutil.Uint64(arg), fullTx)
|
||||
return r, err
|
||||
}
|
||||
|
||||
func (c *client) getTransactionByBlockHashAndIndex(ctx context.Context, block common.Hash, index uint64) (*simpleTransaction, error) {
|
||||
var r *simpleTransaction
|
||||
err := c.RPC.CallContext(ctx, &r, "eth_getTransactionByBlockHashAndIndex", block, hexutil.Uint64(index))
|
||||
return r, err
|
||||
}
|
||||
|
||||
func (c *client) getTransactionByBlockNumberAndIndex(ctx context.Context, block uint64, index uint64) (*simpleTransaction, error) {
|
||||
var r *simpleTransaction
|
||||
err := c.RPC.CallContext(ctx, &r, "eth_getTransactionByBlockNumberAndIndex", hexutil.Uint64(block), hexutil.Uint64(index))
|
||||
return r, err
|
||||
}
|
||||
|
||||
func (c *client) getBlockTransactionCountByHash(ctx context.Context, block common.Hash) (uint64, error) {
|
||||
var r hexutil.Uint64
|
||||
err := c.RPC.CallContext(ctx, &r, "eth_getBlockTransactionCountByHash", block)
|
||||
return uint64(r), err
|
||||
}
|
||||
|
||||
func (c *client) getBlockTransactionCountByNumber(ctx context.Context, block uint64) (uint64, error) {
|
||||
var r hexutil.Uint64
|
||||
err := c.RPC.CallContext(ctx, &r, "eth_getBlockTransactionCountByNumber", hexutil.Uint64(block))
|
||||
return uint64(r), err
|
||||
}
|
||||
|
||||
func (c *client) getBlockReceipts(ctx context.Context, arg any) ([]*types.Receipt, error) {
|
||||
var result []*types.Receipt
|
||||
err := c.RPC.CallContext(ctx, &result, "eth_getBlockReceipts", arg)
|
||||
return result, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ var (
|
|||
}
|
||||
historyTestEarliestFlag = &cli.IntFlag{
|
||||
Name: "earliest",
|
||||
Usage: "JSON file containing filter test queries",
|
||||
Usage: "The earliest block to test queries",
|
||||
Value: 0,
|
||||
Category: flags.TestingCategory,
|
||||
}
|
||||
|
|
@ -138,7 +138,7 @@ func calcReceiptsHash(rcpt []*types.Receipt) common.Hash {
|
|||
func writeJSON(fileName string, value any) {
|
||||
file, err := os.Create(fileName)
|
||||
if err != nil {
|
||||
exit(fmt.Errorf("Error creating %s: %v", fileName, err))
|
||||
exit(fmt.Errorf("error creating %s: %v", fileName, err))
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
|
|
|||
|
|
@ -20,10 +20,8 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
"github.com/ethereum/go-ethereum/internal/debug"
|
||||
"github.com/ethereum/go-ethereum/internal/flags"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
|
|
@ -49,6 +47,7 @@ func init() {
|
|||
runTestCommand,
|
||||
historyGenerateCommand,
|
||||
filterGenerateCommand,
|
||||
traceGenerateCommand,
|
||||
filterPerfCommand,
|
||||
}
|
||||
}
|
||||
|
|
@ -57,26 +56,6 @@ func main() {
|
|||
exit(app.Run(os.Args))
|
||||
}
|
||||
|
||||
type client struct {
|
||||
Eth *ethclient.Client
|
||||
RPC *rpc.Client
|
||||
}
|
||||
|
||||
func makeClient(ctx *cli.Context) *client {
|
||||
if ctx.NArg() < 1 {
|
||||
exit("missing RPC endpoint URL as command-line argument")
|
||||
}
|
||||
url := ctx.Args().First()
|
||||
cl, err := rpc.Dial(url)
|
||||
if err != nil {
|
||||
exit(fmt.Errorf("Could not create RPC client at %s: %v", url, err))
|
||||
}
|
||||
return &client{
|
||||
RPC: cl,
|
||||
Eth: ethclient.NewClient(cl),
|
||||
}
|
||||
}
|
||||
|
||||
func exit(err any) {
|
||||
if err == nil {
|
||||
os.Exit(0)
|
||||
|
|
|
|||
1
cmd/workload/queries/trace_mainnet.json
Normal file
1
cmd/workload/queries/trace_mainnet.json
Normal file
File diff suppressed because one or more lines are too long
1
cmd/workload/queries/trace_sepolia.json
Normal file
1
cmd/workload/queries/trace_sepolia.json
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -21,7 +21,6 @@ import (
|
|||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"slices"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/history"
|
||||
"github.com/ethereum/go-ethereum/internal/flags"
|
||||
|
|
@ -45,10 +44,13 @@ var (
|
|||
testPatternFlag,
|
||||
testTAPFlag,
|
||||
testSlowFlag,
|
||||
testArchiveFlag,
|
||||
testSepoliaFlag,
|
||||
testMainnetFlag,
|
||||
filterQueryFileFlag,
|
||||
historyTestFileFlag,
|
||||
traceTestFileFlag,
|
||||
traceTestInvalidOutputFlag,
|
||||
},
|
||||
}
|
||||
testPatternFlag = &cli.StringFlag{
|
||||
|
|
@ -67,6 +69,12 @@ var (
|
|||
Value: false,
|
||||
Category: flags.TestingCategory,
|
||||
}
|
||||
testArchiveFlag = &cli.BoolFlag{
|
||||
Name: "archive",
|
||||
Usage: "Enable archive tests",
|
||||
Value: false,
|
||||
Category: flags.TestingCategory,
|
||||
}
|
||||
testSepoliaFlag = &cli.BoolFlag{
|
||||
Name: "sepolia",
|
||||
Usage: "Use test cases for sepolia network",
|
||||
|
|
@ -86,6 +94,7 @@ type testConfig struct {
|
|||
filterQueryFile string
|
||||
historyTestFile string
|
||||
historyPruneBlock *uint64
|
||||
traceTestFile string
|
||||
}
|
||||
|
||||
var errPrunedHistory = fmt.Errorf("attempt to access pruned history")
|
||||
|
|
@ -125,36 +134,85 @@ func testConfigFromCLI(ctx *cli.Context) (cfg testConfig) {
|
|||
cfg.historyTestFile = "queries/history_mainnet.json"
|
||||
cfg.historyPruneBlock = new(uint64)
|
||||
*cfg.historyPruneBlock = history.PrunePoints[params.MainnetGenesisHash].BlockNumber
|
||||
cfg.traceTestFile = "queries/trace_mainnet.json"
|
||||
case ctx.Bool(testSepoliaFlag.Name):
|
||||
cfg.fsys = builtinTestFiles
|
||||
cfg.filterQueryFile = "queries/filter_queries_sepolia.json"
|
||||
cfg.historyTestFile = "queries/history_sepolia.json"
|
||||
cfg.historyPruneBlock = new(uint64)
|
||||
*cfg.historyPruneBlock = history.PrunePoints[params.SepoliaGenesisHash].BlockNumber
|
||||
cfg.traceTestFile = "queries/trace_sepolia.json"
|
||||
default:
|
||||
cfg.fsys = os.DirFS(".")
|
||||
cfg.filterQueryFile = ctx.String(filterQueryFileFlag.Name)
|
||||
cfg.historyTestFile = ctx.String(historyTestFileFlag.Name)
|
||||
cfg.traceTestFile = ctx.String(traceTestFileFlag.Name)
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
// workloadTest represents a single test in the workload. It's a wrapper
|
||||
// of utesting.Test by adding a few additional attributes.
|
||||
type workloadTest struct {
|
||||
utesting.Test
|
||||
|
||||
archive bool // Flag whether the archive node (full state history) is required for this test
|
||||
}
|
||||
|
||||
func newWorkLoadTest(name string, fn func(t *utesting.T)) workloadTest {
|
||||
return workloadTest{
|
||||
Test: utesting.Test{
|
||||
Name: name,
|
||||
Fn: fn,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newSlowWorkloadTest(name string, fn func(t *utesting.T)) workloadTest {
|
||||
t := newWorkLoadTest(name, fn)
|
||||
t.Slow = true
|
||||
return t
|
||||
}
|
||||
|
||||
func newArchiveWorkloadTest(name string, fn func(t *utesting.T)) workloadTest {
|
||||
t := newWorkLoadTest(name, fn)
|
||||
t.archive = true
|
||||
return t
|
||||
}
|
||||
|
||||
func filterTests(tests []workloadTest, pattern string, filterFn func(t workloadTest) bool) []utesting.Test {
|
||||
var utests []utesting.Test
|
||||
for _, t := range tests {
|
||||
if filterFn(t) {
|
||||
utests = append(utests, t.Test)
|
||||
}
|
||||
}
|
||||
if pattern == "" {
|
||||
return utests
|
||||
}
|
||||
return utesting.MatchTests(utests, pattern)
|
||||
}
|
||||
|
||||
func runTestCmd(ctx *cli.Context) error {
|
||||
cfg := testConfigFromCLI(ctx)
|
||||
filterSuite := newFilterTestSuite(cfg)
|
||||
historySuite := newHistoryTestSuite(cfg)
|
||||
traceSuite := newTraceTestSuite(cfg, ctx)
|
||||
|
||||
// Filter test cases.
|
||||
tests := filterSuite.allTests()
|
||||
tests = append(tests, historySuite.allTests()...)
|
||||
if ctx.IsSet(testPatternFlag.Name) {
|
||||
tests = utesting.MatchTests(tests, ctx.String(testPatternFlag.Name))
|
||||
tests = append(tests, traceSuite.allTests()...)
|
||||
|
||||
utests := filterTests(tests, ctx.String(testPatternFlag.Name), func(t workloadTest) bool {
|
||||
if t.Slow && !ctx.Bool(testSlowFlag.Name) {
|
||||
return false
|
||||
}
|
||||
if !ctx.Bool(testSlowFlag.Name) {
|
||||
tests = slices.DeleteFunc(tests, func(test utesting.Test) bool {
|
||||
return test.Slow
|
||||
if t.archive && !ctx.Bool(testArchiveFlag.Name) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// Disable logging unless explicitly enabled.
|
||||
if !ctx.IsSet("verbosity") && !ctx.IsSet("vmodule") {
|
||||
|
|
@ -166,7 +224,7 @@ func runTestCmd(ctx *cli.Context) error {
|
|||
if ctx.Bool(testTAPFlag.Name) {
|
||||
run = utesting.RunTAP
|
||||
}
|
||||
results := run(tests, os.Stdout)
|
||||
results := run(utests, os.Stdout)
|
||||
if utesting.CountFailures(results) > 0 {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
|
|
|||
126
cmd/workload/tracetest.go
Normal file
126
cmd/workload/tracetest.go
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
// Copyright 2025 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers"
|
||||
"github.com/ethereum/go-ethereum/internal/utesting"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// traceTest is the content of a history test.
|
||||
type traceTest struct {
|
||||
TxHashes []common.Hash `json:"txHashes"`
|
||||
TraceConfigs []tracers.TraceConfig `json:"traceConfigs"`
|
||||
ResultHashes []common.Hash `json:"resultHashes"`
|
||||
}
|
||||
|
||||
type traceTestSuite struct {
|
||||
cfg testConfig
|
||||
tests traceTest
|
||||
invalidDir string
|
||||
}
|
||||
|
||||
func newTraceTestSuite(cfg testConfig, ctx *cli.Context) *traceTestSuite {
|
||||
s := &traceTestSuite{
|
||||
cfg: cfg,
|
||||
invalidDir: ctx.String(traceTestInvalidOutputFlag.Name),
|
||||
}
|
||||
if err := s.loadTests(); err != nil {
|
||||
exit(err)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *traceTestSuite) loadTests() error {
|
||||
file, err := s.cfg.fsys.Open(s.cfg.traceTestFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't open traceTestFile: %v", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if err := json.NewDecoder(file).Decode(&s.tests); err != nil {
|
||||
return fmt.Errorf("invalid JSON in %s: %v", s.cfg.traceTestFile, err)
|
||||
}
|
||||
if len(s.tests.TxHashes) == 0 {
|
||||
return fmt.Errorf("traceTestFile %s has no test data", s.cfg.traceTestFile)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *traceTestSuite) allTests() []workloadTest {
|
||||
return []workloadTest{
|
||||
newArchiveWorkloadTest("Trace/Transaction", s.traceTransaction),
|
||||
}
|
||||
}
|
||||
|
||||
// traceTransaction runs all transaction tracing tests
|
||||
func (s *traceTestSuite) traceTransaction(t *utesting.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
for i, hash := range s.tests.TxHashes {
|
||||
config := s.tests.TraceConfigs[i]
|
||||
result, err := s.cfg.client.Geth.TraceTransaction(ctx, hash, &config)
|
||||
if err != nil {
|
||||
t.Fatalf("Transaction %d (hash %v): error %v", i, hash, err)
|
||||
}
|
||||
blob, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
t.Fatalf("Transaction %d (hash %v): error %v", i, hash, err)
|
||||
continue
|
||||
}
|
||||
if crypto.Keccak256Hash(blob) != s.tests.ResultHashes[i] {
|
||||
t.Errorf("Transaction %d (hash %v): invalid result", i, hash)
|
||||
|
||||
writeInvalidTraceResult(s.invalidDir, hash, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func writeInvalidTraceResult(dir string, hash common.Hash, result any) {
|
||||
if dir == "" {
|
||||
return
|
||||
}
|
||||
err := os.MkdirAll(dir, os.ModePerm)
|
||||
if err != nil {
|
||||
log.Info("Failed to make output directory", "err", err)
|
||||
return
|
||||
}
|
||||
name := filepath.Join(dir, "invalid"+"_"+hash.String())
|
||||
file, err := os.Create(name)
|
||||
if err != nil {
|
||||
exit(fmt.Errorf("error creating %s: %v", name, err))
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
data, _ := json.MarshalIndent(result, "", " ")
|
||||
_, err = file.Write(data)
|
||||
if err != nil {
|
||||
exit(fmt.Errorf("error writing %s: %v", name, err))
|
||||
return
|
||||
}
|
||||
}
|
||||
195
cmd/workload/tracetestgen.go
Normal file
195
cmd/workload/tracetestgen.go
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
// Copyright 2025 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers/logger"
|
||||
"github.com/ethereum/go-ethereum/internal/flags"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultBlocksToTrace = 64 // the number of states assumed to be available
|
||||
|
||||
traceGenerateCommand = &cli.Command{
|
||||
Name: "tracegen",
|
||||
Usage: "Generates tests for state tracing",
|
||||
ArgsUsage: "<RPC endpoint URL>",
|
||||
Action: generateTraceTests,
|
||||
Flags: []cli.Flag{
|
||||
traceTestFileFlag,
|
||||
traceTestResultOutputFlag,
|
||||
traceTestBlockFlag,
|
||||
},
|
||||
}
|
||||
|
||||
traceTestFileFlag = &cli.StringFlag{
|
||||
Name: "trace-tests",
|
||||
Usage: "JSON file containing trace test queries",
|
||||
Value: "trace_tests.json",
|
||||
Category: flags.TestingCategory,
|
||||
}
|
||||
traceTestResultOutputFlag = &cli.StringFlag{
|
||||
Name: "trace-output",
|
||||
Usage: "Folder containing the trace output files",
|
||||
Value: "",
|
||||
Category: flags.TestingCategory,
|
||||
}
|
||||
traceTestBlockFlag = &cli.IntFlag{
|
||||
Name: "trace-blocks",
|
||||
Usage: "The number of blocks for tracing",
|
||||
Value: defaultBlocksToTrace,
|
||||
Category: flags.TestingCategory,
|
||||
}
|
||||
traceTestInvalidOutputFlag = &cli.StringFlag{
|
||||
Name: "trace-invalid",
|
||||
Usage: "Folder containing the mismatched trace output files",
|
||||
Value: "",
|
||||
Category: flags.TestingCategory,
|
||||
}
|
||||
)
|
||||
|
||||
func generateTraceTests(clictx *cli.Context) error {
|
||||
var (
|
||||
client = makeClient(clictx)
|
||||
outputFile = clictx.String(traceTestFileFlag.Name)
|
||||
outputDir = clictx.String(traceTestResultOutputFlag.Name)
|
||||
blocks = clictx.Int(traceTestBlockFlag.Name)
|
||||
ctx = context.Background()
|
||||
test = new(traceTest)
|
||||
)
|
||||
if outputDir != "" {
|
||||
err := os.MkdirAll(outputDir, os.ModePerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
latest, err := client.Eth.BlockNumber(ctx)
|
||||
if err != nil {
|
||||
exit(err)
|
||||
}
|
||||
if latest < uint64(blocks) {
|
||||
exit(fmt.Errorf("node seems not synced, latest block is %d", latest))
|
||||
}
|
||||
// Get blocks and assign block info into the test
|
||||
var (
|
||||
start = time.Now()
|
||||
logged = time.Now()
|
||||
failed int
|
||||
)
|
||||
log.Info("Trace transactions around the chain tip", "head", latest, "blocks", blocks)
|
||||
|
||||
for i := 0; i < blocks; i++ {
|
||||
number := latest - uint64(i)
|
||||
block, err := client.Eth.BlockByNumber(ctx, big.NewInt(int64(number)))
|
||||
if err != nil {
|
||||
exit(err)
|
||||
}
|
||||
for _, tx := range block.Transactions() {
|
||||
config, configName := randomTraceOption()
|
||||
result, err := client.Geth.TraceTransaction(ctx, tx.Hash(), config)
|
||||
if err != nil {
|
||||
failed += 1
|
||||
break
|
||||
}
|
||||
blob, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
failed += 1
|
||||
break
|
||||
}
|
||||
test.TxHashes = append(test.TxHashes, tx.Hash())
|
||||
test.TraceConfigs = append(test.TraceConfigs, *config)
|
||||
test.ResultHashes = append(test.ResultHashes, crypto.Keccak256Hash(blob))
|
||||
writeTraceResult(outputDir, tx.Hash(), result, configName)
|
||||
}
|
||||
if time.Since(logged) > time.Second*8 {
|
||||
logged = time.Now()
|
||||
log.Info("Tracing transactions", "executed", len(test.TxHashes), "failed", failed, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||
}
|
||||
}
|
||||
log.Info("Traced transactions", "executed", len(test.TxHashes), "failed", failed, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||
|
||||
// Write output file.
|
||||
writeJSON(outputFile, test)
|
||||
return nil
|
||||
}
|
||||
|
||||
func randomTraceOption() (*tracers.TraceConfig, string) {
|
||||
x := rand.Intn(11)
|
||||
if x == 0 {
|
||||
// struct-logger, with all fields enabled, very heavy
|
||||
return &tracers.TraceConfig{
|
||||
Config: &logger.Config{
|
||||
EnableMemory: true,
|
||||
EnableReturnData: true,
|
||||
},
|
||||
}, "structAll"
|
||||
}
|
||||
if x == 1 {
|
||||
// default options for struct-logger, with stack and storage capture
|
||||
// enabled
|
||||
return &tracers.TraceConfig{
|
||||
Config: &logger.Config{},
|
||||
}, "structDefault"
|
||||
}
|
||||
if x == 2 || x == 3 || x == 4 {
|
||||
// struct-logger with storage capture enabled
|
||||
return &tracers.TraceConfig{
|
||||
Config: &logger.Config{
|
||||
DisableStack: true,
|
||||
},
|
||||
}, "structStorage"
|
||||
}
|
||||
// Native tracer
|
||||
loggers := []string{"callTracer", "4byteTracer", "flatCallTracer", "muxTracer", "noopTracer", "prestateTracer"}
|
||||
return &tracers.TraceConfig{
|
||||
Tracer: &loggers[x-5],
|
||||
}, loggers[x-5]
|
||||
}
|
||||
|
||||
func writeTraceResult(dir string, hash common.Hash, result any, configName string) {
|
||||
if dir == "" {
|
||||
return
|
||||
}
|
||||
name := filepath.Join(dir, configName+"_"+hash.String())
|
||||
file, err := os.Create(name)
|
||||
if err != nil {
|
||||
exit(fmt.Errorf("error creating %s: %v", name, err))
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
data, _ := json.MarshalIndent(result, "", " ")
|
||||
_, err = file.Write(data)
|
||||
if err != nil {
|
||||
exit(fmt.Errorf("error writing %s: %v", name, err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -47,30 +47,37 @@ func NewBasicLRU[K comparable, V any](capacity int) BasicLRU[K, V] {
|
|||
|
||||
// Add adds a value to the cache. Returns true if an item was evicted to store the new item.
|
||||
func (c *BasicLRU[K, V]) Add(key K, value V) (evicted bool) {
|
||||
_, _, evicted = c.Add3(key, value)
|
||||
return evicted
|
||||
}
|
||||
|
||||
// Add3 adds a value to the cache. If an item was evicted to store the new one, it returns the evicted item.
|
||||
func (c *BasicLRU[K, V]) Add3(key K, value V) (ek K, ev V, evicted bool) {
|
||||
item, ok := c.items[key]
|
||||
if ok {
|
||||
// Already exists in cache.
|
||||
item.value = value
|
||||
c.items[key] = item
|
||||
c.list.moveToFront(item.elem)
|
||||
return false
|
||||
return ek, ev, false
|
||||
}
|
||||
|
||||
var elem *listElem[K]
|
||||
if c.Len() >= c.cap {
|
||||
elem = c.list.removeLast()
|
||||
delete(c.items, elem.v)
|
||||
evicted = true
|
||||
ek = elem.v
|
||||
ev = c.items[ek].value
|
||||
delete(c.items, ek)
|
||||
} else {
|
||||
elem = new(listElem[K])
|
||||
}
|
||||
|
||||
// Store the new item.
|
||||
// Note that, if another item was evicted, we re-use its list element here.
|
||||
// Note that if another item was evicted, we re-use its list element here.
|
||||
elem.v = key
|
||||
c.items[key] = cacheItem[K, V]{elem, value}
|
||||
c.list.pushElem(elem)
|
||||
return evicted
|
||||
return ek, ev, evicted
|
||||
}
|
||||
|
||||
// Contains reports whether the given key exists in the cache.
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
|
@ -445,11 +444,6 @@ func (beacon *Beacon) CalcDifficulty(chain consensus.ChainHeaderReader, time uin
|
|||
return beaconDifficulty
|
||||
}
|
||||
|
||||
// APIs implements consensus.Engine, returning the user facing RPC APIs.
|
||||
func (beacon *Beacon) APIs(chain consensus.ChainHeaderReader) []rpc.API {
|
||||
return beacon.ethone.APIs(chain)
|
||||
}
|
||||
|
||||
// Close shutdowns the consensus engine
|
||||
func (beacon *Beacon) Close() error {
|
||||
return beacon.ethone.Close()
|
||||
|
|
|
|||
|
|
@ -1,235 +0,0 @@
|
|||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package clique
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
// API is a user facing RPC API to allow controlling the signer and voting
|
||||
// mechanisms of the proof-of-authority scheme.
|
||||
type API struct {
|
||||
chain consensus.ChainHeaderReader
|
||||
clique *Clique
|
||||
}
|
||||
|
||||
// GetSnapshot retrieves the state snapshot at a given block.
|
||||
func (api *API) GetSnapshot(number *rpc.BlockNumber) (*Snapshot, error) {
|
||||
// Retrieve the requested block number (or current if none requested)
|
||||
var header *types.Header
|
||||
if number == nil || *number == rpc.LatestBlockNumber {
|
||||
header = api.chain.CurrentHeader()
|
||||
} else {
|
||||
header = api.chain.GetHeaderByNumber(uint64(number.Int64()))
|
||||
}
|
||||
// Ensure we have an actually valid block and return its snapshot
|
||||
if header == nil {
|
||||
return nil, errUnknownBlock
|
||||
}
|
||||
return api.clique.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil)
|
||||
}
|
||||
|
||||
// GetSnapshotAtHash retrieves the state snapshot at a given block.
|
||||
func (api *API) GetSnapshotAtHash(hash common.Hash) (*Snapshot, error) {
|
||||
header := api.chain.GetHeaderByHash(hash)
|
||||
if header == nil {
|
||||
return nil, errUnknownBlock
|
||||
}
|
||||
return api.clique.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil)
|
||||
}
|
||||
|
||||
// GetSigners retrieves the list of authorized signers at the specified block.
|
||||
func (api *API) GetSigners(number *rpc.BlockNumber) ([]common.Address, error) {
|
||||
// Retrieve the requested block number (or current if none requested)
|
||||
var header *types.Header
|
||||
if number == nil || *number == rpc.LatestBlockNumber {
|
||||
header = api.chain.CurrentHeader()
|
||||
} else {
|
||||
header = api.chain.GetHeaderByNumber(uint64(number.Int64()))
|
||||
}
|
||||
// Ensure we have an actually valid block and return the signers from its snapshot
|
||||
if header == nil {
|
||||
return nil, errUnknownBlock
|
||||
}
|
||||
snap, err := api.clique.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return snap.signers(), nil
|
||||
}
|
||||
|
||||
// GetSignersAtHash retrieves the list of authorized signers at the specified block.
|
||||
func (api *API) GetSignersAtHash(hash common.Hash) ([]common.Address, error) {
|
||||
header := api.chain.GetHeaderByHash(hash)
|
||||
if header == nil {
|
||||
return nil, errUnknownBlock
|
||||
}
|
||||
snap, err := api.clique.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return snap.signers(), nil
|
||||
}
|
||||
|
||||
// Proposals returns the current proposals the node tries to uphold and vote on.
|
||||
func (api *API) Proposals() map[common.Address]bool {
|
||||
api.clique.lock.RLock()
|
||||
defer api.clique.lock.RUnlock()
|
||||
|
||||
proposals := make(map[common.Address]bool)
|
||||
for address, auth := range api.clique.proposals {
|
||||
proposals[address] = auth
|
||||
}
|
||||
return proposals
|
||||
}
|
||||
|
||||
// Propose injects a new authorization proposal that the signer will attempt to
|
||||
// push through.
|
||||
func (api *API) Propose(address common.Address, auth bool) {
|
||||
api.clique.lock.Lock()
|
||||
defer api.clique.lock.Unlock()
|
||||
|
||||
api.clique.proposals[address] = auth
|
||||
}
|
||||
|
||||
// Discard drops a currently running proposal, stopping the signer from casting
|
||||
// further votes (either for or against).
|
||||
func (api *API) Discard(address common.Address) {
|
||||
api.clique.lock.Lock()
|
||||
defer api.clique.lock.Unlock()
|
||||
|
||||
delete(api.clique.proposals, address)
|
||||
}
|
||||
|
||||
type status struct {
|
||||
InturnPercent float64 `json:"inturnPercent"`
|
||||
SigningStatus map[common.Address]int `json:"sealerActivity"`
|
||||
NumBlocks uint64 `json:"numBlocks"`
|
||||
}
|
||||
|
||||
// Status returns the status of the last N blocks,
|
||||
// - the number of active signers,
|
||||
// - the number of signers,
|
||||
// - the percentage of in-turn blocks
|
||||
func (api *API) Status() (*status, error) {
|
||||
var (
|
||||
numBlocks = uint64(64)
|
||||
header = api.chain.CurrentHeader()
|
||||
diff = uint64(0)
|
||||
optimals = 0
|
||||
)
|
||||
snap, err := api.clique.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var (
|
||||
signers = snap.signers()
|
||||
end = header.Number.Uint64()
|
||||
start = end - numBlocks
|
||||
)
|
||||
if numBlocks > end {
|
||||
start = 1
|
||||
numBlocks = end - start
|
||||
}
|
||||
signStatus := make(map[common.Address]int)
|
||||
for _, s := range signers {
|
||||
signStatus[s] = 0
|
||||
}
|
||||
for n := start; n < end; n++ {
|
||||
h := api.chain.GetHeaderByNumber(n)
|
||||
if h == nil {
|
||||
return nil, fmt.Errorf("missing block %d", n)
|
||||
}
|
||||
if h.Difficulty.Cmp(diffInTurn) == 0 {
|
||||
optimals++
|
||||
}
|
||||
diff += h.Difficulty.Uint64()
|
||||
sealer, err := api.clique.Author(h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
signStatus[sealer]++
|
||||
}
|
||||
return &status{
|
||||
InturnPercent: float64(100*optimals) / float64(numBlocks),
|
||||
SigningStatus: signStatus,
|
||||
NumBlocks: numBlocks,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type blockNumberOrHashOrRLP struct {
|
||||
*rpc.BlockNumberOrHash
|
||||
RLP hexutil.Bytes `json:"rlp,omitempty"`
|
||||
}
|
||||
|
||||
func (sb *blockNumberOrHashOrRLP) UnmarshalJSON(data []byte) error {
|
||||
bnOrHash := new(rpc.BlockNumberOrHash)
|
||||
// Try to unmarshal bNrOrHash
|
||||
if err := bnOrHash.UnmarshalJSON(data); err == nil {
|
||||
sb.BlockNumberOrHash = bnOrHash
|
||||
return nil
|
||||
}
|
||||
// Try to unmarshal RLP
|
||||
var input string
|
||||
if err := json.Unmarshal(data, &input); err != nil {
|
||||
return err
|
||||
}
|
||||
blob, err := hexutil.Decode(input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sb.RLP = blob
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSigner returns the signer for a specific clique block.
|
||||
// Can be called with a block number, a block hash or a rlp encoded blob.
|
||||
// The RLP encoded blob can either be a block or a header.
|
||||
func (api *API) GetSigner(rlpOrBlockNr *blockNumberOrHashOrRLP) (common.Address, error) {
|
||||
if len(rlpOrBlockNr.RLP) == 0 {
|
||||
blockNrOrHash := rlpOrBlockNr.BlockNumberOrHash
|
||||
var header *types.Header
|
||||
if blockNrOrHash == nil {
|
||||
header = api.chain.CurrentHeader()
|
||||
} else if hash, ok := blockNrOrHash.Hash(); ok {
|
||||
header = api.chain.GetHeaderByHash(hash)
|
||||
} else if number, ok := blockNrOrHash.Number(); ok {
|
||||
header = api.chain.GetHeaderByNumber(uint64(number.Int64()))
|
||||
}
|
||||
if header == nil {
|
||||
return common.Address{}, fmt.Errorf("missing block %v", blockNrOrHash.String())
|
||||
}
|
||||
return api.clique.Author(header)
|
||||
}
|
||||
block := new(types.Block)
|
||||
if err := rlp.DecodeBytes(rlpOrBlockNr.RLP, block); err == nil {
|
||||
return api.clique.Author(block.Header())
|
||||
}
|
||||
header := new(types.Header)
|
||||
if err := rlp.DecodeBytes(rlpOrBlockNr.RLP, header); err != nil {
|
||||
return common.Address{}, err
|
||||
}
|
||||
return api.clique.Author(header)
|
||||
}
|
||||
|
|
@ -41,7 +41,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
)
|
||||
|
||||
|
|
@ -640,15 +639,6 @@ func (c *Clique) Close() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// APIs implements consensus.Engine, returning the user facing RPC API to allow
|
||||
// controlling the signer voting.
|
||||
func (c *Clique) APIs(chain consensus.ChainHeaderReader) []rpc.API {
|
||||
return []rpc.API{{
|
||||
Namespace: "clique",
|
||||
Service: &API{chain: chain, clique: c},
|
||||
}}
|
||||
}
|
||||
|
||||
// SealHash returns the hash of a block prior to it being sealed.
|
||||
func SealHash(header *types.Header) common.Hash {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
// ChainHeaderReader defines a small collection of methods needed to access the local
|
||||
|
|
@ -109,9 +108,6 @@ type Engine interface {
|
|||
// that a new block should have.
|
||||
CalcDifficulty(chain ChainHeaderReader, time uint64, parent *types.Header) *big.Int
|
||||
|
||||
// APIs returns the RPC APIs this consensus engine provides.
|
||||
APIs(chain ChainHeaderReader) []rpc.API
|
||||
|
||||
// Close terminates any background threads maintained by the consensus engine.
|
||||
Close() error
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ import (
|
|||
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
// Ethash is a consensus engine based on proof-of-work implementing the ethash
|
||||
|
|
@ -71,12 +70,6 @@ func (ethash *Ethash) Close() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// APIs implements consensus.Engine, returning no APIs as ethash is an empty
|
||||
// shell in the post-merge world.
|
||||
func (ethash *Ethash) APIs(chain consensus.ChainHeaderReader) []rpc.API {
|
||||
return []rpc.API{}
|
||||
}
|
||||
|
||||
// Seal generates a new sealing request for the given input block and pushes
|
||||
// the result into the given channel. For the ethash engine, this method will
|
||||
// just panic as sealing is not supported anymore.
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ var (
|
|||
headSafeBlockGauge = metrics.NewRegisteredGauge("chain/head/safe", nil)
|
||||
|
||||
chainInfoGauge = metrics.NewRegisteredGaugeInfo("chain/info", nil)
|
||||
chainMgaspsGauge = metrics.NewRegisteredGauge("chain/mgasps", nil)
|
||||
chainMgaspsMeter = metrics.NewRegisteredResettingTimer("chain/mgasps", nil)
|
||||
|
||||
accountReadTimer = metrics.NewRegisteredResettingTimer("chain/account/reads", nil)
|
||||
accountHashTimer = metrics.NewRegisteredResettingTimer("chain/account/hashes", nil)
|
||||
|
|
@ -2067,7 +2067,12 @@ func (bc *BlockChain) processBlock(parentRoot common.Hash, block *types.Block, s
|
|||
triedbCommitTimer.Update(statedb.TrieDBCommits) // Trie database commits are complete, we can mark them
|
||||
|
||||
blockWriteTimer.Update(time.Since(wstart) - max(statedb.AccountCommits, statedb.StorageCommits) /* concurrent */ - statedb.SnapshotCommits - statedb.TrieDBCommits)
|
||||
blockInsertTimer.UpdateSince(startTime)
|
||||
elapsed := time.Since(startTime) + 1 // prevent zero division
|
||||
blockInsertTimer.Update(elapsed)
|
||||
|
||||
// TODO(rjl493456442) generalize the ResettingTimer
|
||||
mgasps := float64(res.GasUsed) * 1000 / float64(elapsed)
|
||||
chainMgaspsMeter.Update(time.Duration(mgasps))
|
||||
|
||||
return &blockProcessingResult{
|
||||
usedGas: res.GasUsed,
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ import (
|
|||
|
||||
// insertStats tracks and reports on block insertion.
|
||||
type insertStats struct {
|
||||
queued, processed, ignored int
|
||||
processed, ignored int
|
||||
usedGas uint64
|
||||
lastIndex int
|
||||
startTime mclock.AbsTime
|
||||
|
|
@ -46,9 +46,6 @@ func (st *insertStats) report(chain []*types.Block, index int, snapDiffItems, sn
|
|||
elapsed = now.Sub(st.startTime) + 1 // prevent zero division
|
||||
mgasps = float64(st.usedGas) * 1000 / float64(elapsed)
|
||||
)
|
||||
// Update the Mgas per second gauge
|
||||
chainMgaspsGauge.Update(int64(mgasps))
|
||||
|
||||
// If we're at the last block of the batch or report period reached, log
|
||||
if index == len(chain)-1 || elapsed >= statsReportLimit {
|
||||
// Count the number of transactions in this segment
|
||||
|
|
@ -78,9 +75,6 @@ func (st *insertStats) report(chain []*types.Block, index int, snapDiffItems, sn
|
|||
}
|
||||
context = append(context, []interface{}{"triedirty", triebufNodes}...)
|
||||
|
||||
if st.queued > 0 {
|
||||
context = append(context, []interface{}{"queued", st.queued}...)
|
||||
}
|
||||
if st.ignored > 0 {
|
||||
context = append(context, []interface{}{"ignored", st.ignored}...)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1769,7 +1769,7 @@ func testRepairWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme s
|
|||
if err != nil {
|
||||
t.Fatalf("Failed to create persistent key-value database: %v", err)
|
||||
}
|
||||
db, err := rawdb.NewDatabaseWithFreezer(pdb, ancient, "", false)
|
||||
db, err := rawdb.Open(pdb, rawdb.OpenOptions{Ancient: ancient})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create persistent freezer database: %v", err)
|
||||
}
|
||||
|
|
@ -1854,7 +1854,7 @@ func testRepairWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme s
|
|||
if err != nil {
|
||||
t.Fatalf("Failed to reopen persistent key-value database: %v", err)
|
||||
}
|
||||
db, err = rawdb.NewDatabaseWithFreezer(pdb, ancient, "", false)
|
||||
db, err = rawdb.Open(pdb, rawdb.OpenOptions{Ancient: ancient})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to reopen persistent freezer database: %v", err)
|
||||
}
|
||||
|
|
@ -1919,7 +1919,7 @@ func testIssue23496(t *testing.T, scheme string) {
|
|||
if err != nil {
|
||||
t.Fatalf("Failed to create persistent key-value database: %v", err)
|
||||
}
|
||||
db, err := rawdb.NewDatabaseWithFreezer(pdb, ancient, "", false)
|
||||
db, err := rawdb.Open(pdb, rawdb.OpenOptions{Ancient: ancient})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create persistent freezer database: %v", err)
|
||||
}
|
||||
|
|
@ -1979,7 +1979,7 @@ func testIssue23496(t *testing.T, scheme string) {
|
|||
if err != nil {
|
||||
t.Fatalf("Failed to reopen persistent key-value database: %v", err)
|
||||
}
|
||||
db, err = rawdb.NewDatabaseWithFreezer(pdb, ancient, "", false)
|
||||
db, err = rawdb.Open(pdb, rawdb.OpenOptions{Ancient: ancient})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to reopen persistent freezer database: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1973,7 +1973,7 @@ func testSetHeadWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme
|
|||
if err != nil {
|
||||
t.Fatalf("Failed to create persistent key-value database: %v", err)
|
||||
}
|
||||
db, err := rawdb.NewDatabaseWithFreezer(pdb, ancient, "", false)
|
||||
db, err := rawdb.Open(pdb, rawdb.OpenOptions{Ancient: ancient})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create persistent freezer database: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ func (basic *snapshotTestBasic) prepare(t *testing.T) (*BlockChain, []*types.Blo
|
|||
if err != nil {
|
||||
t.Fatalf("Failed to create persistent key-value database: %v", err)
|
||||
}
|
||||
db, err := rawdb.NewDatabaseWithFreezer(pdb, ancient, "", false)
|
||||
db, err := rawdb.Open(pdb, rawdb.OpenOptions{Ancient: ancient})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create persistent freezer database: %v", err)
|
||||
}
|
||||
|
|
@ -265,7 +265,7 @@ func (snaptest *crashSnapshotTest) test(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatalf("Failed to create persistent key-value database: %v", err)
|
||||
}
|
||||
newdb, err := rawdb.NewDatabaseWithFreezer(pdb, snaptest.ancient, "", false)
|
||||
newdb, err := rawdb.Open(pdb, rawdb.OpenOptions{Ancient: snaptest.ancient})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create persistent freezer database: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -738,7 +738,7 @@ func testFastVsFullChains(t *testing.T, scheme string) {
|
|||
t.Fatalf("failed to insert receipt %d: %v", n, err)
|
||||
}
|
||||
// Freezer style fast import the chain.
|
||||
ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
|
||||
ancientDb, err := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp freezer db: %v", err)
|
||||
}
|
||||
|
|
@ -824,7 +824,7 @@ func testLightVsFastVsFullChainHeads(t *testing.T, scheme string) {
|
|||
|
||||
// makeDb creates a db instance for testing.
|
||||
makeDb := func() ethdb.Database {
|
||||
db, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
|
||||
db, err := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp freezer db: %v", err)
|
||||
}
|
||||
|
|
@ -1623,7 +1623,7 @@ func testLargeReorgTrieGC(t *testing.T, scheme string) {
|
|||
competitor, _ := GenerateChain(genesis.Config, shared[len(shared)-1], engine, genDb, 2*state.TriesInMemory+1, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{3}) })
|
||||
|
||||
// Import the shared chain and the original canonical one
|
||||
db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
|
||||
db, _ := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
|
||||
defer db.Close()
|
||||
|
||||
chain, err := NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil)
|
||||
|
|
@ -1689,7 +1689,7 @@ func testBlockchainRecovery(t *testing.T, scheme string) {
|
|||
_, blocks, receipts := GenerateChainWithGenesis(gspec, ethash.NewFaker(), int(height), nil)
|
||||
|
||||
// Import the chain as a ancient-first node and ensure all pointers are updated
|
||||
ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false)
|
||||
ancientDb, err := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{Ancient: t.TempDir()})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp freezer db: %v", err)
|
||||
}
|
||||
|
|
@ -1747,7 +1747,7 @@ func testLowDiffLongChain(t *testing.T, scheme string) {
|
|||
})
|
||||
|
||||
// Import the canonical chain
|
||||
diskdb, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
|
||||
diskdb, _ := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
|
||||
defer diskdb.Close()
|
||||
|
||||
chain, err := NewBlockChain(diskdb, DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil)
|
||||
|
|
@ -1959,7 +1959,7 @@ func testInsertKnownChainData(t *testing.T, typ string, scheme string) {
|
|||
b.OffsetTime(-9) // A higher difficulty
|
||||
})
|
||||
// Import the shared chain and the original canonical one
|
||||
chaindb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
|
||||
chaindb, err := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp freezer db: %v", err)
|
||||
}
|
||||
|
|
@ -2122,7 +2122,7 @@ func testInsertKnownChainDataWithMerging(t *testing.T, typ string, mergeHeight i
|
|||
}
|
||||
})
|
||||
// Import the shared chain and the original canonical one
|
||||
chaindb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
|
||||
chaindb, err := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp freezer db: %v", err)
|
||||
}
|
||||
|
|
@ -2496,7 +2496,7 @@ func testSideImportPrunedBlocks(t *testing.T, scheme string) {
|
|||
if err != nil {
|
||||
t.Fatalf("Failed to create persistent key-value database: %v", err)
|
||||
}
|
||||
db, err := rawdb.NewDatabaseWithFreezer(pdb, ancient, "", false)
|
||||
db, err := rawdb.Open(pdb, rawdb.OpenOptions{Ancient: ancient})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create persistent freezer database: %v", err)
|
||||
}
|
||||
|
|
@ -3403,7 +3403,7 @@ func testSetCanonical(t *testing.T, scheme string) {
|
|||
}
|
||||
gen.AddTx(tx)
|
||||
})
|
||||
diskdb, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
|
||||
diskdb, _ := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
|
||||
defer diskdb.Close()
|
||||
|
||||
chain, err := NewBlockChain(diskdb, DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{}, nil)
|
||||
|
|
@ -4199,7 +4199,7 @@ func testChainReorgSnapSync(t *testing.T, ancientLimit uint64) {
|
|||
gen.SetCoinbase(common.Address{0: byte(0xb), 19: byte(i)})
|
||||
})
|
||||
|
||||
db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
|
||||
db, _ := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
|
||||
defer db.Close()
|
||||
|
||||
chain, _ := NewBlockChain(db, DefaultCacheConfigWithScheme(rawdb.PathScheme), gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{}, nil)
|
||||
|
|
@ -4315,7 +4315,7 @@ func testInsertChainWithCutoff(t *testing.T, cutoff uint64, ancientLimit uint64,
|
|||
config := DefaultCacheConfigWithScheme(rawdb.PathScheme)
|
||||
config.ChainHistoryMode = history.KeepPostMerge
|
||||
|
||||
db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
|
||||
db, _ := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
|
||||
defer db.Close()
|
||||
chain, _ := NewBlockChain(db, DefaultCacheConfigWithScheme(rawdb.PathScheme), genesis, nil, beacon.New(ethash.NewFaker()), vm.Config{}, nil)
|
||||
defer chain.Stop()
|
||||
|
|
|
|||
|
|
@ -53,7 +53,6 @@ const (
|
|||
databaseVersion = 2 // reindexed if database version does not match
|
||||
cachedLastBlocks = 1000 // last block of map pointers
|
||||
cachedLvPointers = 1000 // first log value pointer of block pointers
|
||||
cachedBaseRows = 100 // groups of base layer filter row data
|
||||
cachedFilterMaps = 3 // complete filter maps (cached by map renderer)
|
||||
cachedRenderSnapshots = 8 // saved map renderer data at block boundaries
|
||||
)
|
||||
|
|
@ -101,7 +100,6 @@ type FilterMaps struct {
|
|||
filterMapCache *lru.Cache[uint32, filterMap]
|
||||
lastBlockCache *lru.Cache[uint32, lastBlockOfMap]
|
||||
lvPointerCache *lru.Cache[uint64, uint64]
|
||||
baseRowsCache *lru.Cache[uint64, [][]uint32]
|
||||
|
||||
// the matchers set and the fields of FilterMapsMatcherBackend instances are
|
||||
// read and written both by exported functions and the indexer.
|
||||
|
|
@ -264,7 +262,6 @@ func NewFilterMaps(db ethdb.KeyValueStore, initView *ChainView, historyCutoff, f
|
|||
filterMapCache: lru.NewCache[uint32, filterMap](cachedFilterMaps),
|
||||
lastBlockCache: lru.NewCache[uint32, lastBlockOfMap](cachedLastBlocks),
|
||||
lvPointerCache: lru.NewCache[uint64, uint64](cachedLvPointers),
|
||||
baseRowsCache: lru.NewCache[uint64, [][]uint32](cachedBaseRows),
|
||||
renderSnapshots: lru.NewCache[uint64, *renderedMap](cachedRenderSnapshots),
|
||||
}
|
||||
f.checkRevertRange() // revert maps that are inconsistent with the current chain view
|
||||
|
|
@ -348,7 +345,6 @@ func (f *FilterMaps) reset() {
|
|||
f.renderSnapshots.Purge()
|
||||
f.lastBlockCache.Purge()
|
||||
f.lvPointerCache.Purge()
|
||||
f.baseRowsCache.Purge()
|
||||
f.indexLock.Unlock()
|
||||
// deleting the range first ensures that resetDb will be called again at next
|
||||
// startup and any leftover data will be removed even if it cannot finish now.
|
||||
|
|
@ -565,47 +561,69 @@ func (f *FilterMaps) getFilterMap(mapIndex uint32) (filterMap, error) {
|
|||
}
|
||||
fm := make(filterMap, f.mapHeight)
|
||||
for rowIndex := range fm {
|
||||
var err error
|
||||
fm[rowIndex], err = f.getFilterMapRow(mapIndex, uint32(rowIndex), false)
|
||||
rows, err := f.getFilterMapRows([]uint32{mapIndex}, uint32(rowIndex), false)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load filter map %d from database: %v", mapIndex, err)
|
||||
}
|
||||
fm[rowIndex] = rows[0]
|
||||
}
|
||||
f.filterMapCache.Add(mapIndex, fm)
|
||||
return fm, nil
|
||||
}
|
||||
|
||||
// getFilterMapRow fetches the given filter map row. If baseLayerOnly is true
|
||||
// then only the first baseRowLength entries are returned.
|
||||
func (f *FilterMaps) getFilterMapRow(mapIndex, rowIndex uint32, baseLayerOnly bool) (FilterRow, error) {
|
||||
baseMapRowIndex := f.mapRowIndex(mapIndex&-f.baseRowGroupLength, rowIndex)
|
||||
baseRows, ok := f.baseRowsCache.Get(baseMapRowIndex)
|
||||
if !ok {
|
||||
var err error
|
||||
baseRows, err = rawdb.ReadFilterMapBaseRows(f.db, baseMapRowIndex, f.baseRowGroupLength, f.logMapWidth)
|
||||
// getFilterMapRows fetches a set of filter map rows at the corresponding map
|
||||
// indices and a shared row index. If baseLayerOnly is true then only the first
|
||||
// baseRowLength entries are returned.
|
||||
func (f *FilterMaps) getFilterMapRows(mapIndices []uint32, rowIndex uint32, baseLayerOnly bool) ([]FilterRow, error) {
|
||||
rows := make([]FilterRow, len(mapIndices))
|
||||
var ptr int
|
||||
for len(mapIndices) > ptr {
|
||||
baseRowGroup := mapIndices[ptr] / f.baseRowGroupLength
|
||||
groupLength := 1
|
||||
for ptr+groupLength < len(mapIndices) && mapIndices[ptr+groupLength]/f.baseRowGroupLength == baseRowGroup {
|
||||
groupLength++
|
||||
}
|
||||
if err := f.getFilterMapRowsOfGroup(rows[ptr:ptr+groupLength], mapIndices[ptr:ptr+groupLength], rowIndex, baseLayerOnly); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ptr += groupLength
|
||||
}
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
// getFilterMapRowsOfGroup fetches a set of filter map rows at map indices
|
||||
// belonging to the same base row group.
|
||||
func (f *FilterMaps) getFilterMapRowsOfGroup(target []FilterRow, mapIndices []uint32, rowIndex uint32, baseLayerOnly bool) error {
|
||||
baseRowGroup := mapIndices[0] / f.baseRowGroupLength
|
||||
baseMapRowIndex := f.mapRowIndex(baseRowGroup*f.baseRowGroupLength, rowIndex)
|
||||
baseRows, err := rawdb.ReadFilterMapBaseRows(f.db, baseMapRowIndex, f.baseRowGroupLength, f.logMapWidth)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to retrieve filter map %d base rows %d: %v", mapIndex, rowIndex, err)
|
||||
return fmt.Errorf("failed to retrieve base row group %d of row %d: %v", baseRowGroup, rowIndex, err)
|
||||
}
|
||||
f.baseRowsCache.Add(baseMapRowIndex, baseRows)
|
||||
}
|
||||
baseRow := slices.Clone(baseRows[mapIndex&(f.baseRowGroupLength-1)])
|
||||
if baseLayerOnly {
|
||||
return baseRow, nil
|
||||
for i, mapIndex := range mapIndices {
|
||||
if mapIndex/f.baseRowGroupLength != baseRowGroup {
|
||||
panic("mapIndices are not in the same base row group")
|
||||
}
|
||||
row := baseRows[mapIndex&(f.baseRowGroupLength-1)]
|
||||
if !baseLayerOnly {
|
||||
extRow, err := rawdb.ReadFilterMapExtRow(f.db, f.mapRowIndex(mapIndex, rowIndex), f.logMapWidth)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to retrieve filter map %d extended row %d: %v", mapIndex, rowIndex, err)
|
||||
return fmt.Errorf("failed to retrieve filter map %d extended row %d: %v", mapIndex, rowIndex, err)
|
||||
}
|
||||
return FilterRow(append(baseRow, extRow...)), nil
|
||||
row = append(row, extRow...)
|
||||
}
|
||||
target[i] = row
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// storeFilterMapRows stores a set of filter map rows at the corresponding map
|
||||
// indices and a shared row index.
|
||||
func (f *FilterMaps) storeFilterMapRows(batch ethdb.Batch, mapIndices []uint32, rowIndex uint32, rows []FilterRow) error {
|
||||
for len(mapIndices) > 0 {
|
||||
baseMapIndex := mapIndices[0] & -f.baseRowGroupLength
|
||||
baseRowGroup := mapIndices[0] / f.baseRowGroupLength
|
||||
groupLength := 1
|
||||
for groupLength < len(mapIndices) && mapIndices[groupLength]&-f.baseRowGroupLength == baseMapIndex {
|
||||
for groupLength < len(mapIndices) && mapIndices[groupLength]/f.baseRowGroupLength == baseRowGroup {
|
||||
groupLength++
|
||||
}
|
||||
if err := f.storeFilterMapRowsOfGroup(batch, mapIndices[:groupLength], rowIndex, rows[:groupLength]); err != nil {
|
||||
|
|
@ -619,26 +637,20 @@ func (f *FilterMaps) storeFilterMapRows(batch ethdb.Batch, mapIndices []uint32,
|
|||
// storeFilterMapRowsOfGroup stores a set of filter map rows at map indices
|
||||
// belonging to the same base row group.
|
||||
func (f *FilterMaps) storeFilterMapRowsOfGroup(batch ethdb.Batch, mapIndices []uint32, rowIndex uint32, rows []FilterRow) error {
|
||||
baseMapIndex := mapIndices[0] & -f.baseRowGroupLength
|
||||
baseMapRowIndex := f.mapRowIndex(baseMapIndex, rowIndex)
|
||||
baseRowGroup := mapIndices[0] / f.baseRowGroupLength
|
||||
baseMapRowIndex := f.mapRowIndex(baseRowGroup*f.baseRowGroupLength, rowIndex)
|
||||
var baseRows [][]uint32
|
||||
if uint32(len(mapIndices)) != f.baseRowGroupLength { // skip base rows read if all rows are replaced
|
||||
var ok bool
|
||||
baseRows, ok = f.baseRowsCache.Get(baseMapRowIndex)
|
||||
if ok {
|
||||
baseRows = slices.Clone(baseRows)
|
||||
} else {
|
||||
var err error
|
||||
baseRows, err = rawdb.ReadFilterMapBaseRows(f.db, baseMapRowIndex, f.baseRowGroupLength, f.logMapWidth)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to retrieve filter map %d base rows %d for modification: %v", mapIndices[0]&-f.baseRowGroupLength, rowIndex, err)
|
||||
}
|
||||
return fmt.Errorf("failed to retrieve base row group %d of row %d for modification: %v", baseRowGroup, rowIndex, err)
|
||||
}
|
||||
} else {
|
||||
baseRows = make([][]uint32, f.baseRowGroupLength)
|
||||
}
|
||||
for i, mapIndex := range mapIndices {
|
||||
if mapIndex&-f.baseRowGroupLength != baseMapIndex {
|
||||
if mapIndex/f.baseRowGroupLength != baseRowGroup {
|
||||
panic("mapIndices are not in the same base row group")
|
||||
}
|
||||
baseRow := []uint32(rows[i])
|
||||
|
|
@ -650,7 +662,6 @@ func (f *FilterMaps) storeFilterMapRowsOfGroup(batch ethdb.Batch, mapIndices []u
|
|||
baseRows[mapIndex&(f.baseRowGroupLength-1)] = baseRow
|
||||
rawdb.WriteFilterMapExtRow(batch, f.mapRowIndex(mapIndex, rowIndex), extRow, f.logMapWidth)
|
||||
}
|
||||
f.baseRowsCache.Add(baseMapRowIndex, baseRows)
|
||||
rawdb.WriteFilterMapBaseRows(batch, baseMapRowIndex, baseRows, f.logMapWidth)
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -428,13 +428,15 @@ func (ts *testSetup) matcherViewHash() common.Hash {
|
|||
binary.LittleEndian.PutUint32(enc[:4], r)
|
||||
for m := uint32(0); m <= headMap; m++ {
|
||||
binary.LittleEndian.PutUint32(enc[4:8], m)
|
||||
row, _ := mb.GetFilterMapRow(ctx, m, r, false)
|
||||
rows, _ := mb.GetFilterMapRows(ctx, []uint32{m}, r, false)
|
||||
for _, row := range rows {
|
||||
for _, v := range row {
|
||||
binary.LittleEndian.PutUint32(enc[8:], v)
|
||||
hasher.Write(enc[:])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var hash common.Hash
|
||||
hasher.Sum(hash[:0])
|
||||
for i := 0; i < 50; i++ {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
|
@ -45,7 +44,7 @@ var ErrMatchAll = errors.New("match all patterns not supported")
|
|||
type MatcherBackend interface {
|
||||
GetParams() *Params
|
||||
GetBlockLvPointer(ctx context.Context, blockNumber uint64) (uint64, error)
|
||||
GetFilterMapRow(ctx context.Context, mapIndex, rowIndex uint32, baseLayerOnly bool) (FilterRow, error)
|
||||
GetFilterMapRows(ctx context.Context, mapIndices []uint32, rowIndex uint32, baseLayerOnly bool) ([]FilterRow, error)
|
||||
GetLogByLvIndex(ctx context.Context, lvIndex uint64) (*types.Log, error)
|
||||
SyncLogIndex(ctx context.Context) (SyncRange, error)
|
||||
Close()
|
||||
|
|
@ -365,26 +364,32 @@ func (m *singleMatcherInstance) getMatchesForLayer(ctx context.Context, layerInd
|
|||
var st int
|
||||
m.stats.setState(&st, stOther)
|
||||
params := m.backend.GetParams()
|
||||
maskedMapIndex, rowIndex := uint32(math.MaxUint32), uint32(0)
|
||||
for _, mapIndex := range m.mapIndices {
|
||||
filterRows, ok := m.filterRows[mapIndex]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if mm := params.maskedMapIndex(mapIndex, layerIndex); mm != maskedMapIndex {
|
||||
// only recalculate rowIndex when necessary
|
||||
maskedMapIndex = mm
|
||||
rowIndex = params.rowIndex(mapIndex, layerIndex, m.value)
|
||||
var ptr int
|
||||
for len(m.mapIndices) > ptr {
|
||||
// find next group of map indices mapped onto the same row
|
||||
maskedMapIndex := params.maskedMapIndex(m.mapIndices[ptr], layerIndex)
|
||||
rowIndex := params.rowIndex(m.mapIndices[ptr], layerIndex, m.value)
|
||||
groupLength := 1
|
||||
for ptr+groupLength < len(m.mapIndices) && params.maskedMapIndex(m.mapIndices[ptr+groupLength], layerIndex) == maskedMapIndex {
|
||||
groupLength++
|
||||
}
|
||||
if layerIndex == 0 {
|
||||
m.stats.setState(&st, stFetchFirst)
|
||||
} else {
|
||||
m.stats.setState(&st, stFetchMore)
|
||||
}
|
||||
filterRow, err := m.backend.GetFilterMapRow(ctx, mapIndex, rowIndex, layerIndex == 0)
|
||||
groupRows, err := m.backend.GetFilterMapRows(ctx, m.mapIndices[ptr:ptr+groupLength], rowIndex, layerIndex == 0)
|
||||
if err != nil {
|
||||
m.stats.setState(&st, stNone)
|
||||
return nil, fmt.Errorf("failed to retrieve filter map %d row %d: %v", mapIndex, rowIndex, err)
|
||||
return nil, fmt.Errorf("failed to retrieve filter map %d row %d: %v", m.mapIndices[ptr], rowIndex, err)
|
||||
}
|
||||
m.stats.setState(&st, stOther)
|
||||
for i := range groupLength {
|
||||
mapIndex := m.mapIndices[ptr+i]
|
||||
filterRow := groupRows[i]
|
||||
filterRows, ok := m.filterRows[mapIndex]
|
||||
if !ok {
|
||||
panic("dropped map in mapIndices")
|
||||
}
|
||||
if layerIndex == 0 {
|
||||
matchBaseRowAccessMeter.Mark(1)
|
||||
|
|
@ -394,7 +399,6 @@ func (m *singleMatcherInstance) getMatchesForLayer(ctx context.Context, layerInd
|
|||
matchExtRowSizeMeter.Mark(int64(len(filterRow)))
|
||||
}
|
||||
m.stats.addAmount(st, int64(len(filterRow)))
|
||||
m.stats.setState(&st, stOther)
|
||||
filterRows = append(filterRows, filterRow)
|
||||
if uint32(len(filterRow)) < params.maxRowLength(layerIndex) {
|
||||
m.stats.setState(&st, stProcess)
|
||||
|
|
@ -410,6 +414,8 @@ func (m *singleMatcherInstance) getMatchesForLayer(ctx context.Context, layerInd
|
|||
m.filterRows[mapIndex] = filterRows
|
||||
}
|
||||
}
|
||||
ptr += groupLength
|
||||
}
|
||||
m.cleanMapIndices()
|
||||
m.stats.setState(&st, stNone)
|
||||
return results, nil
|
||||
|
|
|
|||
|
|
@ -67,18 +67,15 @@ func (fm *FilterMapsMatcherBackend) Close() {
|
|||
delete(fm.f.matchers, fm)
|
||||
}
|
||||
|
||||
// GetFilterMapRow returns the given row of the given map. If the row is empty
|
||||
// GetFilterMapRows returns the given row of the given map. If the row is empty
|
||||
// then a non-nil zero length row is returned. If baseLayerOnly is true then
|
||||
// only the first baseRowLength entries of the row are guaranteed to be
|
||||
// returned.
|
||||
// Note that the returned slices should not be modified, they should be copied
|
||||
// on write.
|
||||
// GetFilterMapRow implements MatcherBackend.
|
||||
func (fm *FilterMapsMatcherBackend) GetFilterMapRow(ctx context.Context, mapIndex, rowIndex uint32, baseLayerOnly bool) (FilterRow, error) {
|
||||
fm.f.indexLock.RLock()
|
||||
defer fm.f.indexLock.RUnlock()
|
||||
|
||||
return fm.f.getFilterMapRow(mapIndex, rowIndex, baseLayerOnly)
|
||||
// GetFilterMapRows implements MatcherBackend.
|
||||
func (fm *FilterMapsMatcherBackend) GetFilterMapRows(ctx context.Context, mapIndices []uint32, rowIndex uint32, baseLayerOnly bool) ([]FilterRow, error) {
|
||||
return fm.f.getFilterMapRows(mapIndices, rowIndex, baseLayerOnly)
|
||||
}
|
||||
|
||||
// GetBlockLvPointer returns the starting log value index where the log values
|
||||
|
|
|
|||
|
|
@ -416,7 +416,7 @@ func checkReceiptsRLP(have, want types.Receipts) error {
|
|||
func TestAncientStorage(t *testing.T) {
|
||||
// Freezer style fast import the chain.
|
||||
frdir := t.TempDir()
|
||||
db, err := NewDatabaseWithFreezer(NewMemoryDatabase(), frdir, "", false)
|
||||
db, err := Open(NewMemoryDatabase(), OpenOptions{Ancient: frdir})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create database with ancient backend")
|
||||
}
|
||||
|
|
@ -469,7 +469,7 @@ func TestAncientStorage(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestWriteAncientHeaderChain(t *testing.T) {
|
||||
db, err := NewDatabaseWithFreezer(NewMemoryDatabase(), t.TempDir(), "", false)
|
||||
db, err := Open(NewMemoryDatabase(), OpenOptions{Ancient: t.TempDir()})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create database with ancient backend")
|
||||
}
|
||||
|
|
@ -586,7 +586,7 @@ func TestHashesInRange(t *testing.T) {
|
|||
func BenchmarkWriteAncientBlocks(b *testing.B) {
|
||||
// Open freezer database.
|
||||
frdir := b.TempDir()
|
||||
db, err := NewDatabaseWithFreezer(NewMemoryDatabase(), frdir, "", false)
|
||||
db, err := Open(NewMemoryDatabase(), OpenOptions{Ancient: frdir})
|
||||
if err != nil {
|
||||
b.Fatalf("failed to create database with ancient backend")
|
||||
}
|
||||
|
|
@ -684,6 +684,7 @@ type fullLogRLP struct {
|
|||
Topics []common.Hash
|
||||
Data []byte
|
||||
BlockNumber uint64
|
||||
BlockTimestamp uint64
|
||||
TxHash common.Hash
|
||||
TxIndex uint
|
||||
BlockHash common.Hash
|
||||
|
|
@ -696,6 +697,7 @@ func newFullLogRLP(l *types.Log) *fullLogRLP {
|
|||
Topics: l.Topics,
|
||||
Data: l.Data,
|
||||
BlockNumber: l.BlockNumber,
|
||||
BlockTimestamp: l.BlockTimestamp,
|
||||
TxHash: l.TxHash,
|
||||
TxIndex: l.TxIndex,
|
||||
BlockHash: l.BlockHash,
|
||||
|
|
@ -834,7 +836,7 @@ func TestDeriveLogFields(t *testing.T) {
|
|||
// Derive log metadata fields
|
||||
number := big.NewInt(1)
|
||||
hash := common.BytesToHash([]byte{0x03, 0x14})
|
||||
types.Receipts(receipts).DeriveFields(params.TestChainConfig, hash, number.Uint64(), 0, big.NewInt(0), big.NewInt(0), txs)
|
||||
types.Receipts(receipts).DeriveFields(params.TestChainConfig, hash, number.Uint64(), 12, big.NewInt(0), big.NewInt(0), txs)
|
||||
|
||||
// Iterate over all the computed fields and check that they're correct
|
||||
logIndex := uint(0)
|
||||
|
|
@ -846,6 +848,9 @@ func TestDeriveLogFields(t *testing.T) {
|
|||
if receipts[i].Logs[j].BlockHash != hash {
|
||||
t.Errorf("receipts[%d].Logs[%d].BlockHash = %s, want %s", i, j, receipts[i].Logs[j].BlockHash.String(), hash.String())
|
||||
}
|
||||
if receipts[i].Logs[j].BlockTimestamp != 12 {
|
||||
t.Errorf("receipts[%d].Logs[%d].BlockTimestamp = %d, want %d", i, j, receipts[i].Logs[j].BlockTimestamp, 12)
|
||||
}
|
||||
if receipts[i].Logs[j].TxHash != txs[i].Hash() {
|
||||
t.Errorf("receipts[%d].Logs[%d].TxHash = %s, want %s", i, j, receipts[i].Logs[j].TxHash.String(), txs[i].Hash().String())
|
||||
}
|
||||
|
|
@ -890,7 +895,7 @@ func TestHeadersRLPStorage(t *testing.T) {
|
|||
// Have N headers in the freezer
|
||||
frdir := t.TempDir()
|
||||
|
||||
db, err := NewDatabaseWithFreezer(NewMemoryDatabase(), frdir, "", false)
|
||||
db, err := Open(NewMemoryDatabase(), OpenOptions{Ancient: frdir})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create database with ancient backend")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,13 +77,6 @@ func basicRead(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) {
|
|||
}
|
||||
for _, c := range cases {
|
||||
for i := c.start; i < c.limit; i++ {
|
||||
exist, err := db.HasAncient("a", uint64(i))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to check presence, %v", err)
|
||||
}
|
||||
if exist {
|
||||
t.Fatalf("Item %d is already truncated", uint64(i))
|
||||
}
|
||||
_, err = db.Ancient("a", uint64(i))
|
||||
if err == nil {
|
||||
t.Fatal("Error is expected for non-existent item")
|
||||
|
|
@ -93,13 +86,6 @@ func basicRead(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) {
|
|||
|
||||
// Test the items in range should be reachable
|
||||
for i := 10; i < 90; i++ {
|
||||
exist, err := db.HasAncient("a", uint64(i))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to check presence, %v", err)
|
||||
}
|
||||
if !exist {
|
||||
t.Fatalf("Item %d is missing", uint64(i))
|
||||
}
|
||||
blob, err := db.Ancient("a", uint64(i))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to retrieve item, %v", err)
|
||||
|
|
@ -110,13 +96,6 @@ func basicRead(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) {
|
|||
}
|
||||
|
||||
// Test the items in unknown table shouldn't be reachable
|
||||
exist, err := db.HasAncient("b", uint64(0))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to check presence, %v", err)
|
||||
}
|
||||
if exist {
|
||||
t.Fatal("Item in unknown table shouldn't be found")
|
||||
}
|
||||
_, err = db.Ancient("b", uint64(0))
|
||||
if err == nil {
|
||||
t.Fatal("Error is expected for unknown table")
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb/eradb"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
|
|
@ -43,7 +44,10 @@ const (
|
|||
// feature. The background thread will keep moving ancient chain segments from
|
||||
// key-value database to flat files for saving space on live database.
|
||||
type chainFreezer struct {
|
||||
ethdb.AncientStore // Ancient store for storing cold chain segment
|
||||
ancients ethdb.AncientStore // Ancient store for storing cold chain segment
|
||||
|
||||
// Optional Era database used as a backup for the pruned chain.
|
||||
eradb *eradb.Store
|
||||
|
||||
quit chan struct{}
|
||||
wg sync.WaitGroup
|
||||
|
|
@ -56,21 +60,25 @@ type chainFreezer struct {
|
|||
// state freezer (e.g. dev mode).
|
||||
// - if non-empty directory is given, initializes the regular file-based
|
||||
// state freezer.
|
||||
func newChainFreezer(datadir string, namespace string, readonly bool) (*chainFreezer, error) {
|
||||
var (
|
||||
err error
|
||||
freezer ethdb.AncientStore
|
||||
)
|
||||
func newChainFreezer(datadir string, eraDir string, namespace string, readonly bool) (*chainFreezer, error) {
|
||||
if datadir == "" {
|
||||
freezer = NewMemoryFreezer(readonly, chainFreezerTableConfigs)
|
||||
} else {
|
||||
freezer, err = NewFreezer(datadir, namespace, readonly, freezerTableSize, chainFreezerTableConfigs)
|
||||
return &chainFreezer{
|
||||
ancients: NewMemoryFreezer(readonly, chainFreezerTableConfigs),
|
||||
quit: make(chan struct{}),
|
||||
trigger: make(chan chan struct{}),
|
||||
}, nil
|
||||
}
|
||||
freezer, err := NewFreezer(datadir, namespace, readonly, freezerTableSize, chainFreezerTableConfigs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
edb, err := eradb.New(resolveChainEraDir(datadir, eraDir))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &chainFreezer{
|
||||
AncientStore: freezer,
|
||||
ancients: freezer,
|
||||
eradb: edb,
|
||||
quit: make(chan struct{}),
|
||||
trigger: make(chan chan struct{}),
|
||||
}, nil
|
||||
|
|
@ -84,7 +92,11 @@ func (f *chainFreezer) Close() error {
|
|||
close(f.quit)
|
||||
}
|
||||
f.wg.Wait()
|
||||
return f.AncientStore.Close()
|
||||
|
||||
if f.eradb != nil {
|
||||
f.eradb.Close()
|
||||
}
|
||||
return f.ancients.Close()
|
||||
}
|
||||
|
||||
// readHeadNumber returns the number of chain head block. 0 is returned if the
|
||||
|
|
@ -334,3 +346,75 @@ func (f *chainFreezer) freezeRange(nfdb *nofreezedb, number, limit uint64) (hash
|
|||
})
|
||||
return hashes, err
|
||||
}
|
||||
|
||||
// Ancient retrieves an ancient binary blob from the append-only immutable files.
|
||||
func (f *chainFreezer) Ancient(kind string, number uint64) ([]byte, error) {
|
||||
// Lookup the entry in the underlying ancient store, assuming that
|
||||
// headers and hashes are always available.
|
||||
if kind == ChainFreezerHeaderTable || kind == ChainFreezerHashTable {
|
||||
return f.ancients.Ancient(kind, number)
|
||||
}
|
||||
tail, err := f.ancients.Tail()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Lookup the entry in the underlying ancient store if it's not pruned
|
||||
if number >= tail {
|
||||
return f.ancients.Ancient(kind, number)
|
||||
}
|
||||
// Lookup the entry in the optional era backend
|
||||
if f.eradb == nil {
|
||||
return nil, errOutOfBounds
|
||||
}
|
||||
switch kind {
|
||||
case ChainFreezerBodiesTable:
|
||||
return f.eradb.GetRawBody(number)
|
||||
case ChainFreezerReceiptTable:
|
||||
return f.eradb.GetRawReceipts(number)
|
||||
}
|
||||
return nil, errUnknownTable
|
||||
}
|
||||
|
||||
// ReadAncients executes an operation while preventing mutations to the freezer,
|
||||
// i.e. if fn performs multiple reads, they will be consistent with each other.
|
||||
func (f *chainFreezer) ReadAncients(fn func(ethdb.AncientReaderOp) error) (err error) {
|
||||
if store, ok := f.ancients.(*Freezer); ok {
|
||||
store.writeLock.Lock()
|
||||
defer store.writeLock.Unlock()
|
||||
}
|
||||
return fn(f)
|
||||
}
|
||||
|
||||
// Methods below are just pass-through to the underlying ancient store.
|
||||
|
||||
func (f *chainFreezer) Ancients() (uint64, error) {
|
||||
return f.ancients.Ancients()
|
||||
}
|
||||
|
||||
func (f *chainFreezer) Tail() (uint64, error) {
|
||||
return f.ancients.Tail()
|
||||
}
|
||||
|
||||
func (f *chainFreezer) AncientSize(kind string) (uint64, error) {
|
||||
return f.ancients.AncientSize(kind)
|
||||
}
|
||||
|
||||
func (f *chainFreezer) AncientRange(kind string, start, count, maxBytes uint64) ([][]byte, error) {
|
||||
return f.ancients.AncientRange(kind, start, count, maxBytes)
|
||||
}
|
||||
|
||||
func (f *chainFreezer) ModifyAncients(fn func(ethdb.AncientWriteOp) error) (int64, error) {
|
||||
return f.ancients.ModifyAncients(fn)
|
||||
}
|
||||
|
||||
func (f *chainFreezer) TruncateHead(items uint64) (uint64, error) {
|
||||
return f.ancients.TruncateHead(items)
|
||||
}
|
||||
|
||||
func (f *chainFreezer) TruncateTail(items uint64) (uint64, error) {
|
||||
return f.ancients.TruncateTail(items)
|
||||
}
|
||||
|
||||
func (f *chainFreezer) SyncAncient() error {
|
||||
return f.ancients.SyncAncient()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,11 +86,6 @@ type nofreezedb struct {
|
|||
ethdb.KeyValueStore
|
||||
}
|
||||
|
||||
// HasAncient returns an error as we don't have a backing chain freezer.
|
||||
func (db *nofreezedb) HasAncient(kind string, number uint64) (bool, error) {
|
||||
return false, errNotSupported
|
||||
}
|
||||
|
||||
// Ancient returns an error as we don't have a backing chain freezer.
|
||||
func (db *nofreezedb) Ancient(kind string, number uint64) ([]byte, error) {
|
||||
return nil, errNotSupported
|
||||
|
|
@ -186,19 +181,49 @@ func resolveChainFreezerDir(ancient string) string {
|
|||
return freezer
|
||||
}
|
||||
|
||||
// NewDatabaseWithFreezer creates a high level database on top of a given key-
|
||||
// value data store with a freezer moving immutable chain segments into cold
|
||||
// storage. The passed ancient indicates the path of root ancient directory
|
||||
// where the chain freezer can be opened.
|
||||
// resolveChainEraDir is a helper function which resolves the absolute path of era database.
|
||||
func resolveChainEraDir(chainFreezerDir string, era string) string {
|
||||
switch {
|
||||
case era == "":
|
||||
return filepath.Join(chainFreezerDir, "era")
|
||||
case !filepath.IsAbs(era):
|
||||
return filepath.Join(chainFreezerDir, era)
|
||||
default:
|
||||
return era
|
||||
}
|
||||
}
|
||||
|
||||
// NewDatabaseWithFreezer creates a high level database on top of a given key-value store.
|
||||
// The passed ancient indicates the path of root ancient directory where the chain freezer
|
||||
// can be opened.
|
||||
//
|
||||
// Deprecated: use Open.
|
||||
func NewDatabaseWithFreezer(db ethdb.KeyValueStore, ancient string, namespace string, readonly bool) (ethdb.Database, error) {
|
||||
return Open(db, OpenOptions{
|
||||
Ancient: ancient,
|
||||
MetricsNamespace: namespace,
|
||||
ReadOnly: readonly,
|
||||
})
|
||||
}
|
||||
|
||||
// OpenOptions specifies options for opening the database.
|
||||
type OpenOptions struct {
|
||||
Ancient string // ancients directory
|
||||
Era string // era files directory
|
||||
MetricsNamespace string // prefix added to freezer metric names
|
||||
ReadOnly bool
|
||||
}
|
||||
|
||||
// Open creates a high-level database wrapper for the given key-value store.
|
||||
func Open(db ethdb.KeyValueStore, opts OpenOptions) (ethdb.Database, error) {
|
||||
// Create the idle freezer instance. If the given ancient directory is empty,
|
||||
// in-memory chain freezer is used (e.g. dev mode); otherwise the regular
|
||||
// file-based freezer is created.
|
||||
chainFreezerDir := ancient
|
||||
chainFreezerDir := opts.Ancient
|
||||
if chainFreezerDir != "" {
|
||||
chainFreezerDir = resolveChainFreezerDir(chainFreezerDir)
|
||||
}
|
||||
frdb, err := newChainFreezer(chainFreezerDir, namespace, readonly)
|
||||
frdb, err := newChainFreezer(chainFreezerDir, opts.Era, opts.MetricsNamespace, opts.ReadOnly)
|
||||
if err != nil {
|
||||
printChainMetadata(db)
|
||||
return nil, err
|
||||
|
|
@ -282,7 +307,7 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, ancient string, namespace st
|
|||
}
|
||||
}
|
||||
// Freezer is consistent with the key-value database, permit combining the two
|
||||
if !readonly {
|
||||
if !opts.ReadOnly {
|
||||
frdb.wg.Add(1)
|
||||
go func() {
|
||||
frdb.freeze(db)
|
||||
|
|
@ -290,7 +315,7 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, ancient string, namespace st
|
|||
}()
|
||||
}
|
||||
return &freezerdb{
|
||||
ancientRoot: ancient,
|
||||
ancientRoot: opts.Ancient,
|
||||
KeyValueStore: db,
|
||||
chainFreezer: frdb,
|
||||
}, nil
|
||||
|
|
@ -642,7 +667,6 @@ func SafeDeleteRange(db ethdb.KeyValueStore, start, end []byte, hashScheme bool,
|
|||
|
||||
var (
|
||||
count, deleted, skipped int
|
||||
buff = crypto.NewKeccakState()
|
||||
startTime = time.Now()
|
||||
)
|
||||
|
||||
|
|
@ -655,7 +679,7 @@ func SafeDeleteRange(db ethdb.KeyValueStore, start, end []byte, hashScheme bool,
|
|||
|
||||
for it.Next() && bytes.Compare(end, it.Key()) > 0 {
|
||||
// Prevent deletion for trie nodes in hash mode
|
||||
if len(it.Key()) != 32 || crypto.HashData(buff, it.Value()) != common.BytesToHash(it.Key()) {
|
||||
if len(it.Key()) != 32 || crypto.Keccak256Hash(it.Value()) != common.BytesToHash(it.Key()) {
|
||||
if err := batch.Delete(it.Key()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +0,0 @@
|
|||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package rawdb
|
||||
345
core/rawdb/eradb/eradb.go
Normal file
345
core/rawdb/eradb/eradb.go
Normal file
|
|
@ -0,0 +1,345 @@
|
|||
// Copyright 2025 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package eradb implements a history backend using era1 files.
|
||||
package eradb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/lru"
|
||||
"github.com/ethereum/go-ethereum/internal/era"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
const openFileLimit = 64
|
||||
|
||||
var errClosed = errors.New("era store is closed")
|
||||
|
||||
// Store manages read access to a directory of era1 files.
|
||||
// The getter methods are thread-safe.
|
||||
type Store struct {
|
||||
datadir string
|
||||
|
||||
// The mutex protects all remaining fields.
|
||||
mu sync.Mutex
|
||||
cond *sync.Cond
|
||||
lru lru.BasicLRU[uint64, *fileCacheEntry]
|
||||
opening map[uint64]*fileCacheEntry
|
||||
closing bool
|
||||
}
|
||||
|
||||
type fileCacheEntry struct {
|
||||
refcount int // reference count. This is protected by Store.mu!
|
||||
opened chan struct{} // signals opening of file has completed
|
||||
file *era.Era // the file
|
||||
err error // error from opening the file
|
||||
}
|
||||
|
||||
type fileCacheStatus byte
|
||||
|
||||
const (
|
||||
storeClosing fileCacheStatus = iota
|
||||
fileIsNew
|
||||
fileIsOpening
|
||||
fileIsCached
|
||||
)
|
||||
|
||||
// New opens the store directory.
|
||||
func New(datadir string) (*Store, error) {
|
||||
db := &Store{
|
||||
datadir: datadir,
|
||||
lru: lru.NewBasicLRU[uint64, *fileCacheEntry](openFileLimit),
|
||||
opening: make(map[uint64]*fileCacheEntry),
|
||||
}
|
||||
db.cond = sync.NewCond(&db.mu)
|
||||
log.Info("Opened Era store", "datadir", datadir)
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// Close closes all open era1 files in the cache.
|
||||
func (db *Store) Close() {
|
||||
db.mu.Lock()
|
||||
defer db.mu.Unlock()
|
||||
|
||||
// Prevent new cache additions.
|
||||
db.closing = true
|
||||
|
||||
// Deref all active files. Since inactive files have a refcount of one, they will be
|
||||
// closed right here and now after decrementing. Files which are currently being used
|
||||
// have a refcount > 1 and will hit zero when their access finishes.
|
||||
for _, epoch := range db.lru.Keys() {
|
||||
entry, _ := db.lru.Peek(epoch)
|
||||
if entry.derefAndClose(epoch) {
|
||||
db.lru.Remove(epoch)
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for all store access to finish.
|
||||
for db.lru.Len() > 0 || len(db.opening) > 0 {
|
||||
db.cond.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
// GetRawBody returns the raw body for a given block number.
|
||||
func (db *Store) GetRawBody(number uint64) ([]byte, error) {
|
||||
epoch := number / uint64(era.MaxEra1Size)
|
||||
entry := db.getEraByEpoch(epoch)
|
||||
if entry.err != nil {
|
||||
if errors.Is(entry.err, fs.ErrNotExist) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, entry.err
|
||||
}
|
||||
defer db.doneWithFile(epoch, entry)
|
||||
|
||||
return entry.file.GetRawBodyByNumber(number)
|
||||
}
|
||||
|
||||
// GetRawReceipts returns the raw receipts for a given block number.
|
||||
func (db *Store) GetRawReceipts(number uint64) ([]byte, error) {
|
||||
epoch := number / uint64(era.MaxEra1Size)
|
||||
entry := db.getEraByEpoch(epoch)
|
||||
if entry.err != nil {
|
||||
if errors.Is(entry.err, fs.ErrNotExist) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, entry.err
|
||||
}
|
||||
defer db.doneWithFile(epoch, entry)
|
||||
|
||||
data, err := entry.file.GetRawReceiptsByNumber(number)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return convertReceipts(data)
|
||||
}
|
||||
|
||||
// convertReceipts transforms an encoded block receipts list from the format
|
||||
// used by era1 into the 'storage' format used by the go-ethereum ancients database.
|
||||
func convertReceipts(input []byte) ([]byte, error) {
|
||||
var (
|
||||
out bytes.Buffer
|
||||
enc = rlp.NewEncoderBuffer(&out)
|
||||
)
|
||||
blockListIter, err := rlp.NewListIterator(input)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid block receipts list: %v", err)
|
||||
}
|
||||
outerList := enc.List()
|
||||
for i := 0; blockListIter.Next(); i++ {
|
||||
kind, content, _, err := rlp.Split(blockListIter.Value())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("receipt %d invalid: %v", i, err)
|
||||
}
|
||||
var receiptData []byte
|
||||
switch kind {
|
||||
case rlp.Byte:
|
||||
return nil, fmt.Errorf("receipt %d is single byte", i)
|
||||
case rlp.String:
|
||||
// Typed receipt - skip type.
|
||||
receiptData = content[1:]
|
||||
case rlp.List:
|
||||
// Legacy receipt
|
||||
receiptData = blockListIter.Value()
|
||||
}
|
||||
// Convert data list.
|
||||
// Input is [status, gas-used, bloom, logs]
|
||||
// Output is [status, gas-used, logs], i.e. we need to skip the bloom.
|
||||
dataIter, err := rlp.NewListIterator(receiptData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("receipt %d has invalid data: %v", i, err)
|
||||
}
|
||||
innerList := enc.List()
|
||||
for field := 0; dataIter.Next(); field++ {
|
||||
if field == 2 {
|
||||
continue // skip bloom
|
||||
}
|
||||
enc.Write(dataIter.Value())
|
||||
}
|
||||
enc.ListEnd(innerList)
|
||||
if dataIter.Err() != nil {
|
||||
return nil, fmt.Errorf("receipt %d iterator error: %v", i, dataIter.Err())
|
||||
}
|
||||
}
|
||||
enc.ListEnd(outerList)
|
||||
if blockListIter.Err() != nil {
|
||||
return nil, fmt.Errorf("block receipt list iterator error: %v", blockListIter.Err())
|
||||
}
|
||||
enc.Flush()
|
||||
return out.Bytes(), nil
|
||||
}
|
||||
|
||||
// getEraByEpoch opens an era file or gets it from the cache.
|
||||
// The caller can freely access the returned entry's .file and .err
|
||||
// db.doneWithFile must be called when it is done reading the file.
|
||||
func (db *Store) getEraByEpoch(epoch uint64) *fileCacheEntry {
|
||||
stat, entry := db.getCacheEntry(epoch)
|
||||
|
||||
switch stat {
|
||||
case storeClosing:
|
||||
return &fileCacheEntry{err: errClosed}
|
||||
|
||||
case fileIsNew:
|
||||
// Open the file and put it into the cache.
|
||||
e, err := db.openEraFile(epoch)
|
||||
if err != nil {
|
||||
db.fileFailedToOpen(epoch, entry, err)
|
||||
} else {
|
||||
db.fileOpened(epoch, entry, e)
|
||||
}
|
||||
close(entry.opened)
|
||||
|
||||
case fileIsOpening:
|
||||
// Wait for open to finish.
|
||||
<-entry.opened
|
||||
|
||||
case fileIsCached:
|
||||
// Nothing to do.
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("invalid file state %d", stat))
|
||||
}
|
||||
return entry
|
||||
}
|
||||
|
||||
// getCacheEntry gets an open era file from the cache.
|
||||
func (db *Store) getCacheEntry(epoch uint64) (stat fileCacheStatus, entry *fileCacheEntry) {
|
||||
db.mu.Lock()
|
||||
defer db.mu.Unlock()
|
||||
|
||||
if db.closing {
|
||||
return storeClosing, nil
|
||||
}
|
||||
if entry = db.opening[epoch]; entry != nil {
|
||||
stat = fileIsOpening
|
||||
} else if entry, _ = db.lru.Get(epoch); entry != nil {
|
||||
stat = fileIsCached
|
||||
} else {
|
||||
// It's a new file, create an entry in the opening table. Note the entry is
|
||||
// created with an initial refcount of one. We increment the count once more
|
||||
// before returning, but the count will return to one when the file has been
|
||||
// accessed. When the store is closed or the file gets evicted from the cache,
|
||||
// refcount will be decreased by one, thus allowing it to hit zero.
|
||||
entry = &fileCacheEntry{refcount: 1, opened: make(chan struct{})}
|
||||
db.opening[epoch] = entry
|
||||
stat = fileIsNew
|
||||
}
|
||||
entry.refcount++
|
||||
return stat, entry
|
||||
}
|
||||
|
||||
// fileOpened is called after an era file has been successfully opened.
|
||||
func (db *Store) fileOpened(epoch uint64, entry *fileCacheEntry, file *era.Era) {
|
||||
db.mu.Lock()
|
||||
defer db.mu.Unlock()
|
||||
|
||||
delete(db.opening, epoch)
|
||||
db.cond.Signal() // db.opening was modified
|
||||
|
||||
// The database may have been closed while opening the file. When that happens, we
|
||||
// need to close the file here, since it isn't tracked by the LRU yet.
|
||||
if db.closing {
|
||||
entry.err = errClosed
|
||||
file.Close()
|
||||
return
|
||||
}
|
||||
|
||||
// Add it to the LRU. This may evict an existing item, which we have to close.
|
||||
entry.file = file
|
||||
evictedEpoch, evictedEntry, _ := db.lru.Add3(epoch, entry)
|
||||
if evictedEntry != nil {
|
||||
evictedEntry.derefAndClose(evictedEpoch)
|
||||
}
|
||||
}
|
||||
|
||||
// fileFailedToOpen is called when an era file could not be opened.
|
||||
func (db *Store) fileFailedToOpen(epoch uint64, entry *fileCacheEntry, err error) {
|
||||
db.mu.Lock()
|
||||
defer db.mu.Unlock()
|
||||
|
||||
delete(db.opening, epoch)
|
||||
db.cond.Signal() // db.opening was modified
|
||||
entry.err = err
|
||||
}
|
||||
|
||||
func (db *Store) openEraFile(epoch uint64) (*era.Era, error) {
|
||||
// File name scheme is <network>-<epoch>-<root>.
|
||||
glob := fmt.Sprintf("*-%05d-*.era1", epoch)
|
||||
matches, err := filepath.Glob(filepath.Join(db.datadir, glob))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(matches) > 1 {
|
||||
return nil, fmt.Errorf("multiple era1 files found for epoch %d", epoch)
|
||||
}
|
||||
if len(matches) == 0 {
|
||||
return nil, fs.ErrNotExist
|
||||
}
|
||||
filename := matches[0]
|
||||
|
||||
e, err := era.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Sanity-check start block.
|
||||
if e.Start()%uint64(era.MaxEra1Size) != 0 {
|
||||
return nil, fmt.Errorf("pre-merge era1 file has invalid boundary. %d %% %d != 0", e.Start(), era.MaxEra1Size)
|
||||
}
|
||||
log.Debug("Opened era1 file", "epoch", epoch)
|
||||
return e, nil
|
||||
}
|
||||
|
||||
// doneWithFile signals that the caller has finished using a file.
|
||||
// This decrements the refcount and ensures the file is closed by the last user.
|
||||
func (db *Store) doneWithFile(epoch uint64, entry *fileCacheEntry) {
|
||||
db.mu.Lock()
|
||||
defer db.mu.Unlock()
|
||||
|
||||
if entry.err != nil {
|
||||
return
|
||||
}
|
||||
if entry.derefAndClose(epoch) {
|
||||
// Delete closed entry from LRU if it is still present.
|
||||
if e, _ := db.lru.Peek(epoch); e == entry {
|
||||
db.lru.Remove(epoch)
|
||||
db.cond.Signal() // db.lru was modified
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// derefAndClose decrements the reference counter and closes the file
|
||||
// when it hits zero.
|
||||
func (entry *fileCacheEntry) derefAndClose(epoch uint64) (closed bool) {
|
||||
entry.refcount--
|
||||
if entry.refcount > 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
closeErr := entry.file.Close()
|
||||
if closeErr == nil {
|
||||
log.Debug("Closed era1 file", "epoch", epoch)
|
||||
} else {
|
||||
log.Warn("Error closing era1 file", "epoch", epoch, "err", closeErr)
|
||||
}
|
||||
return true
|
||||
}
|
||||
103
core/rawdb/eradb/eradb_test.go
Normal file
103
core/rawdb/eradb/eradb_test.go
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
// Copyright 2025 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package eradb
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestEraDatabase(t *testing.T) {
|
||||
db, err := New("testdata")
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
|
||||
r, err := db.GetRawBody(175881)
|
||||
require.NoError(t, err)
|
||||
var body *types.Body
|
||||
err = rlp.DecodeBytes(r, &body)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, body, "block body not found")
|
||||
assert.Equal(t, 3, len(body.Transactions))
|
||||
|
||||
r, err = db.GetRawReceipts(175881)
|
||||
require.NoError(t, err)
|
||||
var receipts []*types.ReceiptForStorage
|
||||
err = rlp.DecodeBytes(r, &receipts)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, receipts, "receipts not found")
|
||||
assert.Equal(t, 3, len(receipts), "receipts length mismatch")
|
||||
}
|
||||
|
||||
func TestEraDatabaseConcurrentOpen(t *testing.T) {
|
||||
db, err := New("testdata")
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
|
||||
const N = 25
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(N)
|
||||
for range N {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
r, err := db.GetRawBody(1024)
|
||||
if err != nil {
|
||||
t.Error("err:", err)
|
||||
}
|
||||
if len(r) == 0 {
|
||||
t.Error("empty body")
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestEraDatabaseConcurrentOpenClose(t *testing.T) {
|
||||
db, err := New("testdata")
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
|
||||
const N = 10
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(N)
|
||||
for range N {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
r, err := db.GetRawBody(1024)
|
||||
if err == errClosed {
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Error("err:", err)
|
||||
}
|
||||
if len(r) == 0 {
|
||||
t.Error("empty body")
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
db.Close()
|
||||
}()
|
||||
wg.Wait()
|
||||
}
|
||||
BIN
core/rawdb/eradb/testdata/sepolia-00000-643a00f7.era1
vendored
Normal file
BIN
core/rawdb/eradb/testdata/sepolia-00000-643a00f7.era1
vendored
Normal file
Binary file not shown.
BIN
core/rawdb/eradb/testdata/sepolia-00021-b8814b14.era1
vendored
Normal file
BIN
core/rawdb/eradb/testdata/sepolia-00021-b8814b14.era1
vendored
Normal file
Binary file not shown.
|
|
@ -172,10 +172,7 @@ func (f *Freezer) Close() error {
|
|||
errs = append(errs, err)
|
||||
}
|
||||
})
|
||||
if errs != nil {
|
||||
return fmt.Errorf("%v", errs)
|
||||
}
|
||||
return nil
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
// AncientDatadir returns the path of the ancient store.
|
||||
|
|
@ -183,15 +180,6 @@ func (f *Freezer) AncientDatadir() (string, error) {
|
|||
return f.datadir, nil
|
||||
}
|
||||
|
||||
// HasAncient returns an indicator whether the specified ancient data exists
|
||||
// in the freezer.
|
||||
func (f *Freezer) HasAncient(kind string, number uint64) (bool, error) {
|
||||
if table := f.tables[kind]; table != nil {
|
||||
return table.has(number), nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Ancient retrieves an ancient binary blob from the append-only immutable files.
|
||||
func (f *Freezer) Ancient(kind string, number uint64) ([]byte, error) {
|
||||
if table := f.tables[kind]; table != nil {
|
||||
|
|
|
|||
|
|
@ -45,14 +45,6 @@ func newMemoryTable(name string, config freezerTableConfig) *memoryTable {
|
|||
return &memoryTable{name: name, config: config}
|
||||
}
|
||||
|
||||
// has returns an indicator whether the specified data exists.
|
||||
func (t *memoryTable) has(number uint64) bool {
|
||||
t.lock.RLock()
|
||||
defer t.lock.RUnlock()
|
||||
|
||||
return number >= t.offset && number < t.items
|
||||
}
|
||||
|
||||
// retrieve retrieves multiple items in sequence, starting from the index 'start'.
|
||||
// It will return:
|
||||
// - at most 'count' items,
|
||||
|
|
@ -232,17 +224,6 @@ func NewMemoryFreezer(readonly bool, tableName map[string]freezerTableConfig) *M
|
|||
}
|
||||
}
|
||||
|
||||
// HasAncient returns an indicator whether the specified data exists.
|
||||
func (f *MemoryFreezer) HasAncient(kind string, number uint64) (bool, error) {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
|
||||
if table := f.tables[kind]; table != nil {
|
||||
return table.has(number), nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Ancient retrieves an ancient binary blob from the in-memory freezer.
|
||||
func (f *MemoryFreezer) Ancient(kind string, number uint64) ([]byte, error) {
|
||||
f.lock.RLock()
|
||||
|
|
|
|||
|
|
@ -105,15 +105,6 @@ func (f *resettableFreezer) Close() error {
|
|||
return f.freezer.Close()
|
||||
}
|
||||
|
||||
// HasAncient returns an indicator whether the specified ancient data exists
|
||||
// in the freezer
|
||||
func (f *resettableFreezer) HasAncient(kind string, number uint64) (bool, error) {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
|
||||
return f.freezer.HasAncient(kind, number)
|
||||
}
|
||||
|
||||
// Ancient retrieves an ancient binary blob from the append-only immutable files.
|
||||
func (f *resettableFreezer) Ancient(kind string, number uint64) ([]byte, error) {
|
||||
f.lock.RLock()
|
||||
|
|
|
|||
|
|
@ -1106,12 +1106,6 @@ func (t *freezerTable) retrieveItems(start, count, maxBytes uint64) ([]byte, []i
|
|||
return output, sizes, nil
|
||||
}
|
||||
|
||||
// has returns an indicator whether the specified number data is still accessible
|
||||
// in the freezer table.
|
||||
func (t *freezerTable) has(number uint64) bool {
|
||||
return t.items.Load() > number && t.itemHidden.Load() <= number
|
||||
}
|
||||
|
||||
// size returns the total data size in the freezer table.
|
||||
func (t *freezerTable) size() (uint64, error) {
|
||||
t.lock.RLock()
|
||||
|
|
|
|||
|
|
@ -357,9 +357,6 @@ func checkAncientCount(t *testing.T, f *Freezer, kind string, n uint64) {
|
|||
// Check at index n-1.
|
||||
if n > 0 {
|
||||
index := n - 1
|
||||
if ok, _ := f.HasAncient(kind, index); !ok {
|
||||
t.Errorf("HasAncient(%q, %d) returned false unexpectedly", kind, index)
|
||||
}
|
||||
if _, err := f.Ancient(kind, index); err != nil {
|
||||
t.Errorf("Ancient(%q, %d) returned unexpected error %q", kind, index, err)
|
||||
}
|
||||
|
|
@ -367,9 +364,6 @@ func checkAncientCount(t *testing.T, f *Freezer, kind string, n uint64) {
|
|||
|
||||
// Check at index n.
|
||||
index := n
|
||||
if ok, _ := f.HasAncient(kind, index); ok {
|
||||
t.Errorf("HasAncient(%q, %d) returned true unexpectedly", kind, index)
|
||||
}
|
||||
if _, err := f.Ancient(kind, index); err == nil {
|
||||
t.Errorf("Ancient(%q, %d) didn't return expected error", kind, index)
|
||||
} else if err != errOutOfBounds {
|
||||
|
|
|
|||
|
|
@ -50,12 +50,6 @@ func (t *table) Get(key []byte) ([]byte, error) {
|
|||
return t.db.Get(append([]byte(t.prefix), key...))
|
||||
}
|
||||
|
||||
// HasAncient is a noop passthrough that just forwards the request to the underlying
|
||||
// database.
|
||||
func (t *table) HasAncient(kind string, number uint64) (bool, error) {
|
||||
return t.db.HasAncient(kind, number)
|
||||
}
|
||||
|
||||
// Ancient is a noop passthrough that just forwards the request to the underlying
|
||||
// database.
|
||||
func (t *table) Ancient(kind string, number uint64) ([]byte, error) {
|
||||
|
|
|
|||
|
|
@ -182,7 +182,11 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
|
|||
log.Error("Failed to decode the value returned by iterator", "error", err)
|
||||
continue
|
||||
}
|
||||
account.Storage[common.BytesToHash(s.trie.GetKey(storageIt.Key))] = common.Bytes2Hex(content)
|
||||
key := s.trie.GetKey(storageIt.Key)
|
||||
if key == nil {
|
||||
continue
|
||||
}
|
||||
account.Storage[common.BytesToHash(key)] = common.Bytes2Hex(content)
|
||||
}
|
||||
}
|
||||
c.OnAccount(address, account)
|
||||
|
|
|
|||
|
|
@ -408,6 +408,7 @@ func (ch storageChange) copy() journalEntry {
|
|||
account: ch.account,
|
||||
key: ch.key,
|
||||
prevvalue: ch.prevvalue,
|
||||
origvalue: ch.origvalue,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -206,7 +206,6 @@ func (r *flatReader) Storage(addr common.Address, key common.Hash) (common.Hash,
|
|||
type trieReader struct {
|
||||
root common.Hash // State root which uniquely represent a state
|
||||
db *triedb.Database // Database for loading trie
|
||||
buff crypto.KeccakState // Buffer for keccak256 hashing
|
||||
|
||||
// Main trie, resolved in constructor. Note either the Merkle-Patricia-tree
|
||||
// or Verkle-tree is not safe for concurrent read.
|
||||
|
|
@ -235,7 +234,6 @@ func newTrieReader(root common.Hash, db *triedb.Database, cache *utils.PointCach
|
|||
return &trieReader{
|
||||
root: root,
|
||||
db: db,
|
||||
buff: crypto.NewKeccakState(),
|
||||
mainTrie: tr,
|
||||
subRoots: make(map[common.Address]common.Hash),
|
||||
subTries: make(map[common.Address]Trie),
|
||||
|
|
@ -298,7 +296,7 @@ func (r *trieReader) Storage(addr common.Address, key common.Hash) (common.Hash,
|
|||
root = r.subRoots[addr]
|
||||
}
|
||||
var err error
|
||||
tr, err = trie.NewStateTrie(trie.StorageTrieID(r.root, crypto.HashData(r.buff, addr.Bytes()), root), r.db)
|
||||
tr, err = trie.NewStateTrie(trie.StorageTrieID(r.root, crypto.Keccak256Hash(addr.Bytes()), root), r.db)
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -377,7 +377,6 @@ func (s *stateObject) updateRoot() {
|
|||
// fulfills the storage diffs into the given accountUpdate struct.
|
||||
func (s *stateObject) commitStorage(op *accountUpdate) {
|
||||
var (
|
||||
buf = crypto.NewKeccakState()
|
||||
encode = func(val common.Hash) []byte {
|
||||
if val == (common.Hash{}) {
|
||||
return nil
|
||||
|
|
@ -394,7 +393,7 @@ func (s *stateObject) commitStorage(op *accountUpdate) {
|
|||
if val == s.originStorage[key] {
|
||||
continue
|
||||
}
|
||||
hash := crypto.HashData(buf, key[:])
|
||||
hash := crypto.Keccak256Hash(key[:])
|
||||
if op.storages == nil {
|
||||
op.storages = make(map[common.Hash][]byte)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -252,11 +252,12 @@ func (s *StateDB) AddLog(log *types.Log) {
|
|||
|
||||
// GetLogs returns the logs matching the specified transaction hash, and annotates
|
||||
// them with the given blockNumber and blockHash.
|
||||
func (s *StateDB) GetLogs(hash common.Hash, blockNumber uint64, blockHash common.Hash) []*types.Log {
|
||||
func (s *StateDB) GetLogs(hash common.Hash, blockNumber uint64, blockHash common.Hash, blockTime uint64) []*types.Log {
|
||||
logs := s.logs[hash]
|
||||
for _, l := range logs {
|
||||
l.BlockNumber = blockNumber
|
||||
l.BlockHash = blockHash
|
||||
l.BlockTimestamp = blockTime
|
||||
}
|
||||
return logs
|
||||
}
|
||||
|
|
@ -1063,7 +1064,6 @@ func (s *StateDB) deleteStorage(addr common.Address, addrHash common.Hash, root
|
|||
func (s *StateDB) handleDestruction(noStorageWiping bool) (map[common.Hash]*accountDelete, []*trienode.NodeSet, error) {
|
||||
var (
|
||||
nodes []*trienode.NodeSet
|
||||
buf = crypto.NewKeccakState()
|
||||
deletes = make(map[common.Hash]*accountDelete)
|
||||
)
|
||||
for addr, prevObj := range s.stateObjectsDestruct {
|
||||
|
|
@ -1078,7 +1078,7 @@ func (s *StateDB) handleDestruction(noStorageWiping bool) (map[common.Hash]*acco
|
|||
continue
|
||||
}
|
||||
// The account was existent, it can be either case (c) or (d).
|
||||
addrHash := crypto.HashData(buf, addr.Bytes())
|
||||
addrHash := crypto.Keccak256Hash(addr.Bytes())
|
||||
op := &accountDelete{
|
||||
address: addr,
|
||||
origin: types.SlimAccountRLP(*prev),
|
||||
|
|
|
|||
|
|
@ -669,9 +669,9 @@ func (test *snapshotTest) checkEqual(state, checkstate *StateDB) error {
|
|||
return fmt.Errorf("got GetRefund() == %d, want GetRefund() == %d",
|
||||
state.GetRefund(), checkstate.GetRefund())
|
||||
}
|
||||
if !reflect.DeepEqual(state.GetLogs(common.Hash{}, 0, common.Hash{}), checkstate.GetLogs(common.Hash{}, 0, common.Hash{})) {
|
||||
if !reflect.DeepEqual(state.GetLogs(common.Hash{}, 0, common.Hash{}, 0), checkstate.GetLogs(common.Hash{}, 0, common.Hash{}, 0)) {
|
||||
return fmt.Errorf("got GetLogs(common.Hash{}) == %v, want GetLogs(common.Hash{}) == %v",
|
||||
state.GetLogs(common.Hash{}, 0, common.Hash{}), checkstate.GetLogs(common.Hash{}, 0, common.Hash{}))
|
||||
state.GetLogs(common.Hash{}, 0, common.Hash{}, 0), checkstate.GetLogs(common.Hash{}, 0, common.Hash{}, 0))
|
||||
}
|
||||
if !maps.Equal(state.journal.dirties, checkstate.journal.dirties) {
|
||||
getKeys := func(dirty map[common.Address]int) string {
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
|
|||
}
|
||||
statedb.SetTxContext(tx.Hash(), i)
|
||||
|
||||
receipt, err := ApplyTransactionWithEVM(msg, gp, statedb, blockNumber, blockHash, tx, usedGas, evm)
|
||||
receipt, err := ApplyTransactionWithEVM(msg, gp, statedb, blockNumber, blockHash, context.Time, tx, usedGas, evm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
|
||||
}
|
||||
|
|
@ -136,7 +136,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
|
|||
// ApplyTransactionWithEVM attempts to apply a transaction to the given state database
|
||||
// and uses the input parameters for its environment similar to ApplyTransaction. However,
|
||||
// this method takes an already created EVM instance as input.
|
||||
func ApplyTransactionWithEVM(msg *Message, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (receipt *types.Receipt, err error) {
|
||||
func ApplyTransactionWithEVM(msg *Message, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, blockTime uint64, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (receipt *types.Receipt, err error) {
|
||||
if hooks := evm.Config.Tracer; hooks != nil {
|
||||
if hooks.OnTxStart != nil {
|
||||
hooks.OnTxStart(evm.GetVMContext(), tx, msg.From)
|
||||
|
|
@ -165,11 +165,11 @@ func ApplyTransactionWithEVM(msg *Message, gp *GasPool, statedb *state.StateDB,
|
|||
statedb.AccessEvents().Merge(evm.AccessEvents)
|
||||
}
|
||||
|
||||
return MakeReceipt(evm, result, statedb, blockNumber, blockHash, tx, *usedGas, root), nil
|
||||
return MakeReceipt(evm, result, statedb, blockNumber, blockHash, blockTime, tx, *usedGas, root), nil
|
||||
}
|
||||
|
||||
// MakeReceipt generates the receipt object for a transaction given its execution result.
|
||||
func MakeReceipt(evm *vm.EVM, result *ExecutionResult, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas uint64, root []byte) *types.Receipt {
|
||||
func MakeReceipt(evm *vm.EVM, result *ExecutionResult, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, blockTime uint64, tx *types.Transaction, usedGas uint64, root []byte) *types.Receipt {
|
||||
// Create a new receipt for the transaction, storing the intermediate root and gas used
|
||||
// by the tx.
|
||||
receipt := &types.Receipt{Type: tx.Type(), PostState: root, CumulativeGasUsed: usedGas}
|
||||
|
|
@ -192,7 +192,7 @@ func MakeReceipt(evm *vm.EVM, result *ExecutionResult, statedb *state.StateDB, b
|
|||
}
|
||||
|
||||
// Set the receipt logs and create the bloom filter.
|
||||
receipt.Logs = statedb.GetLogs(tx.Hash(), blockNumber.Uint64(), blockHash)
|
||||
receipt.Logs = statedb.GetLogs(tx.Hash(), blockNumber.Uint64(), blockHash, blockTime)
|
||||
receipt.Bloom = types.CreateBloom(receipt)
|
||||
receipt.BlockHash = blockHash
|
||||
receipt.BlockNumber = blockNumber
|
||||
|
|
@ -210,7 +210,7 @@ func ApplyTransaction(evm *vm.EVM, gp *GasPool, statedb *state.StateDB, header *
|
|||
return nil, err
|
||||
}
|
||||
// Create a new context to be used in the EVM environment
|
||||
return ApplyTransactionWithEVM(msg, gp, statedb, header.Number, header.Hash(), tx, usedGas, evm)
|
||||
return ApplyTransactionWithEVM(msg, gp, statedb, header.Number, header.Hash(), header.Time, tx, usedGas, evm)
|
||||
}
|
||||
|
||||
// ProcessBeaconBlockRoot applies the EIP-4788 system call to the beacon block root
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ func TestTxIndexer(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
|
||||
db, _ := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
|
||||
rawdb.WriteAncientBlocks(db, append([]*types.Block{gspec.ToBlock()}, blocks...), types.EncodeBlockReceiptLists(append([]types.Receipts{{}}, receipts...)))
|
||||
|
||||
// Index the initial blocks from ancient store
|
||||
|
|
@ -235,7 +235,7 @@ func TestTxIndexerRepair(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
|
||||
db, _ := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
|
||||
encReceipts := types.EncodeBlockReceiptLists(append([]types.Receipts{{}}, receipts...))
|
||||
rawdb.WriteAncientBlocks(db, append([]*types.Block{gspec.ToBlock()}, blocks...), encReceipts)
|
||||
|
||||
|
|
@ -426,7 +426,7 @@ func TestTxIndexerReport(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
|
||||
db, _ := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
|
||||
encReceipts := types.EncodeBlockReceiptLists(append([]types.Receipts{{}}, receipts...))
|
||||
rawdb.WriteAncientBlocks(db, append([]*types.Block{gspec.ToBlock()}, blocks...), encReceipts)
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/txpool"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
|
|
@ -62,6 +61,12 @@ const (
|
|||
// limit can never hurt.
|
||||
txMaxSize = 1024 * 1024
|
||||
|
||||
// maxBlobsPerTx is the maximum number of blobs that a single transaction can
|
||||
// carry. We choose a smaller limit than the protocol-permitted MaxBlobsPerBlock
|
||||
// in order to ensure network and txpool stability.
|
||||
// Note: if you increase this, validation will fail on txMaxSize.
|
||||
maxBlobsPerTx = 7
|
||||
|
||||
// maxTxsPerAccount is the maximum number of blob transactions admitted from
|
||||
// a single account. The limit is enforced to minimize the DoS potential of
|
||||
// a private tx cancelling publicly propagated blobs.
|
||||
|
|
@ -1099,6 +1104,7 @@ func (p *BlobPool) ValidateTxBasics(tx *types.Transaction) error {
|
|||
Accept: 1 << types.BlobTxType,
|
||||
MaxSize: txMaxSize,
|
||||
MinTip: p.gasTip.ToBig(),
|
||||
MaxBlobCount: maxBlobsPerTx,
|
||||
}
|
||||
return txpool.ValidateTransaction(tx, p.head, p.signer, opts)
|
||||
}
|
||||
|
|
@ -1295,27 +1301,13 @@ func (p *BlobPool) GetMetadata(hash common.Hash) *txpool.TxMetadata {
|
|||
}
|
||||
}
|
||||
|
||||
// GetBlobs returns a number of blobs are proofs for the given versioned hashes.
|
||||
// GetBlobs returns a number of blobs and proofs for the given versioned hashes.
|
||||
// This is a utility method for the engine API, enabling consensus clients to
|
||||
// retrieve blobs from the pools directly instead of the network.
|
||||
func (p *BlobPool) GetBlobs(vhashes []common.Hash) ([]*kzg4844.Blob, []*kzg4844.Proof) {
|
||||
// Create a map of the blob hash to indices for faster fills
|
||||
var (
|
||||
blobs = make([]*kzg4844.Blob, len(vhashes))
|
||||
proofs = make([]*kzg4844.Proof, len(vhashes))
|
||||
)
|
||||
index := make(map[common.Hash]int)
|
||||
for i, vhash := range vhashes {
|
||||
index[vhash] = i
|
||||
}
|
||||
// Iterate over the blob hashes, pulling transactions that fill it. Take care
|
||||
// to also fill anything else the transaction might include (probably will).
|
||||
for i, vhash := range vhashes {
|
||||
// If already filled by a previous fetch, skip
|
||||
if blobs[i] != nil {
|
||||
continue
|
||||
}
|
||||
// Unfilled, retrieve the datastore item (in a short lock)
|
||||
func (p *BlobPool) GetBlobs(vhashes []common.Hash) []*types.BlobTxSidecar {
|
||||
sidecars := make([]*types.BlobTxSidecar, len(vhashes))
|
||||
for idx, vhash := range vhashes {
|
||||
// Retrieve the datastore item (in a short lock)
|
||||
p.lock.RLock()
|
||||
id, exists := p.lookup.storeidOfBlob(vhash)
|
||||
if !exists {
|
||||
|
|
@ -1335,16 +1327,24 @@ func (p *BlobPool) GetBlobs(vhashes []common.Hash) ([]*kzg4844.Blob, []*kzg4844.
|
|||
log.Error("Blobs corrupted for traced transaction", "id", id, "err", err)
|
||||
continue
|
||||
}
|
||||
// Fill anything requested, not just the current versioned hash
|
||||
sidecar := item.BlobTxSidecar()
|
||||
for j, blobhash := range item.BlobHashes() {
|
||||
if idx, ok := index[blobhash]; ok {
|
||||
blobs[idx] = &sidecar.Blobs[j]
|
||||
proofs[idx] = &sidecar.Proofs[j]
|
||||
sidecars[idx] = item.BlobTxSidecar()
|
||||
}
|
||||
return sidecars
|
||||
}
|
||||
|
||||
// AvailableBlobs returns the number of blobs that are available in the subpool.
|
||||
func (p *BlobPool) AvailableBlobs(vhashes []common.Hash) int {
|
||||
available := 0
|
||||
for _, vhash := range vhashes {
|
||||
// Retrieve the datastore item (in a short lock)
|
||||
p.lock.RLock()
|
||||
_, exists := p.lookup.storeidOfBlob(vhash)
|
||||
p.lock.RUnlock()
|
||||
if exists {
|
||||
available++
|
||||
}
|
||||
}
|
||||
}
|
||||
return blobs, proofs
|
||||
return available
|
||||
}
|
||||
|
||||
// Add inserts a set of blob transactions into the pool if they pass validation (both
|
||||
|
|
|
|||
|
|
@ -417,8 +417,23 @@ func verifyBlobRetrievals(t *testing.T, pool *BlobPool) {
|
|||
for i := range testBlobVHashes {
|
||||
copy(hashes[i][:], testBlobVHashes[i][:])
|
||||
}
|
||||
blobs, proofs := pool.GetBlobs(hashes)
|
||||
|
||||
sidecars := pool.GetBlobs(hashes)
|
||||
var blobs []*kzg4844.Blob
|
||||
var proofs []*kzg4844.Proof
|
||||
for idx, sidecar := range sidecars {
|
||||
if sidecar == nil {
|
||||
blobs = append(blobs, nil)
|
||||
proofs = append(proofs, nil)
|
||||
continue
|
||||
}
|
||||
blobHashes := sidecar.BlobHashes()
|
||||
for i, hash := range blobHashes {
|
||||
if hash == hashes[idx] {
|
||||
blobs = append(blobs, &sidecar.Blobs[i])
|
||||
proofs = append(proofs, &sidecar.Proofs[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
// Cross validate what we received vs what we wanted
|
||||
if len(blobs) != len(hashes) || len(proofs) != len(hashes) {
|
||||
t.Errorf("retrieved blobs/proofs size mismatch: have %d/%d, want %d", len(blobs), len(proofs), len(hashes))
|
||||
|
|
@ -1142,6 +1157,65 @@ func TestChangingSlotterSize(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestBlobCountLimit tests the blobpool enforced limits on the max blob count.
|
||||
func TestBlobCountLimit(t *testing.T) {
|
||||
var (
|
||||
key1, _ = crypto.GenerateKey()
|
||||
key2, _ = crypto.GenerateKey()
|
||||
|
||||
addr1 = crypto.PubkeyToAddress(key1.PublicKey)
|
||||
addr2 = crypto.PubkeyToAddress(key2.PublicKey)
|
||||
)
|
||||
|
||||
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
|
||||
statedb.AddBalance(addr1, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified)
|
||||
statedb.AddBalance(addr2, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified)
|
||||
statedb.Commit(0, true, false)
|
||||
|
||||
// Make Prague-enabled custom chain config.
|
||||
cancunTime := uint64(0)
|
||||
pragueTime := uint64(0)
|
||||
config := ¶ms.ChainConfig{
|
||||
ChainID: big.NewInt(1),
|
||||
LondonBlock: big.NewInt(0),
|
||||
BerlinBlock: big.NewInt(0),
|
||||
CancunTime: &cancunTime,
|
||||
PragueTime: &pragueTime,
|
||||
BlobScheduleConfig: ¶ms.BlobScheduleConfig{
|
||||
Cancun: params.DefaultCancunBlobConfig,
|
||||
Prague: params.DefaultPragueBlobConfig,
|
||||
},
|
||||
}
|
||||
chain := &testBlockChain{
|
||||
config: config,
|
||||
basefee: uint256.NewInt(1050),
|
||||
blobfee: uint256.NewInt(105),
|
||||
statedb: statedb,
|
||||
}
|
||||
pool := New(Config{Datadir: t.TempDir()}, chain, nil)
|
||||
if err := pool.Init(1, chain.CurrentBlock(), newReserver()); err != nil {
|
||||
t.Fatalf("failed to create blob pool: %v", err)
|
||||
}
|
||||
|
||||
// Attempt to add transactions.
|
||||
var (
|
||||
tx1 = makeMultiBlobTx(0, 1, 1000, 100, 7, key1)
|
||||
tx2 = makeMultiBlobTx(0, 1, 800, 70, 8, key2)
|
||||
)
|
||||
errs := pool.Add([]*types.Transaction{tx1, tx2}, true)
|
||||
|
||||
// Check that first succeeds second fails.
|
||||
if errs[0] != nil {
|
||||
t.Fatalf("expected tx with 7 blobs to succeed")
|
||||
}
|
||||
if !errors.Is(errs[1], txpool.ErrTxBlobLimitExceeded) {
|
||||
t.Fatalf("expected tx with 8 blobs to fail, got: %v", errs[1])
|
||||
}
|
||||
|
||||
verifyPoolInternals(t, pool)
|
||||
pool.Close()
|
||||
}
|
||||
|
||||
// Tests that adding transaction will correctly store it in the persistent store
|
||||
// and update all the indices.
|
||||
//
|
||||
|
|
|
|||
|
|
@ -58,6 +58,10 @@ var (
|
|||
// making the transaction invalid, rather a DOS protection.
|
||||
ErrOversizedData = errors.New("oversized data")
|
||||
|
||||
// ErrTxBlobLimitExceeded is returned if a transaction would exceed the number
|
||||
// of blobs allowed by blobpool.
|
||||
ErrTxBlobLimitExceeded = errors.New("transaction blob limit exceeded")
|
||||
|
||||
// ErrAlreadyReserved is returned if the sender address has a pending transaction
|
||||
// in a different subpool. For example, this error is returned in response to any
|
||||
// input transaction of non-blob type when a blob transaction from this sender
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/txpool"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
|
|
@ -1063,12 +1062,6 @@ func (pool *LegacyPool) GetMetadata(hash common.Hash) *txpool.TxMetadata {
|
|||
}
|
||||
}
|
||||
|
||||
// GetBlobs is not supported by the legacy transaction pool, it is just here to
|
||||
// implement the txpool.SubPool interface.
|
||||
func (pool *LegacyPool) GetBlobs(vhashes []common.Hash) ([]*kzg4844.Blob, []*kzg4844.Proof) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Has returns an indicator whether txpool has a transaction cached with the
|
||||
// given hash.
|
||||
func (pool *LegacyPool) Has(hash common.Hash) bool {
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
|
@ -133,11 +132,6 @@ type SubPool interface {
|
|||
// given transaction hash.
|
||||
GetMetadata(hash common.Hash) *TxMetadata
|
||||
|
||||
// GetBlobs returns a number of blobs are proofs for the given versioned hashes.
|
||||
// This is a utility method for the engine API, enabling consensus clients to
|
||||
// retrieve blobs from the pools directly instead of the network.
|
||||
GetBlobs(vhashes []common.Hash) ([]*kzg4844.Blob, []*kzg4844.Proof)
|
||||
|
||||
// ValidateTxBasics checks whether a transaction is valid according to the consensus
|
||||
// rules, but does not check state-dependent validation such as sufficient balance.
|
||||
// This check is meant as a static check which can be performed without holding the
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
|
|
@ -308,22 +307,6 @@ func (p *TxPool) GetMetadata(hash common.Hash) *TxMetadata {
|
|||
return nil
|
||||
}
|
||||
|
||||
// GetBlobs returns a number of blobs are proofs for the given versioned hashes.
|
||||
// This is a utility method for the engine API, enabling consensus clients to
|
||||
// retrieve blobs from the pools directly instead of the network.
|
||||
func (p *TxPool) GetBlobs(vhashes []common.Hash) ([]*kzg4844.Blob, []*kzg4844.Proof) {
|
||||
for _, subpool := range p.subpools {
|
||||
// It's an ugly to assume that only one pool will be capable of returning
|
||||
// anything meaningful for this call, but anythingh else requires merging
|
||||
// partial responses and that's too annoying to do until we get a second
|
||||
// blobpool (probably never).
|
||||
if blobs, proofs := subpool.GetBlobs(vhashes); blobs != nil {
|
||||
return blobs, proofs
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Add enqueues a batch of transactions into the pool if they are valid. Due
|
||||
// to the large transaction churn, add may postpone fully integrating the tx
|
||||
// to a later point to batch multiple ones together.
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ type ValidationOptions struct {
|
|||
|
||||
Accept uint8 // Bitmap of transaction types that should be accepted for the calling pool
|
||||
MaxSize uint64 // Maximum size of a transaction that the caller can meaningfully handle
|
||||
MaxBlobCount int // Maximum number of blobs allowed per transaction
|
||||
MinTip *big.Int // Minimum gas tip needed to allow a transaction into the caller pool
|
||||
}
|
||||
|
||||
|
|
@ -63,6 +64,9 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
|
|||
if opts.Accept&(1<<tx.Type()) == 0 {
|
||||
return fmt.Errorf("%w: tx type %v not supported by this pool", core.ErrTxTypeNotSupported, tx.Type())
|
||||
}
|
||||
if blobCount := len(tx.BlobHashes()); blobCount > opts.MaxBlobCount {
|
||||
return fmt.Errorf("%w: blob count %v, limit %v", ErrTxBlobLimitExceeded, blobCount, opts.MaxBlobCount)
|
||||
}
|
||||
// Before performing any expensive validations, sanity check that the tx is
|
||||
// smaller than the maximum limit the pool can meaningfully handle
|
||||
if tx.Size() > opts.MaxSize {
|
||||
|
|
@ -134,14 +138,26 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
|
|||
return fmt.Errorf("%w: gas tip cap %v, minimum needed %v", ErrTxGasPriceTooLow, tx.GasTipCap(), opts.MinTip)
|
||||
}
|
||||
if tx.Type() == types.BlobTxType {
|
||||
// Ensure the blob fee cap satisfies the minimum blob gas price
|
||||
if tx.BlobGasFeeCapIntCmp(blobTxMinBlobGasPrice) < 0 {
|
||||
return fmt.Errorf("%w: blob fee cap %v, minimum needed %v", ErrTxGasPriceTooLow, tx.BlobGasFeeCap(), blobTxMinBlobGasPrice)
|
||||
return validateBlobTx(tx, head, opts)
|
||||
}
|
||||
if tx.Type() == types.SetCodeTxType {
|
||||
if len(tx.SetCodeAuthorizations()) == 0 {
|
||||
return fmt.Errorf("set code tx must have at least one authorization tuple")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateBlobTx implements the blob-transaction specific validations.
|
||||
func validateBlobTx(tx *types.Transaction, head *types.Header, opts *ValidationOptions) error {
|
||||
sidecar := tx.BlobTxSidecar()
|
||||
if sidecar == nil {
|
||||
return errors.New("missing sidecar in blob transaction")
|
||||
}
|
||||
// Ensure the blob fee cap satisfies the minimum blob gas price
|
||||
if tx.BlobGasFeeCapIntCmp(blobTxMinBlobGasPrice) < 0 {
|
||||
return fmt.Errorf("%w: blob fee cap %v, minimum needed %v", ErrTxGasPriceTooLow, tx.BlobGasFeeCap(), blobTxMinBlobGasPrice)
|
||||
}
|
||||
// Ensure the number of items in the blob transaction and various side
|
||||
// data match up before doing any expensive validations
|
||||
hashes := tx.BlobHashes()
|
||||
|
|
@ -152,31 +168,26 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
|
|||
if len(hashes) > maxBlobs {
|
||||
return fmt.Errorf("too many blobs in transaction: have %d, permitted %d", len(hashes), maxBlobs)
|
||||
}
|
||||
// Ensure commitments, proofs and hashes are valid
|
||||
if err := validateBlobSidecar(hashes, sidecar); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if tx.Type() == types.SetCodeTxType {
|
||||
if len(tx.SetCodeAuthorizations()) == 0 {
|
||||
return fmt.Errorf("set code tx must have at least one authorization tuple")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateBlobSidecar(hashes []common.Hash, sidecar *types.BlobTxSidecar) error {
|
||||
if len(sidecar.Blobs) != len(hashes) {
|
||||
return fmt.Errorf("invalid number of %d blobs compared to %d blob hashes", len(sidecar.Blobs), len(hashes))
|
||||
}
|
||||
if len(sidecar.Proofs) != len(hashes) {
|
||||
return fmt.Errorf("invalid number of %d blob proofs compared to %d blob hashes", len(sidecar.Proofs), len(hashes))
|
||||
}
|
||||
if err := sidecar.ValidateBlobCommitmentHashes(hashes); err != nil {
|
||||
return err
|
||||
}
|
||||
// Blob commitments match with the hashes in the transaction, verify the
|
||||
// blobs themselves via KZG
|
||||
// Fork-specific sidecar checks, including proof verification.
|
||||
if opts.Config.IsOsaka(head.Number, head.Time) {
|
||||
return validateBlobSidecarOsaka(sidecar, hashes)
|
||||
}
|
||||
return validateBlobSidecarLegacy(sidecar, hashes)
|
||||
}
|
||||
|
||||
func validateBlobSidecarLegacy(sidecar *types.BlobTxSidecar, hashes []common.Hash) error {
|
||||
if sidecar.Version != 0 {
|
||||
return fmt.Errorf("invalid sidecar version pre-osaka: %v", sidecar.Version)
|
||||
}
|
||||
if len(sidecar.Proofs) != len(hashes) {
|
||||
return fmt.Errorf("invalid number of %d blob proofs expected %d", len(sidecar.Proofs), len(hashes))
|
||||
}
|
||||
for i := range sidecar.Blobs {
|
||||
if err := kzg4844.VerifyBlobProof(&sidecar.Blobs[i], sidecar.Commitments[i], sidecar.Proofs[i]); err != nil {
|
||||
return fmt.Errorf("invalid blob %d: %v", i, err)
|
||||
|
|
@ -185,6 +196,16 @@ func validateBlobSidecar(hashes []common.Hash, sidecar *types.BlobTxSidecar) err
|
|||
return nil
|
||||
}
|
||||
|
||||
func validateBlobSidecarOsaka(sidecar *types.BlobTxSidecar, hashes []common.Hash) error {
|
||||
if sidecar.Version != 1 {
|
||||
return fmt.Errorf("invalid sidecar version post-osaka: %v", sidecar.Version)
|
||||
}
|
||||
if len(sidecar.Proofs) != len(hashes)*kzg4844.CellProofsPerBlob {
|
||||
return fmt.Errorf("invalid number of %d blob proofs expected %d", len(sidecar.Proofs), len(hashes)*kzg4844.CellProofsPerBlob)
|
||||
}
|
||||
return kzg4844.VerifyCellProofs(sidecar.Blobs, sidecar.Commitments, sidecar.Proofs)
|
||||
}
|
||||
|
||||
// ValidationOptionsWithState define certain differences between stateful transaction
|
||||
// validation across the different pools without having to duplicate those checks.
|
||||
type ValidationOptionsWithState struct {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ func (l Log) MarshalJSON() ([]byte, error) {
|
|||
TxHash common.Hash `json:"transactionHash" gencodec:"required" rlp:"-"`
|
||||
TxIndex hexutil.Uint `json:"transactionIndex" rlp:"-"`
|
||||
BlockHash common.Hash `json:"blockHash" rlp:"-"`
|
||||
BlockTimestamp uint64 `json:"blockTimestamp" rlp:"-"`
|
||||
Index hexutil.Uint `json:"logIndex" rlp:"-"`
|
||||
Removed bool `json:"removed" rlp:"-"`
|
||||
}
|
||||
|
|
@ -33,6 +34,7 @@ func (l Log) MarshalJSON() ([]byte, error) {
|
|||
enc.TxHash = l.TxHash
|
||||
enc.TxIndex = hexutil.Uint(l.TxIndex)
|
||||
enc.BlockHash = l.BlockHash
|
||||
enc.BlockTimestamp = l.BlockTimestamp
|
||||
enc.Index = hexutil.Uint(l.Index)
|
||||
enc.Removed = l.Removed
|
||||
return json.Marshal(&enc)
|
||||
|
|
@ -48,6 +50,7 @@ func (l *Log) UnmarshalJSON(input []byte) error {
|
|||
TxHash *common.Hash `json:"transactionHash" gencodec:"required" rlp:"-"`
|
||||
TxIndex *hexutil.Uint `json:"transactionIndex" rlp:"-"`
|
||||
BlockHash *common.Hash `json:"blockHash" rlp:"-"`
|
||||
BlockTimestamp *uint64 `json:"blockTimestamp" rlp:"-"`
|
||||
Index *hexutil.Uint `json:"logIndex" rlp:"-"`
|
||||
Removed *bool `json:"removed" rlp:"-"`
|
||||
}
|
||||
|
|
@ -80,6 +83,9 @@ func (l *Log) UnmarshalJSON(input []byte) error {
|
|||
if dec.BlockHash != nil {
|
||||
l.BlockHash = *dec.BlockHash
|
||||
}
|
||||
if dec.BlockTimestamp != nil {
|
||||
l.BlockTimestamp = *dec.BlockTimestamp
|
||||
}
|
||||
if dec.Index != nil {
|
||||
l.Index = uint(*dec.Index)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,6 +45,8 @@ type Log struct {
|
|||
TxIndex uint `json:"transactionIndex" rlp:"-"`
|
||||
// hash of the block in which the transaction was included
|
||||
BlockHash common.Hash `json:"blockHash" rlp:"-"`
|
||||
// timestamp of the block in which the transaction was included
|
||||
BlockTimestamp uint64 `json:"blockTimestamp" rlp:"-"`
|
||||
// index of the log in the block
|
||||
Index uint `json:"logIndex" rlp:"-"`
|
||||
|
||||
|
|
|
|||
|
|
@ -367,6 +367,7 @@ func (rs Receipts) DeriveFields(config *params.ChainConfig, hash common.Hash, nu
|
|||
for j := 0; j < len(rs[i].Logs); j++ {
|
||||
rs[i].Logs[j].BlockNumber = number
|
||||
rs[i].Logs[j].BlockHash = hash
|
||||
rs[i].Logs[j].BlockTimestamp = time
|
||||
rs[i].Logs[j].TxHash = rs[i].TxHash
|
||||
rs[i].Logs[j].TxIndex = uint(i)
|
||||
rs[i].Logs[j].Index = logIndex
|
||||
|
|
|
|||
|
|
@ -177,6 +177,7 @@ func getTestReceipts() Receipts {
|
|||
TxHash: txs[0].Hash(),
|
||||
TxIndex: 0,
|
||||
BlockHash: blockHash,
|
||||
BlockTimestamp: blockTime,
|
||||
Index: 0,
|
||||
},
|
||||
{
|
||||
|
|
@ -187,6 +188,7 @@ func getTestReceipts() Receipts {
|
|||
TxHash: txs[0].Hash(),
|
||||
TxIndex: 0,
|
||||
BlockHash: blockHash,
|
||||
BlockTimestamp: blockTime,
|
||||
Index: 1,
|
||||
},
|
||||
},
|
||||
|
|
@ -211,6 +213,7 @@ func getTestReceipts() Receipts {
|
|||
TxHash: txs[1].Hash(),
|
||||
TxIndex: 1,
|
||||
BlockHash: blockHash,
|
||||
BlockTimestamp: blockTime,
|
||||
Index: 2,
|
||||
},
|
||||
{
|
||||
|
|
@ -221,6 +224,7 @@ func getTestReceipts() Receipts {
|
|||
TxHash: txs[1].Hash(),
|
||||
TxIndex: 1,
|
||||
BlockHash: blockHash,
|
||||
BlockTimestamp: blockTime,
|
||||
Index: 3,
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -19,9 +19,12 @@ package types
|
|||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"slices"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
|
|
@ -55,6 +58,7 @@ type BlobTx struct {
|
|||
|
||||
// BlobTxSidecar contains the blobs of a blob transaction.
|
||||
type BlobTxSidecar struct {
|
||||
Version byte // Version
|
||||
Blobs []kzg4844.Blob // Blobs needed by the blob pool
|
||||
Commitments []kzg4844.Commitment // Commitments needed by the blob pool
|
||||
Proofs []kzg4844.Proof // Proofs needed by the blob pool
|
||||
|
|
@ -70,6 +74,20 @@ func (sc *BlobTxSidecar) BlobHashes() []common.Hash {
|
|||
return h
|
||||
}
|
||||
|
||||
// CellProofsAt returns the cell proofs for blob with index idx.
|
||||
func (sc *BlobTxSidecar) CellProofsAt(idx int) []kzg4844.Proof {
|
||||
var cellProofs []kzg4844.Proof
|
||||
for i := range kzg4844.CellProofsPerBlob {
|
||||
index := idx*kzg4844.CellProofsPerBlob + i
|
||||
if index > len(sc.Proofs) {
|
||||
return nil
|
||||
}
|
||||
proof := sc.Proofs[index]
|
||||
cellProofs = append(cellProofs, proof)
|
||||
}
|
||||
return cellProofs
|
||||
}
|
||||
|
||||
// encodedSize computes the RLP size of the sidecar elements. This does NOT return the
|
||||
// encoded size of the BlobTxSidecar, it's just a helper for tx.Size().
|
||||
func (sc *BlobTxSidecar) encodedSize() uint64 {
|
||||
|
|
@ -102,14 +120,55 @@ func (sc *BlobTxSidecar) ValidateBlobCommitmentHashes(hashes []common.Hash) erro
|
|||
return nil
|
||||
}
|
||||
|
||||
// blobTxWithBlobs is used for encoding of transactions when blobs are present.
|
||||
type blobTxWithBlobs struct {
|
||||
// blobTxWithBlobs represents blob tx with its corresponding sidecar.
|
||||
// This is an interface because sidecars are versioned.
|
||||
type blobTxWithBlobs interface {
|
||||
tx() *BlobTx
|
||||
assign(*BlobTxSidecar) error
|
||||
}
|
||||
|
||||
type blobTxWithBlobsV0 struct {
|
||||
BlobTx *BlobTx
|
||||
Blobs []kzg4844.Blob
|
||||
Commitments []kzg4844.Commitment
|
||||
Proofs []kzg4844.Proof
|
||||
}
|
||||
|
||||
type blobTxWithBlobsV1 struct {
|
||||
BlobTx *BlobTx
|
||||
Version byte
|
||||
Blobs []kzg4844.Blob
|
||||
Commitments []kzg4844.Commitment
|
||||
Proofs []kzg4844.Proof
|
||||
}
|
||||
|
||||
func (btx *blobTxWithBlobsV0) tx() *BlobTx {
|
||||
return btx.BlobTx
|
||||
}
|
||||
|
||||
func (btx *blobTxWithBlobsV0) assign(sc *BlobTxSidecar) error {
|
||||
sc.Version = 0
|
||||
sc.Blobs = btx.Blobs
|
||||
sc.Commitments = btx.Commitments
|
||||
sc.Proofs = btx.Proofs
|
||||
return nil
|
||||
}
|
||||
|
||||
func (btx *blobTxWithBlobsV1) tx() *BlobTx {
|
||||
return btx.BlobTx
|
||||
}
|
||||
|
||||
func (btx *blobTxWithBlobsV1) assign(sc *BlobTxSidecar) error {
|
||||
if btx.Version != 1 {
|
||||
return fmt.Errorf("unsupported blob tx version %d", btx.Version)
|
||||
}
|
||||
sc.Version = 1
|
||||
sc.Blobs = btx.Blobs
|
||||
sc.Commitments = btx.Commitments
|
||||
sc.Proofs = btx.Proofs
|
||||
return nil
|
||||
}
|
||||
|
||||
// copy creates a deep copy of the transaction data and initializes all fields.
|
||||
func (tx *BlobTx) copy() TxData {
|
||||
cpy := &BlobTx{
|
||||
|
|
@ -158,9 +217,9 @@ func (tx *BlobTx) copy() TxData {
|
|||
}
|
||||
if tx.Sidecar != nil {
|
||||
cpy.Sidecar = &BlobTxSidecar{
|
||||
Blobs: append([]kzg4844.Blob(nil), tx.Sidecar.Blobs...),
|
||||
Commitments: append([]kzg4844.Commitment(nil), tx.Sidecar.Commitments...),
|
||||
Proofs: append([]kzg4844.Proof(nil), tx.Sidecar.Proofs...),
|
||||
Blobs: slices.Clone(tx.Sidecar.Blobs),
|
||||
Commitments: slices.Clone(tx.Sidecar.Commitments),
|
||||
Proofs: slices.Clone(tx.Sidecar.Proofs),
|
||||
}
|
||||
}
|
||||
return cpy
|
||||
|
|
@ -215,48 +274,98 @@ func (tx *BlobTx) withSidecar(sideCar *BlobTxSidecar) *BlobTx {
|
|||
}
|
||||
|
||||
func (tx *BlobTx) encode(b *bytes.Buffer) error {
|
||||
if tx.Sidecar == nil {
|
||||
switch {
|
||||
case tx.Sidecar == nil:
|
||||
return rlp.Encode(b, tx)
|
||||
}
|
||||
inner := &blobTxWithBlobs{
|
||||
|
||||
case tx.Sidecar.Version == 0:
|
||||
return rlp.Encode(b, &blobTxWithBlobsV0{
|
||||
BlobTx: tx,
|
||||
Blobs: tx.Sidecar.Blobs,
|
||||
Commitments: tx.Sidecar.Commitments,
|
||||
Proofs: tx.Sidecar.Proofs,
|
||||
})
|
||||
|
||||
case tx.Sidecar.Version == 1:
|
||||
return rlp.Encode(b, &blobTxWithBlobsV1{
|
||||
BlobTx: tx,
|
||||
Version: tx.Sidecar.Version,
|
||||
Blobs: tx.Sidecar.Blobs,
|
||||
Commitments: tx.Sidecar.Commitments,
|
||||
Proofs: tx.Sidecar.Proofs,
|
||||
})
|
||||
|
||||
default:
|
||||
return errors.New("unsupported sidecar version")
|
||||
}
|
||||
return rlp.Encode(b, inner)
|
||||
}
|
||||
|
||||
func (tx *BlobTx) decode(input []byte) error {
|
||||
// Here we need to support two formats: the network protocol encoding of the tx (with
|
||||
// blobs) or the canonical encoding without blobs.
|
||||
// Here we need to support two outer formats: the network protocol encoding of the tx
|
||||
// (with blobs) or the canonical encoding without blobs.
|
||||
//
|
||||
// The two encodings can be distinguished by checking whether the first element of the
|
||||
// input list is itself a list.
|
||||
// The canonical encoding is just a list of fields:
|
||||
//
|
||||
// [chainID, nonce, ...]
|
||||
//
|
||||
// The network encoding is a list where the first element is the tx in the canonical encoding,
|
||||
// and the remaining elements are the 'sidecar':
|
||||
//
|
||||
// [[chainID, nonce, ...], ...]
|
||||
//
|
||||
// The two outer encodings can be distinguished by checking whether the first element
|
||||
// of the input list is itself a list. If it's the canonical encoding, the first
|
||||
// element is the chainID, which is a number.
|
||||
|
||||
outerList, _, err := rlp.SplitList(input)
|
||||
firstElem, _, err := rlp.SplitList(input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
firstElemKind, _, _, err := rlp.Split(outerList)
|
||||
firstElemKind, _, secondElem, err := rlp.Split(firstElem)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if firstElemKind != rlp.List {
|
||||
// Blob tx without blobs.
|
||||
return rlp.DecodeBytes(input, tx)
|
||||
}
|
||||
// It's a tx with blobs.
|
||||
var inner blobTxWithBlobs
|
||||
if err := rlp.DecodeBytes(input, &inner); err != nil {
|
||||
|
||||
// Now we know it's the network encoding with the blob sidecar. Here we again need to
|
||||
// support multiple encodings: legacy sidecars (v0) with a blob proof, and versioned
|
||||
// sidecars.
|
||||
//
|
||||
// The legacy encoding is:
|
||||
//
|
||||
// [tx, blobs, commitments, proofs]
|
||||
//
|
||||
// The versioned encoding is:
|
||||
//
|
||||
// [tx, version, blobs, ...]
|
||||
//
|
||||
// We can tell the two apart by checking whether the second element is the version byte.
|
||||
// For legacy sidecar the second element is a list of blobs.
|
||||
|
||||
secondElemKind, _, _, err := rlp.Split(secondElem)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*tx = *inner.BlobTx
|
||||
tx.Sidecar = &BlobTxSidecar{
|
||||
Blobs: inner.Blobs,
|
||||
Commitments: inner.Commitments,
|
||||
Proofs: inner.Proofs,
|
||||
var payload blobTxWithBlobs
|
||||
if secondElemKind == rlp.List {
|
||||
// No version byte: blob sidecar v0.
|
||||
payload = new(blobTxWithBlobsV0)
|
||||
} else {
|
||||
// It has a version byte. Decode as v1, version is checked by assign()
|
||||
payload = new(blobTxWithBlobsV1)
|
||||
}
|
||||
if err := rlp.DecodeBytes(input, payload); err != nil {
|
||||
return err
|
||||
}
|
||||
sc := new(BlobTxSidecar)
|
||||
if err := payload.assign(sc); err != nil {
|
||||
return err
|
||||
}
|
||||
*tx = *payload.tx()
|
||||
tx.Sidecar = sc
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,98 +0,0 @@
|
|||
// Copyright 2024 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package vm
|
||||
|
||||
// eofCodeBitmap collects data locations in code.
|
||||
func eofCodeBitmap(code []byte) bitvec {
|
||||
// The bitmap is 4 bytes longer than necessary, in case the code
|
||||
// ends with a PUSH32, the algorithm will push zeroes onto the
|
||||
// bitvector outside the bounds of the actual code.
|
||||
bits := make(bitvec, len(code)/8+1+4)
|
||||
return eofCodeBitmapInternal(code, bits)
|
||||
}
|
||||
|
||||
// eofCodeBitmapInternal is the internal implementation of codeBitmap for EOF
|
||||
// code validation.
|
||||
func eofCodeBitmapInternal(code, bits bitvec) bitvec {
|
||||
for pc := uint64(0); pc < uint64(len(code)); {
|
||||
var (
|
||||
op = OpCode(code[pc])
|
||||
numbits uint16
|
||||
)
|
||||
pc++
|
||||
|
||||
if op == RJUMPV {
|
||||
// RJUMPV is unique as it has a variable sized operand.
|
||||
// The total size is determined by the count byte which
|
||||
// immediate follows RJUMPV. Truncation will be caught
|
||||
// in other validation steps -- for now, just return a
|
||||
// valid bitmap for as much of the code as is
|
||||
// available.
|
||||
end := uint64(len(code))
|
||||
if pc >= end {
|
||||
// Count missing, no more bits to mark.
|
||||
return bits
|
||||
}
|
||||
numbits = uint16(code[pc])*2 + 3
|
||||
if pc+uint64(numbits) > end {
|
||||
// Jump table is truncated, mark as many bits
|
||||
// as possible.
|
||||
numbits = uint16(end - pc)
|
||||
}
|
||||
} else {
|
||||
numbits = uint16(Immediates(op))
|
||||
if numbits == 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if numbits >= 8 {
|
||||
for ; numbits >= 16; numbits -= 16 {
|
||||
bits.set16(pc)
|
||||
pc += 16
|
||||
}
|
||||
for ; numbits >= 8; numbits -= 8 {
|
||||
bits.set8(pc)
|
||||
pc += 8
|
||||
}
|
||||
}
|
||||
switch numbits {
|
||||
case 1:
|
||||
bits.set1(pc)
|
||||
pc += 1
|
||||
case 2:
|
||||
bits.setN(set2BitsMask, pc)
|
||||
pc += 2
|
||||
case 3:
|
||||
bits.setN(set3BitsMask, pc)
|
||||
pc += 3
|
||||
case 4:
|
||||
bits.setN(set4BitsMask, pc)
|
||||
pc += 4
|
||||
case 5:
|
||||
bits.setN(set5BitsMask, pc)
|
||||
pc += 5
|
||||
case 6:
|
||||
bits.setN(set6BitsMask, pc)
|
||||
pc += 6
|
||||
case 7:
|
||||
bits.setN(set7BitsMask, pc)
|
||||
pc += 7
|
||||
}
|
||||
}
|
||||
return bits
|
||||
}
|
||||
|
|
@ -105,31 +105,3 @@ func BenchmarkJumpdestOpAnalysis(bench *testing.B) {
|
|||
op = STOP
|
||||
bench.Run(op.String(), bencher)
|
||||
}
|
||||
|
||||
func BenchmarkJumpdestOpEOFAnalysis(bench *testing.B) {
|
||||
var op OpCode
|
||||
bencher := func(b *testing.B) {
|
||||
code := make([]byte, analysisCodeSize)
|
||||
b.SetBytes(analysisCodeSize)
|
||||
for i := range code {
|
||||
code[i] = byte(op)
|
||||
}
|
||||
bits := make(bitvec, len(code)/8+1+4)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
clear(bits)
|
||||
eofCodeBitmapInternal(code, bits)
|
||||
}
|
||||
}
|
||||
for op = PUSH1; op <= PUSH32; op++ {
|
||||
bench.Run(op.String(), bencher)
|
||||
}
|
||||
op = JUMPDEST
|
||||
bench.Run(op.String(), bencher)
|
||||
op = STOP
|
||||
bench.Run(op.String(), bencher)
|
||||
op = RJUMPV
|
||||
bench.Run(op.String(), bencher)
|
||||
op = EOFCREATE
|
||||
bench.Run(op.String(), bencher)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ var PrecompiledContractsByzantium = PrecompiledContracts{
|
|||
common.BytesToAddress([]byte{0x2}): &sha256hash{},
|
||||
common.BytesToAddress([]byte{0x3}): &ripemd160hash{},
|
||||
common.BytesToAddress([]byte{0x4}): &dataCopy{},
|
||||
common.BytesToAddress([]byte{0x5}): &bigModExp{eip2565: false},
|
||||
common.BytesToAddress([]byte{0x5}): &bigModExp{eip2565: false, eip7823: false, eip7883: false},
|
||||
common.BytesToAddress([]byte{0x6}): &bn256AddByzantium{},
|
||||
common.BytesToAddress([]byte{0x7}): &bn256ScalarMulByzantium{},
|
||||
common.BytesToAddress([]byte{0x8}): &bn256PairingByzantium{},
|
||||
|
|
@ -79,7 +79,7 @@ var PrecompiledContractsIstanbul = PrecompiledContracts{
|
|||
common.BytesToAddress([]byte{0x2}): &sha256hash{},
|
||||
common.BytesToAddress([]byte{0x3}): &ripemd160hash{},
|
||||
common.BytesToAddress([]byte{0x4}): &dataCopy{},
|
||||
common.BytesToAddress([]byte{0x5}): &bigModExp{eip2565: false},
|
||||
common.BytesToAddress([]byte{0x5}): &bigModExp{eip2565: false, eip7823: false, eip7883: false},
|
||||
common.BytesToAddress([]byte{0x6}): &bn256AddIstanbul{},
|
||||
common.BytesToAddress([]byte{0x7}): &bn256ScalarMulIstanbul{},
|
||||
common.BytesToAddress([]byte{0x8}): &bn256PairingIstanbul{},
|
||||
|
|
@ -93,7 +93,7 @@ var PrecompiledContractsBerlin = PrecompiledContracts{
|
|||
common.BytesToAddress([]byte{0x2}): &sha256hash{},
|
||||
common.BytesToAddress([]byte{0x3}): &ripemd160hash{},
|
||||
common.BytesToAddress([]byte{0x4}): &dataCopy{},
|
||||
common.BytesToAddress([]byte{0x5}): &bigModExp{eip2565: true},
|
||||
common.BytesToAddress([]byte{0x5}): &bigModExp{eip2565: true, eip7823: false, eip7883: false},
|
||||
common.BytesToAddress([]byte{0x6}): &bn256AddIstanbul{},
|
||||
common.BytesToAddress([]byte{0x7}): &bn256ScalarMulIstanbul{},
|
||||
common.BytesToAddress([]byte{0x8}): &bn256PairingIstanbul{},
|
||||
|
|
@ -107,7 +107,7 @@ var PrecompiledContractsCancun = PrecompiledContracts{
|
|||
common.BytesToAddress([]byte{0x2}): &sha256hash{},
|
||||
common.BytesToAddress([]byte{0x3}): &ripemd160hash{},
|
||||
common.BytesToAddress([]byte{0x4}): &dataCopy{},
|
||||
common.BytesToAddress([]byte{0x5}): &bigModExp{eip2565: true},
|
||||
common.BytesToAddress([]byte{0x5}): &bigModExp{eip2565: true, eip7823: false, eip7883: false},
|
||||
common.BytesToAddress([]byte{0x6}): &bn256AddIstanbul{},
|
||||
common.BytesToAddress([]byte{0x7}): &bn256ScalarMulIstanbul{},
|
||||
common.BytesToAddress([]byte{0x8}): &bn256PairingIstanbul{},
|
||||
|
|
@ -122,7 +122,7 @@ var PrecompiledContractsPrague = PrecompiledContracts{
|
|||
common.BytesToAddress([]byte{0x02}): &sha256hash{},
|
||||
common.BytesToAddress([]byte{0x03}): &ripemd160hash{},
|
||||
common.BytesToAddress([]byte{0x04}): &dataCopy{},
|
||||
common.BytesToAddress([]byte{0x05}): &bigModExp{eip2565: true},
|
||||
common.BytesToAddress([]byte{0x05}): &bigModExp{eip2565: true, eip7823: false, eip7883: false},
|
||||
common.BytesToAddress([]byte{0x06}): &bn256AddIstanbul{},
|
||||
common.BytesToAddress([]byte{0x07}): &bn256ScalarMulIstanbul{},
|
||||
common.BytesToAddress([]byte{0x08}): &bn256PairingIstanbul{},
|
||||
|
|
@ -141,7 +141,30 @@ var PrecompiledContractsBLS = PrecompiledContractsPrague
|
|||
|
||||
var PrecompiledContractsVerkle = PrecompiledContractsBerlin
|
||||
|
||||
// PrecompiledContractsOsaka contains the set of pre-compiled Ethereum
|
||||
// contracts used in the Osaka release.
|
||||
var PrecompiledContractsOsaka = PrecompiledContracts{
|
||||
common.BytesToAddress([]byte{0x01}): &ecrecover{},
|
||||
common.BytesToAddress([]byte{0x02}): &sha256hash{},
|
||||
common.BytesToAddress([]byte{0x03}): &ripemd160hash{},
|
||||
common.BytesToAddress([]byte{0x04}): &dataCopy{},
|
||||
common.BytesToAddress([]byte{0x05}): &bigModExp{eip2565: true, eip7823: true, eip7883: true},
|
||||
common.BytesToAddress([]byte{0x06}): &bn256AddIstanbul{},
|
||||
common.BytesToAddress([]byte{0x07}): &bn256ScalarMulIstanbul{},
|
||||
common.BytesToAddress([]byte{0x08}): &bn256PairingIstanbul{},
|
||||
common.BytesToAddress([]byte{0x09}): &blake2F{},
|
||||
common.BytesToAddress([]byte{0x0a}): &kzgPointEvaluation{},
|
||||
common.BytesToAddress([]byte{0x0b}): &bls12381G1Add{},
|
||||
common.BytesToAddress([]byte{0x0c}): &bls12381G1MultiExp{},
|
||||
common.BytesToAddress([]byte{0x0d}): &bls12381G2Add{},
|
||||
common.BytesToAddress([]byte{0x0e}): &bls12381G2MultiExp{},
|
||||
common.BytesToAddress([]byte{0x0f}): &bls12381Pairing{},
|
||||
common.BytesToAddress([]byte{0x10}): &bls12381MapG1{},
|
||||
common.BytesToAddress([]byte{0x11}): &bls12381MapG2{},
|
||||
}
|
||||
|
||||
var (
|
||||
PrecompiledAddressesOsaka []common.Address
|
||||
PrecompiledAddressesPrague []common.Address
|
||||
PrecompiledAddressesCancun []common.Address
|
||||
PrecompiledAddressesBerlin []common.Address
|
||||
|
|
@ -169,12 +192,17 @@ func init() {
|
|||
for k := range PrecompiledContractsPrague {
|
||||
PrecompiledAddressesPrague = append(PrecompiledAddressesPrague, k)
|
||||
}
|
||||
for k := range PrecompiledContractsOsaka {
|
||||
PrecompiledAddressesOsaka = append(PrecompiledAddressesOsaka, k)
|
||||
}
|
||||
}
|
||||
|
||||
func activePrecompiledContracts(rules params.Rules) PrecompiledContracts {
|
||||
switch {
|
||||
case rules.IsVerkle:
|
||||
return PrecompiledContractsVerkle
|
||||
case rules.IsOsaka:
|
||||
return PrecompiledContractsOsaka
|
||||
case rules.IsPrague:
|
||||
return PrecompiledContractsPrague
|
||||
case rules.IsCancun:
|
||||
|
|
@ -198,6 +226,8 @@ func ActivePrecompiledContracts(rules params.Rules) PrecompiledContracts {
|
|||
// ActivePrecompiles returns the precompile addresses enabled with the current configuration.
|
||||
func ActivePrecompiles(rules params.Rules) []common.Address {
|
||||
switch {
|
||||
case rules.IsOsaka:
|
||||
return PrecompiledAddressesOsaka
|
||||
case rules.IsPrague:
|
||||
return PrecompiledAddressesPrague
|
||||
case rules.IsCancun:
|
||||
|
|
@ -317,6 +347,8 @@ func (c *dataCopy) Run(in []byte) ([]byte, error) {
|
|||
// bigModExp implements a native big integer exponential modular operation.
|
||||
type bigModExp struct {
|
||||
eip2565 bool
|
||||
eip7823 bool
|
||||
eip7883 bool
|
||||
}
|
||||
|
||||
var (
|
||||
|
|
@ -392,8 +424,12 @@ func (c *bigModExp) RequiredGas(input []byte) uint64 {
|
|||
adjExpLen := new(big.Int)
|
||||
if expLen.Cmp(big32) > 0 {
|
||||
adjExpLen.Sub(expLen, big32)
|
||||
if c.eip7883 {
|
||||
adjExpLen.Lsh(adjExpLen, 4)
|
||||
} else {
|
||||
adjExpLen.Lsh(adjExpLen, 3)
|
||||
}
|
||||
}
|
||||
adjExpLen.Add(adjExpLen, big.NewInt(int64(msb)))
|
||||
// Calculate the gas cost of the operation
|
||||
gas := new(big.Int)
|
||||
|
|
@ -402,8 +438,11 @@ func (c *bigModExp) RequiredGas(input []byte) uint64 {
|
|||
} else {
|
||||
gas.Set(modLen)
|
||||
}
|
||||
|
||||
maxLenOver32 := gas.Cmp(big32) > 0
|
||||
if c.eip2565 {
|
||||
// EIP-2565 has three changes
|
||||
// EIP-2565 (Berlin fork) has three changes:
|
||||
//
|
||||
// 1. Different multComplexity (inlined here)
|
||||
// in EIP-2565 (https://eips.ethereum.org/EIPS/eip-2565):
|
||||
//
|
||||
|
|
@ -415,6 +454,16 @@ func (c *bigModExp) RequiredGas(input []byte) uint64 {
|
|||
gas.Rsh(gas, 3)
|
||||
gas.Mul(gas, gas)
|
||||
|
||||
var minPrice uint64 = 200
|
||||
if c.eip7883 {
|
||||
minPrice = 500
|
||||
if maxLenOver32 {
|
||||
gas.Add(gas, gas)
|
||||
} else {
|
||||
gas = big.NewInt(16)
|
||||
}
|
||||
}
|
||||
|
||||
if adjExpLen.Cmp(big1) > 0 {
|
||||
gas.Mul(gas, adjExpLen)
|
||||
}
|
||||
|
|
@ -423,18 +472,15 @@ func (c *bigModExp) RequiredGas(input []byte) uint64 {
|
|||
if gas.BitLen() > 64 {
|
||||
return math.MaxUint64
|
||||
}
|
||||
// 3. Minimum price of 200 gas
|
||||
if gas.Uint64() < 200 {
|
||||
return 200
|
||||
}
|
||||
return gas.Uint64()
|
||||
return max(minPrice, gas.Uint64())
|
||||
}
|
||||
|
||||
// Pre-Berlin logic.
|
||||
gas = modexpMultComplexity(gas)
|
||||
if adjExpLen.Cmp(big1) > 0 {
|
||||
gas.Mul(gas, adjExpLen)
|
||||
}
|
||||
gas.Div(gas, big20)
|
||||
|
||||
if gas.BitLen() > 64 {
|
||||
return math.MaxUint64
|
||||
}
|
||||
|
|
@ -456,6 +502,10 @@ func (c *bigModExp) Run(input []byte) ([]byte, error) {
|
|||
if baseLen == 0 && modLen == 0 {
|
||||
return []byte{}, nil
|
||||
}
|
||||
// enforce size cap for inputs
|
||||
if c.eip7823 && max(baseLen, expLen, modLen) > 1024 {
|
||||
return nil, fmt.Errorf("one or more of base/exponent/modulus length exceeded 1024 bytes")
|
||||
}
|
||||
// Retrieve the operands and execute the exponentiation
|
||||
var (
|
||||
base = new(big.Int).SetBytes(getData(input, 0, baseLen))
|
||||
|
|
|
|||
|
|
@ -50,8 +50,9 @@ var allPrecompiles = map[common.Address]PrecompiledContract{
|
|||
common.BytesToAddress([]byte{2}): &sha256hash{},
|
||||
common.BytesToAddress([]byte{3}): &ripemd160hash{},
|
||||
common.BytesToAddress([]byte{4}): &dataCopy{},
|
||||
common.BytesToAddress([]byte{5}): &bigModExp{eip2565: false},
|
||||
common.BytesToAddress([]byte{0xf5}): &bigModExp{eip2565: true},
|
||||
common.BytesToAddress([]byte{5}): &bigModExp{eip2565: false, eip7883: false},
|
||||
common.BytesToAddress([]byte{0xf5}): &bigModExp{eip2565: true, eip7883: false},
|
||||
common.BytesToAddress([]byte{0xf6}): &bigModExp{eip2565: true, eip7883: true},
|
||||
common.BytesToAddress([]byte{6}): &bn256AddIstanbul{},
|
||||
common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{},
|
||||
common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{},
|
||||
|
|
@ -238,6 +239,9 @@ func BenchmarkPrecompiledModExp(b *testing.B) { benchJson("modexp", "05", b) }
|
|||
func TestPrecompiledModExpEip2565(t *testing.T) { testJson("modexp_eip2565", "f5", t) }
|
||||
func BenchmarkPrecompiledModExpEip2565(b *testing.B) { benchJson("modexp_eip2565", "f5", b) }
|
||||
|
||||
func TestPrecompiledModExpEip7883(t *testing.T) { testJson("modexp_eip7883", "f6", t) }
|
||||
func BenchmarkPrecompiledModExpEip7883(b *testing.B) { benchJson("modexp_eip7883", "f6", b) }
|
||||
|
||||
// Tests the sample inputs from the elliptic curve addition EIP 213.
|
||||
func TestPrecompiledBn256Add(t *testing.T) { testJson("bn256Add", "06", t) }
|
||||
func BenchmarkPrecompiledBn256Add(b *testing.B) { benchJson("bn256Add", "06", b) }
|
||||
|
|
@ -370,7 +374,7 @@ func BenchmarkPrecompiledBLS12381G1MultiExpWorstCase(b *testing.B) {
|
|||
Name: "WorstCaseG1",
|
||||
NoBenchmark: false,
|
||||
}
|
||||
benchmarkPrecompiled("f0c", testcase, b)
|
||||
benchmarkPrecompiled("f0b", testcase, b)
|
||||
}
|
||||
|
||||
// BenchmarkPrecompiledBLS12381G2MultiExpWorstCase benchmarks the worst case we could find that still fits a gaslimit of 10MGas.
|
||||
|
|
@ -391,5 +395,5 @@ func BenchmarkPrecompiledBLS12381G2MultiExpWorstCase(b *testing.B) {
|
|||
Name: "WorstCaseG2",
|
||||
NoBenchmark: false,
|
||||
}
|
||||
benchmarkPrecompiled("f0f", testcase, b)
|
||||
benchmarkPrecompiled("f0d", testcase, b)
|
||||
}
|
||||
|
|
|
|||
170
core/vm/eips.go
170
core/vm/eips.go
|
|
@ -531,176 +531,6 @@ func enable4762(jt *JumpTable) {
|
|||
}
|
||||
}
|
||||
|
||||
// enableEOF applies the EOF changes.
|
||||
// OBS! For EOF, there are two changes:
|
||||
// 1. Two separate jumptables are required. One, EOF-jumptable, is used by
|
||||
// eof contracts. This one contains things like RJUMP.
|
||||
// 2. The regular non-eof jumptable also needs to be modified, specifically to
|
||||
// modify how EXTCODECOPY works under the hood.
|
||||
//
|
||||
// This method _only_ deals with case 1.
|
||||
func enableEOF(jt *JumpTable) {
|
||||
// Deprecate opcodes
|
||||
undefined := &operation{
|
||||
execute: opUndefined,
|
||||
constantGas: 0,
|
||||
minStack: minStack(0, 0),
|
||||
maxStack: maxStack(0, 0),
|
||||
undefined: true,
|
||||
}
|
||||
jt[CALL] = undefined
|
||||
jt[CALLCODE] = undefined
|
||||
jt[DELEGATECALL] = undefined
|
||||
jt[STATICCALL] = undefined
|
||||
jt[SELFDESTRUCT] = undefined
|
||||
jt[JUMP] = undefined
|
||||
jt[JUMPI] = undefined
|
||||
jt[PC] = undefined
|
||||
jt[CREATE] = undefined
|
||||
jt[CREATE2] = undefined
|
||||
jt[CODESIZE] = undefined
|
||||
jt[CODECOPY] = undefined
|
||||
jt[EXTCODESIZE] = undefined
|
||||
jt[EXTCODECOPY] = undefined
|
||||
jt[EXTCODEHASH] = undefined
|
||||
jt[GAS] = undefined
|
||||
// Allow 0xFE to terminate sections
|
||||
jt[INVALID] = &operation{
|
||||
execute: opUndefined,
|
||||
constantGas: 0,
|
||||
minStack: minStack(0, 0),
|
||||
maxStack: maxStack(0, 0),
|
||||
}
|
||||
|
||||
// New opcodes
|
||||
jt[RJUMP] = &operation{
|
||||
execute: opRjump,
|
||||
constantGas: GasQuickStep,
|
||||
minStack: minStack(0, 0),
|
||||
maxStack: maxStack(0, 0),
|
||||
}
|
||||
jt[RJUMPI] = &operation{
|
||||
execute: opRjumpi,
|
||||
constantGas: GasFastishStep,
|
||||
minStack: minStack(1, 0),
|
||||
maxStack: maxStack(1, 0),
|
||||
}
|
||||
jt[RJUMPV] = &operation{
|
||||
execute: opRjumpv,
|
||||
constantGas: GasFastishStep,
|
||||
minStack: minStack(1, 0),
|
||||
maxStack: maxStack(1, 0),
|
||||
}
|
||||
jt[CALLF] = &operation{
|
||||
execute: opCallf,
|
||||
constantGas: GasFastStep,
|
||||
minStack: minStack(0, 0),
|
||||
maxStack: maxStack(0, 0),
|
||||
}
|
||||
jt[RETF] = &operation{
|
||||
execute: opRetf,
|
||||
constantGas: GasFastestStep,
|
||||
minStack: minStack(0, 0),
|
||||
maxStack: maxStack(0, 0),
|
||||
}
|
||||
jt[JUMPF] = &operation{
|
||||
execute: opJumpf,
|
||||
constantGas: GasFastStep,
|
||||
minStack: minStack(0, 0),
|
||||
maxStack: maxStack(0, 0),
|
||||
}
|
||||
jt[EOFCREATE] = &operation{
|
||||
execute: opEOFCreate,
|
||||
constantGas: params.Create2Gas,
|
||||
dynamicGas: gasEOFCreate,
|
||||
minStack: minStack(4, 1),
|
||||
maxStack: maxStack(4, 1),
|
||||
memorySize: memoryEOFCreate,
|
||||
}
|
||||
jt[RETURNCONTRACT] = &operation{
|
||||
execute: opReturnContract,
|
||||
// returncontract has zero constant gas cost
|
||||
dynamicGas: pureMemoryGascost,
|
||||
minStack: minStack(2, 0),
|
||||
maxStack: maxStack(2, 0),
|
||||
memorySize: memoryReturnContract,
|
||||
}
|
||||
jt[DATALOAD] = &operation{
|
||||
execute: opDataLoad,
|
||||
constantGas: GasFastishStep,
|
||||
minStack: minStack(1, 1),
|
||||
maxStack: maxStack(1, 1),
|
||||
}
|
||||
jt[DATALOADN] = &operation{
|
||||
execute: opDataLoadN,
|
||||
constantGas: GasFastestStep,
|
||||
minStack: minStack(0, 1),
|
||||
maxStack: maxStack(0, 1),
|
||||
}
|
||||
jt[DATASIZE] = &operation{
|
||||
execute: opDataSize,
|
||||
constantGas: GasQuickStep,
|
||||
minStack: minStack(0, 1),
|
||||
maxStack: maxStack(0, 1),
|
||||
}
|
||||
jt[DATACOPY] = &operation{
|
||||
execute: opDataCopy,
|
||||
constantGas: GasFastestStep,
|
||||
dynamicGas: memoryCopierGas(2),
|
||||
minStack: minStack(3, 0),
|
||||
maxStack: maxStack(3, 0),
|
||||
memorySize: memoryDataCopy,
|
||||
}
|
||||
jt[DUPN] = &operation{
|
||||
execute: opDupN,
|
||||
constantGas: GasFastestStep,
|
||||
minStack: minStack(0, 1),
|
||||
maxStack: maxStack(0, 1),
|
||||
}
|
||||
jt[SWAPN] = &operation{
|
||||
execute: opSwapN,
|
||||
constantGas: GasFastestStep,
|
||||
minStack: minStack(0, 0),
|
||||
maxStack: maxStack(0, 0),
|
||||
}
|
||||
jt[EXCHANGE] = &operation{
|
||||
execute: opExchange,
|
||||
constantGas: GasFastestStep,
|
||||
minStack: minStack(0, 0),
|
||||
maxStack: maxStack(0, 0),
|
||||
}
|
||||
jt[RETURNDATALOAD] = &operation{
|
||||
execute: opReturnDataLoad,
|
||||
constantGas: GasFastestStep,
|
||||
minStack: minStack(1, 1),
|
||||
maxStack: maxStack(1, 1),
|
||||
}
|
||||
jt[EXTCALL] = &operation{
|
||||
execute: opExtCall,
|
||||
constantGas: params.WarmStorageReadCostEIP2929,
|
||||
dynamicGas: makeCallVariantGasCallEIP2929(gasExtCall, 0),
|
||||
minStack: minStack(4, 1),
|
||||
maxStack: maxStack(4, 1),
|
||||
memorySize: memoryExtCall,
|
||||
}
|
||||
jt[EXTDELEGATECALL] = &operation{
|
||||
execute: opExtDelegateCall,
|
||||
dynamicGas: makeCallVariantGasCallEIP2929(gasExtDelegateCall, 0),
|
||||
constantGas: params.WarmStorageReadCostEIP2929,
|
||||
minStack: minStack(3, 1),
|
||||
maxStack: maxStack(3, 1),
|
||||
memorySize: memoryExtCall,
|
||||
}
|
||||
jt[EXTSTATICCALL] = &operation{
|
||||
execute: opExtStaticCall,
|
||||
constantGas: params.WarmStorageReadCostEIP2929,
|
||||
dynamicGas: makeCallVariantGasCallEIP2929(gasExtStaticCall, 0),
|
||||
minStack: minStack(3, 1),
|
||||
maxStack: maxStack(3, 1),
|
||||
memorySize: memoryExtCall,
|
||||
}
|
||||
}
|
||||
|
||||
// enable7702 the EIP-7702 changes to support delegation designators.
|
||||
func enable7702(jt *JumpTable) {
|
||||
jt[CALL].dynamicGas = gasCallEIP7702
|
||||
|
|
|
|||
501
core/vm/eof.go
501
core/vm/eof.go
|
|
@ -1,501 +0,0 @@
|
|||
// Copyright 2024 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package vm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
const (
|
||||
offsetVersion = 2
|
||||
offsetTypesKind = 3
|
||||
offsetCodeKind = 6
|
||||
|
||||
kindTypes = 1
|
||||
kindCode = 2
|
||||
kindContainer = 3
|
||||
kindData = 4
|
||||
|
||||
eofFormatByte = 0xef
|
||||
eof1Version = 1
|
||||
|
||||
maxInputItems = 127
|
||||
maxOutputItems = 128
|
||||
maxStackHeight = 1023
|
||||
maxContainerSections = 256
|
||||
)
|
||||
|
||||
var eofMagic = []byte{0xef, 0x00}
|
||||
|
||||
// HasEOFByte returns true if code starts with 0xEF byte
|
||||
func HasEOFByte(code []byte) bool {
|
||||
return len(code) != 0 && code[0] == eofFormatByte
|
||||
}
|
||||
|
||||
// hasEOFMagic returns true if code starts with magic defined by EIP-3540
|
||||
func hasEOFMagic(code []byte) bool {
|
||||
return len(eofMagic) <= len(code) && bytes.Equal(eofMagic, code[0:len(eofMagic)])
|
||||
}
|
||||
|
||||
// isEOFVersion1 returns true if the code's version byte equals eof1Version. It
|
||||
// does not verify the EOF magic is valid.
|
||||
func isEOFVersion1(code []byte) bool {
|
||||
return 2 < len(code) && code[2] == byte(eof1Version)
|
||||
}
|
||||
|
||||
// Container is an EOF container object.
|
||||
type Container struct {
|
||||
types []*functionMetadata
|
||||
codeSections [][]byte
|
||||
subContainers []*Container
|
||||
subContainerCodes [][]byte
|
||||
data []byte
|
||||
dataSize int // might be more than len(data)
|
||||
}
|
||||
|
||||
// functionMetadata is an EOF function signature.
|
||||
type functionMetadata struct {
|
||||
inputs uint8
|
||||
outputs uint8
|
||||
maxStackHeight uint16
|
||||
}
|
||||
|
||||
// stackDelta returns the #outputs - #inputs
|
||||
func (meta *functionMetadata) stackDelta() int {
|
||||
return int(meta.outputs) - int(meta.inputs)
|
||||
}
|
||||
|
||||
// checkInputs checks the current minimum stack (stackMin) against the required inputs
|
||||
// of the metadata, and returns an error if the stack is too shallow.
|
||||
func (meta *functionMetadata) checkInputs(stackMin int) error {
|
||||
if int(meta.inputs) > stackMin {
|
||||
return ErrStackUnderflow{stackLen: stackMin, required: int(meta.inputs)}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkStackMax checks the if current maximum stack combined with the
|
||||
// function max stack will result in a stack overflow, and if so returns an error.
|
||||
func (meta *functionMetadata) checkStackMax(stackMax int) error {
|
||||
newMaxStack := stackMax + int(meta.maxStackHeight) - int(meta.inputs)
|
||||
if newMaxStack > int(params.StackLimit) {
|
||||
return ErrStackOverflow{stackLen: newMaxStack, limit: int(params.StackLimit)}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalBinary encodes an EOF container into binary format.
|
||||
func (c *Container) MarshalBinary() []byte {
|
||||
// Build EOF prefix.
|
||||
b := make([]byte, 2)
|
||||
copy(b, eofMagic)
|
||||
b = append(b, eof1Version)
|
||||
|
||||
// Write section headers.
|
||||
b = append(b, kindTypes)
|
||||
b = binary.BigEndian.AppendUint16(b, uint16(len(c.types)*4))
|
||||
b = append(b, kindCode)
|
||||
b = binary.BigEndian.AppendUint16(b, uint16(len(c.codeSections)))
|
||||
for _, codeSection := range c.codeSections {
|
||||
b = binary.BigEndian.AppendUint16(b, uint16(len(codeSection)))
|
||||
}
|
||||
var encodedContainer [][]byte
|
||||
if len(c.subContainers) != 0 {
|
||||
b = append(b, kindContainer)
|
||||
b = binary.BigEndian.AppendUint16(b, uint16(len(c.subContainers)))
|
||||
for _, section := range c.subContainers {
|
||||
encoded := section.MarshalBinary()
|
||||
b = binary.BigEndian.AppendUint16(b, uint16(len(encoded)))
|
||||
encodedContainer = append(encodedContainer, encoded)
|
||||
}
|
||||
}
|
||||
b = append(b, kindData)
|
||||
b = binary.BigEndian.AppendUint16(b, uint16(c.dataSize))
|
||||
b = append(b, 0) // terminator
|
||||
|
||||
// Write section contents.
|
||||
for _, ty := range c.types {
|
||||
b = append(b, []byte{ty.inputs, ty.outputs, byte(ty.maxStackHeight >> 8), byte(ty.maxStackHeight & 0x00ff)}...)
|
||||
}
|
||||
for _, code := range c.codeSections {
|
||||
b = append(b, code...)
|
||||
}
|
||||
for _, section := range encodedContainer {
|
||||
b = append(b, section...)
|
||||
}
|
||||
b = append(b, c.data...)
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
// UnmarshalBinary decodes an EOF container.
|
||||
func (c *Container) UnmarshalBinary(b []byte, isInitcode bool) error {
|
||||
return c.unmarshalContainer(b, isInitcode, true)
|
||||
}
|
||||
|
||||
// UnmarshalSubContainer decodes an EOF container that is inside another container.
|
||||
func (c *Container) UnmarshalSubContainer(b []byte, isInitcode bool) error {
|
||||
return c.unmarshalContainer(b, isInitcode, false)
|
||||
}
|
||||
|
||||
func (c *Container) unmarshalContainer(b []byte, isInitcode bool, topLevel bool) error {
|
||||
if !hasEOFMagic(b) {
|
||||
return fmt.Errorf("%w: want %x", errInvalidMagic, eofMagic)
|
||||
}
|
||||
if len(b) < 14 {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
if len(b) > params.MaxInitCodeSize {
|
||||
return ErrMaxInitCodeSizeExceeded
|
||||
}
|
||||
if !isEOFVersion1(b) {
|
||||
return fmt.Errorf("%w: have %d, want %d", errInvalidVersion, b[2], eof1Version)
|
||||
}
|
||||
|
||||
var (
|
||||
kind, typesSize, dataSize int
|
||||
codeSizes []int
|
||||
err error
|
||||
)
|
||||
|
||||
// Parse type section header.
|
||||
kind, typesSize, err = parseSection(b, offsetTypesKind)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if kind != kindTypes {
|
||||
return fmt.Errorf("%w: found section kind %x instead", errMissingTypeHeader, kind)
|
||||
}
|
||||
if typesSize < 4 || typesSize%4 != 0 {
|
||||
return fmt.Errorf("%w: type section size must be divisible by 4, have %d", errInvalidTypeSize, typesSize)
|
||||
}
|
||||
if typesSize/4 > 1024 {
|
||||
return fmt.Errorf("%w: type section must not exceed 4*1024, have %d", errInvalidTypeSize, typesSize)
|
||||
}
|
||||
|
||||
// Parse code section header.
|
||||
kind, codeSizes, err = parseSectionList(b, offsetCodeKind)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if kind != kindCode {
|
||||
return fmt.Errorf("%w: found section kind %x instead", errMissingCodeHeader, kind)
|
||||
}
|
||||
if len(codeSizes) != typesSize/4 {
|
||||
return fmt.Errorf("%w: mismatch of code sections found and type signatures, types %d, code %d", errInvalidCodeSize, typesSize/4, len(codeSizes))
|
||||
}
|
||||
|
||||
// Parse (optional) container section header.
|
||||
var containerSizes []int
|
||||
offset := offsetCodeKind + 2 + 2*len(codeSizes) + 1
|
||||
if offset < len(b) && b[offset] == kindContainer {
|
||||
kind, containerSizes, err = parseSectionList(b, offset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if kind != kindContainer {
|
||||
panic("somethings wrong")
|
||||
}
|
||||
if len(containerSizes) == 0 {
|
||||
return fmt.Errorf("%w: total container count must not be zero", errInvalidContainerSectionSize)
|
||||
}
|
||||
offset = offset + 2 + 2*len(containerSizes) + 1
|
||||
}
|
||||
|
||||
// Parse data section header.
|
||||
kind, dataSize, err = parseSection(b, offset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if kind != kindData {
|
||||
return fmt.Errorf("%w: found section %x instead", errMissingDataHeader, kind)
|
||||
}
|
||||
c.dataSize = dataSize
|
||||
|
||||
// Check for terminator.
|
||||
offsetTerminator := offset + 3
|
||||
if len(b) < offsetTerminator {
|
||||
return fmt.Errorf("%w: invalid offset terminator", io.ErrUnexpectedEOF)
|
||||
}
|
||||
if b[offsetTerminator] != 0 {
|
||||
return fmt.Errorf("%w: have %x", errMissingTerminator, b[offsetTerminator])
|
||||
}
|
||||
|
||||
// Verify overall container size.
|
||||
expectedSize := offsetTerminator + typesSize + sum(codeSizes) + dataSize + 1
|
||||
if len(containerSizes) != 0 {
|
||||
expectedSize += sum(containerSizes)
|
||||
}
|
||||
if len(b) < expectedSize-dataSize {
|
||||
return fmt.Errorf("%w: have %d, want %d", errInvalidContainerSize, len(b), expectedSize)
|
||||
}
|
||||
// Only check that the expected size is not exceed on non-initcode
|
||||
if (!topLevel || !isInitcode) && len(b) > expectedSize {
|
||||
return fmt.Errorf("%w: have %d, want %d", errInvalidContainerSize, len(b), expectedSize)
|
||||
}
|
||||
|
||||
// Parse types section.
|
||||
idx := offsetTerminator + 1
|
||||
var types = make([]*functionMetadata, 0, typesSize/4)
|
||||
for i := 0; i < typesSize/4; i++ {
|
||||
sig := &functionMetadata{
|
||||
inputs: b[idx+i*4],
|
||||
outputs: b[idx+i*4+1],
|
||||
maxStackHeight: binary.BigEndian.Uint16(b[idx+i*4+2:]),
|
||||
}
|
||||
if sig.inputs > maxInputItems {
|
||||
return fmt.Errorf("%w for section %d: have %d", errTooManyInputs, i, sig.inputs)
|
||||
}
|
||||
if sig.outputs > maxOutputItems {
|
||||
return fmt.Errorf("%w for section %d: have %d", errTooManyOutputs, i, sig.outputs)
|
||||
}
|
||||
if sig.maxStackHeight > maxStackHeight {
|
||||
return fmt.Errorf("%w for section %d: have %d", errTooLargeMaxStackHeight, i, sig.maxStackHeight)
|
||||
}
|
||||
types = append(types, sig)
|
||||
}
|
||||
if types[0].inputs != 0 || types[0].outputs != 0x80 {
|
||||
return fmt.Errorf("%w: have %d, %d", errInvalidSection0Type, types[0].inputs, types[0].outputs)
|
||||
}
|
||||
c.types = types
|
||||
|
||||
// Parse code sections.
|
||||
idx += typesSize
|
||||
codeSections := make([][]byte, len(codeSizes))
|
||||
for i, size := range codeSizes {
|
||||
if size == 0 {
|
||||
return fmt.Errorf("%w for section %d: size must not be 0", errInvalidCodeSize, i)
|
||||
}
|
||||
codeSections[i] = b[idx : idx+size]
|
||||
idx += size
|
||||
}
|
||||
c.codeSections = codeSections
|
||||
// Parse the optional container sizes.
|
||||
if len(containerSizes) != 0 {
|
||||
if len(containerSizes) > maxContainerSections {
|
||||
return fmt.Errorf("%w number of container section exceed: %v: have %v", errInvalidContainerSectionSize, maxContainerSections, len(containerSizes))
|
||||
}
|
||||
subContainerCodes := make([][]byte, 0, len(containerSizes))
|
||||
subContainers := make([]*Container, 0, len(containerSizes))
|
||||
for i, size := range containerSizes {
|
||||
if size == 0 || idx+size > len(b) {
|
||||
return fmt.Errorf("%w for section %d: size must not be 0", errInvalidContainerSectionSize, i)
|
||||
}
|
||||
subC := new(Container)
|
||||
end := min(idx+size, len(b))
|
||||
if err := subC.unmarshalContainer(b[idx:end], isInitcode, false); err != nil {
|
||||
if topLevel {
|
||||
return fmt.Errorf("%w in sub container %d", err, i)
|
||||
}
|
||||
return err
|
||||
}
|
||||
subContainers = append(subContainers, subC)
|
||||
subContainerCodes = append(subContainerCodes, b[idx:end])
|
||||
|
||||
idx += size
|
||||
}
|
||||
c.subContainers = subContainers
|
||||
c.subContainerCodes = subContainerCodes
|
||||
}
|
||||
|
||||
//Parse data section.
|
||||
end := len(b)
|
||||
if !isInitcode {
|
||||
end = min(idx+dataSize, len(b))
|
||||
}
|
||||
if topLevel && len(b) != idx+dataSize {
|
||||
return errTruncatedTopLevelContainer
|
||||
}
|
||||
c.data = b[idx:end]
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateCode validates each code section of the container against the EOF v1
|
||||
// rule set.
|
||||
func (c *Container) ValidateCode(jt *JumpTable, isInitCode bool) error {
|
||||
refBy := notRefByEither
|
||||
if isInitCode {
|
||||
refBy = refByEOFCreate
|
||||
}
|
||||
return c.validateSubContainer(jt, refBy)
|
||||
}
|
||||
|
||||
func (c *Container) validateSubContainer(jt *JumpTable, refBy int) error {
|
||||
visited := make(map[int]struct{})
|
||||
subContainerVisited := make(map[int]int)
|
||||
toVisit := []int{0}
|
||||
for len(toVisit) > 0 {
|
||||
// TODO check if this can be used as a DOS
|
||||
// Theres and edge case here where we mark something as visited that we visit before,
|
||||
// This should not trigger a re-visit
|
||||
// e.g. 0 -> 1, 2, 3
|
||||
// 1 -> 2, 3
|
||||
// should not mean 2 and 3 should be visited twice
|
||||
var (
|
||||
index = toVisit[0]
|
||||
code = c.codeSections[index]
|
||||
)
|
||||
if _, ok := visited[index]; !ok {
|
||||
res, err := validateCode(code, index, c, jt, refBy == refByEOFCreate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
visited[index] = struct{}{}
|
||||
// Mark all sections that can be visited from here.
|
||||
for idx := range res.visitedCode {
|
||||
if _, ok := visited[idx]; !ok {
|
||||
toVisit = append(toVisit, idx)
|
||||
}
|
||||
}
|
||||
// Mark all subcontainer that can be visited from here.
|
||||
for idx, reference := range res.visitedSubContainers {
|
||||
// Make sure subcontainers are only ever referenced by either EOFCreate or ReturnContract
|
||||
if ref, ok := subContainerVisited[idx]; ok && ref != reference {
|
||||
return errors.New("section referenced by both EOFCreate and ReturnContract")
|
||||
}
|
||||
subContainerVisited[idx] = reference
|
||||
}
|
||||
if refBy == refByReturnContract && res.isInitCode {
|
||||
return errIncompatibleContainerKind
|
||||
}
|
||||
if refBy == refByEOFCreate && res.isRuntime {
|
||||
return errIncompatibleContainerKind
|
||||
}
|
||||
}
|
||||
toVisit = toVisit[1:]
|
||||
}
|
||||
// Make sure every code section is visited at least once.
|
||||
if len(visited) != len(c.codeSections) {
|
||||
return errUnreachableCode
|
||||
}
|
||||
for idx, container := range c.subContainers {
|
||||
reference, ok := subContainerVisited[idx]
|
||||
if !ok {
|
||||
return errOrphanedSubcontainer
|
||||
}
|
||||
if err := container.validateSubContainer(jt, reference); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseSection decodes a (kind, size) pair from an EOF header.
|
||||
func parseSection(b []byte, idx int) (kind, size int, err error) {
|
||||
if idx+3 >= len(b) {
|
||||
return 0, 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
kind = int(b[idx])
|
||||
size = int(binary.BigEndian.Uint16(b[idx+1:]))
|
||||
return kind, size, nil
|
||||
}
|
||||
|
||||
// parseSectionList decodes a (kind, len, []codeSize) section list from an EOF
|
||||
// header.
|
||||
func parseSectionList(b []byte, idx int) (kind int, list []int, err error) {
|
||||
if idx >= len(b) {
|
||||
return 0, nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
kind = int(b[idx])
|
||||
list, err = parseList(b, idx+1)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
return kind, list, nil
|
||||
}
|
||||
|
||||
// parseList decodes a list of uint16..
|
||||
func parseList(b []byte, idx int) ([]int, error) {
|
||||
if len(b) < idx+2 {
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
count := binary.BigEndian.Uint16(b[idx:])
|
||||
if len(b) <= idx+2+int(count)*2 {
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
list := make([]int, count)
|
||||
for i := 0; i < int(count); i++ {
|
||||
list[i] = int(binary.BigEndian.Uint16(b[idx+2+2*i:]))
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// parseUint16 parses a 16 bit unsigned integer.
|
||||
func parseUint16(b []byte) (int, error) {
|
||||
if len(b) < 2 {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
return int(binary.BigEndian.Uint16(b)), nil
|
||||
}
|
||||
|
||||
// parseInt16 parses a 16 bit signed integer.
|
||||
func parseInt16(b []byte) int {
|
||||
return int(int16(b[1]) | int16(b[0])<<8)
|
||||
}
|
||||
|
||||
// sum computes the sum of a slice.
|
||||
func sum(list []int) (s int) {
|
||||
for _, n := range list {
|
||||
s += n
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Container) String() string {
|
||||
var output = []string{
|
||||
"Header",
|
||||
fmt.Sprintf(" - EOFMagic: %02x", eofMagic),
|
||||
fmt.Sprintf(" - EOFVersion: %02x", eof1Version),
|
||||
fmt.Sprintf(" - KindType: %02x", kindTypes),
|
||||
fmt.Sprintf(" - TypesSize: %04x", len(c.types)*4),
|
||||
fmt.Sprintf(" - KindCode: %02x", kindCode),
|
||||
fmt.Sprintf(" - KindData: %02x", kindData),
|
||||
fmt.Sprintf(" - DataSize: %04x", len(c.data)),
|
||||
fmt.Sprintf(" - Number of code sections: %d", len(c.codeSections)),
|
||||
}
|
||||
for i, code := range c.codeSections {
|
||||
output = append(output, fmt.Sprintf(" - Code section %d length: %04x", i, len(code)))
|
||||
}
|
||||
|
||||
output = append(output, fmt.Sprintf(" - Number of subcontainers: %d", len(c.subContainers)))
|
||||
if len(c.subContainers) > 0 {
|
||||
for i, section := range c.subContainers {
|
||||
output = append(output, fmt.Sprintf(" - subcontainer %d length: %04x\n", i, len(section.MarshalBinary())))
|
||||
}
|
||||
}
|
||||
output = append(output, "Body")
|
||||
for i, typ := range c.types {
|
||||
output = append(output, fmt.Sprintf(" - Type %v: %x", i,
|
||||
[]byte{typ.inputs, typ.outputs, byte(typ.maxStackHeight >> 8), byte(typ.maxStackHeight & 0x00ff)}))
|
||||
}
|
||||
for i, code := range c.codeSections {
|
||||
output = append(output, fmt.Sprintf(" - Code section %d: %#x", i, code))
|
||||
}
|
||||
for i, section := range c.subContainers {
|
||||
output = append(output, fmt.Sprintf(" - Subcontainer %d: %x", i, section.MarshalBinary()))
|
||||
}
|
||||
output = append(output, fmt.Sprintf(" - Data: %#x", c.data))
|
||||
return strings.Join(output, "\n")
|
||||
}
|
||||
|
|
@ -1,235 +0,0 @@
|
|||
// Copyright 2024 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package vm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
func validateControlFlow(code []byte, section int, metadata []*functionMetadata, jt *JumpTable) (int, error) {
|
||||
var (
|
||||
maxStackHeight = int(metadata[section].inputs)
|
||||
visitCount = 0
|
||||
next = make([]int, 0, 1)
|
||||
)
|
||||
var (
|
||||
stackBoundsMax = make([]uint16, len(code))
|
||||
stackBoundsMin = make([]uint16, len(code))
|
||||
)
|
||||
setBounds := func(pos, min, maxi int) {
|
||||
// The stackboundMax slice is a bit peculiar. We use `0` to denote
|
||||
// not set. Therefore, we use `1` to represent the value `0`, and so on.
|
||||
// So if the caller wants to store `1` as max bound, we internally store it as
|
||||
// `2`.
|
||||
if stackBoundsMax[pos] == 0 { // Not yet set
|
||||
visitCount++
|
||||
}
|
||||
if maxi < 65535 {
|
||||
stackBoundsMax[pos] = uint16(maxi + 1)
|
||||
}
|
||||
stackBoundsMin[pos] = uint16(min)
|
||||
maxStackHeight = max(maxStackHeight, maxi)
|
||||
}
|
||||
getStackMaxMin := func(pos int) (ok bool, min, max int) {
|
||||
maxi := stackBoundsMax[pos]
|
||||
if maxi == 0 { // Not yet set
|
||||
return false, 0, 0
|
||||
}
|
||||
return true, int(stackBoundsMin[pos]), int(maxi - 1)
|
||||
}
|
||||
// set the initial stack bounds
|
||||
setBounds(0, int(metadata[section].inputs), int(metadata[section].inputs))
|
||||
|
||||
qualifiedExit := false
|
||||
for pos := 0; pos < len(code); pos++ {
|
||||
op := OpCode(code[pos])
|
||||
ok, currentStackMin, currentStackMax := getStackMaxMin(pos)
|
||||
if !ok {
|
||||
return 0, errUnreachableCode
|
||||
}
|
||||
|
||||
switch op {
|
||||
case CALLF:
|
||||
arg, _ := parseUint16(code[pos+1:])
|
||||
newSection := metadata[arg]
|
||||
if err := newSection.checkInputs(currentStackMin); err != nil {
|
||||
return 0, fmt.Errorf("%w: at pos %d", err, pos)
|
||||
}
|
||||
if err := newSection.checkStackMax(currentStackMax); err != nil {
|
||||
return 0, fmt.Errorf("%w: at pos %d", err, pos)
|
||||
}
|
||||
delta := newSection.stackDelta()
|
||||
currentStackMax += delta
|
||||
currentStackMin += delta
|
||||
case RETF:
|
||||
/* From the spec:
|
||||
> for RETF the following must hold: stack_height_max == stack_height_min == types[current_code_index].outputs,
|
||||
|
||||
In other words: RETF must unambiguously return all items remaining on the stack.
|
||||
*/
|
||||
if currentStackMax != currentStackMin {
|
||||
return 0, fmt.Errorf("%w: max %d, min %d, at pos %d", errInvalidOutputs, currentStackMax, currentStackMin, pos)
|
||||
}
|
||||
numOutputs := int(metadata[section].outputs)
|
||||
if numOutputs >= maxOutputItems {
|
||||
return 0, fmt.Errorf("%w: at pos %d", errInvalidNonReturningFlag, pos)
|
||||
}
|
||||
if numOutputs != currentStackMin {
|
||||
return 0, fmt.Errorf("%w: have %d, want %d, at pos %d", errInvalidOutputs, numOutputs, currentStackMin, pos)
|
||||
}
|
||||
qualifiedExit = true
|
||||
case JUMPF:
|
||||
arg, _ := parseUint16(code[pos+1:])
|
||||
newSection := metadata[arg]
|
||||
|
||||
if err := newSection.checkStackMax(currentStackMax); err != nil {
|
||||
return 0, fmt.Errorf("%w: at pos %d", err, pos)
|
||||
}
|
||||
|
||||
if newSection.outputs == 0x80 {
|
||||
if err := newSection.checkInputs(currentStackMin); err != nil {
|
||||
return 0, fmt.Errorf("%w: at pos %d", err, pos)
|
||||
}
|
||||
} else {
|
||||
if currentStackMax != currentStackMin {
|
||||
return 0, fmt.Errorf("%w: max %d, min %d, at pos %d", errInvalidOutputs, currentStackMax, currentStackMin, pos)
|
||||
}
|
||||
wantStack := int(metadata[section].outputs) - newSection.stackDelta()
|
||||
if currentStackMax != wantStack {
|
||||
return 0, fmt.Errorf("%w: at pos %d", errInvalidOutputs, pos)
|
||||
}
|
||||
}
|
||||
qualifiedExit = qualifiedExit || newSection.outputs < maxOutputItems
|
||||
case DUPN:
|
||||
arg := int(code[pos+1]) + 1
|
||||
if want, have := arg, currentStackMin; want > have {
|
||||
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
|
||||
}
|
||||
case SWAPN:
|
||||
arg := int(code[pos+1]) + 1
|
||||
if want, have := arg+1, currentStackMin; want > have {
|
||||
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
|
||||
}
|
||||
case EXCHANGE:
|
||||
arg := int(code[pos+1])
|
||||
n := arg>>4 + 1
|
||||
m := arg&0x0f + 1
|
||||
if want, have := n+m+1, currentStackMin; want > have {
|
||||
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
|
||||
}
|
||||
default:
|
||||
if want, have := jt[op].minStack, currentStackMin; want > have {
|
||||
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
|
||||
}
|
||||
}
|
||||
if !terminals[op] && op != CALLF {
|
||||
change := int(params.StackLimit) - jt[op].maxStack
|
||||
currentStackMax += change
|
||||
currentStackMin += change
|
||||
}
|
||||
next = next[:0]
|
||||
switch op {
|
||||
case RJUMP:
|
||||
nextPos := pos + 2 + parseInt16(code[pos+1:])
|
||||
next = append(next, nextPos)
|
||||
// We set the stack bounds of the destination
|
||||
// and skip the argument, only for RJUMP, all other opcodes are handled later
|
||||
if nextPos+1 < pos {
|
||||
ok, nextMin, nextMax := getStackMaxMin(nextPos + 1)
|
||||
if !ok {
|
||||
return 0, errInvalidBackwardJump
|
||||
}
|
||||
if nextMax != currentStackMax || nextMin != currentStackMin {
|
||||
return 0, errInvalidMaxStackHeight
|
||||
}
|
||||
} else {
|
||||
ok, nextMin, nextMax := getStackMaxMin(nextPos + 1)
|
||||
if !ok {
|
||||
setBounds(nextPos+1, currentStackMin, currentStackMax)
|
||||
} else {
|
||||
setBounds(nextPos+1, min(nextMin, currentStackMin), max(nextMax, currentStackMax))
|
||||
}
|
||||
}
|
||||
case RJUMPI:
|
||||
arg := parseInt16(code[pos+1:])
|
||||
next = append(next, pos+2)
|
||||
next = append(next, pos+2+arg)
|
||||
case RJUMPV:
|
||||
count := int(code[pos+1]) + 1
|
||||
next = append(next, pos+1+2*count)
|
||||
for i := 0; i < count; i++ {
|
||||
arg := parseInt16(code[pos+2+2*i:])
|
||||
next = append(next, pos+1+2*count+arg)
|
||||
}
|
||||
default:
|
||||
if imm := int(immediates[op]); imm != 0 {
|
||||
next = append(next, pos+imm)
|
||||
} else {
|
||||
// Simple op, no operand.
|
||||
next = append(next, pos)
|
||||
}
|
||||
}
|
||||
|
||||
if op != RJUMP && !terminals[op] {
|
||||
for _, instr := range next {
|
||||
nextPC := instr + 1
|
||||
if nextPC >= len(code) {
|
||||
return 0, fmt.Errorf("%w: end with %s, pos %d", errInvalidCodeTermination, op, pos)
|
||||
}
|
||||
if nextPC > pos {
|
||||
// target reached via forward jump or seq flow
|
||||
ok, nextMin, nextMax := getStackMaxMin(nextPC)
|
||||
if !ok {
|
||||
setBounds(nextPC, currentStackMin, currentStackMax)
|
||||
} else {
|
||||
setBounds(nextPC, min(nextMin, currentStackMin), max(nextMax, currentStackMax))
|
||||
}
|
||||
} else {
|
||||
// target reached via backwards jump
|
||||
ok, nextMin, nextMax := getStackMaxMin(nextPC)
|
||||
if !ok {
|
||||
return 0, errInvalidBackwardJump
|
||||
}
|
||||
if currentStackMax != nextMax {
|
||||
return 0, fmt.Errorf("%w want %d as current max got %d at pos %d,", errInvalidBackwardJump, currentStackMax, nextMax, pos)
|
||||
}
|
||||
if currentStackMin != nextMin {
|
||||
return 0, fmt.Errorf("%w want %d as current min got %d at pos %d,", errInvalidBackwardJump, currentStackMin, nextMin, pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if op == RJUMP {
|
||||
pos += 2 // skip the immediate
|
||||
} else {
|
||||
pos = next[0]
|
||||
}
|
||||
}
|
||||
if qualifiedExit != (metadata[section].outputs < maxOutputItems) {
|
||||
return 0, fmt.Errorf("%w no RETF or qualified JUMPF", errInvalidNonReturningFlag)
|
||||
}
|
||||
if maxStackHeight >= int(params.StackLimit) {
|
||||
return 0, ErrStackOverflow{maxStackHeight, int(params.StackLimit)}
|
||||
}
|
||||
if maxStackHeight != int(metadata[section].maxStackHeight) {
|
||||
return 0, fmt.Errorf("%w in code section %d: have %d, want %d", errInvalidMaxStackHeight, section, maxStackHeight, metadata[section].maxStackHeight)
|
||||
}
|
||||
return visitCount, nil
|
||||
}
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
// Copyright 2024 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package vm
|
||||
|
||||
// immediate denotes how many immediate bytes an operation uses. This information
|
||||
// is not required during runtime, only during EOF-validation, so is not
|
||||
// places into the op-struct in the instruction table.
|
||||
// Note: the immediates is fork-agnostic, and assumes that validity of opcodes at
|
||||
// the given time is performed elsewhere.
|
||||
var immediates [256]uint8
|
||||
|
||||
// terminals denotes whether instructions can be the final opcode in a code section.
|
||||
// Note: the terminals is fork-agnostic, and assumes that validity of opcodes at
|
||||
// the given time is performed elsewhere.
|
||||
var terminals [256]bool
|
||||
|
||||
func init() {
|
||||
// The legacy pushes
|
||||
for i := uint8(1); i < 33; i++ {
|
||||
immediates[int(PUSH0)+int(i)] = i
|
||||
}
|
||||
// And new eof opcodes.
|
||||
immediates[DATALOADN] = 2
|
||||
immediates[RJUMP] = 2
|
||||
immediates[RJUMPI] = 2
|
||||
immediates[RJUMPV] = 3
|
||||
immediates[CALLF] = 2
|
||||
immediates[JUMPF] = 2
|
||||
immediates[DUPN] = 1
|
||||
immediates[SWAPN] = 1
|
||||
immediates[EXCHANGE] = 1
|
||||
immediates[EOFCREATE] = 1
|
||||
immediates[RETURNCONTRACT] = 1
|
||||
|
||||
// Define the terminals.
|
||||
terminals[STOP] = true
|
||||
terminals[RETF] = true
|
||||
terminals[JUMPF] = true
|
||||
terminals[RETURNCONTRACT] = true
|
||||
terminals[RETURN] = true
|
||||
terminals[REVERT] = true
|
||||
terminals[INVALID] = true
|
||||
}
|
||||
|
||||
// Immediates returns the number bytes of immediates (argument not from
|
||||
// stack but from code) a given opcode has.
|
||||
// OBS:
|
||||
// - This function assumes EOF instruction-set. It cannot be upon in
|
||||
// a. pre-EOF code
|
||||
// b. post-EOF but legacy code
|
||||
// - RJUMPV is unique as it has a variable sized operand. The total size is
|
||||
// determined by the count byte which immediately follows RJUMPV. This method
|
||||
// will return '3' for RJUMPV, which is the minimum.
|
||||
func Immediates(op OpCode) int {
|
||||
return int(immediates[op])
|
||||
}
|
||||
|
|
@ -1,112 +0,0 @@
|
|||
// Copyright 2024 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package vm
|
||||
|
||||
// opRjump implements the RJUMP opcode.
|
||||
func opRjump(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// opRjumpi implements the RJUMPI opcode
|
||||
func opRjumpi(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// opRjumpv implements the RJUMPV opcode
|
||||
func opRjumpv(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// opCallf implements the CALLF opcode
|
||||
func opCallf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// opRetf implements the RETF opcode
|
||||
func opRetf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// opJumpf implements the JUMPF opcode
|
||||
func opJumpf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// opEOFCreate implements the EOFCREATE opcode
|
||||
func opEOFCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// opReturnContract implements the RETURNCONTRACT opcode
|
||||
func opReturnContract(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// opDataLoad implements the DATALOAD opcode
|
||||
func opDataLoad(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// opDataLoadN implements the DATALOADN opcode
|
||||
func opDataLoadN(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// opDataSize implements the DATASIZE opcode
|
||||
func opDataSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// opDataCopy implements the DATACOPY opcode
|
||||
func opDataCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// opDupN implements the DUPN opcode
|
||||
func opDupN(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// opSwapN implements the SWAPN opcode
|
||||
func opSwapN(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// opExchange implements the EXCHANGE opcode
|
||||
func opExchange(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// opReturnDataLoad implements the RETURNDATALOAD opcode
|
||||
func opReturnDataLoad(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// opExtCall implements the EOFCREATE opcode
|
||||
func opExtCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// opExtDelegateCall implements the EXTDELEGATECALL opcode
|
||||
func opExtDelegateCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// opExtStaticCall implements the EXTSTATICCALL opcode
|
||||
func opExtStaticCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
|
@ -1,117 +0,0 @@
|
|||
// Copyright 2024 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package vm
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
func TestEOFMarshaling(t *testing.T) {
|
||||
for i, test := range []struct {
|
||||
want Container
|
||||
err error
|
||||
}{
|
||||
{
|
||||
want: Container{
|
||||
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
|
||||
codeSections: [][]byte{common.Hex2Bytes("604200")},
|
||||
data: []byte{0x01, 0x02, 0x03},
|
||||
dataSize: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
want: Container{
|
||||
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
|
||||
codeSections: [][]byte{common.Hex2Bytes("604200")},
|
||||
data: []byte{0x01, 0x02, 0x03},
|
||||
dataSize: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
want: Container{
|
||||
types: []*functionMetadata{
|
||||
{inputs: 0, outputs: 0x80, maxStackHeight: 1},
|
||||
{inputs: 2, outputs: 3, maxStackHeight: 4},
|
||||
{inputs: 1, outputs: 1, maxStackHeight: 1},
|
||||
},
|
||||
codeSections: [][]byte{
|
||||
common.Hex2Bytes("604200"),
|
||||
common.Hex2Bytes("6042604200"),
|
||||
common.Hex2Bytes("00"),
|
||||
},
|
||||
data: []byte{},
|
||||
},
|
||||
},
|
||||
} {
|
||||
var (
|
||||
b = test.want.MarshalBinary()
|
||||
got Container
|
||||
)
|
||||
t.Logf("b: %#x", b)
|
||||
if err := got.UnmarshalBinary(b, true); err != nil && err != test.err {
|
||||
t.Fatalf("test %d: got error \"%v\", want \"%v\"", i, err, test.err)
|
||||
}
|
||||
if !reflect.DeepEqual(got, test.want) {
|
||||
t.Fatalf("test %d: got %+v, want %+v", i, got, test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEOFSubcontainer(t *testing.T) {
|
||||
var subcontainer = new(Container)
|
||||
if err := subcontainer.UnmarshalBinary(common.Hex2Bytes("ef000101000402000100010400000000800000fe"), true); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
container := Container{
|
||||
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
|
||||
codeSections: [][]byte{common.Hex2Bytes("604200")},
|
||||
subContainers: []*Container{subcontainer},
|
||||
data: []byte{0x01, 0x02, 0x03},
|
||||
dataSize: 3,
|
||||
}
|
||||
var (
|
||||
b = container.MarshalBinary()
|
||||
got Container
|
||||
)
|
||||
if err := got.UnmarshalBinary(b, true); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if res := got.MarshalBinary(); !reflect.DeepEqual(res, b) {
|
||||
t.Fatalf("invalid marshalling, want %v got %v", b, res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshaling(t *testing.T) {
|
||||
tests := []string{
|
||||
"EF000101000402000100040400000000800000E0000000",
|
||||
"ef0001010004020001000d04000000008000025fe100055f5fe000035f600100",
|
||||
}
|
||||
for i, test := range tests {
|
||||
s, err := hex.DecodeString(test)
|
||||
if err != nil {
|
||||
t.Fatalf("test %d: error decoding: %v", i, err)
|
||||
}
|
||||
var got Container
|
||||
if err := got.UnmarshalBinary(s, true); err != nil {
|
||||
t.Fatalf("test %d: got error %v", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,255 +0,0 @@
|
|||
// Copyright 2024 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package vm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Below are all possible errors that can occur during validation of
|
||||
// EOF containers.
|
||||
var (
|
||||
errInvalidMagic = errors.New("invalid magic")
|
||||
errUndefinedInstruction = errors.New("undefined instruction")
|
||||
errTruncatedImmediate = errors.New("truncated immediate")
|
||||
errInvalidSectionArgument = errors.New("invalid section argument")
|
||||
errInvalidCallArgument = errors.New("callf into non-returning section")
|
||||
errInvalidDataloadNArgument = errors.New("invalid dataloadN argument")
|
||||
errInvalidJumpDest = errors.New("invalid jump destination")
|
||||
errInvalidBackwardJump = errors.New("invalid backward jump")
|
||||
errInvalidOutputs = errors.New("invalid number of outputs")
|
||||
errInvalidMaxStackHeight = errors.New("invalid max stack height")
|
||||
errInvalidCodeTermination = errors.New("invalid code termination")
|
||||
errEOFCreateWithTruncatedSection = errors.New("eofcreate with truncated section")
|
||||
errOrphanedSubcontainer = errors.New("subcontainer not referenced at all")
|
||||
errIncompatibleContainerKind = errors.New("incompatible container kind")
|
||||
errStopAndReturnContract = errors.New("Stop/Return and Returncontract in the same code section")
|
||||
errStopInInitCode = errors.New("initcode contains a RETURN or STOP opcode")
|
||||
errTruncatedTopLevelContainer = errors.New("truncated top level container")
|
||||
errUnreachableCode = errors.New("unreachable code")
|
||||
errInvalidNonReturningFlag = errors.New("invalid non-returning flag, bad RETF")
|
||||
errInvalidVersion = errors.New("invalid version")
|
||||
errMissingTypeHeader = errors.New("missing type header")
|
||||
errInvalidTypeSize = errors.New("invalid type section size")
|
||||
errMissingCodeHeader = errors.New("missing code header")
|
||||
errInvalidCodeSize = errors.New("invalid code size")
|
||||
errInvalidContainerSectionSize = errors.New("invalid container section size")
|
||||
errMissingDataHeader = errors.New("missing data header")
|
||||
errMissingTerminator = errors.New("missing header terminator")
|
||||
errTooManyInputs = errors.New("invalid type content, too many inputs")
|
||||
errTooManyOutputs = errors.New("invalid type content, too many outputs")
|
||||
errInvalidSection0Type = errors.New("invalid section 0 type, input and output should be zero and non-returning (0x80)")
|
||||
errTooLargeMaxStackHeight = errors.New("invalid type content, max stack height exceeds limit")
|
||||
errInvalidContainerSize = errors.New("invalid container size")
|
||||
)
|
||||
|
||||
const (
|
||||
notRefByEither = iota
|
||||
refByReturnContract
|
||||
refByEOFCreate
|
||||
)
|
||||
|
||||
type validationResult struct {
|
||||
visitedCode map[int]struct{}
|
||||
visitedSubContainers map[int]int
|
||||
isInitCode bool
|
||||
isRuntime bool
|
||||
}
|
||||
|
||||
// validateCode validates the code parameter against the EOF v1 validity requirements.
|
||||
func validateCode(code []byte, section int, container *Container, jt *JumpTable, isInitCode bool) (*validationResult, error) {
|
||||
var (
|
||||
i = 0
|
||||
// Tracks the number of actual instructions in the code (e.g.
|
||||
// non-immediate values). This is used at the end to determine
|
||||
// if each instruction is reachable.
|
||||
count = 0
|
||||
op OpCode
|
||||
analysis bitvec
|
||||
visitedCode map[int]struct{}
|
||||
visitedSubcontainers map[int]int
|
||||
hasReturnContract bool
|
||||
hasStop bool
|
||||
)
|
||||
// This loop visits every single instruction and verifies:
|
||||
// * if the instruction is valid for the given jump table.
|
||||
// * if the instruction has an immediate value, it is not truncated.
|
||||
// * if performing a relative jump, all jump destinations are valid.
|
||||
// * if changing code sections, the new code section index is valid and
|
||||
// will not cause a stack overflow.
|
||||
for i < len(code) {
|
||||
count++
|
||||
op = OpCode(code[i])
|
||||
if jt[op].undefined {
|
||||
return nil, fmt.Errorf("%w: op %s, pos %d", errUndefinedInstruction, op, i)
|
||||
}
|
||||
size := int(immediates[op])
|
||||
if size != 0 && len(code) <= i+size {
|
||||
return nil, fmt.Errorf("%w: op %s, pos %d", errTruncatedImmediate, op, i)
|
||||
}
|
||||
switch op {
|
||||
case RJUMP, RJUMPI:
|
||||
if err := checkDest(code, &analysis, i+1, i+3, len(code)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case RJUMPV:
|
||||
maxSize := int(code[i+1])
|
||||
length := maxSize + 1
|
||||
if len(code) <= i+length {
|
||||
return nil, fmt.Errorf("%w: jump table truncated, op %s, pos %d", errTruncatedImmediate, op, i)
|
||||
}
|
||||
offset := i + 2
|
||||
for j := 0; j < length; j++ {
|
||||
if err := checkDest(code, &analysis, offset+j*2, offset+(length*2), len(code)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
i += 2 * maxSize
|
||||
case CALLF:
|
||||
arg, _ := parseUint16(code[i+1:])
|
||||
if arg >= len(container.types) {
|
||||
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", errInvalidSectionArgument, arg, len(container.types), i)
|
||||
}
|
||||
if container.types[arg].outputs == 0x80 {
|
||||
return nil, fmt.Errorf("%w: section %v", errInvalidCallArgument, arg)
|
||||
}
|
||||
if visitedCode == nil {
|
||||
visitedCode = make(map[int]struct{})
|
||||
}
|
||||
visitedCode[arg] = struct{}{}
|
||||
case JUMPF:
|
||||
arg, _ := parseUint16(code[i+1:])
|
||||
if arg >= len(container.types) {
|
||||
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", errInvalidSectionArgument, arg, len(container.types), i)
|
||||
}
|
||||
if container.types[arg].outputs != 0x80 && container.types[arg].outputs > container.types[section].outputs {
|
||||
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", errInvalidOutputs, arg, len(container.types), i)
|
||||
}
|
||||
if visitedCode == nil {
|
||||
visitedCode = make(map[int]struct{})
|
||||
}
|
||||
visitedCode[arg] = struct{}{}
|
||||
case DATALOADN:
|
||||
arg, _ := parseUint16(code[i+1:])
|
||||
// TODO why are we checking this? We should just pad
|
||||
if arg+32 > len(container.data) {
|
||||
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", errInvalidDataloadNArgument, arg, len(container.data), i)
|
||||
}
|
||||
case RETURNCONTRACT:
|
||||
if !isInitCode {
|
||||
return nil, errIncompatibleContainerKind
|
||||
}
|
||||
arg := int(code[i+1])
|
||||
if arg >= len(container.subContainers) {
|
||||
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", errUnreachableCode, arg, len(container.subContainers), i)
|
||||
}
|
||||
if visitedSubcontainers == nil {
|
||||
visitedSubcontainers = make(map[int]int)
|
||||
}
|
||||
// We need to store per subcontainer how it was referenced
|
||||
if v, ok := visitedSubcontainers[arg]; ok && v != refByReturnContract {
|
||||
return nil, fmt.Errorf("section already referenced, arg :%d", arg)
|
||||
}
|
||||
if hasStop {
|
||||
return nil, errStopAndReturnContract
|
||||
}
|
||||
hasReturnContract = true
|
||||
visitedSubcontainers[arg] = refByReturnContract
|
||||
case EOFCREATE:
|
||||
arg := int(code[i+1])
|
||||
if arg >= len(container.subContainers) {
|
||||
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", errUnreachableCode, arg, len(container.subContainers), i)
|
||||
}
|
||||
if ct := container.subContainers[arg]; len(ct.data) != ct.dataSize {
|
||||
return nil, fmt.Errorf("%w: container %d, have %d, claimed %d, pos %d", errEOFCreateWithTruncatedSection, arg, len(ct.data), ct.dataSize, i)
|
||||
}
|
||||
if visitedSubcontainers == nil {
|
||||
visitedSubcontainers = make(map[int]int)
|
||||
}
|
||||
// We need to store per subcontainer how it was referenced
|
||||
if v, ok := visitedSubcontainers[arg]; ok && v != refByEOFCreate {
|
||||
return nil, fmt.Errorf("section already referenced, arg :%d", arg)
|
||||
}
|
||||
visitedSubcontainers[arg] = refByEOFCreate
|
||||
case STOP, RETURN:
|
||||
if isInitCode {
|
||||
return nil, errStopInInitCode
|
||||
}
|
||||
if hasReturnContract {
|
||||
return nil, errStopAndReturnContract
|
||||
}
|
||||
hasStop = true
|
||||
}
|
||||
i += size + 1
|
||||
}
|
||||
// Code sections may not "fall through" and require proper termination.
|
||||
// Therefore, the last instruction must be considered terminal or RJUMP.
|
||||
if !terminals[op] && op != RJUMP {
|
||||
return nil, fmt.Errorf("%w: end with %s, pos %d", errInvalidCodeTermination, op, i)
|
||||
}
|
||||
if paths, err := validateControlFlow(code, section, container.types, jt); err != nil {
|
||||
return nil, err
|
||||
} else if paths != count {
|
||||
// TODO(matt): return actual position of unreachable code
|
||||
return nil, errUnreachableCode
|
||||
}
|
||||
return &validationResult{
|
||||
visitedCode: visitedCode,
|
||||
visitedSubContainers: visitedSubcontainers,
|
||||
isInitCode: hasReturnContract,
|
||||
isRuntime: hasStop,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// checkDest parses a relative offset at code[0:2] and checks if it is a valid jump destination.
|
||||
func checkDest(code []byte, analysis *bitvec, imm, from, length int) error {
|
||||
if len(code) < imm+2 {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
if analysis != nil && *analysis == nil {
|
||||
*analysis = eofCodeBitmap(code)
|
||||
}
|
||||
offset := parseInt16(code[imm:])
|
||||
dest := from + offset
|
||||
if dest < 0 || dest >= length {
|
||||
return fmt.Errorf("%w: out-of-bounds offset: offset %d, dest %d, pos %d", errInvalidJumpDest, offset, dest, imm)
|
||||
}
|
||||
if !analysis.codeSegment(uint64(dest)) {
|
||||
return fmt.Errorf("%w: offset into immediate: offset %d, dest %d, pos %d", errInvalidJumpDest, offset, dest, imm)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//// disasm is a helper utility to show a sequence of comma-separated operations,
|
||||
//// with immediates shown inline,
|
||||
//// e.g: PUSH1(0x00),EOFCREATE(0x00),
|
||||
//func disasm(code []byte) string {
|
||||
// var ops []string
|
||||
// for i := 0; i < len(code); i++ {
|
||||
// var op string
|
||||
// if args := immediates[code[i]]; args > 0 {
|
||||
// op = fmt.Sprintf("%v(%#x)", OpCode(code[i]).String(), code[i+1:i+1+int(args)])
|
||||
// i += int(args)
|
||||
// } else {
|
||||
// op = OpCode(code[i]).String()
|
||||
// }
|
||||
// ops = append(ops, op)
|
||||
// }
|
||||
// return strings.Join(ops, ",")
|
||||
//}
|
||||
|
|
@ -1,517 +0,0 @@
|
|||
// Copyright 2024 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package vm
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
func TestValidateCode(t *testing.T) {
|
||||
for i, test := range []struct {
|
||||
code []byte
|
||||
section int
|
||||
metadata []*functionMetadata
|
||||
err error
|
||||
}{
|
||||
{
|
||||
code: []byte{
|
||||
byte(CALLER),
|
||||
byte(POP),
|
||||
byte(STOP),
|
||||
},
|
||||
section: 0,
|
||||
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
|
||||
},
|
||||
{
|
||||
code: []byte{
|
||||
byte(CALLF), 0x00, 0x00,
|
||||
byte(RETF),
|
||||
},
|
||||
section: 0,
|
||||
metadata: []*functionMetadata{{inputs: 0, outputs: 0, maxStackHeight: 0}},
|
||||
},
|
||||
{
|
||||
code: []byte{
|
||||
byte(ADDRESS),
|
||||
byte(CALLF), 0x00, 0x00,
|
||||
byte(POP),
|
||||
byte(RETF),
|
||||
},
|
||||
section: 0,
|
||||
metadata: []*functionMetadata{{inputs: 0, outputs: 0, maxStackHeight: 1}},
|
||||
},
|
||||
{
|
||||
code: []byte{
|
||||
byte(CALLER),
|
||||
byte(POP),
|
||||
},
|
||||
section: 0,
|
||||
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
|
||||
err: errInvalidCodeTermination,
|
||||
},
|
||||
{
|
||||
code: []byte{
|
||||
byte(RJUMP),
|
||||
byte(0x00),
|
||||
byte(0x01),
|
||||
byte(CALLER),
|
||||
byte(STOP),
|
||||
},
|
||||
section: 0,
|
||||
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 0}},
|
||||
err: errUnreachableCode,
|
||||
},
|
||||
{
|
||||
code: []byte{
|
||||
byte(PUSH1),
|
||||
byte(0x42),
|
||||
byte(ADD),
|
||||
byte(STOP),
|
||||
},
|
||||
section: 0,
|
||||
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
|
||||
err: ErrStackUnderflow{stackLen: 1, required: 2},
|
||||
},
|
||||
{
|
||||
code: []byte{
|
||||
byte(PUSH1),
|
||||
byte(0x42),
|
||||
byte(POP),
|
||||
byte(STOP),
|
||||
},
|
||||
section: 0,
|
||||
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 2}},
|
||||
err: errInvalidMaxStackHeight,
|
||||
},
|
||||
{
|
||||
code: []byte{
|
||||
byte(PUSH0),
|
||||
byte(RJUMPI),
|
||||
byte(0x00),
|
||||
byte(0x01),
|
||||
byte(PUSH1),
|
||||
byte(0x42), // jumps to here
|
||||
byte(POP),
|
||||
byte(STOP),
|
||||
},
|
||||
section: 0,
|
||||
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
|
||||
err: errInvalidJumpDest,
|
||||
},
|
||||
{
|
||||
code: []byte{
|
||||
byte(PUSH0),
|
||||
byte(RJUMPV),
|
||||
byte(0x01),
|
||||
byte(0x00),
|
||||
byte(0x01),
|
||||
byte(0x00),
|
||||
byte(0x02),
|
||||
byte(PUSH1),
|
||||
byte(0x42), // jumps to here
|
||||
byte(POP), // and here
|
||||
byte(STOP),
|
||||
},
|
||||
section: 0,
|
||||
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
|
||||
err: errInvalidJumpDest,
|
||||
},
|
||||
{
|
||||
code: []byte{
|
||||
byte(PUSH0),
|
||||
byte(RJUMPV),
|
||||
byte(0x00),
|
||||
byte(STOP),
|
||||
},
|
||||
section: 0,
|
||||
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
|
||||
err: errTruncatedImmediate,
|
||||
},
|
||||
{
|
||||
code: []byte{
|
||||
byte(RJUMP), 0x00, 0x03,
|
||||
byte(JUMPDEST), // this code is unreachable to forward jumps alone
|
||||
byte(JUMPDEST),
|
||||
byte(RETURN),
|
||||
byte(PUSH1), 20,
|
||||
byte(PUSH1), 39,
|
||||
byte(PUSH1), 0x00,
|
||||
byte(DATACOPY),
|
||||
byte(PUSH1), 20,
|
||||
byte(PUSH1), 0x00,
|
||||
byte(RJUMP), 0xff, 0xef,
|
||||
},
|
||||
section: 0,
|
||||
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 3}},
|
||||
err: errUnreachableCode,
|
||||
},
|
||||
{
|
||||
code: []byte{
|
||||
byte(PUSH1), 1,
|
||||
byte(RJUMPI), 0x00, 0x03,
|
||||
byte(JUMPDEST),
|
||||
byte(JUMPDEST),
|
||||
byte(STOP),
|
||||
byte(PUSH1), 20,
|
||||
byte(PUSH1), 39,
|
||||
byte(PUSH1), 0x00,
|
||||
byte(DATACOPY),
|
||||
byte(PUSH1), 20,
|
||||
byte(PUSH1), 0x00,
|
||||
byte(RETURN),
|
||||
},
|
||||
section: 0,
|
||||
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 3}},
|
||||
},
|
||||
{
|
||||
code: []byte{
|
||||
byte(PUSH1), 1,
|
||||
byte(RJUMPV), 0x01, 0x00, 0x03, 0xff, 0xf8,
|
||||
byte(JUMPDEST),
|
||||
byte(JUMPDEST),
|
||||
byte(STOP),
|
||||
byte(PUSH1), 20,
|
||||
byte(PUSH1), 39,
|
||||
byte(PUSH1), 0x00,
|
||||
byte(DATACOPY),
|
||||
byte(PUSH1), 20,
|
||||
byte(PUSH1), 0x00,
|
||||
byte(RETURN),
|
||||
},
|
||||
section: 0,
|
||||
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 3}},
|
||||
},
|
||||
{
|
||||
code: []byte{
|
||||
byte(STOP),
|
||||
byte(STOP),
|
||||
byte(INVALID),
|
||||
},
|
||||
section: 0,
|
||||
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 0}},
|
||||
err: errUnreachableCode,
|
||||
},
|
||||
{
|
||||
code: []byte{
|
||||
byte(RETF),
|
||||
},
|
||||
section: 0,
|
||||
metadata: []*functionMetadata{{inputs: 0, outputs: 1, maxStackHeight: 0}},
|
||||
err: errInvalidOutputs,
|
||||
},
|
||||
{
|
||||
code: []byte{
|
||||
byte(RETF),
|
||||
},
|
||||
section: 0,
|
||||
metadata: []*functionMetadata{{inputs: 3, outputs: 3, maxStackHeight: 3}},
|
||||
},
|
||||
{
|
||||
code: []byte{
|
||||
byte(CALLF), 0x00, 0x01,
|
||||
byte(POP),
|
||||
byte(STOP),
|
||||
},
|
||||
section: 0,
|
||||
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}, {inputs: 0, outputs: 1, maxStackHeight: 0}},
|
||||
},
|
||||
{
|
||||
code: []byte{
|
||||
byte(ORIGIN),
|
||||
byte(ORIGIN),
|
||||
byte(CALLF), 0x00, 0x01,
|
||||
byte(POP),
|
||||
byte(RETF),
|
||||
},
|
||||
section: 0,
|
||||
metadata: []*functionMetadata{{inputs: 0, outputs: 0, maxStackHeight: 2}, {inputs: 2, outputs: 1, maxStackHeight: 2}},
|
||||
},
|
||||
} {
|
||||
container := &Container{
|
||||
types: test.metadata,
|
||||
data: make([]byte, 0),
|
||||
subContainers: make([]*Container, 0),
|
||||
}
|
||||
_, err := validateCode(test.code, test.section, container, &eofInstructionSet, false)
|
||||
if !errors.Is(err, test.err) {
|
||||
t.Errorf("test %d (%s): unexpected error (want: %v, got: %v)", i, common.Bytes2Hex(test.code), test.err, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkRJUMPI tries to benchmark the RJUMPI opcode validation
|
||||
// For this we do a bunch of RJUMPIs that jump backwards (in a potential infinite loop).
|
||||
func BenchmarkRJUMPI(b *testing.B) {
|
||||
snippet := []byte{
|
||||
byte(PUSH0),
|
||||
byte(RJUMPI), 0xFF, 0xFC,
|
||||
}
|
||||
code := []byte{}
|
||||
for i := 0; i < params.MaxCodeSize/len(snippet)-1; i++ {
|
||||
code = append(code, snippet...)
|
||||
}
|
||||
code = append(code, byte(STOP))
|
||||
container := &Container{
|
||||
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
|
||||
data: make([]byte, 0),
|
||||
subContainers: make([]*Container, 0),
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := validateCode(code, 0, container, &eofInstructionSet, false)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkRJUMPV tries to benchmark the validation of the RJUMPV opcode
|
||||
// for this we set up as many RJUMPV opcodes with a full jumptable (containing 0s) as possible.
|
||||
func BenchmarkRJUMPV(b *testing.B) {
|
||||
snippet := []byte{
|
||||
byte(PUSH0),
|
||||
byte(RJUMPV),
|
||||
0xff, // count
|
||||
0x00, 0x00,
|
||||
}
|
||||
for i := 0; i < 255; i++ {
|
||||
snippet = append(snippet, []byte{0x00, 0x00}...)
|
||||
}
|
||||
code := []byte{}
|
||||
for i := 0; i < 24576/len(snippet)-1; i++ {
|
||||
code = append(code, snippet...)
|
||||
}
|
||||
code = append(code, byte(PUSH0))
|
||||
code = append(code, byte(STOP))
|
||||
container := &Container{
|
||||
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
|
||||
data: make([]byte, 0),
|
||||
subContainers: make([]*Container, 0),
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := validateCode(code, 0, container, &pragueInstructionSet, false)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkEOFValidation tries to benchmark the code validation for the CALLF/RETF operation.
|
||||
// For this we set up code that calls into 1024 code sections which can either
|
||||
// - just contain a RETF opcode
|
||||
// - or code to again call into 1024 code sections.
|
||||
// We can't have all code sections calling each other, otherwise we would exceed 48KB.
|
||||
func BenchmarkEOFValidation(b *testing.B) {
|
||||
var container Container
|
||||
var code []byte
|
||||
maxSections := 1024
|
||||
for i := 0; i < maxSections; i++ {
|
||||
code = append(code, byte(CALLF))
|
||||
code = binary.BigEndian.AppendUint16(code, uint16(i%(maxSections-1))+1)
|
||||
}
|
||||
// First container
|
||||
container.codeSections = append(container.codeSections, append(code, byte(STOP)))
|
||||
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 0})
|
||||
|
||||
inner := []byte{
|
||||
byte(RETF),
|
||||
}
|
||||
|
||||
for i := 0; i < 1023; i++ {
|
||||
container.codeSections = append(container.codeSections, inner)
|
||||
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0, maxStackHeight: 0})
|
||||
}
|
||||
|
||||
for i := 0; i < 12; i++ {
|
||||
container.codeSections[i+1] = append(code, byte(RETF))
|
||||
}
|
||||
|
||||
bin := container.MarshalBinary()
|
||||
if len(bin) > 48*1024 {
|
||||
b.Fatal("Exceeds 48Kb")
|
||||
}
|
||||
|
||||
var container2 Container
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := container2.UnmarshalBinary(bin, true); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
if err := container2.ValidateCode(&pragueInstructionSet, false); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkEOFValidation2 tries to benchmark the code validation for the CALLF/RETF operation.
|
||||
// For this we set up code that calls into 1024 code sections which
|
||||
// - contain calls to some other code sections.
|
||||
// We can't have all code sections calling each other, otherwise we would exceed 48KB.
|
||||
func BenchmarkEOFValidation2(b *testing.B) {
|
||||
var container Container
|
||||
var code []byte
|
||||
maxSections := 1024
|
||||
for i := 0; i < maxSections; i++ {
|
||||
code = append(code, byte(CALLF))
|
||||
code = binary.BigEndian.AppendUint16(code, uint16(i%(maxSections-1))+1)
|
||||
}
|
||||
code = append(code, byte(STOP))
|
||||
// First container
|
||||
container.codeSections = append(container.codeSections, code)
|
||||
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 0})
|
||||
|
||||
inner := []byte{
|
||||
byte(CALLF), 0x03, 0xE8,
|
||||
byte(CALLF), 0x03, 0xE9,
|
||||
byte(CALLF), 0x03, 0xF0,
|
||||
byte(CALLF), 0x03, 0xF1,
|
||||
byte(CALLF), 0x03, 0xF2,
|
||||
byte(CALLF), 0x03, 0xF3,
|
||||
byte(CALLF), 0x03, 0xF4,
|
||||
byte(CALLF), 0x03, 0xF5,
|
||||
byte(CALLF), 0x03, 0xF6,
|
||||
byte(CALLF), 0x03, 0xF7,
|
||||
byte(CALLF), 0x03, 0xF8,
|
||||
byte(CALLF), 0x03, 0xF,
|
||||
byte(RETF),
|
||||
}
|
||||
|
||||
for i := 0; i < 1023; i++ {
|
||||
container.codeSections = append(container.codeSections, inner)
|
||||
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0, maxStackHeight: 0})
|
||||
}
|
||||
|
||||
bin := container.MarshalBinary()
|
||||
if len(bin) > 48*1024 {
|
||||
b.Fatal("Exceeds 48Kb")
|
||||
}
|
||||
|
||||
var container2 Container
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := container2.UnmarshalBinary(bin, true); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
if err := container2.ValidateCode(&pragueInstructionSet, false); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkEOFValidation3 tries to benchmark the code validation for the CALLF/RETF and RJUMPI/V operations.
|
||||
// For this we set up code that calls into 1024 code sections which either
|
||||
// - contain an RJUMP opcode
|
||||
// - contain calls to other code sections
|
||||
// We can't have all code sections calling each other, otherwise we would exceed 48KB.
|
||||
func BenchmarkEOFValidation3(b *testing.B) {
|
||||
var container Container
|
||||
var code []byte
|
||||
snippet := []byte{
|
||||
byte(PUSH0),
|
||||
byte(RJUMPV),
|
||||
0xff, // count
|
||||
0x00, 0x00,
|
||||
}
|
||||
for i := 0; i < 255; i++ {
|
||||
snippet = append(snippet, []byte{0x00, 0x00}...)
|
||||
}
|
||||
code = append(code, snippet...)
|
||||
// First container, calls into all other containers
|
||||
maxSections := 1024
|
||||
for i := 0; i < maxSections; i++ {
|
||||
code = append(code, byte(CALLF))
|
||||
code = binary.BigEndian.AppendUint16(code, uint16(i%(maxSections-1))+1)
|
||||
}
|
||||
code = append(code, byte(STOP))
|
||||
container.codeSections = append(container.codeSections, code)
|
||||
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 1})
|
||||
|
||||
// Other containers
|
||||
for i := 0; i < 1023; i++ {
|
||||
container.codeSections = append(container.codeSections, []byte{byte(RJUMP), 0x00, 0x00, byte(RETF)})
|
||||
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0, maxStackHeight: 0})
|
||||
}
|
||||
// Other containers
|
||||
for i := 0; i < 68; i++ {
|
||||
container.codeSections[i+1] = append(snippet, byte(RETF))
|
||||
container.types[i+1] = &functionMetadata{inputs: 0, outputs: 0, maxStackHeight: 1}
|
||||
}
|
||||
bin := container.MarshalBinary()
|
||||
if len(bin) > 48*1024 {
|
||||
b.Fatal("Exceeds 48Kb")
|
||||
}
|
||||
b.ResetTimer()
|
||||
b.ReportMetric(float64(len(bin)), "bytes")
|
||||
for i := 0; i < b.N; i++ {
|
||||
for k := 0; k < 40; k++ {
|
||||
var container2 Container
|
||||
if err := container2.UnmarshalBinary(bin, true); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
if err := container2.ValidateCode(&pragueInstructionSet, false); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRJUMPI_2(b *testing.B) {
|
||||
code := []byte{
|
||||
byte(PUSH0),
|
||||
byte(RJUMPI), 0xFF, 0xFC,
|
||||
}
|
||||
for i := 0; i < params.MaxCodeSize/4-1; i++ {
|
||||
code = append(code, byte(PUSH0))
|
||||
x := -4 * i
|
||||
code = append(code, byte(RJUMPI))
|
||||
code = binary.BigEndian.AppendUint16(code, uint16(x))
|
||||
}
|
||||
code = append(code, byte(STOP))
|
||||
container := &Container{
|
||||
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
|
||||
data: make([]byte, 0),
|
||||
subContainers: make([]*Container, 0),
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := validateCode(code, 0, container, &pragueInstructionSet, false)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func FuzzUnmarshalBinary(f *testing.F) {
|
||||
f.Fuzz(func(_ *testing.T, input []byte) {
|
||||
var container Container
|
||||
container.UnmarshalBinary(input, true)
|
||||
})
|
||||
}
|
||||
|
||||
func FuzzValidate(f *testing.F) {
|
||||
f.Fuzz(func(_ *testing.T, code []byte, maxStack uint16) {
|
||||
var container Container
|
||||
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: maxStack})
|
||||
validateCode(code, 0, &container, &pragueInstructionSet, true)
|
||||
})
|
||||
}
|
||||
|
|
@ -485,20 +485,3 @@ func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me
|
|||
}
|
||||
return gas, nil
|
||||
}
|
||||
|
||||
func gasExtCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func gasExtDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
func gasExtStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// gasEOFCreate returns the gas-cost for EOF-Create. Hashing charge needs to be
|
||||
// deducted in the opcode itself, since it depends on the immediate
|
||||
func gasEOFCreate(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,7 +62,6 @@ var (
|
|||
cancunInstructionSet = newCancunInstructionSet()
|
||||
verkleInstructionSet = newVerkleInstructionSet()
|
||||
pragueInstructionSet = newPragueInstructionSet()
|
||||
eofInstructionSet = newEOFInstructionSetForTesting()
|
||||
)
|
||||
|
||||
// JumpTable contains the EVM opcodes supported at a given fork.
|
||||
|
|
@ -92,16 +91,6 @@ func newVerkleInstructionSet() JumpTable {
|
|||
return validate(instructionSet)
|
||||
}
|
||||
|
||||
func NewEOFInstructionSetForTesting() JumpTable {
|
||||
return newEOFInstructionSetForTesting()
|
||||
}
|
||||
|
||||
func newEOFInstructionSetForTesting() JumpTable {
|
||||
instructionSet := newPragueInstructionSet()
|
||||
enableEOF(&instructionSet)
|
||||
return validate(instructionSet)
|
||||
}
|
||||
|
||||
func newPragueInstructionSet() JumpTable {
|
||||
instructionSet := newCancunInstructionSet()
|
||||
enable7702(&instructionSet) // EIP-7702 Setcode transaction type
|
||||
|
|
|
|||
|
|
@ -120,19 +120,3 @@ func memoryRevert(stack *Stack) (uint64, bool) {
|
|||
func memoryLog(stack *Stack) (uint64, bool) {
|
||||
return calcMemSize64(stack.Back(0), stack.Back(1))
|
||||
}
|
||||
|
||||
func memoryExtCall(stack *Stack) (uint64, bool) {
|
||||
return calcMemSize64(stack.Back(1), stack.Back(2))
|
||||
}
|
||||
|
||||
func memoryDataCopy(stack *Stack) (uint64, bool) {
|
||||
return calcMemSize64(stack.Back(0), stack.Back(2))
|
||||
}
|
||||
|
||||
func memoryEOFCreate(stack *Stack) (uint64, bool) {
|
||||
return calcMemSize64(stack.Back(2), stack.Back(3))
|
||||
}
|
||||
|
||||
func memoryReturnContract(stack *Stack) (uint64, bool) {
|
||||
return calcMemSize64(stack.Back(0), stack.Back(1))
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue