Merge branch 'main' of github.com:berachain/bera-geth into update-main

This commit is contained in:
Cal Bera 2025-07-21 20:33:16 -07:00
commit effa365faa
77 changed files with 2174 additions and 501 deletions

View file

@ -0,0 +1,10 @@
{
"permissions": {
"allow": [
"Bash(git add:*)",
"Bash(git commit:*)",
"Bash(git push:*)"
],
"deny": []
}
}

View file

@ -1,27 +0,0 @@
on:
workflow_dispatch:
### Note we cannot use cron-triggered builds right now, Gitea seems to have
### a few bugs in that area. So this workflow is scheduled using an external
### triggering mechanism and workflow_dispatch.
#
# schedule:
# - cron: '0 15 * * *'
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 }}

View file

@ -1,46 +0,0 @@
on:
push:
tags:
- "v*"
workflow_dispatch:
### Note we cannot use cron-triggered builds right now, Gitea seems to have
### a few bugs in that area. So this workflow is scheduled using an external
### triggering mechanism and workflow_dispatch.
#
# schedule:
# - cron: '0 16 * * *'
jobs:
ppa:
name: PPA Upload
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Show environment
run: |
env
- 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 }}

31
.github/CODEOWNERS vendored
View file

@ -1,33 +1,4 @@
# Lines starting with '#' are comments. # Lines starting with '#' are comments.
# Each line is a file pattern followed by one or more owners. # Each line is a file pattern followed by one or more owners.
accounts/usbwallet/ @gballet * @calbera
accounts/scwallet/ @gballet
accounts/abi/ @gballet @MariusVanDerWijden
beacon/engine/ @MariusVanDerWijden @lightclient @fjl
beacon/light/ @zsfelfoldi
beacon/merkle/ @zsfelfoldi
beacon/types/ @zsfelfoldi @fjl
beacon/params/ @zsfelfoldi @fjl
cmd/evm/ @MariusVanDerWijden @lightclient
core/state/ @rjl493456442
crypto/ @gballet @jwasinger @fjl
core/ @rjl493456442
eth/ @rjl493456442
eth/catalyst/ @MariusVanDerWijden @lightclient @fjl @jwasinger
eth/tracers/ @s1na
ethclient/ @fjl
ethdb/ @rjl493456442
event/ @fjl
trie/ @rjl493456442
triedb/ @rjl493456442
core/tracing/ @s1na
graphql/ @s1na
internal/ethapi/ @fjl @s1na @lightclient
internal/era/ @lightclient
miner/ @MariusVanDerWijden @fjl @rjl493456442
node/ @fjl
p2p/ @fjl @zsfelfoldi
rlp/ @fjl
params/ @fjl @gballet @rjl493456442 @zsfelfoldi
rpc/ @fjl

View file

@ -21,7 +21,7 @@ Please make sure your contributions adhere to our coding guidelines:
(i.e. uses [gofmt](https://golang.org/cmd/gofmt/)). (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)).
* Code must be documented adhering to the official Go * Code must be documented adhering to the official Go
[commentary](https://golang.org/doc/effective_go.html#commentary) guidelines. [commentary](https://golang.org/doc/effective_go.html#commentary) guidelines.
* Pull requests need to be based on and opened against the `master` branch. * Pull requests need to be based on and opened against the `main` branch.
* Commit messages should be prefixed with the package(s) they modify. * Commit messages should be prefixed with the package(s) they modify.
* E.g. "eth, rpc: make trace configs optional" * E.g. "eth, rpc: make trace configs optional"

11
.github/dependabot.yml vendored Normal file
View file

@ -0,0 +1,11 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "gomod" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"

BIN
.github/meta/bera-geth.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -1,5 +1,5 @@
# Number of days of inactivity before an Issue is closed for lack of response # Number of days of inactivity before an Issue is closed for lack of response
daysUntilClose: 30 daysUntilClose: 366
# Label requiring a response # Label requiring a response
responseRequiredLabel: "need:more-information" responseRequiredLabel: "need:more-information"
# Comment to post when closing an Issue for lack of response. Set to `false` to disable # Comment to post when closing an Issue for lack of response. Set to `false` to disable

View file

@ -1,16 +1,16 @@
name: CI
on: on:
push: push:
branches: branches: [ main ]
- master
pull_request: pull_request:
branches: branches: [ main ]
- master
workflow_dispatch: workflow_dispatch:
jobs: jobs:
lint: lint:
name: Lint name: Lint
runs-on: self-hosted-ghr runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
@ -34,15 +34,9 @@ jobs:
go run build/ci.go check_generate go run build/ci.go check_generate
go run build/ci.go check_baddeps go run build/ci.go check_baddeps
test: tests:
name: Test name: Tests
needs: lint runs-on: ubuntu-latest
runs-on: self-hosted-ghr
strategy:
matrix:
go:
- '1.24'
- '1.23'
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
@ -51,8 +45,8 @@ jobs:
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: ${{ matrix.go }} go-version: 1.24.0
cache: false cache: false
- name: Run tests - name: Run tests
run: go test ./... run: go test -short ./...

43
.github/workflows/docker.yml vendored Normal file
View file

@ -0,0 +1,43 @@
name: Docker
on:
push:
tags:
- "v1.*"
branches:
- "main"
jobs:
docker:
name: Docker Image
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
attestations: write
id-token: write
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: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push multi-arch images to GHCR
run: |
go run build/ci.go dockerx -platform linux/amd64,linux/arm64 -hub ghcr.io/berachain/bera-geth -upload

13
.github/workflows/release.asc vendored Normal file
View file

@ -0,0 +1,13 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
mDMEaFNE5RYJKwYBBAHaRw8BAQdAzKOuVGKVlxDTv29pw/g7reYDWw0d7Lma13oe
vBBggee0G2JlcmEtZ2V0aC1saW51eC1zaWduaW5nLWtleYiTBBMWCgA7FiEECD2r
J1G161Sj6o/AVxC/N/yI0gUFAmhTROUCGwMFCwkIBwICIgIGFQoJCAsCBBYCAwEC
HgcCF4AACgkQVxC/N/yI0gVTYwD+KiFp07+KNJ/eUlsc2j6Utz9zU7aHKEEh4LNF
uahgRC4A/i1+QkR9oo14o3lbg8yWPnI5v8QQwBd7i3GNExxPXrsMuDgEaFNE5RIK
KwYBBAGXVQEFAQEHQF1SxadHSBJud54oIYyHxq/r8GFmPyahf4ISUI/62X4BAwEI
B4h4BBgWCgAgFiEECD2rJ1G161Sj6o/AVxC/N/yI0gUFAmhTROUCGwwACgkQVxC/
N/yI0gV08QEAhpMEKbnYk9GzBSvRdgtHaYeLLqUtTp1W7Ug+RUO4338A/RWO27B8
8mAF2WACCu8lLErv5V/YI3rkDiu/MSit+p0A
=Ubr7
-----END PGP PUBLIC KEY BLOCK-----

226
.github/workflows/release.yml vendored Normal file
View file

@ -0,0 +1,226 @@
name: Release
on:
push:
tags:
- "v1.*"
jobs:
extract-version:
name: Extract Version
runs-on: ubuntu-latest
steps:
- name: Extract version
run: echo "VERSION=$(echo ${GITHUB_REF#refs/tags/})" >> $GITHUB_OUTPUT
id: extract_version
outputs:
VERSION: ${{ steps.extract_version.outputs.VERSION }}
linux-intel:
name: Linux Build (intel)
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: |
sudo apt-get update
sudo apt-get -yq --no-install-suggests --no-install-recommends install gcc-multilib
- name: Build (amd64)
run: |
go run build/ci.go install -static -arch amd64 -dlgo
- name: Create archive (amd64)
run: |
go run build/ci.go archive -arch amd64 -type tar
- name: Upload artifacts (amd64)
uses: actions/upload-artifact@v4
with:
name: bera-geth-linux-amd64-archives
path: |
bera-geth-linux-amd64-*.tar.gz
bera-geth-alltools-linux-amd64-*.tar.gz
- name: Cleanup bin
run: rm -f build/bin/*
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: |
sudo apt-get update
sudo 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
sudo ln -s /usr/include/asm-generic /usr/include/asm
- name: Build (arm64)
run: |
go run build/ci.go install -static -dlgo -arch arm64 -cc aarch64-linux-gnu-gcc
- name: Create archive (arm64)
run: |
go run build/ci.go archive -arch arm64 -type tar
- name: Upload artifacts (arm64)
uses: actions/upload-artifact@v4
with:
name: bera-geth-linux-arm64-archives
path: |
bera-geth-linux-arm64-*.tar.gz
bera-geth-alltools-linux-arm64-*.tar.gz
- name: Cleanup bin
run: rm -fr build/bin/*
draft-release:
name: Draft Release
needs: [extract-version, linux-intel, linux-arm]
runs-on: ubuntu-latest
env:
VERSION: ${{ needs.extract-version.outputs.VERSION }}
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # This is necessary for generating the changelog
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: ./artifacts
- name: Generate full changelog
id: changelog
run: |
echo "CHANGELOG<<EOF" >> $GITHUB_OUTPUT
echo "$(git log --pretty=format:"- %s" $(git describe --tags --abbrev=0 ${{ env.VERSION }}^)..${{ env.VERSION }})" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Create release draft
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Debug: Show what was downloaded
echo "Contents of artifacts directory:"
ls -la ./artifacts/ || echo "Artifacts directory not found"
# Move all artifacts to current directory
if [ -d "./artifacts" ]; then
find ./artifacts -name "*.tar.gz" -exec mv {} . \;
else
echo "Warning: No artifacts directory found"
fi
# List available files for debugging
echo "Available files:"
ls -la *.tar.gz || echo "No tar.gz files found"
# Get actual archive names
GETH_AMD64=$(ls bera-geth-linux-amd64-*.tar.gz 2>/dev/null | grep -v alltools | head -1)
GETH_ARM64=$(ls bera-geth-linux-arm64-*.tar.gz 2>/dev/null | grep -v alltools | head -1)
GETH_ALLTOOLS_AMD64=$(ls bera-geth-alltools-linux-amd64-*.tar.gz 2>/dev/null | head -1)
GETH_ALLTOOLS_ARM64=$(ls bera-geth-alltools-linux-arm64-*.tar.gz 2>/dev/null | head -1)
echo "Found archives:"
echo " AMD64: ${GETH_AMD64:-NOT FOUND}"
echo " ARM64: ${GETH_ARM64:-NOT FOUND}"
echo " Alltools AMD64: ${GETH_ALLTOOLS_AMD64:-NOT FOUND}"
echo " Alltools ARM64: ${GETH_ALLTOOLS_ARM64:-NOT FOUND}"
# For tag builds, use the actual tag
RELEASE_TAG="${{ env.VERSION }}"
echo "Release tag: $RELEASE_TAG"
echo "GitHub ref: ${{ github.ref }}"
echo "GitHub SHA: ${{ github.sha }}"
# The tag should already exist since it triggered this workflow
# We just need to ensure gh release uses it correctly
body=$(cat <<- 'ENDBODY'
![image](https://raw.githubusercontent.com/berachain/bera-geth/main/.github/meta/bera-geth.png)
## Summary
Add a summary, including:
- Critical bug fixes
- New features
- Any breaking changes (and what to expect)
## Update Priority
This table provides priorities for which classes of users should update particular components.
| User Class | Priority |
|----------------------|-----------------|
| Payload Builders | <TODO> |
| Non-Payload Builders | <TODO> |
## All Changes
${{ steps.changelog.outputs.CHANGELOG }}
## Binaries
| System | Architecture | Binary | Notes |
|:---:|:---:|:---:|:---|
| <img src="https://simpleicons.org/icons/linux.svg" style="width: 32px;"/> | amd64 | [GETH_AMD64_PLACEHOLDER](https://github.com/${{ github.repository }}/releases/download/RELEASE_TAG_PLACEHOLDER/GETH_AMD64_PLACEHOLDER) | Most Linux systems |
| <img src="https://simpleicons.org/icons/linux.svg" style="width: 32px;"/> | arm64 | [GETH_ARM64_PLACEHOLDER](https://github.com/${{ github.repository }}/releases/download/RELEASE_TAG_PLACEHOLDER/GETH_ARM64_PLACEHOLDER) | 64-bit ARM systems |
| <img src="https://simpleicons.org/icons/linux.svg" style="width: 32px;"/> | amd64 | [GETH_ALLTOOLS_AMD64_PLACEHOLDER](https://github.com/${{ github.repository }}/releases/download/RELEASE_TAG_PLACEHOLDER/GETH_ALLTOOLS_AMD64_PLACEHOLDER) | All tools bundle (amd64) |
| <img src="https://simpleicons.org/icons/linux.svg" style="width: 32px;"/> | arm64 | [GETH_ALLTOOLS_ARM64_PLACEHOLDER](https://github.com/${{ github.repository }}/releases/download/RELEASE_TAG_PLACEHOLDER/GETH_ALLTOOLS_ARM64_PLACEHOLDER) | All tools bundle (arm64) |
| **System** | **Option** | - | **Resource** |
| <img src="https://simpleicons.org/icons/docker.svg" style="width: 32px;"/> | Docker | | [ghcr.io/berachain/bera-geth](https://ghcr.io/berachain/bera-geth) |
### Installation
The archives contain the geth binary and license file. Extract and run:
```bash
tar -xzf GETH_AMD64_PLACEHOLDER
./geth
```
The **alltools** archives additionally contain:
- `abigen` - Source code generator for Ethereum contracts
- `evm` - Developer utility for the Ethereum Virtual Machine
- `rlpdump` - Developer utility for RLP data
- `clef` - Ethereum account management tool
ENDBODY
)
# Replace placeholders with actual filenames and release tag
body="${body//GETH_AMD64_PLACEHOLDER/$GETH_AMD64}"
body="${body//GETH_ARM64_PLACEHOLDER/$GETH_ARM64}"
body="${body//GETH_ALLTOOLS_AMD64_PLACEHOLDER/$GETH_ALLTOOLS_AMD64}"
body="${body//GETH_ALLTOOLS_ARM64_PLACEHOLDER/$GETH_ALLTOOLS_ARM64}"
body="${body//RELEASE_TAG_PLACEHOLDER/$RELEASE_TAG}"
# Create assets array for gh release
assets=()
for asset in bera-geth-*.tar.gz; do
if [ -f "$asset" ]; then
assets+=("$asset")
fi
done
# Create the release with the actual tag
# Using --target with GITHUB_SHA ensures we create the release at the right commit
echo "$body" | gh release create "$RELEASE_TAG" \
--draft \
--title "Bera Geth $RELEASE_TAG" \
-F - \
"${assets[@]}"

3
.gitignore vendored
View file

@ -56,3 +56,6 @@ cmd/evm/evm
cmd/geth/geth cmd/geth/geth
cmd/rlpdump/rlpdump cmd/rlpdump/rlpdump
cmd/workload/workload cmd/workload/workload
# claude
.claude

22
CHANGELOG.md Normal file
View file

@ -0,0 +1,22 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [v1.TBD.0]
### Added
- Add support for the Berachain mainnet (`--berachain`) and Bepolia networks
(`--bepolia`)
- Add support for configuring the Prague1 fork on Berachain networks.
### Changed
- BRIP-2: Allowed configuration of the `MinimumBaseFeeWei`. For Berachain, this will be set as 1 gwei. (https://github.com/berachain/bera-geth/pull/2)
- BRIP-2: Allowed configuration of the `BaseFeeChangeDenominator`. For Berachain, this will be set as 48 (a 6x increase corresponding to 6x faster block times). (https://github.com/berachain/bera-geth/pull/2)
- [WIP] Updated release GH workflow to publish tarballs and push built images to GHCR.
- Removed support for appveyor, gitea, travis, circleci.

145
CLAUDE.md Normal file
View file

@ -0,0 +1,145 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
This is **bera-geth**, Berachain's fork of go-ethereum that implements the Berachain blockchain network. Key differences from upstream:
- **Custom base fee mechanism**: 1 gwei minimum with 6x faster adjustments via Prague1 fork
- **Networks**: Berachain mainnet (Chain ID 80094) and Bepolia testnet (Chain ID 80069)
- **Optimized for faster block times** while maintaining Ethereum compatibility
## Development Commands
### Building
```bash
make geth # Build main geth binary
make all # Build all executables
make evm # Build EVM utility
```
### Testing
```bash
make test # Run all tests
go test ./path/to/package # Run specific package tests
go test -run TestName # Run specific test
```
### Code Quality
```bash
make lint # Run linters
make fmt # Format code with gofmt
```
### Development Tools
```bash
make devtools # Install development dependencies
```
### Cleaning
```bash
make clean # Clean build artifacts
```
## Codebase Architecture
### Core Components
**Core Blockchain Logic** (`/core/`)
- `blockchain.go` - Main blockchain state management
- `state_processor.go` - Transaction execution
- `types/` - Core data structures (blocks, transactions, receipts)
- `vm/` - Ethereum Virtual Machine implementation
- `txpool/` - Transaction pool management
**Consensus Layer** (`/consensus/`)
- `beacon/` - Proof-of-stake consensus (primary)
- `misc/eip1559/` - Base fee calculation with Berachain modifications
- `clique/` - Proof-of-authority for testing
**Network Protocol** (`/eth/`)
- `backend.go` - Main Ethereum service
- `downloader/` - Block/state synchronization
- `protocols/` - Network protocol handlers
**P2P Networking** (`/p2p/`)
- `discover/` - Node discovery protocols
- `rlpx/` - Encrypted communication
### Key Entry Points
**Main Binary**: `/cmd/geth/main.go`
- CLI application setup
- Network selection logic
- Node initialization
**Other Executables**: `/cmd/`
- `clef/` - Account management
- `abigen/` - ABI binding generator
- `evm/` - Standalone EVM executor
### Configuration Management
**Chain Configs**: `/params/config.go`
- Network parameters and fork schedules
- Berachain/Bepolia network definitions
- Prague1 fork configuration
**Network Discovery**: `/params/bootnodes.go`
- Bootstrap nodes for peer discovery
### Berachain-Specific Modifications
**Base Fee Changes** (`/consensus/misc/eip1559/eip1559.go:116-121`)
- Prague1 fork enforces 1 gwei minimum base fee
- BaseFeeChangeDenominator set to 48 (6x faster adjustment)
**Network Configurations** (`/params/config.go:77-99, 107-129`)
- BerachainChainConfig with Chain ID 80094
- BepoliaChainConfig with Chain ID 80069
- Custom Prague1 fork activation parameters
### Test Organization
Tests follow Go conventions with `*_test.go` files alongside source code:
- Unit tests in package directories
- Integration tests in `/tests/`
- Consensus tests for Ethereum specification compliance
- EVM operation tests in `/core/vm/testdata/`
### Build System
**Primary Build**: `/build/ci.go`
- Cross-platform compilation
- Docker image building
- Release packaging
**Simple Builds**: `/Makefile`
- Development-focused targets
- Requires Go 1.23+ and C compiler
## Development Workflow
1. **Architecture Understanding**: The codebase follows Ethereum's layered architecture (consensus → state → networking → application)
2. **Testing Strategy**: Always run tests after changes with `make test`
3. **Code Style**: Use `make fmt` and `make lint` before committing
4. **Network Testing**: Use `--bepolia` flag for testnet development
5. **Debugging**: Use `./build/bin/geth --help` for comprehensive CLI options
## Network-Specific Usage
### Berachain Mainnet
```bash
./build/bin/geth --berachain
```
### Bepolia Testnet
```bash
./build/bin/geth --bepolia
```
### Development Mode
```bash
./build/bin/geth --dev
```

View file

@ -9,18 +9,18 @@ FROM golang:1.24-alpine AS builder
RUN apk add --no-cache gcc musl-dev linux-headers git RUN apk add --no-cache gcc musl-dev linux-headers git
# Get dependencies - will also be cached if we won't change go.mod/go.sum # Get dependencies - will also be cached if we won't change go.mod/go.sum
COPY go.mod /go-ethereum/ COPY go.mod /bera-geth/
COPY go.sum /go-ethereum/ COPY go.sum /bera-geth/
RUN cd /go-ethereum && go mod download RUN cd /bera-geth && go mod download
ADD . /go-ethereum ADD . /bera-geth
RUN cd /go-ethereum && go run build/ci.go install -static ./cmd/geth RUN cd /bera-geth && go run build/ci.go install -static ./cmd/geth
# Pull Geth into a second stage deploy alpine container # Pull Geth into a second stage deploy alpine container
FROM alpine:latest FROM alpine:latest
RUN apk add --no-cache ca-certificates RUN apk add --no-cache ca-certificates
COPY --from=builder /go-ethereum/build/bin/geth /usr/local/bin/ COPY --from=builder /bera-geth/build/bin/geth /usr/local/bin/
EXPOSE 8545 8546 30303 30303/udp EXPOSE 8545 8546 30303 30303/udp
ENTRYPOINT ["geth"] ENTRYPOINT ["geth"]

View file

@ -9,25 +9,25 @@ FROM golang:1.24-alpine AS builder
RUN apk add --no-cache gcc musl-dev linux-headers git RUN apk add --no-cache gcc musl-dev linux-headers git
# Get dependencies - will also be cached if we won't change go.mod/go.sum # Get dependencies - will also be cached if we won't change go.mod/go.sum
COPY go.mod /go-ethereum/ COPY go.mod /bera-geth/
COPY go.sum /go-ethereum/ COPY go.sum /bera-geth/
RUN cd /go-ethereum && go mod download RUN cd /bera-geth && go mod download
ADD . /go-ethereum ADD . /bera-geth
# This is not strictly necessary, but it matches the "Dockerfile" steps, thus # This is not strictly necessary, but it matches the "Dockerfile" steps, thus
# makes it so that under certain circumstances, the docker layer can be cached, # makes it so that under certain circumstances, the docker layer can be cached,
# and the builder can jump to the next (build all) command, with the go cache fully loaded. # and the builder can jump to the next (build all) command, with the go cache fully loaded.
# #
RUN cd /go-ethereum && go run build/ci.go install -static ./cmd/geth RUN cd /bera-geth && go run build/ci.go install -static ./cmd/geth
RUN cd /go-ethereum && go run build/ci.go install -static RUN cd /bera-geth && go run build/ci.go install -static
# Pull all binaries into a second stage deploy alpine container # Pull all binaries into a second stage deploy alpine container
FROM alpine:latest FROM alpine:latest
RUN apk add --no-cache ca-certificates RUN apk add --no-cache ca-certificates
COPY --from=builder /go-ethereum/build/bin/* /usr/local/bin/ COPY --from=builder /bera-geth/build/bin/* /usr/local/bin/
EXPOSE 8545 8546 30303 30303/udp EXPOSE 8545 8546 30303 30303/udp

View file

@ -1,59 +0,0 @@
clone_depth: 5
version: "{branch}.{build}"
image:
- Ubuntu
- Visual Studio 2019
environment:
matrix:
- GETH_ARCH: amd64
GETH_MINGW: 'C:\msys64\mingw64'
- GETH_ARCH: 386
GETH_MINGW: 'C:\msys64\mingw32'
install:
- git submodule update --init --depth 1 --recursive
- go version
for:
# Linux has its own script without -arch and -cc.
# The linux builder also runs lint.
- matrix:
only:
- image: Ubuntu
build_script:
- go run build/ci.go lint
- go run build/ci.go check_generate
- go run build/ci.go check_baddeps
- go run build/ci.go install -dlgo
test_script:
- go run build/ci.go test -dlgo -short
# linux/386 is disabled.
- matrix:
exclude:
- image: Ubuntu
GETH_ARCH: 386
# Windows builds for amd64 + 386.
- matrix:
only:
- image: Visual Studio 2019
environment:
# We use gcc from MSYS2 because it is the most recent compiler version available on
# AppVeyor. Note: gcc.exe only works properly if the corresponding bin/ directory is
# contained in PATH.
GETH_CC: '%GETH_MINGW%\bin\gcc.exe'
PATH: '%GETH_MINGW%\bin;C:\Program Files (x86)\NSIS\;%PATH%'
build_script:
- 'echo %GETH_ARCH%'
- 'echo %GETH_CC%'
- '%GETH_CC% --version'
- go run build/ci.go install -dlgo -arch %GETH_ARCH% -cc %GETH_CC%
after_build:
# Upload builds. Note that ci.go makes this a no-op PR builds.
- go run build/ci.go archive -arch %GETH_ARCH% -type zip -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
- go run build/ci.go nsis -arch %GETH_ARCH% -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
test_script:
- go run build/ci.go test -dlgo -arch %GETH_ARCH% -cc %GETH_CC% -short

View file

@ -101,6 +101,17 @@ func (ec *engineClient) callNewPayload(fork string, event types.ChainHeadEvent)
params = []any{execData} params = []any{execData}
) )
switch fork { switch fork {
case "electra1":
// TODO(BRIP-4): Add ParentProposerPubkey to the chainHeadEvent to add to the newPayload call.
method = "engine_newPayloadV4P11"
parentBeaconRoot := event.BeaconHead.ParentRoot
blobHashes := collectBlobHashes(event.Block)
hexRequests := make([]hexutil.Bytes, len(event.ExecRequests))
for i := range event.ExecRequests {
hexRequests[i] = hexutil.Bytes(event.ExecRequests[i])
}
parentProposerPubkey := &common.Pubkey{}
params = append(params, blobHashes, parentBeaconRoot, hexRequests, parentProposerPubkey)
case "electra": case "electra":
method = "engine_newPayloadV4" method = "engine_newPayloadV4"
parentBeaconRoot := event.BeaconHead.ParentRoot parentBeaconRoot := event.BeaconHead.ParentRoot
@ -145,6 +156,8 @@ func (ec *engineClient) callForkchoiceUpdated(fork string, event types.ChainHead
var method string var method string
switch fork { switch fork {
case "electra1":
method = "engine_forkchoiceUpdatedV3P11"
case "deneb", "electra": case "deneb", "electra":
method = "engine_forkchoiceUpdatedV3" method = "engine_forkchoiceUpdatedV3"
case "capella": case "capella":

View file

@ -21,6 +21,7 @@ func (p PayloadAttributes) MarshalJSON() ([]byte, error) {
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"` SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"` Withdrawals []*types.Withdrawal `json:"withdrawals"`
BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"` BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"`
ProposerPubkey *common.Pubkey `json:"parentProposerPubkey"`
} }
var enc PayloadAttributes var enc PayloadAttributes
enc.Timestamp = hexutil.Uint64(p.Timestamp) enc.Timestamp = hexutil.Uint64(p.Timestamp)
@ -28,6 +29,7 @@ func (p PayloadAttributes) MarshalJSON() ([]byte, error) {
enc.SuggestedFeeRecipient = p.SuggestedFeeRecipient enc.SuggestedFeeRecipient = p.SuggestedFeeRecipient
enc.Withdrawals = p.Withdrawals enc.Withdrawals = p.Withdrawals
enc.BeaconRoot = p.BeaconRoot enc.BeaconRoot = p.BeaconRoot
enc.ProposerPubkey = p.ProposerPubkey
return json.Marshal(&enc) return json.Marshal(&enc)
} }
@ -39,6 +41,7 @@ func (p *PayloadAttributes) UnmarshalJSON(input []byte) error {
SuggestedFeeRecipient *common.Address `json:"suggestedFeeRecipient" gencodec:"required"` SuggestedFeeRecipient *common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"` Withdrawals []*types.Withdrawal `json:"withdrawals"`
BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"` BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"`
ProposerPubkey *common.Pubkey `json:"parentProposerPubkey"`
} }
var dec PayloadAttributes var dec PayloadAttributes
if err := json.Unmarshal(input, &dec); err != nil { if err := json.Unmarshal(input, &dec); err != nil {
@ -62,5 +65,8 @@ func (p *PayloadAttributes) UnmarshalJSON(input []byte) error {
if dec.BeaconRoot != nil { if dec.BeaconRoot != nil {
p.BeaconRoot = dec.BeaconRoot p.BeaconRoot = dec.BeaconRoot
} }
if dec.ProposerPubkey != nil {
p.ProposerPubkey = dec.ProposerPubkey
}
return nil return nil
} }

View file

@ -33,9 +33,10 @@ import (
type PayloadVersion byte type PayloadVersion byte
var ( var (
PayloadV1 PayloadVersion = 0x1 PayloadV1 PayloadVersion = 0x1
PayloadV2 PayloadVersion = 0x2 PayloadV2 PayloadVersion = 0x2
PayloadV3 PayloadVersion = 0x3 PayloadV3 PayloadVersion = 0x3
PayloadV3P11 PayloadVersion = 0x31
) )
//go:generate go run github.com/fjl/gencodec -type PayloadAttributes -field-override payloadAttributesMarshaling -out gen_blockparams.go //go:generate go run github.com/fjl/gencodec -type PayloadAttributes -field-override payloadAttributesMarshaling -out gen_blockparams.go
@ -48,6 +49,7 @@ type PayloadAttributes struct {
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"` SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"` Withdrawals []*types.Withdrawal `json:"withdrawals"`
BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"` BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"`
ProposerPubkey *common.Pubkey `json:"parentProposerPubkey"` // Berachain: only used prague1 and onwards
} }
// JSON type overrides for PayloadAttributes. // JSON type overrides for PayloadAttributes.
@ -218,8 +220,8 @@ func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) {
// and that the blockhash of the constructed block matches the parameters. Nil // and that the blockhash of the constructed block matches the parameters. Nil
// Withdrawals value will propagate through the returned block. Empty // Withdrawals value will propagate through the returned block. Empty
// Withdrawals value must be passed via non-nil, length 0 value in data. // Withdrawals value must be passed via non-nil, length 0 value in data.
func ExecutableDataToBlock(data ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, requests [][]byte) (*types.Block, error) { func ExecutableDataToBlock(data ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, requests [][]byte, proposerPubkey *common.Pubkey) (*types.Block, error) {
block, err := ExecutableDataToBlockNoHash(data, versionedHashes, beaconRoot, requests) block, err := ExecutableDataToBlockNoHash(data, versionedHashes, beaconRoot, requests, proposerPubkey)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -232,7 +234,7 @@ func ExecutableDataToBlock(data ExecutableData, versionedHashes []common.Hash, b
// ExecutableDataToBlockNoHash is analogous to ExecutableDataToBlock, but is used // ExecutableDataToBlockNoHash is analogous to ExecutableDataToBlock, but is used
// for stateless execution, so it skips checking if the executable data hashes to // for stateless execution, so it skips checking if the executable data hashes to
// the requested hash (stateless has to *compute* the root hash, it's not given). // the requested hash (stateless has to *compute* the root hash, it's not given).
func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, requests [][]byte) (*types.Block, error) { func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, requests [][]byte, proposerPubkey *common.Pubkey) (*types.Block, error) {
txs, err := decodeTransactions(data.Transactions) txs, err := decodeTransactions(data.Transactions)
if err != nil { if err != nil {
return nil, err return nil, err
@ -275,26 +277,27 @@ func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.H
} }
header := &types.Header{ header := &types.Header{
ParentHash: data.ParentHash, ParentHash: data.ParentHash,
UncleHash: types.EmptyUncleHash, UncleHash: types.EmptyUncleHash,
Coinbase: data.FeeRecipient, Coinbase: data.FeeRecipient,
Root: data.StateRoot, Root: data.StateRoot,
TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)), TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)),
ReceiptHash: data.ReceiptsRoot, ReceiptHash: data.ReceiptsRoot,
Bloom: types.BytesToBloom(data.LogsBloom), Bloom: types.BytesToBloom(data.LogsBloom),
Difficulty: common.Big0, Difficulty: common.Big0,
Number: new(big.Int).SetUint64(data.Number), Number: new(big.Int).SetUint64(data.Number),
GasLimit: data.GasLimit, GasLimit: data.GasLimit,
GasUsed: data.GasUsed, GasUsed: data.GasUsed,
Time: data.Timestamp, Time: data.Timestamp,
BaseFee: data.BaseFeePerGas, BaseFee: data.BaseFeePerGas,
Extra: data.ExtraData, Extra: data.ExtraData,
MixDigest: data.Random, MixDigest: data.Random,
WithdrawalsHash: withdrawalsRoot, WithdrawalsHash: withdrawalsRoot,
ExcessBlobGas: data.ExcessBlobGas, ExcessBlobGas: data.ExcessBlobGas,
BlobGasUsed: data.BlobGasUsed, BlobGasUsed: data.BlobGasUsed,
ParentBeaconRoot: beaconRoot, ParentBeaconRoot: beaconRoot,
RequestsHash: requestsHash, RequestsHash: requestsHash,
ParentProposerPubkey: proposerPubkey,
} }
return types.NewBlockWithHeader(header). return types.NewBlockWithHeader(header).
WithBody(types.Body{Transactions: txs, Uncles: nil, Withdrawals: data.Withdrawals}). WithBody(types.Body{Transactions: txs, Uncles: nil, Withdrawals: data.Withdrawals}).

View file

@ -572,9 +572,9 @@ func doArchive(cmdline []string) {
var ( var (
env = build.Env() env = build.Env()
basegeth = archiveBasename(*arch, version.Archive(env.Commit)) basegeth = archiveBasename(*arch, version.Archive(env.Tag, env.Commit))
geth = "geth-" + basegeth + ext geth = "bera-geth-" + basegeth + ext
alltools = "geth-alltools-" + basegeth + ext alltools = "bera-geth-alltools-" + basegeth + ext
) )
maybeSkipArchive(env) maybeSkipArchive(env)
if err := build.WriteArchive(geth, gethArchiveFiles); err != nil { if err := build.WriteArchive(geth, gethArchiveFiles); err != nil {
@ -650,17 +650,17 @@ func maybeSkipArchive(env build.Environment) {
log.Printf("skipping archive creation because this is a PR build") log.Printf("skipping archive creation because this is a PR build")
os.Exit(0) os.Exit(0)
} }
if env.Branch != "master" && !strings.HasPrefix(env.Tag, "v1.") { if env.Branch != "main" && !strings.HasPrefix(env.Tag, "v1.") {
log.Printf("skipping archive creation because branch %q, tag %q is not on the inclusion list", env.Branch, env.Tag) log.Printf("skipping archive creation because branch %q, tag %q is not on the inclusion list", env.Branch, env.Tag)
os.Exit(0) os.Exit(0)
} }
} }
// Builds the docker images and optionally uploads them to Docker Hub. // Builds the docker images and optionally uploads them to GHCR.
func doDockerBuildx(cmdline []string) { func doDockerBuildx(cmdline []string) {
var ( var (
platform = flag.String("platform", "", `Push a multi-arch docker image for the specified architectures (usually "linux/amd64,linux/arm64")`) platform = flag.String("platform", "", `Push a multi-arch docker image for the specified architectures (usually "linux/amd64,linux/arm64")`)
hubImage = flag.String("hub", "ethereum/client-go", `Where to upload the docker image`) hubImage = flag.String("hub", "ghcr.io/berachain/bera-geth", `Where to upload the docker image`)
upload = flag.Bool("upload", false, `Whether to trigger upload`) upload = flag.Bool("upload", false, `Whether to trigger upload`)
) )
flag.CommandLine.Parse(cmdline) flag.CommandLine.Parse(cmdline)
@ -679,21 +679,17 @@ func doDockerBuildx(cmdline []string) {
build.MustRun(auther) build.MustRun(auther)
} }
// Retrieve the version infos to build and push to the following paths: // Retrieve the version infos to build and push to the following paths:
// - ethereum/client-go:latest - Pushes to the master branch, Geth only // - ethereum/client-go:latest - Pushes to the main branch, Geth only
// - ethereum/client-go:stable - Version tag publish on GitHub, Geth only // - ethereum/client-go:alltools-latest - Pushes to the main branch, Geth & tools
// - ethereum/client-go:alltools-latest - Pushes to the master branch, Geth & tools
// - ethereum/client-go:alltools-stable - Version tag publish on GitHub, Geth & tools
// - ethereum/client-go:release-<major>.<minor> - Version tag publish on GitHub, Geth only
// - ethereum/client-go:alltools-release-<major>.<minor> - Version tag publish on GitHub, Geth & tools
// - ethereum/client-go:v<major>.<minor>.<patch> - Version tag publish on GitHub, Geth only // - ethereum/client-go:v<major>.<minor>.<patch> - Version tag publish on GitHub, Geth only
// - ethereum/client-go:alltools-v<major>.<minor>.<patch> - Version tag publish on GitHub, Geth & tools // - ethereum/client-go:alltools-v<major>.<minor>.<patch> - Version tag publish on GitHub, Geth & tools
var tags []string var tags []string
switch { switch {
case env.Branch == "master": case env.Branch == "main":
tags = []string{"latest"} tags = []string{"latest"}
case strings.HasPrefix(env.Tag, "v1."): case strings.HasPrefix(env.Tag, "v1."):
tags = []string{"stable", fmt.Sprintf("release-%v", version.Family), "v" + version.Semantic} tags = []string{env.Tag}
} }
// Need to create a mult-arch builder // Need to create a mult-arch builder
check := exec.Command("docker", "buildx", "inspect", "multi-arch-builder") check := exec.Command("docker", "buildx", "inspect", "multi-arch-builder")
@ -1097,7 +1093,7 @@ func doWindowsInstaller(cmdline []string) {
if env.Commit != "" { if env.Commit != "" {
ver[2] += "-" + env.Commit[:8] ver[2] += "-" + env.Commit[:8]
} }
installer, err := filepath.Abs("geth-" + archiveBasename(*arch, version.Archive(env.Commit)) + ".exe") installer, err := filepath.Abs("geth-" + archiveBasename(*arch, version.Archive(env.Tag, env.Commit)) + ".exe")
if err != nil { if err != nil {
log.Fatalf("Failed to convert installer file path: %v", err) log.Fatalf("Failed to convert installer file path: %v", err)
} }

View file

@ -1,32 +0,0 @@
machine:
services:
- docker
dependencies:
cache_directories:
- "~/.ethash" # Cache the ethash DAG generated by hive for consecutive builds
- "~/.docker" # Cache all docker images manually to avoid lengthy rebuilds
override:
# Restore all previously cached docker images
- mkdir -p ~/.docker
- for img in `ls ~/.docker`; do docker load -i ~/.docker/$img; done
# Pull in and hive, restore cached ethash DAGs and do a dry run
- go get -u github.com/karalabe/hive
- (cd ~/.go_workspace/src/github.com/karalabe/hive && mkdir -p workspace/ethash/ ~/.ethash)
- (cd ~/.go_workspace/src/github.com/karalabe/hive && cp -r ~/.ethash/. workspace/ethash/)
- (cd ~/.go_workspace/src/github.com/karalabe/hive && hive --docker-noshell --client=NONE --test=. --sim=. --loglevel=6)
# Cache all the docker images and the ethash DAGs
- for img in `docker images | grep -v "^<none>" | tail -n +2 | awk '{print $1}'`; do docker save $img > ~/.docker/`echo $img | tr '/' ':'`.tar; done
- cp -r ~/.go_workspace/src/github.com/karalabe/hive/workspace/ethash/. ~/.ethash
test:
override:
# Build Geth and move into a known folder
- make geth
- cp ./build/bin/geth $HOME/geth
# Run hive and move all generated logs into the public artifacts folder
- (cd ~/.go_workspace/src/github.com/karalabe/hive && hive --docker-noshell --client=go-ethereum:local --override=$HOME/geth --test=. --sim=.)
- cp -r ~/.go_workspace/src/github.com/karalabe/hive/workspace/logs/* $CIRCLE_ARTIFACTS

View file

@ -236,6 +236,10 @@ func ethFilter(args []string) (nodeFilter, error) {
filter = forkid.NewStaticFilter(params.HoleskyChainConfig, core.DefaultHoleskyGenesisBlock().ToBlock()) filter = forkid.NewStaticFilter(params.HoleskyChainConfig, core.DefaultHoleskyGenesisBlock().ToBlock())
case "hoodi": case "hoodi":
filter = forkid.NewStaticFilter(params.HoodiChainConfig, core.DefaultHoodiGenesisBlock().ToBlock()) filter = forkid.NewStaticFilter(params.HoodiChainConfig, core.DefaultHoodiGenesisBlock().ToBlock())
case "berachain":
filter = forkid.NewStaticFilter(params.BerachainChainConfig, core.DefaultBerachainGenesisBlock().ToBlock())
case "bepolia":
filter = forkid.NewStaticFilter(params.BepoliaChainConfig, core.DefaultBepoliaGenesisBlock().ToBlock())
default: default:
return nil, fmt.Errorf("unknown network %q", args[0]) return nil, fmt.Errorf("unknown network %q", args[0])
} }

View file

@ -56,6 +56,7 @@ type header struct {
BlobGasUsed *uint64 `json:"blobGasUsed" rlp:"optional"` BlobGasUsed *uint64 `json:"blobGasUsed" rlp:"optional"`
ExcessBlobGas *uint64 `json:"excessBlobGas" rlp:"optional"` ExcessBlobGas *uint64 `json:"excessBlobGas" rlp:"optional"`
ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"` ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
ParentProposerPubkey *common.Pubkey `json:"parentProposerPubkey" rlp:"optional"`
} }
type headerMarshaling struct { type headerMarshaling struct {
@ -117,25 +118,26 @@ func (c *cliqueInput) UnmarshalJSON(input []byte) error {
// ToBlock converts i into a *types.Block // ToBlock converts i into a *types.Block
func (i *bbInput) ToBlock() *types.Block { func (i *bbInput) ToBlock() *types.Block {
header := &types.Header{ header := &types.Header{
ParentHash: i.Header.ParentHash, ParentHash: i.Header.ParentHash,
UncleHash: types.EmptyUncleHash, UncleHash: types.EmptyUncleHash,
Coinbase: common.Address{}, Coinbase: common.Address{},
Root: i.Header.Root, Root: i.Header.Root,
TxHash: types.EmptyTxsHash, TxHash: types.EmptyTxsHash,
ReceiptHash: types.EmptyReceiptsHash, ReceiptHash: types.EmptyReceiptsHash,
Bloom: i.Header.Bloom, Bloom: i.Header.Bloom,
Difficulty: common.Big0, Difficulty: common.Big0,
Number: i.Header.Number, Number: i.Header.Number,
GasLimit: i.Header.GasLimit, GasLimit: i.Header.GasLimit,
GasUsed: i.Header.GasUsed, GasUsed: i.Header.GasUsed,
Time: i.Header.Time, Time: i.Header.Time,
Extra: i.Header.Extra, Extra: i.Header.Extra,
MixDigest: i.Header.MixDigest, MixDigest: i.Header.MixDigest,
BaseFee: i.Header.BaseFee, BaseFee: i.Header.BaseFee,
WithdrawalsHash: i.Header.WithdrawalsHash, WithdrawalsHash: i.Header.WithdrawalsHash,
BlobGasUsed: i.Header.BlobGasUsed, BlobGasUsed: i.Header.BlobGasUsed,
ExcessBlobGas: i.Header.ExcessBlobGas, ExcessBlobGas: i.Header.ExcessBlobGas,
ParentBeaconRoot: i.Header.ParentBeaconBlockRoot, ParentBeaconRoot: i.Header.ParentBeaconBlockRoot,
ParentProposerPubkey: i.Header.ParentProposerPubkey,
} }
// Fill optional values. // Fill optional values.

View file

@ -101,6 +101,7 @@ type stEnv struct {
ParentExcessBlobGas *uint64 `json:"parentExcessBlobGas,omitempty"` ParentExcessBlobGas *uint64 `json:"parentExcessBlobGas,omitempty"`
ParentBlobGasUsed *uint64 `json:"parentBlobGasUsed,omitempty"` ParentBlobGasUsed *uint64 `json:"parentBlobGasUsed,omitempty"`
ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot"` ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot"`
ParentProposerPubkey *common.Pubkey `json:"parentProposerPubkey"`
} }
type stEnvMarshaling struct { type stEnvMarshaling struct {
@ -220,6 +221,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
) )
core.ProcessParentBlockHash(prevHash, evm) core.ProcessParentBlockHash(prevHash, evm)
} }
// TODO(BRIP-4): Validate Prague1 block rules.
for i := 0; txIt.Next(); i++ { for i := 0; txIt.Next(); i++ {
tx, err := txIt.Tx() tx, err := txIt.Tx()
if err != nil { if err != nil {

View file

@ -38,6 +38,7 @@ func (h header) MarshalJSON() ([]byte, error) {
BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed" rlp:"optional"` BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed" rlp:"optional"`
ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas" rlp:"optional"` ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas" rlp:"optional"`
ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"` ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
ParentProposerPubkey *common.Pubkey `json:"parentProposerPubkey" rlp:"optional"`
} }
var enc header var enc header
enc.ParentHash = h.ParentHash enc.ParentHash = h.ParentHash
@ -60,6 +61,7 @@ func (h header) MarshalJSON() ([]byte, error) {
enc.BlobGasUsed = (*math.HexOrDecimal64)(h.BlobGasUsed) enc.BlobGasUsed = (*math.HexOrDecimal64)(h.BlobGasUsed)
enc.ExcessBlobGas = (*math.HexOrDecimal64)(h.ExcessBlobGas) enc.ExcessBlobGas = (*math.HexOrDecimal64)(h.ExcessBlobGas)
enc.ParentBeaconBlockRoot = h.ParentBeaconBlockRoot enc.ParentBeaconBlockRoot = h.ParentBeaconBlockRoot
enc.ParentProposerPubkey = h.ParentProposerPubkey
return json.Marshal(&enc) return json.Marshal(&enc)
} }
@ -86,6 +88,7 @@ func (h *header) UnmarshalJSON(input []byte) error {
BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed" rlp:"optional"` BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed" rlp:"optional"`
ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas" rlp:"optional"` ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas" rlp:"optional"`
ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"` ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
ParentProposerPubkey *common.Pubkey `json:"parentProposerPubkey" rlp:"optional"`
} }
var dec header var dec header
if err := json.Unmarshal(input, &dec); err != nil { if err := json.Unmarshal(input, &dec); err != nil {
@ -155,5 +158,8 @@ func (h *header) UnmarshalJSON(input []byte) error {
if dec.ParentBeaconBlockRoot != nil { if dec.ParentBeaconBlockRoot != nil {
h.ParentBeaconBlockRoot = dec.ParentBeaconBlockRoot h.ParentBeaconBlockRoot = dec.ParentBeaconBlockRoot
} }
if dec.ParentProposerPubkey != nil {
h.ParentProposerPubkey = dec.ParentProposerPubkey
}
return nil return nil
} }

View file

@ -37,6 +37,7 @@ func (s stEnv) MarshalJSON() ([]byte, error) {
ParentExcessBlobGas *math.HexOrDecimal64 `json:"parentExcessBlobGas,omitempty"` ParentExcessBlobGas *math.HexOrDecimal64 `json:"parentExcessBlobGas,omitempty"`
ParentBlobGasUsed *math.HexOrDecimal64 `json:"parentBlobGasUsed,omitempty"` ParentBlobGasUsed *math.HexOrDecimal64 `json:"parentBlobGasUsed,omitempty"`
ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot"` ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot"`
ParentProposerPubkey *common.Pubkey `json:"parentProposerPubkey"`
} }
var enc stEnv var enc stEnv
enc.Coinbase = common.UnprefixedAddress(s.Coinbase) enc.Coinbase = common.UnprefixedAddress(s.Coinbase)
@ -59,6 +60,7 @@ func (s stEnv) MarshalJSON() ([]byte, error) {
enc.ParentExcessBlobGas = (*math.HexOrDecimal64)(s.ParentExcessBlobGas) enc.ParentExcessBlobGas = (*math.HexOrDecimal64)(s.ParentExcessBlobGas)
enc.ParentBlobGasUsed = (*math.HexOrDecimal64)(s.ParentBlobGasUsed) enc.ParentBlobGasUsed = (*math.HexOrDecimal64)(s.ParentBlobGasUsed)
enc.ParentBeaconBlockRoot = s.ParentBeaconBlockRoot enc.ParentBeaconBlockRoot = s.ParentBeaconBlockRoot
enc.ParentProposerPubkey = s.ParentProposerPubkey
return json.Marshal(&enc) return json.Marshal(&enc)
} }
@ -85,6 +87,7 @@ func (s *stEnv) UnmarshalJSON(input []byte) error {
ParentExcessBlobGas *math.HexOrDecimal64 `json:"parentExcessBlobGas,omitempty"` ParentExcessBlobGas *math.HexOrDecimal64 `json:"parentExcessBlobGas,omitempty"`
ParentBlobGasUsed *math.HexOrDecimal64 `json:"parentBlobGasUsed,omitempty"` ParentBlobGasUsed *math.HexOrDecimal64 `json:"parentBlobGasUsed,omitempty"`
ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot"` ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot"`
ParentProposerPubkey *common.Pubkey `json:"parentProposerPubkey"`
} }
var dec stEnv var dec stEnv
if err := json.Unmarshal(input, &dec); err != nil { if err := json.Unmarshal(input, &dec); err != nil {
@ -154,5 +157,8 @@ func (s *stEnv) UnmarshalJSON(input []byte) error {
if dec.ParentBeaconBlockRoot != nil { if dec.ParentBeaconBlockRoot != nil {
s.ParentBeaconBlockRoot = dec.ParentBeaconBlockRoot s.ParentBeaconBlockRoot = dec.ParentBeaconBlockRoot
} }
if dec.ParentProposerPubkey != nil {
s.ParentProposerPubkey = dec.ParentProposerPubkey
}
return nil return nil
} }

View file

@ -150,6 +150,9 @@ func Transition(ctx *cli.Context) error {
if err := applyCancunChecks(&prestate.Env, chainConfig); err != nil { if err := applyCancunChecks(&prestate.Env, chainConfig); err != nil {
return err return err
} }
if err := applyPrague1Checks(&prestate.Env, chainConfig); err != nil {
return err
}
// Configure tracer // Configure tracer
if ctx.IsSet(TraceTracerFlag.Name) { // Custom tracing if ctx.IsSet(TraceTracerFlag.Name) { // Custom tracing
@ -265,6 +268,19 @@ func applyCancunChecks(env *stEnv, chainConfig *params.ChainConfig) error {
return nil return nil
} }
func applyPrague1Checks(env *stEnv, chainConfig *params.ChainConfig) error {
if !chainConfig.IsPrague1(big.NewInt(int64(env.Number)), env.Timestamp) {
env.ParentProposerPubkey = nil // un-set it if it has been set too early
return nil
}
// Post-prague1
// We require BRIP-0004 proposer pubkey to be set in the env
if env.ParentProposerPubkey == nil {
return NewError(ErrorConfig, errors.New("post-prague1 env requires parentProposerPubkey to be set"))
}
return nil
}
type Alloc map[common.Address]types.Account type Alloc map[common.Address]types.Account
func (g Alloc) OnRoot(common.Hash) {} func (g Alloc) OnRoot(common.Hash) {}

View file

@ -479,6 +479,10 @@ func importHistory(ctx *cli.Context) error {
network = "holesky" network = "holesky"
case ctx.Bool(utils.HoodiFlag.Name): case ctx.Bool(utils.HoodiFlag.Name):
network = "hoodi" network = "hoodi"
case ctx.Bool(utils.BerachainFlag.Name):
network = "berachain"
case ctx.Bool(utils.BepoliaFlag.Name):
network = "bepolia"
} }
} else { } else {
// No network flag set, try to determine network based on files // No network flag set, try to determine network based on files
@ -715,6 +719,10 @@ func downloadEra(ctx *cli.Context) error {
case ctx.IsSet(utils.MainnetFlag.Name): case ctx.IsSet(utils.MainnetFlag.Name):
case ctx.IsSet(utils.SepoliaFlag.Name): case ctx.IsSet(utils.SepoliaFlag.Name):
network = "sepolia" network = "sepolia"
case ctx.IsSet(utils.BerachainFlag.Name):
network = "berachain"
case ctx.IsSet(utils.BepoliaFlag.Name):
network = "bepolia"
default: default:
return fmt.Errorf("unsupported network, no known era1 checksums") return fmt.Errorf("unsupported network, no known era1 checksums")
} }

View file

@ -285,6 +285,9 @@ func main() {
func prepare(ctx *cli.Context) { func prepare(ctx *cli.Context) {
// If we're running a known preset, log it for convenience. // If we're running a known preset, log it for convenience.
switch { switch {
case ctx.IsSet(utils.MainnetFlag.Name):
log.Info("Starting Geth on Ethereum mainnet...")
case ctx.IsSet(utils.SepoliaFlag.Name): case ctx.IsSet(utils.SepoliaFlag.Name):
log.Info("Starting Geth on Sepolia testnet...") log.Info("Starting Geth on Sepolia testnet...")
@ -294,15 +297,20 @@ func prepare(ctx *cli.Context) {
case ctx.IsSet(utils.HoodiFlag.Name): case ctx.IsSet(utils.HoodiFlag.Name):
log.Info("Starting Geth on Hoodi testnet...") log.Info("Starting Geth on Hoodi testnet...")
case !ctx.IsSet(utils.NetworkIdFlag.Name): case ctx.IsSet(utils.BepoliaFlag.Name):
log.Info("Starting Geth on Ethereum mainnet...") log.Info("Starting Geth on Bepolia testnet...")
case !ctx.IsSet(utils.NetworkIdFlag.Name), ctx.IsSet(utils.BerachainFlag.Name):
log.Info("Starting Geth on Berachain mainnet...")
} }
// If we're a full node on mainnet without --cache specified, bump default cache allowance
// If we're a full node on Berachain mainnet without --cache specified, bump default cache allowance
if !ctx.IsSet(utils.CacheFlag.Name) && !ctx.IsSet(utils.NetworkIdFlag.Name) { if !ctx.IsSet(utils.CacheFlag.Name) && !ctx.IsSet(utils.NetworkIdFlag.Name) {
// Make sure we're not on any supported preconfigured testnet either // Make sure we're not on any supported preconfigured testnet either
if !ctx.IsSet(utils.HoleskyFlag.Name) && if !ctx.IsSet(utils.HoleskyFlag.Name) &&
!ctx.IsSet(utils.SepoliaFlag.Name) && !ctx.IsSet(utils.SepoliaFlag.Name) &&
!ctx.IsSet(utils.HoodiFlag.Name) && !ctx.IsSet(utils.HoodiFlag.Name) &&
!ctx.IsSet(utils.BepoliaFlag.Name) &&
!ctx.IsSet(utils.DeveloperFlag.Name) { !ctx.IsSet(utils.DeveloperFlag.Name) {
// Nope, we're really on mainnet. Bump that cache up! // Nope, we're really on mainnet. Bump that cache up!
log.Info("Bumping default cache on mainnet", "provided", ctx.Int(utils.CacheFlag.Name), "updated", 4096) log.Info("Bumping default cache on mainnet", "provided", ctx.Int(utils.CacheFlag.Name), "updated", 4096)

View file

@ -139,7 +139,7 @@ var (
} }
NetworkIdFlag = &cli.Uint64Flag{ NetworkIdFlag = &cli.Uint64Flag{
Name: "networkid", Name: "networkid",
Usage: "Explicitly set network id (integer)(For testnets: use --sepolia, --holesky, --hoodi instead)", Usage: "Explicitly set network id (integer)(Testnet: use --bepolia)(For Ethereum: use --mainnet, --sepolia, etc.)",
Value: ethconfig.Defaults.NetworkId, Value: ethconfig.Defaults.NetworkId,
Category: flags.EthCategory, Category: flags.EthCategory,
} }
@ -163,6 +163,17 @@ var (
Usage: "Hoodi network: pre-configured proof-of-stake test network", Usage: "Hoodi network: pre-configured proof-of-stake test network",
Category: flags.EthCategory, Category: flags.EthCategory,
} }
// Berachain
BerachainFlag = &cli.BoolFlag{
Name: "berachain",
Usage: "Berachain mainnet",
Category: flags.EthCategory,
}
BepoliaFlag = &cli.BoolFlag{
Name: "bepolia",
Usage: "Bepolia network: pre-configured proof-of-stake test network",
Category: flags.EthCategory,
}
// Dev mode // Dev mode
DeveloperFlag = &cli.BoolFlag{ DeveloperFlag = &cli.BoolFlag{
Name: "dev", Name: "dev",
@ -449,7 +460,7 @@ var (
// Performance tuning settings // Performance tuning settings
CacheFlag = &cli.IntFlag{ CacheFlag = &cli.IntFlag{
Name: "cache", Name: "cache",
Usage: "Megabytes of memory allocated to internal caching (default = 4096 mainnet full node, 128 light mode)", Usage: "Megabytes of memory allocated to internal caching (default = 4096 berachain mainnet full node, 128 light mode)",
Value: 1024, Value: 1024,
Category: flags.PerfCategory, Category: flags.PerfCategory,
} }

View file

@ -37,6 +37,8 @@ var (
Flags: []cli.Flag{ Flags: []cli.Flag{
testSepoliaFlag, testSepoliaFlag,
testMainnetFlag, testMainnetFlag,
testBepoliaFlag,
testBerachainFlag,
filterQueryFileFlag, filterQueryFileFlag,
filterErrorFileFlag, filterErrorFileFlag,
}, },

View file

@ -85,6 +85,16 @@ var (
Usage: "Use test cases for mainnet network", Usage: "Use test cases for mainnet network",
Category: flags.TestingCategory, Category: flags.TestingCategory,
} }
testBepoliaFlag = &cli.BoolFlag{
Name: "bepolia",
Usage: "Use test cases for bepolia network",
Category: flags.TestingCategory,
}
testBerachainFlag = &cli.BoolFlag{
Name: "berachain",
Usage: "Use test cases for berachain mainnet network",
Category: flags.TestingCategory,
}
) )
// testConfig holds the parameters for testing. // testConfig holds the parameters for testing.
@ -117,10 +127,13 @@ func validateHistoryPruneErr(err error, blockNum uint64, historyPruneBlock *uint
return err return err
} }
// TODO(bera): Add test filter & history queries for Berachain mainnet & Bepolia in queries/ folder
func testConfigFromCLI(ctx *cli.Context) (cfg testConfig) { func testConfigFromCLI(ctx *cli.Context) (cfg testConfig) {
flags.CheckExclusive(ctx, testMainnetFlag, testSepoliaFlag) flags.CheckExclusive(ctx, testMainnetFlag, testSepoliaFlag, testBerachainFlag, testBepoliaFlag)
if (ctx.IsSet(testMainnetFlag.Name) || ctx.IsSet(testSepoliaFlag.Name)) && ctx.IsSet(filterQueryFileFlag.Name) { if (ctx.IsSet(testMainnetFlag.Name) || ctx.IsSet(testSepoliaFlag.Name) || ctx.IsSet(testBerachainFlag.Name) || ctx.IsSet(testBepoliaFlag.Name)) &&
exit(filterQueryFileFlag.Name + " cannot be used with " + testMainnetFlag.Name + " or " + testSepoliaFlag.Name) ctx.IsSet(filterQueryFileFlag.Name) {
exit(filterQueryFileFlag.Name + " cannot be used with " + testMainnetFlag.Name + " or " +
testSepoliaFlag.Name + "or" + testBerachainFlag.Name + " or " + testBepoliaFlag.Name)
} }
// configure ethclient // configure ethclient
@ -166,6 +179,44 @@ func testConfigFromCLI(ctx *cli.Context) (cfg testConfig) {
} }
cfg.historyPruneBlock = new(uint64) cfg.historyPruneBlock = new(uint64)
*cfg.historyPruneBlock = history.PrunePoints[params.SepoliaGenesisHash].BlockNumber *cfg.historyPruneBlock = history.PrunePoints[params.SepoliaGenesisHash].BlockNumber
case ctx.Bool(testBerachainFlag.Name):
cfg.fsys = builtinTestFiles
if ctx.IsSet(filterQueryFileFlag.Name) {
cfg.filterQueryFile = ctx.String(filterQueryFileFlag.Name)
} else {
cfg.filterQueryFile = "queries/filter_queries_berachain.json"
}
if ctx.IsSet(historyTestFileFlag.Name) {
cfg.historyTestFile = ctx.String(historyTestFileFlag.Name)
} else {
cfg.historyTestFile = "queries/history_berachain.json"
}
if ctx.IsSet(traceTestFileFlag.Name) {
cfg.traceTestFile = ctx.String(traceTestFileFlag.Name)
} else {
cfg.traceTestFile = "queries/trace_berachain.json"
}
cfg.historyPruneBlock = new(uint64)
*cfg.historyPruneBlock = history.PrunePoints[params.BerachainGenesisHash].BlockNumber
case ctx.Bool(testBepoliaFlag.Name):
cfg.fsys = builtinTestFiles
if ctx.IsSet(filterQueryFileFlag.Name) {
cfg.filterQueryFile = ctx.String(filterQueryFileFlag.Name)
} else {
cfg.filterQueryFile = "queries/filter_queries_bepolia.json"
}
if ctx.IsSet(historyTestFileFlag.Name) {
cfg.historyTestFile = ctx.String(historyTestFileFlag.Name)
} else {
cfg.historyTestFile = "queries/history_bepolia.json"
}
if ctx.IsSet(traceTestFileFlag.Name) {
cfg.traceTestFile = ctx.String(traceTestFileFlag.Name)
} else {
cfg.traceTestFile = "queries/trace_bepolia.json"
}
cfg.historyPruneBlock = new(uint64)
*cfg.historyPruneBlock = history.PrunePoints[params.BepoliaGenesisHash].BlockNumber
default: default:
cfg.fsys = os.DirFS(".") cfg.fsys = os.DirFS(".")
cfg.filterQueryFile = ctx.String(filterQueryFileFlag.Name) cfg.filterQueryFile = ctx.String(filterQueryFileFlag.Name)

View file

@ -39,11 +39,16 @@ const (
HashLength = 32 HashLength = 32
// AddressLength is the expected length of the address // AddressLength is the expected length of the address
AddressLength = 20 AddressLength = 20
// Berachain: PubkeyLength represents the expected byte length of a BLS12-381 public key
// as used by the beacon chain.
PubkeyLength = 48
) )
var ( var (
hashT = reflect.TypeOf(Hash{}) hashT = reflect.TypeOf(Hash{})
addressT = reflect.TypeOf(Address{}) addressT = reflect.TypeOf(Address{})
pubkeyT = reflect.TypeOf(Pubkey{})
// MaxAddress represents the maximum possible address value. // MaxAddress represents the maximum possible address value.
MaxAddress = HexToAddress("0xffffffffffffffffffffffffffffffffffffffff") MaxAddress = HexToAddress("0xffffffffffffffffffffffffffffffffffffffff")
@ -486,3 +491,60 @@ func (b PrettyBytes) TerminalString() string {
} }
return fmt.Sprintf("%#x...%x (%dB)", b[:3], b[len(b)-3:], len(b)) return fmt.Sprintf("%#x...%x (%dB)", b[:3], b[len(b)-3:], len(b))
} }
/////////// Berachain: Pubkey
// Pubkey represents a fixed-length 48-byte BLS public key.
// JSON and text serialization use 0x-prefixed hex strings.
type Pubkey [PubkeyLength]byte
// Bytes returns a copy of the underlying byte slice.
func (p Pubkey) Bytes() []byte { return p[:] }
// String returns the hex-encoded string representation of the pubkey.
func (p Pubkey) String() string { return hexutil.Encode(p[:]) }
// Format implements fmt.Formatter.
// Pubkey supports the %v, %s, %q, %x, %X and %d format verbs.
func (p Pubkey) Format(s fmt.State, c rune) {
hexb := make([]byte, 2+len(p)*2)
copy(hexb, "0x")
hex.Encode(hexb[2:], p[:])
switch c {
case 'x', 'X':
if !s.Flag('#') {
hexb = hexb[2:]
}
if c == 'X' {
hexb = bytes.ToUpper(hexb)
}
fallthrough
case 'v', 's':
s.Write(hexb)
case 'q':
q := []byte{'"'}
s.Write(q)
s.Write(hexb)
s.Write(q)
case 'd':
fmt.Fprint(s, ([len(p)]byte)(p))
default:
fmt.Fprintf(s, "%%!%c(pubkey=%x)", c, p)
}
}
// MarshalText encodes the pubkey as a 0x-prefixed hex string.
func (p Pubkey) MarshalText() ([]byte, error) {
return hexutil.Bytes(p[:]).MarshalText()
}
// UnmarshalText decodes a 0x-prefixed hex string into the pubkey.
func (p *Pubkey) UnmarshalText(input []byte) error {
return hexutil.UnmarshalFixedText("Pubkey", input, p[:])
}
// UnmarshalJSON decodes a JSON string containing the 0x-prefixed hex pubkey.
func (p *Pubkey) UnmarshalJSON(input []byte) error {
return hexutil.UnmarshalFixedJSON(pubkeyT, input, p[:])
}

View file

@ -282,6 +282,17 @@ func (beacon *Beacon) verifyHeader(chain consensus.ChainHeaderReader, header, pa
return err return err
} }
} }
// Berachain specific: verify the existence of the proposer pubkey.
prague1 := chain.Config().IsPrague1(header.Number, header.Time)
if !prague1 {
if header.ParentProposerPubkey != nil {
return fmt.Errorf("invalid proposer pubkey: have %#x, expected nil", header.ParentProposerPubkey)
}
} else {
if header.ParentProposerPubkey == nil {
return errors.New("header is missing proposer pubkey")
}
}
return nil return nil
} }

View file

@ -54,6 +54,21 @@ func VerifyEIP1559Header(config *params.ChainConfig, parent, header *types.Heade
// CalcBaseFee calculates the basefee of the header. // CalcBaseFee calculates the basefee of the header.
func CalcBaseFee(config *params.ChainConfig, parent *types.Header) *big.Int { func CalcBaseFee(config *params.ChainConfig, parent *types.Header) *big.Int {
calculatedBaseFee := calcBaseFee(config, parent)
// Starting at the Prague1 fork, the base fee must be at least the minimum base fee.
if config.IsPrague1(parent.Number, parent.Time) {
minBaseFee := new(big.Int).SetUint64(config.Berachain.Prague1.MinimumBaseFeeWei)
if calculatedBaseFee.Cmp(minBaseFee) < 0 {
calculatedBaseFee = minBaseFee
}
}
return calculatedBaseFee
}
// calcBaseFee calculates the basefee of the header in wei.
func calcBaseFee(config *params.ChainConfig, parent *types.Header) *big.Int {
// If the current block is the first EIP-1559 block, return the InitialBaseFee. // If the current block is the first EIP-1559 block, return the InitialBaseFee.
if !config.IsLondon(parent.Number) { if !config.IsLondon(parent.Number) {
return new(big.Int).SetUint64(params.InitialBaseFee) return new(big.Int).SetUint64(params.InitialBaseFee)
@ -76,7 +91,7 @@ func CalcBaseFee(config *params.ChainConfig, parent *types.Header) *big.Int {
num.SetUint64(parent.GasUsed - parentGasTarget) num.SetUint64(parent.GasUsed - parentGasTarget)
num.Mul(num, parent.BaseFee) num.Mul(num, parent.BaseFee)
num.Div(num, denom.SetUint64(parentGasTarget)) num.Div(num, denom.SetUint64(parentGasTarget))
num.Div(num, denom.SetUint64(config.BaseFeeChangeDenominator())) num.Div(num, denom.SetUint64(config.BaseFeeChangeDenominator(parent.Number, parent.Time)))
if num.Cmp(common.Big1) < 0 { if num.Cmp(common.Big1) < 0 {
return num.Add(parent.BaseFee, common.Big1) return num.Add(parent.BaseFee, common.Big1)
} }
@ -87,7 +102,7 @@ func CalcBaseFee(config *params.ChainConfig, parent *types.Header) *big.Int {
num.SetUint64(parentGasTarget - parent.GasUsed) num.SetUint64(parentGasTarget - parent.GasUsed)
num.Mul(num, parent.BaseFee) num.Mul(num, parent.BaseFee)
num.Div(num, denom.SetUint64(parentGasTarget)) num.Div(num, denom.SetUint64(parentGasTarget))
num.Div(num, denom.SetUint64(config.BaseFeeChangeDenominator())) num.Div(num, denom.SetUint64(config.BaseFeeChangeDenominator(parent.Number, parent.Time)))
baseFee := num.Sub(parent.BaseFee, num) baseFee := num.Sub(parent.BaseFee, num)
if baseFee.Cmp(common.Big0) < 0 { if baseFee.Cmp(common.Big0) < 0 {

View file

@ -19,7 +19,9 @@ package core
import ( import (
"errors" "errors"
"fmt" "fmt"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
@ -47,7 +49,7 @@ func NewBlockValidator(config *params.ChainConfig, blockchain *BlockChain) *Bloc
// ValidateBody validates the given block's uncles and verifies the block // ValidateBody validates the given block's uncles and verifies the block
// header's transaction and uncle roots. The headers are assumed to be already // header's transaction and uncle roots. The headers are assumed to be already
// validated at this point. // validated at this point. Also it the Prague1 block according to BRIP-0004.
func (v *BlockValidator) ValidateBody(block *types.Block) error { func (v *BlockValidator) ValidateBody(block *types.Block) error {
// check EIP 7934 RLP-encoded block size cap // check EIP 7934 RLP-encoded block size cap
if v.config.IsOsaka(block.Number(), block.Time()) && block.Size() > params.MaxBlockSize { if v.config.IsOsaka(block.Number(), block.Time()) && block.Size() > params.MaxBlockSize {
@ -85,9 +87,37 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {
return errors.New("withdrawals present in block body") return errors.New("withdrawals present in block body")
} }
// Berachain: Pre-compute expected PoL tx hash when in Prague1.
isPrague1 := v.config.IsPrague1(block.Number(), block.Time())
var expectedPoLHash common.Hash
if isPrague1 {
polTx, err := types.NewPoLTx(
v.config.ChainID,
v.config.Berachain.Prague1.PoLDistributorAddress,
new(big.Int).Sub(block.Number(), big.NewInt(1)),
params.PoLTxGasLimit,
block.BaseFee(),
block.ProposerPubkey(),
)
if err != nil {
return fmt.Errorf("failed to create expected PoL tx: %w", err)
}
expectedPoLHash = polTx.Hash()
}
// Blob transactions may be present after the Cancun fork. // Blob transactions may be present after the Cancun fork.
var blobs int var blobs int
for i, tx := range block.Transactions() { for i, tx := range block.Transactions() {
// Berachain: validate the PoL tx is only the first tx in the block.
switch {
case isPrague1 && i == 0:
if tx.Hash() != expectedPoLHash {
return fmt.Errorf("PoL tx hash mismatch: have %v, want %v", tx.Hash(), expectedPoLHash)
}
case tx.Type() == types.PoLTxType:
return fmt.Errorf("invalid block: tx at index %d is a PoL tx", i)
}
// Count the number of blobs to validate against the header's blobGasUsed // Count the number of blobs to validate against the header's blobGasUsed
blobs += len(tx.BlobHashes()) blobs += len(tx.BlobHashes())

View file

@ -30,8 +30,127 @@ import (
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie"
) )
// -----------------------------------------------------------------------------
// Berachain:Prague-1 PoL validation tests
// -----------------------------------------------------------------------------
// newPrague1Config returns a ChainConfig where Prague (and Prague-1) are active
// from timestamp 0. The caller can specify the PoL distributor address.
func newPrague1Config(distributor common.Address) *params.ChainConfig {
zero := uint64(0)
cfg := *params.AllDevChainProtocolChanges // copy
cfg.Berachain.Prague1 = params.Prague1Config{
Time: &zero,
PoLDistributorAddress: distributor,
}
return &cfg
}
// buildTestChain initialises an in-memory blockchain with the provided config
// and returns the chain and its validator.
func buildTestChain(t *testing.T, cfg *params.ChainConfig) (*BlockChain, Validator) {
t.Helper()
db := rawdb.NewMemoryDatabase()
genesis := &Genesis{Config: cfg}
chain, err := NewBlockChain(db, genesis, ethash.NewFaker(), nil)
if err != nil {
t.Fatalf("failed to create blockchain: %v", err)
}
return chain, chain.Validator()
}
// samplePubkey returns a deterministic 48-byte pubkey for tests.
func samplePubkey() *common.Pubkey {
var pk common.Pubkey
for i := 0; i < common.PubkeyLength; i++ {
pk[i] = byte(i)
}
return &pk
}
// makeBlock builds a child block on top of parent with the supplied txs and timestamp.
func makeBlock(parent *types.Header, txs types.Transactions, timestamp uint64) *types.Block {
header := &types.Header{
ParentHash: parent.Hash(),
Number: new(big.Int).Add(parent.Number, big.NewInt(1)),
Time: timestamp,
GasLimit: params.GenesisGasLimit * 10,
Difficulty: big.NewInt(1),
ParentProposerPubkey: samplePubkey(),
BaseFee: big.NewInt(1000000000),
}
return types.NewBlock(header, &types.Body{Transactions: txs}, nil, trie.NewStackTrie(nil))
}
func TestValidateBody_Prague1_Valid(t *testing.T) {
distributor := common.HexToAddress("0x1111111111111111111111111111111111111111")
cfg := newPrague1Config(distributor)
chain, validator := buildTestChain(t, cfg)
// Build PoL tx + dummy tx.
polTx, err := types.NewPoLTx(cfg.ChainID, distributor, big.NewInt(0), params.PoLTxGasLimit, big.NewInt(1000000000), samplePubkey())
if err != nil {
t.Fatalf("failed to create PoL tx: %v", err)
}
dummyTx := types.NewTx(&types.LegacyTx{Nonce: 1})
block := makeBlock(chain.CurrentHeader(), types.Transactions{polTx, dummyTx}, 1)
if err := validator.ValidateBody(block); err != nil {
t.Fatalf("ValidateBody returned error for valid Prague1 block: %v", err)
}
}
func TestValidateBody_Prague1_InvalidHash(t *testing.T) {
distributor := common.HexToAddress("0x2222222222222222222222222222222222222222")
cfg := newPrague1Config(distributor)
chain, validator := buildTestChain(t, cfg)
// PoL tx with WRONG pubkey (different from header.ParentProposerPubkey).
wrongPk := &common.Pubkey{}
polTx, _ := types.NewPoLTx(cfg.ChainID, distributor, big.NewInt(0), params.PoLTxGasLimit, big.NewInt(1000000000), wrongPk)
block := makeBlock(chain.CurrentHeader(), types.Transactions{polTx}, 1)
if err := validator.ValidateBody(block); err == nil {
t.Fatalf("expected error due to invalid PoL hash, got nil")
}
}
func TestValidateBody_Prague1_MisplacedPoL(t *testing.T) {
distributor := common.HexToAddress("0x3333333333333333333333333333333333333333")
cfg := newPrague1Config(distributor)
chain, validator := buildTestChain(t, cfg)
polTx, _ := types.NewPoLTx(cfg.ChainID, distributor, big.NewInt(0), params.PoLTxGasLimit, big.NewInt(1000000000), samplePubkey())
dummyTx := types.NewTx(&types.LegacyTx{Nonce: 1})
// PoL tx placed second.
block := makeBlock(chain.CurrentHeader(), types.Transactions{dummyTx, polTx}, 1)
if err := validator.ValidateBody(block); err == nil {
t.Fatalf("expected error for PoL tx at index >0, got nil")
}
}
func TestValidateBody_PrePrague1_PoLProhibited(t *testing.T) {
distributor := common.HexToAddress("0x4444444444444444444444444444444444444444")
// Prague time active, but Prague1 *future* at timestamp 1000.
future := uint64(1000)
cfg := *params.AllDevChainProtocolChanges
cfg.Berachain.Prague1.Time = &future
cfg.Berachain.Prague1.PoLDistributorAddress = distributor
chain, validator := buildTestChain(t, &cfg)
polTx, _ := types.NewPoLTx(cfg.ChainID, distributor, big.NewInt(0), params.PoLTxGasLimit, big.NewInt(1000000000), samplePubkey())
block := makeBlock(chain.CurrentHeader(), types.Transactions{polTx}, 1) // timestamp 1 < future
if err := validator.ValidateBody(block); err == nil {
t.Fatalf("expected error: PoL tx before Prague1 fork should be invalid")
}
}
// Tests that simple header verification works, for both good and bad blocks. // Tests that simple header verification works, for both good and bad blocks.
func TestHeaderVerification(t *testing.T) { func TestHeaderVerification(t *testing.T) {
testHeaderVerification(t, rawdb.HashScheme) testHeaderVerification(t, rawdb.HashScheme)

View file

@ -102,6 +102,12 @@ func (b *BlockGen) SetParentBeaconRoot(root common.Hash) {
ProcessBeaconBlockRoot(root, vm.NewEVM(blockContext, b.statedb, b.cm.config, vm.Config{})) ProcessBeaconBlockRoot(root, vm.NewEVM(blockContext, b.statedb, b.cm.config, vm.Config{}))
} }
// SetParentProposerPubkey sets the parent proposer pubkey field of the generated
// block.
func (b *BlockGen) SetParentProposerPubkey(pubkey common.Pubkey) {
b.header.ParentProposerPubkey = &pubkey
}
// addTx adds a transaction to the generated block. If no coinbase has // addTx adds a transaction to the generated block. If no coinbase has
// been set, the block's coinbase is set to the zero address. // been set, the block's coinbase is set to the zero address.
// //
@ -116,9 +122,16 @@ func (b *BlockGen) addTx(bc *BlockChain, vmConfig vm.Config, tx *types.Transacti
var ( var (
blockContext = NewEVMBlockContext(b.header, bc, &b.header.Coinbase) blockContext = NewEVMBlockContext(b.header, bc, &b.header.Coinbase)
evm = vm.NewEVM(blockContext, b.statedb, b.cm.config, vmConfig) evm = vm.NewEVM(blockContext, b.statedb, b.cm.config, vmConfig)
gasPool = b.gasPool
gasUsed = &b.header.GasUsed
) )
// Berachain: PoL txs do not count towards block gas.
if tx.Type() == types.PoLTxType {
gasPool = new(GasPool).AddGas(params.PoLTxGasLimit)
gasUsed = new(uint64)
}
b.statedb.SetTxContext(tx.Hash(), len(b.txs)) b.statedb.SetTxContext(tx.Hash(), len(b.txs))
receipt, err := ApplyTransaction(evm, b.gasPool, b.statedb, b.header, tx, &b.header.GasUsed) receipt, err := ApplyTransaction(evm, gasPool, b.statedb, b.header, tx, gasUsed)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -617,6 +630,9 @@ func (cm *chainMaker) makeHeader(parent *types.Block, state *state.StateDB, engi
header.BlobGasUsed = new(uint64) header.BlobGasUsed = new(uint64)
header.ParentBeaconRoot = new(common.Hash) header.ParentBeaconRoot = new(common.Hash)
} }
if cm.config.IsPrague1(header.Number, header.Time) {
header.ParentProposerPubkey = new(common.Pubkey)
}
return header return header
} }

View file

@ -223,6 +223,10 @@ func getGenesisState(db ethdb.Database, blockhash common.Hash) (alloc types.Gene
genesis = DefaultHoleskyGenesisBlock() genesis = DefaultHoleskyGenesisBlock()
case params.HoodiGenesisHash: case params.HoodiGenesisHash:
genesis = DefaultHoodiGenesisBlock() genesis = DefaultHoodiGenesisBlock()
case params.BerachainGenesisHash:
genesis = DefaultBerachainGenesisBlock()
case params.BepoliaGenesisHash:
genesis = DefaultBepoliaGenesisBlock()
} }
if genesis != nil { if genesis != nil {
return genesis.Alloc, nil return genesis.Alloc, nil
@ -438,6 +442,10 @@ func (g *Genesis) chainConfigOrDefault(ghash common.Hash, stored *params.ChainCo
return params.SepoliaChainConfig return params.SepoliaChainConfig
case ghash == params.HoodiGenesisHash: case ghash == params.HoodiGenesisHash:
return params.HoodiChainConfig return params.HoodiChainConfig
case ghash == params.BerachainGenesisHash:
return params.BerachainChainConfig
case ghash == params.BepoliaGenesisHash:
return params.BepoliaChainConfig
default: default:
return stored return stored
} }
@ -518,6 +526,12 @@ func (g *Genesis) toBlockWithRoot(root common.Hash) *types.Block {
if conf.IsPrague(num, g.Timestamp) { if conf.IsPrague(num, g.Timestamp) {
head.RequestsHash = &types.EmptyRequestsHash head.RequestsHash = &types.EmptyRequestsHash
} }
if conf.IsPrague1(num, g.Timestamp) {
// BRIP-0004: The parentProposerPubkey of the genesis block is always
// the zero pubkey. This is because the genesis block does not have a parent
// by definition.
head.ParentProposerPubkey = new(common.Pubkey)
}
} }
return types.NewBlock(head, &types.Body{Withdrawals: withdrawals}, nil, trie.NewStackTrie(nil)) return types.NewBlock(head, &types.Body{Withdrawals: withdrawals}, nil, trie.NewStackTrie(nil))
} }
@ -644,6 +658,32 @@ func DefaultHoodiGenesisBlock() *Genesis {
} }
} }
// DefaultBerachainGenesisBlock returns the Berachain main net genesis block.
func DefaultBerachainGenesisBlock() *Genesis {
return &Genesis{
Config: params.BerachainChainConfig,
Nonce: 0x1234,
ExtraData: []byte{},
GasLimit: 0x1c9c380,
Difficulty: big.NewInt(0x01),
Timestamp: 1737381600,
Alloc: decodePrealloc(berachainAllocData),
}
}
// DefaultBepoliaGenesisBlock returns the Bepolia network genesis block.
func DefaultBepoliaGenesisBlock() *Genesis {
return &Genesis{
Config: params.BepoliaChainConfig,
Nonce: 0x1234,
ExtraData: []byte{},
GasLimit: 0x1c9c380,
Difficulty: big.NewInt(0x01),
Timestamp: 1739976735,
Alloc: decodePrealloc(bepoliaAllocData),
}
}
// DeveloperGenesisBlock returns the 'geth --dev' genesis block. // DeveloperGenesisBlock returns the 'geth --dev' genesis block.
func DeveloperGenesisBlock(gasLimit uint64, faucet *common.Address) *Genesis { func DeveloperGenesisBlock(gasLimit uint64, faucet *common.Address) *Genesis {
// Override the default period to the user requested one // Override the default period to the user requested one

File diff suppressed because one or more lines are too long

View file

@ -84,6 +84,15 @@ func testSetupGenesis(t *testing.T, scheme string) {
wantHash: params.MainnetGenesisHash, wantHash: params.MainnetGenesisHash,
wantConfig: params.MainnetChainConfig, wantConfig: params.MainnetChainConfig,
}, },
{
name: "berachain block in DB, genesis == nil",
fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, *params.ConfigCompatError, error) {
DefaultBerachainGenesisBlock().MustCommit(db, triedb.NewDatabase(db, newDbConfig(scheme)))
return SetupGenesisBlock(db, triedb.NewDatabase(db, newDbConfig(scheme)), nil)
},
wantHash: params.BerachainGenesisHash,
wantConfig: params.BerachainChainConfig,
},
{ {
name: "custom block in DB, genesis == nil", name: "custom block in DB, genesis == nil",
fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, *params.ConfigCompatError, error) { fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, *params.ConfigCompatError, error) {
@ -103,6 +112,15 @@ func testSetupGenesis(t *testing.T, scheme string) {
}, },
wantErr: &GenesisMismatchError{Stored: customghash, New: params.SepoliaGenesisHash}, wantErr: &GenesisMismatchError{Stored: customghash, New: params.SepoliaGenesisHash},
}, },
{
name: "custom block in DB, genesis == bepolia",
fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, *params.ConfigCompatError, error) {
tdb := triedb.NewDatabase(db, newDbConfig(scheme))
customg.Commit(db, tdb)
return SetupGenesisBlock(db, tdb, DefaultBepoliaGenesisBlock())
},
wantErr: &GenesisMismatchError{Stored: customghash, New: params.BepoliaGenesisHash},
},
{ {
name: "custom block in DB, genesis == hoodi", name: "custom block in DB, genesis == hoodi",
fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, *params.ConfigCompatError, error) { fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, *params.ConfigCompatError, error) {
@ -188,6 +206,8 @@ func TestGenesisHashes(t *testing.T) {
{DefaultSepoliaGenesisBlock(), params.SepoliaGenesisHash}, {DefaultSepoliaGenesisBlock(), params.SepoliaGenesisHash},
{DefaultHoleskyGenesisBlock(), params.HoleskyGenesisHash}, {DefaultHoleskyGenesisBlock(), params.HoleskyGenesisHash},
{DefaultHoodiGenesisBlock(), params.HoodiGenesisHash}, {DefaultHoodiGenesisBlock(), params.HoodiGenesisHash},
{DefaultBerachainGenesisBlock(), params.BerachainGenesisHash},
{DefaultBepoliaGenesisBlock(), params.BepoliaGenesisHash},
} { } {
// Test via MustCommit // Test via MustCommit
db := rawdb.NewMemoryDatabase() db := rawdb.NewMemoryDatabase()

View file

@ -413,6 +413,10 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr
nBlobs += len(tx.BlobHashes()) nBlobs += len(tx.BlobHashes())
} }
header.Root = common.BytesToHash(hasher.Sum(nil)) header.Root = common.BytesToHash(hasher.Sum(nil))
if config.IsPrague1(header.Number, header.Time) {
proposerPubkey := common.Pubkey{0x01, 0x02, 0x03}
header.ParentProposerPubkey = &proposerPubkey
}
if config.IsCancun(header.Number, header.Time) { if config.IsCancun(header.Number, header.Time) {
excess := eip4844.CalcExcessBlobGas(config, parent.Header(), header.Time) excess := eip4844.CalcExcessBlobGas(config, parent.Header(), header.Time)
used := uint64(nBlobs * params.BlobTxBlobGasPerBlob) used := uint64(nBlobs * params.BlobTxBlobGasPerBlob)

View file

@ -166,6 +166,9 @@ type Message struct {
// When SkipFromEOACheck is true, the message sender is not checked to be an EOA. // When SkipFromEOACheck is true, the message sender is not checked to be an EOA.
SkipFromEOACheck bool SkipFromEOACheck bool
// Berachain:IsPoLTx is true if the message is a PoL tx.
IsPoLTx bool
} }
// TransactionToMessage converts a transaction into a Message. // TransactionToMessage converts a transaction into a Message.
@ -185,6 +188,7 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In
SkipFromEOACheck: false, SkipFromEOACheck: false,
BlobHashes: tx.BlobHashes(), BlobHashes: tx.BlobHashes(),
BlobGasFeeCap: tx.BlobGasFeeCap(), BlobGasFeeCap: tx.BlobGasFeeCap(),
IsPoLTx: tx.Type() == types.PoLTxType,
} }
// If baseFee provided, set gasPrice to effectiveGasPrice. // If baseFee provided, set gasPrice to effectiveGasPrice.
if baseFee != nil { if baseFee != nil {
@ -207,9 +211,30 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In
// state and would never be accepted within a block. // state and would never be accepted within a block.
func ApplyMessage(evm *vm.EVM, msg *Message, gp *GasPool) (*ExecutionResult, error) { func ApplyMessage(evm *vm.EVM, msg *Message, gp *GasPool) (*ExecutionResult, error) {
evm.SetTxContext(NewEVMTxContext(msg)) evm.SetTxContext(NewEVMTxContext(msg))
// Berachain: ApplyPoLMessage is used to apply the PoL tx to the EVM, skipping the
// normal state transition checks.
if msg.IsPoLTx {
return ApplyPoLMessage(msg, evm), nil
}
return newStateTransition(evm, msg, gp).execute() return newStateTransition(evm, msg, gp).execute()
} }
// Berachain: ApplyPoLMessage applies the BRIP-0004 PoL tx to the EVM. No gas is consumed.
func ApplyPoLMessage(msg *Message, evm *vm.EVM) *ExecutionResult {
evm.StateDB.AddAddressToAccessList(*msg.To)
result := &ExecutionResult{}
ret, leftOverGas, err := evm.Call(msg.From, *msg.To, msg.Data, msg.GasLimit, common.U2560)
if err != nil {
result.Err = fmt.Errorf("PoL tx failed to execute: %v", err)
}
result.ReturnData = ret
result.MaxUsedGas = msg.GasLimit - leftOverGas // To inform how much gas was needed to run the msg.
return result
}
// stateTransition represents a state transition. // stateTransition represents a state transition.
// //
// == The State Transitioning Model // == The State Transitioning Model

View file

@ -327,6 +327,11 @@ func (p *TxPool) Add(txs []*types.Transaction, sync bool) []error {
// Mark this transaction belonging to no-subpool // Mark this transaction belonging to no-subpool
splits[i] = -1 splits[i] = -1
// Berachain: PoL txs are rejected.
if tx.Type() == types.PoLTxType {
continue
}
// Try to find a subpool that accepts the transaction // Try to find a subpool that accepts the transaction
for j, subpool := range p.subpools { for j, subpool := range p.subpools {
if subpool.Filter(tx) { if subpool.Filter(tx) {

View file

@ -106,6 +106,9 @@ type Header struct {
// RequestsHash was added by EIP-7685 and is ignored in legacy headers. // RequestsHash was added by EIP-7685 and is ignored in legacy headers.
RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"` RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"`
// ParentProposerPubkey was added by BRIP-0004 and is ignored in legacy headers.
ParentProposerPubkey *common.Pubkey `json:"parentProposerPubkey" rlp:"optional"`
} }
// field type overrides for gencodec // field type overrides for gencodec
@ -329,6 +332,10 @@ func CopyHeader(h *Header) *Header {
cpy.RequestsHash = new(common.Hash) cpy.RequestsHash = new(common.Hash)
*cpy.RequestsHash = *h.RequestsHash *cpy.RequestsHash = *h.RequestsHash
} }
if h.ParentProposerPubkey != nil {
cpy.ParentProposerPubkey = new(common.Pubkey)
*cpy.ParentProposerPubkey = *h.ParentProposerPubkey
}
return &cpy return &cpy
} }
@ -408,8 +415,9 @@ func (b *Block) BaseFee() *big.Int {
return new(big.Int).Set(b.header.BaseFee) return new(big.Int).Set(b.header.BaseFee)
} }
func (b *Block) BeaconRoot() *common.Hash { return b.header.ParentBeaconRoot } func (b *Block) BeaconRoot() *common.Hash { return b.header.ParentBeaconRoot }
func (b *Block) RequestsHash() *common.Hash { return b.header.RequestsHash } func (b *Block) RequestsHash() *common.Hash { return b.header.RequestsHash }
func (b *Block) ProposerPubkey() *common.Pubkey { return b.header.ParentProposerPubkey }
func (b *Block) ExcessBlobGas() *uint64 { func (b *Block) ExcessBlobGas() *uint64 {
var excessBlobGas *uint64 var excessBlobGas *uint64

View file

@ -16,28 +16,29 @@ var _ = (*headerMarshaling)(nil)
// MarshalJSON marshals as JSON. // MarshalJSON marshals as JSON.
func (h Header) MarshalJSON() ([]byte, error) { func (h Header) MarshalJSON() ([]byte, error) {
type Header struct { type Header struct {
ParentHash common.Hash `json:"parentHash" gencodec:"required"` ParentHash common.Hash `json:"parentHash" gencodec:"required"`
UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"` UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"`
Coinbase common.Address `json:"miner"` Coinbase common.Address `json:"miner"`
Root common.Hash `json:"stateRoot" gencodec:"required"` Root common.Hash `json:"stateRoot" gencodec:"required"`
TxHash common.Hash `json:"transactionsRoot" gencodec:"required"` TxHash common.Hash `json:"transactionsRoot" gencodec:"required"`
ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"` ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"`
Bloom Bloom `json:"logsBloom" gencodec:"required"` Bloom Bloom `json:"logsBloom" gencodec:"required"`
Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"` Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"`
Number *hexutil.Big `json:"number" gencodec:"required"` Number *hexutil.Big `json:"number" gencodec:"required"`
GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"` GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"` GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
Time hexutil.Uint64 `json:"timestamp" gencodec:"required"` Time hexutil.Uint64 `json:"timestamp" gencodec:"required"`
Extra hexutil.Bytes `json:"extraData" gencodec:"required"` Extra hexutil.Bytes `json:"extraData" gencodec:"required"`
MixDigest common.Hash `json:"mixHash"` MixDigest common.Hash `json:"mixHash"`
Nonce BlockNonce `json:"nonce"` Nonce BlockNonce `json:"nonce"`
BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"` BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"`
WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"` WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"`
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed" rlp:"optional"` BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed" rlp:"optional"`
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"` ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"`
ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"` ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"` RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"`
Hash common.Hash `json:"hash"` ParentProposerPubkey *common.Pubkey `json:"parentProposerPubkey" rlp:"optional"`
Hash common.Hash `json:"hash"`
} }
var enc Header var enc Header
enc.ParentHash = h.ParentHash enc.ParentHash = h.ParentHash
@ -61,6 +62,7 @@ func (h Header) MarshalJSON() ([]byte, error) {
enc.ExcessBlobGas = (*hexutil.Uint64)(h.ExcessBlobGas) enc.ExcessBlobGas = (*hexutil.Uint64)(h.ExcessBlobGas)
enc.ParentBeaconRoot = h.ParentBeaconRoot enc.ParentBeaconRoot = h.ParentBeaconRoot
enc.RequestsHash = h.RequestsHash enc.RequestsHash = h.RequestsHash
enc.ParentProposerPubkey = h.ParentProposerPubkey
enc.Hash = h.Hash() enc.Hash = h.Hash()
return json.Marshal(&enc) return json.Marshal(&enc)
} }
@ -68,27 +70,28 @@ func (h Header) MarshalJSON() ([]byte, error) {
// UnmarshalJSON unmarshals from JSON. // UnmarshalJSON unmarshals from JSON.
func (h *Header) UnmarshalJSON(input []byte) error { func (h *Header) UnmarshalJSON(input []byte) error {
type Header struct { type Header struct {
ParentHash *common.Hash `json:"parentHash" gencodec:"required"` ParentHash *common.Hash `json:"parentHash" gencodec:"required"`
UncleHash *common.Hash `json:"sha3Uncles" gencodec:"required"` UncleHash *common.Hash `json:"sha3Uncles" gencodec:"required"`
Coinbase *common.Address `json:"miner"` Coinbase *common.Address `json:"miner"`
Root *common.Hash `json:"stateRoot" gencodec:"required"` Root *common.Hash `json:"stateRoot" gencodec:"required"`
TxHash *common.Hash `json:"transactionsRoot" gencodec:"required"` TxHash *common.Hash `json:"transactionsRoot" gencodec:"required"`
ReceiptHash *common.Hash `json:"receiptsRoot" gencodec:"required"` ReceiptHash *common.Hash `json:"receiptsRoot" gencodec:"required"`
Bloom *Bloom `json:"logsBloom" gencodec:"required"` Bloom *Bloom `json:"logsBloom" gencodec:"required"`
Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"` Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"`
Number *hexutil.Big `json:"number" gencodec:"required"` Number *hexutil.Big `json:"number" gencodec:"required"`
GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"` GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"` GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
Time *hexutil.Uint64 `json:"timestamp" gencodec:"required"` Time *hexutil.Uint64 `json:"timestamp" gencodec:"required"`
Extra *hexutil.Bytes `json:"extraData" gencodec:"required"` Extra *hexutil.Bytes `json:"extraData" gencodec:"required"`
MixDigest *common.Hash `json:"mixHash"` MixDigest *common.Hash `json:"mixHash"`
Nonce *BlockNonce `json:"nonce"` Nonce *BlockNonce `json:"nonce"`
BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"` BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"`
WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"` WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"`
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed" rlp:"optional"` BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed" rlp:"optional"`
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"` ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"`
ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"` ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"` RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"`
ParentProposerPubkey *common.Pubkey `json:"parentProposerPubkey" rlp:"optional"`
} }
var dec Header var dec Header
if err := json.Unmarshal(input, &dec); err != nil { if err := json.Unmarshal(input, &dec); err != nil {
@ -169,5 +172,8 @@ func (h *Header) UnmarshalJSON(input []byte) error {
if dec.RequestsHash != nil { if dec.RequestsHash != nil {
h.RequestsHash = dec.RequestsHash h.RequestsHash = dec.RequestsHash
} }
if dec.ParentProposerPubkey != nil {
h.ParentProposerPubkey = dec.ParentProposerPubkey
}
return nil return nil
} }

View file

@ -43,7 +43,8 @@ func (obj *Header) EncodeRLP(_w io.Writer) error {
_tmp4 := obj.ExcessBlobGas != nil _tmp4 := obj.ExcessBlobGas != nil
_tmp5 := obj.ParentBeaconRoot != nil _tmp5 := obj.ParentBeaconRoot != nil
_tmp6 := obj.RequestsHash != nil _tmp6 := obj.RequestsHash != nil
if _tmp1 || _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 { _tmp7 := obj.ParentProposerPubkey != nil
if _tmp1 || _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 || _tmp7 {
if obj.BaseFee == nil { if obj.BaseFee == nil {
w.Write(rlp.EmptyString) w.Write(rlp.EmptyString)
} else { } else {
@ -53,41 +54,48 @@ func (obj *Header) EncodeRLP(_w io.Writer) error {
w.WriteBigInt(obj.BaseFee) w.WriteBigInt(obj.BaseFee)
} }
} }
if _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 { if _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 || _tmp7 {
if obj.WithdrawalsHash == nil { if obj.WithdrawalsHash == nil {
w.Write([]byte{0x80}) w.Write([]byte{0x80})
} else { } else {
w.WriteBytes(obj.WithdrawalsHash[:]) w.WriteBytes(obj.WithdrawalsHash[:])
} }
} }
if _tmp3 || _tmp4 || _tmp5 || _tmp6 { if _tmp3 || _tmp4 || _tmp5 || _tmp6 || _tmp7 {
if obj.BlobGasUsed == nil { if obj.BlobGasUsed == nil {
w.Write([]byte{0x80}) w.Write([]byte{0x80})
} else { } else {
w.WriteUint64((*obj.BlobGasUsed)) w.WriteUint64((*obj.BlobGasUsed))
} }
} }
if _tmp4 || _tmp5 || _tmp6 { if _tmp4 || _tmp5 || _tmp6 || _tmp7 {
if obj.ExcessBlobGas == nil { if obj.ExcessBlobGas == nil {
w.Write([]byte{0x80}) w.Write([]byte{0x80})
} else { } else {
w.WriteUint64((*obj.ExcessBlobGas)) w.WriteUint64((*obj.ExcessBlobGas))
} }
} }
if _tmp5 || _tmp6 { if _tmp5 || _tmp6 || _tmp7 {
if obj.ParentBeaconRoot == nil { if obj.ParentBeaconRoot == nil {
w.Write([]byte{0x80}) w.Write([]byte{0x80})
} else { } else {
w.WriteBytes(obj.ParentBeaconRoot[:]) w.WriteBytes(obj.ParentBeaconRoot[:])
} }
} }
if _tmp6 { if _tmp6 || _tmp7 {
if obj.RequestsHash == nil { if obj.RequestsHash == nil {
w.Write([]byte{0x80}) w.Write([]byte{0x80})
} else { } else {
w.WriteBytes(obj.RequestsHash[:]) w.WriteBytes(obj.RequestsHash[:])
} }
} }
if _tmp7 {
if obj.ParentProposerPubkey == nil {
w.Write([]byte{0x80})
} else {
w.WriteBytes(obj.ParentProposerPubkey[:])
}
}
w.ListEnd(_tmp0) w.ListEnd(_tmp0)
return w.Flush() return w.Flush()
} }

View file

@ -205,7 +205,7 @@ func (r *Receipt) decodeTyped(b []byte) error {
return errShortTypedReceipt return errShortTypedReceipt
} }
switch b[0] { switch b[0] {
case DynamicFeeTxType, AccessListTxType, BlobTxType, SetCodeTxType: case DynamicFeeTxType, AccessListTxType, BlobTxType, SetCodeTxType, PoLTxType:
var data receiptRLP var data receiptRLP
err := rlp.DecodeBytes(b[1:], &data) err := rlp.DecodeBytes(b[1:], &data)
if err != nil { if err != nil {
@ -368,7 +368,7 @@ func (rs Receipts) EncodeIndex(i int, w *bytes.Buffer) {
} }
w.WriteByte(r.Type) w.WriteByte(r.Type)
switch r.Type { switch r.Type {
case AccessListTxType, DynamicFeeTxType, BlobTxType, SetCodeTxType: case AccessListTxType, DynamicFeeTxType, BlobTxType, SetCodeTxType, PoLTxType:
rlp.Encode(w, data) rlp.Encode(w, data)
default: default:
// For unsupported types, write nothing. Since this is for // For unsupported types, write nothing. Since this is for

View file

@ -49,6 +49,9 @@ const (
DynamicFeeTxType = 0x02 DynamicFeeTxType = 0x02
BlobTxType = 0x03 BlobTxType = 0x03
SetCodeTxType = 0x04 SetCodeTxType = 0x04
// Berachain specific.
PoLTxType = 0x7E
) )
// Transaction is an Ethereum transaction. // Transaction is an Ethereum transaction.
@ -211,6 +214,8 @@ func (tx *Transaction) decodeTyped(b []byte) (TxData, error) {
inner = new(BlobTx) inner = new(BlobTx)
case SetCodeTxType: case SetCodeTxType:
inner = new(SetCodeTx) inner = new(SetCodeTx)
case PoLTxType:
inner = new(PoLTx)
default: default:
return nil, ErrTxTypeNotSupported return nil, ErrTxTypeNotSupported
} }

View file

@ -507,6 +507,33 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error {
} }
} }
case PoLTxType:
var itx PoLTx
inner = &itx
if dec.ChainID == nil {
return errors.New("missing required field 'chainId' in transaction")
}
itx.ChainID = (*big.Int)(dec.ChainID)
if dec.To == nil {
return errors.New("missing required field 'to' in transaction")
}
itx.To = *dec.To
if dec.Nonce == nil {
return errors.New("missing required field 'nonce' in transaction")
}
itx.Nonce = uint64(*dec.Nonce)
if dec.Gas == nil {
return errors.New("missing required field 'gas' for txdata")
}
itx.GasLimit = uint64(*dec.Gas)
if dec.Value == nil {
return errors.New("missing required field 'value' in transaction")
}
if dec.Input == nil {
return errors.New("missing required field 'input' in transaction")
}
itx.Data = *dec.Input
default: default:
return ErrTxTypeNotSupported return ErrTxTypeNotSupported
} }

View file

@ -42,6 +42,8 @@ type sigCache struct {
func MakeSigner(config *params.ChainConfig, blockNumber *big.Int, blockTime uint64) Signer { func MakeSigner(config *params.ChainConfig, blockNumber *big.Int, blockTime uint64) Signer {
var signer Signer var signer Signer
switch { switch {
case config.IsPrague1(blockNumber, blockTime):
signer = NewPrague1Signer(config.ChainID)
case config.IsPrague(blockNumber, blockTime): case config.IsPrague(blockNumber, blockTime):
signer = NewPragueSigner(config.ChainID) signer = NewPragueSigner(config.ChainID)
case config.IsCancun(blockNumber, blockTime): case config.IsCancun(blockNumber, blockTime):
@ -71,6 +73,8 @@ func LatestSigner(config *params.ChainConfig) Signer {
var signer Signer var signer Signer
if config.ChainID != nil { if config.ChainID != nil {
switch { switch {
case config.Berachain.Prague1.Time != nil:
signer = NewPrague1Signer(config.ChainID)
case config.PragueTime != nil: case config.PragueTime != nil:
signer = NewPragueSigner(config.ChainID) signer = NewPragueSigner(config.ChainID)
case config.CancunTime != nil: case config.CancunTime != nil:
@ -100,7 +104,7 @@ func LatestSigner(config *params.ChainConfig) Signer {
func LatestSignerForChainID(chainID *big.Int) Signer { func LatestSignerForChainID(chainID *big.Int) Signer {
var signer Signer var signer Signer
if chainID != nil { if chainID != nil {
signer = NewPragueSigner(chainID) signer = NewPrague1Signer(chainID)
} else { } else {
signer = HomesteadSigner{} signer = HomesteadSigner{}
} }
@ -219,6 +223,9 @@ func newModernSigner(chainID *big.Int, fork forks.Fork) Signer {
if fork >= forks.Prague { if fork >= forks.Prague {
s.txtypes[SetCodeTxType] = struct{}{} s.txtypes[SetCodeTxType] = struct{}{}
} }
if fork >= forks.Prague1 {
s.txtypes[PoLTxType] = struct{}{}
}
return s return s
} }
@ -251,6 +258,12 @@ func (s *modernSigner) Sender(tx *Transaction) (common.Address, error) {
if tx.ChainId().Cmp(s.chainID) != 0 { if tx.ChainId().Cmp(s.chainID) != 0 {
return common.Address{}, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, tx.ChainId(), s.chainID) return common.Address{}, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, tx.ChainId(), s.chainID)
} }
// Berachain specific: PoLTx is sent by system address and carries no signature.
if tt == PoLTxType {
return params.SystemAddress, nil
}
// 'modern' txs are defined to use 0 and 1 as their recovery // 'modern' txs are defined to use 0 and 1 as their recovery
// id, add 27 to become equivalent to unprotected Homestead signatures. // id, add 27 to become equivalent to unprotected Homestead signatures.
V, R, S := tx.RawSignatureValues() V, R, S := tx.RawSignatureValues()
@ -276,6 +289,18 @@ func (s *modernSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *bi
return R, S, V, nil return R, S, V, nil
} }
// NewPrague1Signer returns a signer that accepts
// - BRIP-0004 PoL transactions
// - EIP-7702 set code transactions
// - EIP-4844 blob transactions
// - EIP-1559 dynamic fee transactions
// - EIP-2930 access list transactions,
// - EIP-155 replay protected transactions, and
// - legacy Homestead transactions.
func NewPrague1Signer(chainId *big.Int) Signer {
return newModernSigner(chainId, forks.Prague1)
}
// NewPragueSigner returns a signer that accepts // NewPragueSigner returns a signer that accepts
// - EIP-7702 set code transactions // - EIP-7702 set code transactions
// - EIP-4844 blob transactions // - EIP-4844 blob transactions

179
core/types/tx_pol.go Normal file
View file

@ -0,0 +1,179 @@
// Copyright 2025 Berachain Foundation
// This file is part of the bera-geth library.
//
// The bera-geth 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 bera-geth 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 bera-geth library. If not, see <http://www.gnu.org/licenses/>.
package types
import (
"bytes"
"math/big"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
)
// PoLTx implements the TxData interface.
var _ TxData = (*PoLTx)(nil)
// PoLTx represents an BRIP-0004 transaction. No gas is consumed for execution.
type PoLTx struct {
ChainID *big.Int
From common.Address // system address
To common.Address // address of the PoL Distributor contract
Nonce uint64 // block number distributing for
GasLimit uint64 // artificial gas limit for the PoL tx, not consumed against the block gas limit
GasPrice *big.Int // gas price is set to the baseFee to make the tx valid for EIP-1559 rules
Data []byte // encodes the pubkey distributing for
}
// NewPoLTx creates a new PoL transaction.
func NewPoLTx(
chainID *big.Int,
distributorAddress common.Address,
distributionBlockNumber *big.Int,
gasLimit uint64,
baseFee *big.Int,
pubkey *common.Pubkey,
) (*Transaction, error) {
data, err := getDistributeForData(pubkey)
if err != nil {
return nil, err
}
return NewTx(&PoLTx{
ChainID: chainID,
From: params.SystemAddress,
To: distributorAddress,
Nonce: distributionBlockNumber.Uint64(),
GasLimit: gasLimit,
GasPrice: baseFee,
Data: data,
}), nil
}
func (*PoLTx) txType() byte { return PoLTxType }
// copy creates a deep copy of the transaction data and initializes all fields.
func (tx *PoLTx) copy() TxData {
cpy := &PoLTx{
ChainID: new(big.Int),
From: tx.From,
To: tx.To,
Nonce: tx.Nonce,
GasLimit: tx.GasLimit,
GasPrice: new(big.Int),
Data: common.CopyBytes(tx.Data),
}
if tx.ChainID != nil {
cpy.ChainID.Set(tx.ChainID)
}
if tx.GasPrice != nil {
cpy.GasPrice.Set(tx.GasPrice)
}
return cpy
}
func (tx *PoLTx) chainID() *big.Int { return tx.ChainID }
func (*PoLTx) accessList() AccessList { return nil }
func (tx *PoLTx) data() []byte { return tx.Data }
func (tx *PoLTx) gas() uint64 { return tx.GasLimit }
func (tx *PoLTx) gasPrice() *big.Int { return tx.GasPrice }
func (tx *PoLTx) gasTipCap() *big.Int { return tx.GasPrice }
func (tx *PoLTx) gasFeeCap() *big.Int { return tx.GasPrice }
func (*PoLTx) value() *big.Int { return new(big.Int) }
func (tx *PoLTx) nonce() uint64 { return tx.Nonce }
func (tx *PoLTx) to() *common.Address { return &tx.To }
// No-op: PoLTx is originated from the system address and carries no signature.
func (*PoLTx) rawSignatureValues() (v, r, s *big.Int) {
return nil, nil, nil
}
func (*PoLTx) setSignatureValues(chainID, v, r, s *big.Int) {
// No-op: PoLTx is originated from the system address and carries no signature.
}
// effectiveGasPrice returns the gas price. PoLTx does not pay for gas, but we
// return the baseFee here to make the receipt valid for a 1559 tx.
func (tx *PoLTx) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int {
return dst.Set(tx.GasPrice)
}
func (tx *PoLTx) encode(b *bytes.Buffer) error {
return rlp.Encode(b, tx)
}
func (tx *PoLTx) decode(input []byte) error {
return rlp.DecodeBytes(input, tx)
}
func (tx *PoLTx) sigHash(chainID *big.Int) common.Hash {
return prefixedRlpHash(
PoLTxType, // tx type: 0x7E
[]any{
chainID, // chainID: EIP-155 chain ID
tx.From, // from = system address
tx.To, // to = address of the PoL Distributor contract
tx.Nonce, // nonce = block number distributing for
tx.GasLimit, // gasLimit = artificial gas limit for execution
tx.GasPrice, // gasPrice = baseFee to make the tx valid for EIP-1559 rules
tx.Data, // data ~= pubkey distributing for
})
}
var (
bytesType, _ = abi.NewType("bytes", "", nil)
distributeForMethod = abi.NewMethod(
"distributeFor", "distributeFor", abi.Function, "nonpayable", false, false, []abi.Argument{
{Name: "pubkey", Type: bytesType, Indexed: false},
}, nil,
)
)
// getDistributeForData returns the tx data for the `distributeFor(bytes pubkey)` method.
func getDistributeForData(pubkey *common.Pubkey) ([]byte, error) {
var pubkeyBytes []byte
if pubkey == nil {
pubkeyBytes = common.Pubkey{}.Bytes()
} else {
pubkeyBytes = pubkey.Bytes()
}
arguments, err := distributeForMethod.Inputs.Pack(pubkeyBytes)
if err != nil {
return nil, err
}
return append(distributeForMethod.ID, arguments...), nil
}
// IsPoLDistribution returns true if the transaction is a PoL distribution.
func IsPoLDistribution(to *common.Address, data []byte, distributorAddress common.Address) bool {
// Txs that call the `distributeFor(bytes pubkey)` method on the PoL Distributor
// contract are also consideredPoL txs.
return to != nil && *to == distributorAddress && isDistributeForCall(data)
}
// isDistributeForCall returns true if the provided calldata corresponds to a
// call to the `distributeFor(bytes pubkey)` method defined in BRIP-0004.
//
// The function checks that the first four bytes (the function selector) match
// the ID of the `distributeFor` ABI method declared in tx_pol.go.
func isDistributeForCall(data []byte) bool {
if len(data) < 4 {
return false
}
return bytes.Equal(data[:4], distributeForMethod.ID)
}

128
core/types/tx_pol_test.go Normal file
View file

@ -0,0 +1,128 @@
// Copyright 2025 Berachain Foundation
// This file is part of the bera-geth library.
//
// The bera-geth 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 bera-geth 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 bera-geth library. If not, see <http://www.gnu.org/licenses/>.
package types
import (
"bytes"
"math/big"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/params"
)
// samplePubkey returns a deterministic 48-byte pubkey for tests.
func samplePubkey() *common.Pubkey {
var pk common.Pubkey
for i := 0; i < common.PubkeyLength; i++ {
pk[i] = byte(i)
}
return &pk
}
// TestNewPoLTx_DataPacking verifies that NewPoLTx produces the expected calldata
// (function selector + ABI-encoded pubkey) and that the transaction fields are
// wired up correctly.
func TestNewPoLTx_DataPacking(t *testing.T) {
chainID := big.NewInt(1)
distributor := common.HexToAddress("0x000000000000000000000000000000000000dEaD")
blockNum := big.NewInt(123)
pubkey := samplePubkey()
baseFee := big.NewInt(1000000000)
tx, err := NewPoLTx(chainID, distributor, blockNum, params.PoLTxGasLimit, baseFee, pubkey)
if err != nil {
t.Fatalf("NewPoLTx returned error: %v", err)
}
if tx.Type() != PoLTxType {
t.Fatalf("unexpected tx type: have %d, want %d", tx.Type(), PoLTxType)
}
expectedData, err := getDistributeForData(pubkey)
if err != nil {
t.Fatalf("getDistributeForData failed: %v", err)
}
if !bytes.Equal(tx.Data(), expectedData) {
t.Fatalf("calldata mismatch\n have: %x\n want: %x", tx.Data(), expectedData)
}
// Extra sanity: first 4 bytes must equal the method selector.
if !bytes.Equal(tx.Data()[:4], distributeForMethod.ID) {
t.Fatalf("calldata selector mismatch")
}
}
// TestNewPoLTx_NegativeBlockNumber ensures negative block numbers are handled
// without panicking (uint64 wrap-around is expected).
func TestNewPoLTx_NegativeBlockNumber(t *testing.T) {
chainID := big.NewInt(1)
distributor := common.Address{}
negBlock := big.NewInt(-1)
baseFee := big.NewInt(1000000000)
tx, err := NewPoLTx(chainID, distributor, negBlock, params.PoLTxGasLimit, baseFee, samplePubkey())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got, want := tx.Nonce(), negBlock.Uint64(); got != want {
t.Fatalf("nonce mismatch: have %d, want %d", got, want)
}
}
// TestIsPoLDistribution exercises positive and negative cases for the helper.
func TestIsPoLDistribution(t *testing.T) {
distributor := common.HexToAddress("0x1000000000000000000000000000000000000001")
baseFee := big.NewInt(1000000000)
tx, err := NewPoLTx(big.NewInt(1), distributor, big.NewInt(0), params.PoLTxGasLimit, baseFee, samplePubkey())
if err != nil {
t.Fatalf("failed to build PoL tx: %v", err)
}
// Positive case.
if !IsPoLDistribution(&distributor, tx.Data(), distributor) {
t.Fatalf("expected IsPoLDistribution to return true for valid PoL call")
}
// Wrong address.
otherAddr := common.HexToAddress("0x0200000000000000000000000000000000000002")
if IsPoLDistribution(&otherAddr, tx.Data(), distributor) {
t.Fatalf("expected false when distributor address mismatches")
}
// Too-short data.
shortData := []byte{0x01, 0x02, 0x03}
if IsPoLDistribution(&distributor, shortData, distributor) {
t.Fatalf("expected false for data shorter than selector")
}
// Nil address.
if IsPoLDistribution(nil, tx.Data(), distributor) {
t.Fatalf("expected false when to==nil")
}
}
// TestPoLTx_RawSignatureValues confirms that PoLTx reports no signature.
func TestPoLTx_RawSignatureValues(t *testing.T) {
baseFee := big.NewInt(1000000000)
tx, err := NewPoLTx(big.NewInt(1), common.Address{}, big.NewInt(0), params.PoLTxGasLimit, baseFee, samplePubkey())
if err != nil {
t.Fatalf("failed to create PoL tx: %v", err)
}
v, r, s := tx.RawSignatureValues()
if v != nil || r != nil || s != nil {
t.Fatalf("expected nil signature values, have v=%v r=%v s=%v", v, r, s)
}
}

View file

@ -87,14 +87,17 @@ var caps = []string{
"engine_forkchoiceUpdatedV1", "engine_forkchoiceUpdatedV1",
"engine_forkchoiceUpdatedV2", "engine_forkchoiceUpdatedV2",
"engine_forkchoiceUpdatedV3", "engine_forkchoiceUpdatedV3",
"engine_forkchoiceUpdatedV3P11",
"engine_forkchoiceUpdatedWithWitnessV1", "engine_forkchoiceUpdatedWithWitnessV1",
"engine_forkchoiceUpdatedWithWitnessV2", "engine_forkchoiceUpdatedWithWitnessV2",
"engine_forkchoiceUpdatedWithWitnessV3", "engine_forkchoiceUpdatedWithWitnessV3",
"engine_forkchoiceUpdatedWithWitnessV3P11",
"engine_exchangeTransitionConfigurationV1", "engine_exchangeTransitionConfigurationV1",
"engine_getPayloadV1", "engine_getPayloadV1",
"engine_getPayloadV2", "engine_getPayloadV2",
"engine_getPayloadV3", "engine_getPayloadV3",
"engine_getPayloadV4", "engine_getPayloadV4",
"engine_getPayloadV4P11",
"engine_getPayloadV5", "engine_getPayloadV5",
"engine_getBlobsV1", "engine_getBlobsV1",
"engine_getBlobsV2", "engine_getBlobsV2",
@ -102,14 +105,17 @@ var caps = []string{
"engine_newPayloadV2", "engine_newPayloadV2",
"engine_newPayloadV3", "engine_newPayloadV3",
"engine_newPayloadV4", "engine_newPayloadV4",
"engine_newPayloadV4P11",
"engine_newPayloadWithWitnessV1", "engine_newPayloadWithWitnessV1",
"engine_newPayloadWithWitnessV2", "engine_newPayloadWithWitnessV2",
"engine_newPayloadWithWitnessV3", "engine_newPayloadWithWitnessV3",
"engine_newPayloadWithWitnessV4", "engine_newPayloadWithWitnessV4",
"engine_newPayloadWithWitnessV4P11",
"engine_executeStatelessPayloadV1", "engine_executeStatelessPayloadV1",
"engine_executeStatelessPayloadV2", "engine_executeStatelessPayloadV2",
"engine_executeStatelessPayloadV3", "engine_executeStatelessPayloadV3",
"engine_executeStatelessPayloadV4", "engine_executeStatelessPayloadV4",
"engine_executeStatelessPayloadV4P11",
"engine_getPayloadBodiesByHashV1", "engine_getPayloadBodiesByHashV1",
"engine_getPayloadBodiesByHashV2", "engine_getPayloadBodiesByHashV2",
"engine_getPayloadBodiesByRangeV1", "engine_getPayloadBodiesByRangeV1",
@ -209,6 +215,8 @@ func newConsensusAPIWithoutHeartbeat(eth *eth.Ethereum) *ConsensusAPI {
func (api *ConsensusAPI) ForkchoiceUpdatedV1(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) { func (api *ConsensusAPI) ForkchoiceUpdatedV1(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
if payloadAttributes != nil { if payloadAttributes != nil {
switch { switch {
case payloadAttributes.ProposerPubkey != nil:
return engine.STATUS_INVALID, attributesErr("proposer pubkey not supported in V1")
case payloadAttributes.Withdrawals != nil || payloadAttributes.BeaconRoot != nil: case payloadAttributes.Withdrawals != nil || payloadAttributes.BeaconRoot != nil:
return engine.STATUS_INVALID, paramsErr("withdrawals and beacon root not supported in V1") return engine.STATUS_INVALID, paramsErr("withdrawals and beacon root not supported in V1")
case !api.checkFork(payloadAttributes.Timestamp, forks.Paris, forks.Shanghai): case !api.checkFork(payloadAttributes.Timestamp, forks.Paris, forks.Shanghai):
@ -223,6 +231,8 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(update engine.ForkchoiceStateV1, pa
func (api *ConsensusAPI) ForkchoiceUpdatedV2(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) { func (api *ConsensusAPI) ForkchoiceUpdatedV2(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
if params != nil { if params != nil {
switch { switch {
case params.ProposerPubkey != nil:
return engine.STATUS_INVALID, attributesErr("unexpected proposer pubkey")
case params.BeaconRoot != nil: case params.BeaconRoot != nil:
return engine.STATUS_INVALID, attributesErr("unexpected beacon root") return engine.STATUS_INVALID, attributesErr("unexpected beacon root")
case api.checkFork(params.Timestamp, forks.Paris) && params.Withdrawals != nil: case api.checkFork(params.Timestamp, forks.Paris) && params.Withdrawals != nil:
@ -241,6 +251,8 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV2(update engine.ForkchoiceStateV1, pa
func (api *ConsensusAPI) ForkchoiceUpdatedV3(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) { func (api *ConsensusAPI) ForkchoiceUpdatedV3(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
if params != nil { if params != nil {
switch { switch {
case params.ProposerPubkey != nil:
return engine.STATUS_INVALID, attributesErr("unexpected proposer pubkey")
case params.Withdrawals == nil: case params.Withdrawals == nil:
return engine.STATUS_INVALID, attributesErr("missing withdrawals") return engine.STATUS_INVALID, attributesErr("missing withdrawals")
case params.BeaconRoot == nil: case params.BeaconRoot == nil:
@ -256,6 +268,28 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV3(update engine.ForkchoiceStateV1, pa
return api.forkchoiceUpdated(update, params, engine.PayloadV3, false) return api.forkchoiceUpdated(update, params, engine.PayloadV3, false)
} }
// ForkchoiceUpdatedV3P11 is equivalent to V3 with the addition of parents proposer pubkey
// in the payload attributes. It supports only PayloadAttributesV3P11.
func (api *ConsensusAPI) ForkchoiceUpdatedV3P11(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
if params != nil {
switch {
case params.Withdrawals == nil:
return engine.STATUS_INVALID, attributesErr("missing withdrawals")
case params.BeaconRoot == nil:
return engine.STATUS_INVALID, attributesErr("missing beacon root")
case params.ProposerPubkey == nil:
return engine.STATUS_INVALID, attributesErr("missing proposer pubkey")
case !api.checkFork(params.Timestamp, forks.Prague1):
return engine.STATUS_INVALID, unsupportedForkErr("fcuV3P11 must only be called for prague1 payloads")
}
}
// TODO(matt): the spec requires that fcu is applied when called on a valid
// hash, even if params are wrong. To do this we need to split up
// forkchoiceUpdate into a function that only updates the head and then a
// function that kicks off block construction.
return api.forkchoiceUpdated(update, params, engine.PayloadV3P11, false)
}
func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes, payloadVersion engine.PayloadVersion, payloadWitness bool) (engine.ForkChoiceResponse, error) { func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes, payloadVersion engine.PayloadVersion, payloadWitness bool) (engine.ForkChoiceResponse, error) {
api.forkchoiceLock.Lock() api.forkchoiceLock.Lock()
defer api.forkchoiceLock.Unlock() defer api.forkchoiceLock.Unlock()
@ -383,13 +417,14 @@ func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payl
// will replace it arbitrarily many times in between. // will replace it arbitrarily many times in between.
if payloadAttributes != nil { if payloadAttributes != nil {
args := &miner.BuildPayloadArgs{ args := &miner.BuildPayloadArgs{
Parent: update.HeadBlockHash, Parent: update.HeadBlockHash,
Timestamp: payloadAttributes.Timestamp, Timestamp: payloadAttributes.Timestamp,
FeeRecipient: payloadAttributes.SuggestedFeeRecipient, FeeRecipient: payloadAttributes.SuggestedFeeRecipient,
Random: payloadAttributes.Random, Random: payloadAttributes.Random,
Withdrawals: payloadAttributes.Withdrawals, Withdrawals: payloadAttributes.Withdrawals,
BeaconRoot: payloadAttributes.BeaconRoot, BeaconRoot: payloadAttributes.BeaconRoot,
Version: payloadVersion, ProposerPubkey: payloadAttributes.ProposerPubkey,
Version: payloadVersion,
} }
id := args.Id() id := args.Id()
// If we already are busy generating this work, then we do not need // If we already are busy generating this work, then we do not need
@ -472,6 +507,14 @@ func (api *ConsensusAPI) GetPayloadV4(payloadID engine.PayloadID) (*engine.Execu
return api.getPayload(payloadID, false) return api.getPayload(payloadID, false)
} }
// GetPayloadV4P11 returns a cached payload by id.
func (api *ConsensusAPI) GetPayloadV4P11(payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) {
if !payloadID.Is(engine.PayloadV3P11) {
return nil, engine.UnsupportedFork
}
return api.getPayload(payloadID, false)
}
// GetPayloadV5 returns a cached payload by id. // GetPayloadV5 returns a cached payload by id.
func (api *ConsensusAPI) GetPayloadV5(payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) { func (api *ConsensusAPI) GetPayloadV5(payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) {
if !payloadID.Is(engine.PayloadV3) { if !payloadID.Is(engine.PayloadV3) {
@ -592,7 +635,7 @@ func (api *ConsensusAPI) NewPayloadV1(params engine.ExecutableData) (engine.Payl
if params.Withdrawals != nil { if params.Withdrawals != nil {
return invalidStatus, paramsErr("withdrawals not supported in V1") return invalidStatus, paramsErr("withdrawals not supported in V1")
} }
return api.newPayload(params, nil, nil, nil, false) return api.newPayload(params, nil, nil, nil, false, nil)
} }
// NewPayloadV2 creates an Eth1 block, inserts it in the chain, and returns the status of the chain. // NewPayloadV2 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
@ -613,7 +656,7 @@ func (api *ConsensusAPI) NewPayloadV2(params engine.ExecutableData) (engine.Payl
case params.BlobGasUsed != nil: case params.BlobGasUsed != nil:
return invalidStatus, paramsErr("non-nil blobGasUsed pre-cancun") return invalidStatus, paramsErr("non-nil blobGasUsed pre-cancun")
} }
return api.newPayload(params, nil, nil, nil, false) return api.newPayload(params, nil, nil, nil, false, nil)
} }
// NewPayloadV3 creates an Eth1 block, inserts it in the chain, and returns the status of the chain. // NewPayloadV3 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
@ -632,7 +675,7 @@ func (api *ConsensusAPI) NewPayloadV3(params engine.ExecutableData, versionedHas
case !api.checkFork(params.Timestamp, forks.Cancun): case !api.checkFork(params.Timestamp, forks.Cancun):
return invalidStatus, unsupportedForkErr("newPayloadV3 must only be called for cancun payloads") return invalidStatus, unsupportedForkErr("newPayloadV3 must only be called for cancun payloads")
} }
return api.newPayload(params, versionedHashes, beaconRoot, nil, false) return api.newPayload(params, versionedHashes, beaconRoot, nil, false, nil)
} }
// NewPayloadV4 creates an Eth1 block, inserts it in the chain, and returns the status of the chain. // NewPayloadV4 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
@ -657,10 +700,37 @@ func (api *ConsensusAPI) NewPayloadV4(params engine.ExecutableData, versionedHas
if err := validateRequests(requests); err != nil { if err := validateRequests(requests); err != nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(err) return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(err)
} }
return api.newPayload(params, versionedHashes, beaconRoot, requests, false) return api.newPayload(params, versionedHashes, beaconRoot, requests, false, nil)
} }
func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, requests [][]byte, witness bool) (engine.PayloadStatusV1, error) { // NewPayloadV4P11 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
func (api *ConsensusAPI) NewPayloadV4P11(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, executionRequests []hexutil.Bytes, proposerPubkey *common.Pubkey) (engine.PayloadStatusV1, error) {
switch {
case params.Withdrawals == nil:
return invalidStatus, paramsErr("nil withdrawals post-shanghai")
case params.ExcessBlobGas == nil:
return invalidStatus, paramsErr("nil excessBlobGas post-cancun")
case params.BlobGasUsed == nil:
return invalidStatus, paramsErr("nil blobGasUsed post-cancun")
case versionedHashes == nil:
return invalidStatus, paramsErr("nil versionedHashes post-cancun")
case beaconRoot == nil:
return invalidStatus, paramsErr("nil beaconRoot post-cancun")
case executionRequests == nil:
return invalidStatus, paramsErr("nil executionRequests post-prague")
case proposerPubkey == nil:
return invalidStatus, paramsErr("nil proposerPubkey post-prague1")
case !api.checkFork(params.Timestamp, forks.Prague1):
return invalidStatus, unsupportedForkErr("newPayloadV4P11 must only be called for prague1 payloads")
}
requests := convertRequests(executionRequests)
if err := validateRequests(requests); err != nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(err)
}
return api.newPayload(params, versionedHashes, beaconRoot, requests, false, proposerPubkey)
}
func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, requests [][]byte, witness bool, proposerPubkey *common.Pubkey) (engine.PayloadStatusV1, error) {
// The locking here is, strictly, not required. Without these locks, this can happen: // The locking here is, strictly, not required. Without these locks, this can happen:
// //
// 1. NewPayload( execdata-N ) is invoked from the CL. It goes all the way down to // 1. NewPayload( execdata-N ) is invoked from the CL. It goes all the way down to
@ -678,7 +748,7 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashe
defer api.newPayloadLock.Unlock() defer api.newPayloadLock.Unlock()
log.Trace("Engine API request received", "method", "NewPayload", "number", params.Number, "hash", params.BlockHash) log.Trace("Engine API request received", "method", "NewPayload", "number", params.Number, "hash", params.BlockHash)
block, err := engine.ExecutableDataToBlock(params, versionedHashes, beaconRoot, requests) block, err := engine.ExecutableDataToBlock(params, versionedHashes, beaconRoot, requests, proposerPubkey)
if err != nil { if err != nil {
bgu := "nil" bgu := "nil"
if params.BlobGasUsed != nil { if params.BlobGasUsed != nil {

View file

@ -305,7 +305,7 @@ func TestEth2NewBlock(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Failed to create the executable data, block %d: %v", i, err) t.Fatalf("Failed to create the executable data, block %d: %v", i, err)
} }
block, err := engine.ExecutableDataToBlock(*execData, nil, nil, nil) block, err := engine.ExecutableDataToBlock(*execData, nil, nil, nil, nil)
if err != nil { if err != nil {
t.Fatalf("Failed to convert executable data to block %v", err) t.Fatalf("Failed to convert executable data to block %v", err)
} }
@ -347,7 +347,7 @@ func TestEth2NewBlock(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Failed to create the executable data %v", err) t.Fatalf("Failed to create the executable data %v", err)
} }
block, err := engine.ExecutableDataToBlock(*execData, nil, nil, nil) block, err := engine.ExecutableDataToBlock(*execData, nil, nil, nil, nil)
if err != nil { if err != nil {
t.Fatalf("Failed to convert executable data to block %v", err) t.Fatalf("Failed to convert executable data to block %v", err)
} }
@ -472,10 +472,10 @@ func TestFullAPI(t *testing.T) {
ethservice.TxPool().Add([]*types.Transaction{tx}, false) ethservice.TxPool().Add([]*types.Transaction{tx}, false)
} }
setupBlocks(t, ethservice, 10, parent, callback, nil, nil) setupBlocks(t, ethservice, 10, parent, callback, nil, nil, nil)
} }
func setupBlocks(t *testing.T, ethservice *eth.Ethereum, n int, parent *types.Header, callback func(parent *types.Header), withdrawals [][]*types.Withdrawal, beaconRoots []common.Hash) []*types.Header { func setupBlocks(t *testing.T, ethservice *eth.Ethereum, n int, parent *types.Header, callback func(parent *types.Header), withdrawals [][]*types.Withdrawal, beaconRoots []common.Hash, proposerPubkeys []common.Pubkey) []*types.Header {
api := NewConsensusAPI(ethservice) api := NewConsensusAPI(ethservice)
var blocks []*types.Header var blocks []*types.Header
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
@ -488,9 +488,13 @@ func setupBlocks(t *testing.T, ethservice *eth.Ethereum, n int, parent *types.He
if beaconRoots != nil { if beaconRoots != nil {
h = &beaconRoots[i] h = &beaconRoots[i]
} }
var p *common.Pubkey
if proposerPubkeys != nil {
p = &proposerPubkeys[i]
}
envelope := getNewEnvelope(t, api, parent, w, h) envelope := getNewEnvelope(t, api, parent, w, h, p)
execResp, err := api.newPayload(*envelope.ExecutionPayload, []common.Hash{}, h, envelope.Requests, false) execResp, err := api.newPayload(*envelope.ExecutionPayload, []common.Hash{}, h, envelope.Requests, false, p)
if err != nil { if err != nil {
t.Fatalf("can't execute payload: %v", err) t.Fatalf("can't execute payload: %v", err)
} }
@ -660,12 +664,13 @@ func TestNewPayloadOnInvalidChain(t *testing.T) {
func assembleEnvelope(api *ConsensusAPI, parentHash common.Hash, params *engine.PayloadAttributes) (*engine.ExecutionPayloadEnvelope, error) { func assembleEnvelope(api *ConsensusAPI, parentHash common.Hash, params *engine.PayloadAttributes) (*engine.ExecutionPayloadEnvelope, error) {
args := &miner.BuildPayloadArgs{ args := &miner.BuildPayloadArgs{
Parent: parentHash, Parent: parentHash,
Timestamp: params.Timestamp, Timestamp: params.Timestamp,
FeeRecipient: params.SuggestedFeeRecipient, FeeRecipient: params.SuggestedFeeRecipient,
Random: params.Random, Random: params.Random,
Withdrawals: params.Withdrawals, Withdrawals: params.Withdrawals,
BeaconRoot: params.BeaconRoot, BeaconRoot: params.BeaconRoot,
ProposerPubkey: params.ProposerPubkey,
} }
payload, err := api.eth.Miner().BuildPayload(args, false) payload, err := api.eth.Miner().BuildPayload(args, false)
if err != nil { if err != nil {
@ -691,10 +696,10 @@ func TestEmptyBlocks(t *testing.T) {
api := NewConsensusAPI(ethservice) api := NewConsensusAPI(ethservice)
// Setup 10 blocks on the canonical chain // Setup 10 blocks on the canonical chain
setupBlocks(t, ethservice, 10, commonAncestor, func(parent *types.Header) {}, nil, nil) setupBlocks(t, ethservice, 10, commonAncestor, func(parent *types.Header) {}, nil, nil, nil)
// (1) check LatestValidHash by sending a normal payload (P1'') // (1) check LatestValidHash by sending a normal payload (P1'')
payload := getNewPayload(t, api, commonAncestor, nil, nil) payload := getNewPayload(t, api, commonAncestor, nil, nil, nil)
status, err := api.NewPayloadV1(*payload) status, err := api.NewPayloadV1(*payload)
if err != nil { if err != nil {
@ -708,7 +713,7 @@ func TestEmptyBlocks(t *testing.T) {
} }
// (2) Now send P1' which is invalid // (2) Now send P1' which is invalid
payload = getNewPayload(t, api, commonAncestor, nil, nil) payload = getNewPayload(t, api, commonAncestor, nil, nil, nil)
payload.GasUsed += 1 payload.GasUsed += 1
payload = setBlockhash(payload) payload = setBlockhash(payload)
// Now latestValidHash should be the common ancestor // Now latestValidHash should be the common ancestor
@ -726,7 +731,7 @@ func TestEmptyBlocks(t *testing.T) {
} }
// (3) Now send a payload with unknown parent // (3) Now send a payload with unknown parent
payload = getNewPayload(t, api, commonAncestor, nil, nil) payload = getNewPayload(t, api, commonAncestor, nil, nil, nil)
payload.ParentHash = common.Hash{1} payload.ParentHash = common.Hash{1}
payload = setBlockhash(payload) payload = setBlockhash(payload)
// Now latestValidHash should be the common ancestor // Now latestValidHash should be the common ancestor
@ -742,13 +747,14 @@ func TestEmptyBlocks(t *testing.T) {
} }
} }
func getNewEnvelope(t *testing.T, api *ConsensusAPI, parent *types.Header, withdrawals []*types.Withdrawal, beaconRoot *common.Hash) *engine.ExecutionPayloadEnvelope { func getNewEnvelope(t *testing.T, api *ConsensusAPI, parent *types.Header, withdrawals []*types.Withdrawal, beaconRoot *common.Hash, proposerPubkey *common.Pubkey) *engine.ExecutionPayloadEnvelope {
params := engine.PayloadAttributes{ params := engine.PayloadAttributes{
Timestamp: parent.Time + 1, Timestamp: parent.Time + 1,
Random: crypto.Keccak256Hash([]byte{byte(1)}), Random: crypto.Keccak256Hash([]byte{byte(1)}),
SuggestedFeeRecipient: parent.Coinbase, SuggestedFeeRecipient: parent.Coinbase,
Withdrawals: withdrawals, Withdrawals: withdrawals,
BeaconRoot: beaconRoot, BeaconRoot: beaconRoot,
ProposerPubkey: proposerPubkey,
} }
envelope, err := assembleEnvelope(api, parent.Hash(), &params) envelope, err := assembleEnvelope(api, parent.Hash(), &params)
@ -758,8 +764,8 @@ func getNewEnvelope(t *testing.T, api *ConsensusAPI, parent *types.Header, withd
return envelope return envelope
} }
func getNewPayload(t *testing.T, api *ConsensusAPI, parent *types.Header, withdrawals []*types.Withdrawal, beaconRoot *common.Hash) *engine.ExecutableData { func getNewPayload(t *testing.T, api *ConsensusAPI, parent *types.Header, withdrawals []*types.Withdrawal, beaconRoot *common.Hash, proposerPubkey *common.Pubkey) *engine.ExecutableData {
return getNewEnvelope(t, api, parent, withdrawals, beaconRoot).ExecutionPayload return getNewEnvelope(t, api, parent, withdrawals, beaconRoot, proposerPubkey).ExecutionPayload
} }
// setBlockhash sets the blockhash of a modified ExecutableData. // setBlockhash sets the blockhash of a modified ExecutableData.
@ -820,7 +826,7 @@ func TestTrickRemoteBlockCache(t *testing.T) {
commonAncestor := ethserviceA.BlockChain().CurrentBlock() commonAncestor := ethserviceA.BlockChain().CurrentBlock()
// Setup 10 blocks on the canonical chain // Setup 10 blocks on the canonical chain
setupBlocks(t, ethserviceA, 10, commonAncestor, func(parent *types.Header) {}, nil, nil) setupBlocks(t, ethserviceA, 10, commonAncestor, func(parent *types.Header) {}, nil, nil, nil)
commonAncestor = ethserviceA.BlockChain().CurrentBlock() commonAncestor = ethserviceA.BlockChain().CurrentBlock()
var invalidChain []*engine.ExecutableData var invalidChain []*engine.ExecutableData
@ -829,7 +835,7 @@ func TestTrickRemoteBlockCache(t *testing.T) {
//invalidChain = append(invalidChain, payload1) //invalidChain = append(invalidChain, payload1)
// create an invalid payload2 (P2) // create an invalid payload2 (P2)
payload2 := getNewPayload(t, apiA, commonAncestor, nil, nil) payload2 := getNewPayload(t, apiA, commonAncestor, nil, nil, nil)
//payload2.ParentHash = payload1.BlockHash //payload2.ParentHash = payload1.BlockHash
payload2.GasUsed += 1 payload2.GasUsed += 1
payload2 = setBlockhash(payload2) payload2 = setBlockhash(payload2)
@ -838,7 +844,7 @@ func TestTrickRemoteBlockCache(t *testing.T) {
head := payload2 head := payload2
// create some valid payloads on top // create some valid payloads on top
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
payload := getNewPayload(t, apiA, commonAncestor, nil, nil) payload := getNewPayload(t, apiA, commonAncestor, nil, nil, nil)
payload.ParentHash = head.BlockHash payload.ParentHash = head.BlockHash
payload = setBlockhash(payload) payload = setBlockhash(payload)
invalidChain = append(invalidChain, payload) invalidChain = append(invalidChain, payload)
@ -875,10 +881,10 @@ func TestInvalidBloom(t *testing.T) {
api := NewConsensusAPI(ethservice) api := NewConsensusAPI(ethservice)
// Setup 10 blocks on the canonical chain // Setup 10 blocks on the canonical chain
setupBlocks(t, ethservice, 10, commonAncestor, func(parent *types.Header) {}, nil, nil) setupBlocks(t, ethservice, 10, commonAncestor, func(parent *types.Header) {}, nil, nil, nil)
// (1) check LatestValidHash by sending a normal payload (P1'') // (1) check LatestValidHash by sending a normal payload (P1'')
payload := getNewPayload(t, api, commonAncestor, nil, nil) payload := getNewPayload(t, api, commonAncestor, nil, nil, nil)
payload.LogsBloom = append(payload.LogsBloom, byte(1)) payload.LogsBloom = append(payload.LogsBloom, byte(1))
status, err := api.NewPayloadV1(*payload) status, err := api.NewPayloadV1(*payload)
if err != nil { if err != nil {
@ -935,7 +941,7 @@ func TestSimultaneousNewBlock(t *testing.T) {
t.Fatal(testErr) t.Fatal(testErr)
} }
} }
block, err := engine.ExecutableDataToBlock(*execData, nil, nil, nil) block, err := engine.ExecutableDataToBlock(*execData, nil, nil, nil, nil)
if err != nil { if err != nil {
t.Fatalf("Failed to convert executable data to block %v", err) t.Fatalf("Failed to convert executable data to block %v", err)
} }
@ -1275,7 +1281,7 @@ func setupBodies(t *testing.T) (*node.Node, *eth.Ethereum, []*types.Block) {
} }
// Create the blocks. // Create the blocks.
newHeaders := setupBlocks(t, ethservice, 10, parent, callback, withdrawals, beaconRoots) newHeaders := setupBlocks(t, ethservice, 10, parent, callback, withdrawals, beaconRoots, nil)
newBlocks := make([]*types.Block, len(newHeaders)) newBlocks := make([]*types.Block, len(newHeaders))
for i, header := range newHeaders { for i, header := range newHeaders {
newBlocks[i] = ethservice.BlockChain().GetBlock(header.Hash(), header.Number.Uint64()) newBlocks[i] = ethservice.BlockChain().GetBlock(header.Hash(), header.Number.Uint64())
@ -1529,7 +1535,7 @@ func TestBlockToPayloadWithBlobs(t *testing.T) {
if got := len(envelope.BlobsBundle.Blobs); got != want { if got := len(envelope.BlobsBundle.Blobs); got != want {
t.Fatalf("invalid number of blobs: got %v, want %v", got, want) t.Fatalf("invalid number of blobs: got %v, want %v", got, want)
} }
_, err := engine.ExecutableDataToBlock(*envelope.ExecutionPayload, make([]common.Hash, 1), nil, nil) _, err := engine.ExecutableDataToBlock(*envelope.ExecutionPayload, make([]common.Hash, 1), nil, nil, nil)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }

View file

@ -100,6 +100,8 @@ type SimulatedBeacon struct {
func payloadVersion(config *params.ChainConfig, time uint64) engine.PayloadVersion { func payloadVersion(config *params.ChainConfig, time uint64) engine.PayloadVersion {
switch config.LatestFork(time) { switch config.LatestFork(time) {
case forks.Prague1:
return engine.PayloadV3P11
case forks.Prague, forks.Cancun: case forks.Prague, forks.Cancun:
return engine.PayloadV3 return engine.PayloadV3
case forks.Paris, forks.Shanghai: case forks.Paris, forks.Shanghai:
@ -191,6 +193,10 @@ func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal, timestamp u
} }
version := payloadVersion(c.eth.BlockChain().Config(), timestamp) version := payloadVersion(c.eth.BlockChain().Config(), timestamp)
var proposerPubkey *common.Pubkey
if version == engine.PayloadV3P11 {
proposerPubkey = &common.Pubkey{}
}
var random [32]byte var random [32]byte
rand.Read(random[:]) rand.Read(random[:])
@ -200,6 +206,7 @@ func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal, timestamp u
Withdrawals: withdrawals, Withdrawals: withdrawals,
Random: random, Random: random,
BeaconRoot: &common.Hash{}, BeaconRoot: &common.Hash{},
ProposerPubkey: proposerPubkey,
}, version, false) }, version, false)
if err != nil { if err != nil {
return err return err
@ -256,7 +263,7 @@ func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal, timestamp u
} }
// Mark the payload as canon // Mark the payload as canon
_, err = c.engineAPI.newPayload(*payload, blobHashes, beaconRoot, requests, false) _, err = c.engineAPI.newPayload(*payload, blobHashes, beaconRoot, requests, false, proposerPubkey)
if err != nil { if err != nil {
return err return err
} }
@ -317,6 +324,7 @@ func (c *SimulatedBeacon) Commit() common.Hash {
withdrawals := c.withdrawals.pop(10) withdrawals := c.withdrawals.pop(10)
if err := c.sealBlock(withdrawals, uint64(time.Now().Unix())); err != nil { if err := c.sealBlock(withdrawals, uint64(time.Now().Unix())); err != nil {
log.Warn("Error performing sealing work", "err", err) log.Warn("Error performing sealing work", "err", err)
panic(err)
} }
return c.eth.BlockChain().CurrentBlock().Hash() return c.eth.BlockChain().CurrentBlock().Hash()
} }

View file

@ -37,6 +37,8 @@ import (
func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV1(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) { func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV1(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
if payloadAttributes != nil { if payloadAttributes != nil {
switch { switch {
case payloadAttributes.ProposerPubkey != nil:
return engine.STATUS_INVALID, attributesErr("proposer pubkey not supported in V1")
case payloadAttributes.Withdrawals != nil || payloadAttributes.BeaconRoot != nil: case payloadAttributes.Withdrawals != nil || payloadAttributes.BeaconRoot != nil:
return engine.STATUS_INVALID, paramsErr("withdrawals and beacon root not supported in V1") return engine.STATUS_INVALID, paramsErr("withdrawals and beacon root not supported in V1")
case !api.checkFork(payloadAttributes.Timestamp, forks.Paris, forks.Shanghai): case !api.checkFork(payloadAttributes.Timestamp, forks.Paris, forks.Shanghai):
@ -51,6 +53,8 @@ func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV1(update engine.Forkchoice
func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV2(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) { func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV2(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
if params != nil { if params != nil {
switch { switch {
case params.ProposerPubkey != nil:
return engine.STATUS_INVALID, attributesErr("unexpected proposer pubkey")
case params.BeaconRoot != nil: case params.BeaconRoot != nil:
return engine.STATUS_INVALID, attributesErr("unexpected beacon root") return engine.STATUS_INVALID, attributesErr("unexpected beacon root")
case api.checkFork(params.Timestamp, forks.Paris) && params.Withdrawals != nil: case api.checkFork(params.Timestamp, forks.Paris) && params.Withdrawals != nil:
@ -69,6 +73,8 @@ func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV2(update engine.Forkchoice
func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV3(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) { func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV3(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
if params != nil { if params != nil {
switch { switch {
case params.ProposerPubkey != nil:
return engine.STATUS_INVALID, attributesErr("unexpected proposer pubkey")
case params.Withdrawals == nil: case params.Withdrawals == nil:
return engine.STATUS_INVALID, attributesErr("missing withdrawals") return engine.STATUS_INVALID, attributesErr("missing withdrawals")
case params.BeaconRoot == nil: case params.BeaconRoot == nil:
@ -84,13 +90,35 @@ func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV3(update engine.Forkchoice
return api.forkchoiceUpdated(update, params, engine.PayloadV3, true) return api.forkchoiceUpdated(update, params, engine.PayloadV3, true)
} }
// ForkchoiceUpdatedWithWitnessV3P11 is analogous to ForkchoiceUpdatedV3P11, only it
// generates an execution witness too if block building was requested.
func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV3P11(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
if params != nil {
switch {
case params.Withdrawals == nil:
return engine.STATUS_INVALID, attributesErr("missing withdrawals")
case params.BeaconRoot == nil:
return engine.STATUS_INVALID, attributesErr("missing beacon root")
case params.ProposerPubkey == nil:
return engine.STATUS_INVALID, attributesErr("missing proposer pubkey")
case !api.checkFork(params.Timestamp, forks.Prague1):
return engine.STATUS_INVALID, unsupportedForkErr("fcuV3P11 must only be called for prague1 payloads")
}
}
// TODO(matt): the spec requires that fcu is applied when called on a valid
// hash, even if params are wrong. To do this we need to split up
// forkchoiceUpdate into a function that only updates the head and then a
// function that kicks off block construction.
return api.forkchoiceUpdated(update, params, engine.PayloadV3P11, true)
}
// NewPayloadWithWitnessV1 is analogous to NewPayloadV1, only it also generates // NewPayloadWithWitnessV1 is analogous to NewPayloadV1, only it also generates
// and returns a stateless witness after running the payload. // and returns a stateless witness after running the payload.
func (api *ConsensusAPI) NewPayloadWithWitnessV1(params engine.ExecutableData) (engine.PayloadStatusV1, error) { func (api *ConsensusAPI) NewPayloadWithWitnessV1(params engine.ExecutableData) (engine.PayloadStatusV1, error) {
if params.Withdrawals != nil { if params.Withdrawals != nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("withdrawals not supported in V1")) return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("withdrawals not supported in V1"))
} }
return api.newPayload(params, nil, nil, nil, true) return api.newPayload(params, nil, nil, nil, true, nil)
} }
// NewPayloadWithWitnessV2 is analogous to NewPayloadV2, only it also generates // NewPayloadWithWitnessV2 is analogous to NewPayloadV2, only it also generates
@ -112,7 +140,7 @@ func (api *ConsensusAPI) NewPayloadWithWitnessV2(params engine.ExecutableData) (
case params.BlobGasUsed != nil: case params.BlobGasUsed != nil:
return invalidStatus, paramsErr("non-nil blobGasUsed pre-cancun") return invalidStatus, paramsErr("non-nil blobGasUsed pre-cancun")
} }
return api.newPayload(params, nil, nil, nil, true) return api.newPayload(params, nil, nil, nil, true, nil)
} }
// NewPayloadWithWitnessV3 is analogous to NewPayloadV3, only it also generates // NewPayloadWithWitnessV3 is analogous to NewPayloadV3, only it also generates
@ -132,7 +160,7 @@ func (api *ConsensusAPI) NewPayloadWithWitnessV3(params engine.ExecutableData, v
case !api.checkFork(params.Timestamp, forks.Cancun): case !api.checkFork(params.Timestamp, forks.Cancun):
return invalidStatus, unsupportedForkErr("newPayloadV3 must only be called for cancun payloads") return invalidStatus, unsupportedForkErr("newPayloadV3 must only be called for cancun payloads")
} }
return api.newPayload(params, versionedHashes, beaconRoot, nil, true) return api.newPayload(params, versionedHashes, beaconRoot, nil, true, nil)
} }
// NewPayloadWithWitnessV4 is analogous to NewPayloadV4, only it also generates // NewPayloadWithWitnessV4 is analogous to NewPayloadV4, only it also generates
@ -158,7 +186,35 @@ func (api *ConsensusAPI) NewPayloadWithWitnessV4(params engine.ExecutableData, v
if err := validateRequests(requests); err != nil { if err := validateRequests(requests); err != nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(err) return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(err)
} }
return api.newPayload(params, versionedHashes, beaconRoot, requests, true) return api.newPayload(params, versionedHashes, beaconRoot, requests, true, nil)
}
// NewPayloadWithWitnessV4P11 is analogous to NewPayloadV4P11, only it also generates
// and returns a stateless witness after running the payload.
func (api *ConsensusAPI) NewPayloadWithWitnessV4P11(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, executionRequests []hexutil.Bytes, proposerPubkey *common.Pubkey) (engine.PayloadStatusV1, error) {
switch {
case params.Withdrawals == nil:
return invalidStatus, paramsErr("nil withdrawals post-shanghai")
case params.ExcessBlobGas == nil:
return invalidStatus, paramsErr("nil excessBlobGas post-cancun")
case params.BlobGasUsed == nil:
return invalidStatus, paramsErr("nil blobGasUsed post-cancun")
case versionedHashes == nil:
return invalidStatus, paramsErr("nil versionedHashes post-cancun")
case beaconRoot == nil:
return invalidStatus, paramsErr("nil beaconRoot post-cancun")
case executionRequests == nil:
return invalidStatus, paramsErr("nil executionRequests post-prague")
case proposerPubkey == nil:
return invalidStatus, paramsErr("nil proposerPubkey post-prague1")
case !api.checkFork(params.Timestamp, forks.Prague1):
return invalidStatus, unsupportedForkErr("newPayloadV4P11 must only be called for prague1 payloads")
}
requests := convertRequests(executionRequests)
if err := validateRequests(requests); err != nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(err)
}
return api.newPayload(params, versionedHashes, beaconRoot, requests, true, proposerPubkey)
} }
// ExecuteStatelessPayloadV1 is analogous to NewPayloadV1, only it operates in // ExecuteStatelessPayloadV1 is analogous to NewPayloadV1, only it operates in
@ -167,7 +223,7 @@ func (api *ConsensusAPI) ExecuteStatelessPayloadV1(params engine.ExecutableData,
if params.Withdrawals != nil { if params.Withdrawals != nil {
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("withdrawals not supported in V1")) return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("withdrawals not supported in V1"))
} }
return api.executeStatelessPayload(params, nil, nil, nil, opaqueWitness) return api.executeStatelessPayload(params, nil, nil, nil, opaqueWitness, nil)
} }
// ExecuteStatelessPayloadV2 is analogous to NewPayloadV2, only it operates in // ExecuteStatelessPayloadV2 is analogous to NewPayloadV2, only it operates in
@ -189,7 +245,7 @@ func (api *ConsensusAPI) ExecuteStatelessPayloadV2(params engine.ExecutableData,
case params.BlobGasUsed != nil: case params.BlobGasUsed != nil:
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("non-nil blobGasUsed pre-cancun") return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("non-nil blobGasUsed pre-cancun")
} }
return api.executeStatelessPayload(params, nil, nil, nil, opaqueWitness) return api.executeStatelessPayload(params, nil, nil, nil, opaqueWitness, nil)
} }
// ExecuteStatelessPayloadV3 is analogous to NewPayloadV3, only it operates in // ExecuteStatelessPayloadV3 is analogous to NewPayloadV3, only it operates in
@ -207,9 +263,9 @@ func (api *ConsensusAPI) ExecuteStatelessPayloadV3(params engine.ExecutableData,
case beaconRoot == nil: case beaconRoot == nil:
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil beaconRoot post-cancun") return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil beaconRoot post-cancun")
case !api.checkFork(params.Timestamp, forks.Cancun): case !api.checkFork(params.Timestamp, forks.Cancun):
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, unsupportedForkErr("newPayloadV3 must only be called for cancun payloads") return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, unsupportedForkErr("executeStatelessPayloadV3 must only be called for cancun payloads")
} }
return api.executeStatelessPayload(params, versionedHashes, beaconRoot, nil, opaqueWitness) return api.executeStatelessPayload(params, versionedHashes, beaconRoot, nil, opaqueWitness, nil)
} }
// ExecuteStatelessPayloadV4 is analogous to NewPayloadV4, only it operates in // ExecuteStatelessPayloadV4 is analogous to NewPayloadV4, only it operates in
@ -229,18 +285,46 @@ func (api *ConsensusAPI) ExecuteStatelessPayloadV4(params engine.ExecutableData,
case executionRequests == nil: case executionRequests == nil:
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil executionRequests post-prague") return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil executionRequests post-prague")
case !api.checkFork(params.Timestamp, forks.Prague): case !api.checkFork(params.Timestamp, forks.Prague):
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, unsupportedForkErr("newPayloadV3 must only be called for cancun payloads") return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, unsupportedForkErr("executeStatelessPayloadV4 must only be called for prague payloads")
} }
requests := convertRequests(executionRequests) requests := convertRequests(executionRequests)
if err := validateRequests(requests); err != nil { if err := validateRequests(requests); err != nil {
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(err) return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(err)
} }
return api.executeStatelessPayload(params, versionedHashes, beaconRoot, requests, opaqueWitness) return api.executeStatelessPayload(params, versionedHashes, beaconRoot, requests, opaqueWitness, nil)
} }
func (api *ConsensusAPI) executeStatelessPayload(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, requests [][]byte, opaqueWitness hexutil.Bytes) (engine.StatelessPayloadStatusV1, error) { // ExecuteStatelessPayloadV4P11 is analogous to NewPayloadV4P11, only it operates in
// a stateless mode on top of a provided witness instead of the local database.
func (api *ConsensusAPI) ExecuteStatelessPayloadV4P11(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, executionRequests []hexutil.Bytes, opaqueWitness hexutil.Bytes, proposerPubkey *common.Pubkey) (engine.StatelessPayloadStatusV1, error) {
switch {
case params.Withdrawals == nil:
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil withdrawals post-shanghai")
case params.ExcessBlobGas == nil:
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil excessBlobGas post-cancun")
case params.BlobGasUsed == nil:
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil blobGasUsed post-cancun")
case versionedHashes == nil:
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil versionedHashes post-cancun")
case beaconRoot == nil:
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil beaconRoot post-cancun")
case executionRequests == nil:
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil executionRequests post-prague")
case proposerPubkey == nil:
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil proposerPubkey post-prague1")
case !api.checkFork(params.Timestamp, forks.Prague1):
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, unsupportedForkErr("executeStatelessPayloadV4P11 must only be called for prague1 payloads")
}
requests := convertRequests(executionRequests)
if err := validateRequests(requests); err != nil {
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(err)
}
return api.executeStatelessPayload(params, versionedHashes, beaconRoot, requests, opaqueWitness, proposerPubkey)
}
func (api *ConsensusAPI) executeStatelessPayload(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, requests [][]byte, opaqueWitness hexutil.Bytes, proposerPubkey *common.Pubkey) (engine.StatelessPayloadStatusV1, error) {
log.Trace("Engine API request received", "method", "ExecuteStatelessPayload", "number", params.Number, "hash", params.BlockHash) log.Trace("Engine API request received", "method", "ExecuteStatelessPayload", "number", params.Number, "hash", params.BlockHash)
block, err := engine.ExecutableDataToBlockNoHash(params, versionedHashes, beaconRoot, requests) block, err := engine.ExecutableDataToBlockNoHash(params, versionedHashes, beaconRoot, requests, proposerPubkey)
if err != nil { if err != nil {
bgu := "nil" bgu := "nil"
if params.BlobGasUsed != nil { if params.BlobGasUsed != nil {
@ -268,6 +352,7 @@ func (api *ConsensusAPI) executeStatelessPayload(params engine.ExecutableData, v
"len(params.Transactions)", len(params.Transactions), "len(params.Transactions)", len(params.Transactions),
"len(params.Withdrawals)", len(params.Withdrawals), "len(params.Withdrawals)", len(params.Withdrawals),
"beaconRoot", beaconRoot, "beaconRoot", beaconRoot,
"parentProposerPubkey", proposerPubkey,
"len(requests)", len(requests), "len(requests)", len(requests),
"error", err) "error", err)
errorMsg := err.Error() errorMsg := err.Error()

View file

@ -47,7 +47,7 @@ var FullNodeGPO = gasprice.Config{
IgnorePrice: gasprice.DefaultIgnorePrice, IgnorePrice: gasprice.DefaultIgnorePrice,
} }
// Defaults contains default settings for use on the Ethereum main net. // Defaults contains default settings for use on the Ethereum and Berachain main net.
var Defaults = Config{ var Defaults = Config{
HistoryMode: history.KeepAll, HistoryMode: history.KeepAll,
SyncMode: SnapSync, SyncMode: SnapSync,

View file

@ -984,9 +984,11 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc
return nil, err return nil, err
} }
var ( var (
msg = args.ToMessage(blockContext.BaseFee, true, true) isPrague1 = api.backend.ChainConfig().IsPrague1(blockContext.BlockNumber, blockContext.Time)
tx = args.ToTransaction(types.LegacyTxType) distributorAddress = api.backend.ChainConfig().Berachain.Prague1.PoLDistributorAddress
traceConfig *TraceConfig msg = args.ToMessage(blockContext.BaseFee, true, true, isPrague1, distributorAddress)
tx = args.ToTransaction(types.LegacyTxType, isPrague1, distributorAddress)
traceConfig *TraceConfig
) )
// Lower the basefee to 0 to avoid breaking EVM // Lower the basefee to 0 to avoid breaking EVM
// invariants (basefee < feecap). // invariants (basefee < feecap).

View file

@ -318,7 +318,7 @@ func (t *Transaction) MaxFeePerGas(ctx context.Context) *hexutil.Big {
return nil return nil
} }
switch tx.Type() { switch tx.Type() {
case types.DynamicFeeTxType, types.BlobTxType, types.SetCodeTxType: case types.DynamicFeeTxType, types.BlobTxType, types.SetCodeTxType, types.PoLTxType:
return (*hexutil.Big)(tx.GasFeeCap()) return (*hexutil.Big)(tx.GasFeeCap())
default: default:
return nil return nil
@ -331,7 +331,7 @@ func (t *Transaction) MaxPriorityFeePerGas(ctx context.Context) *hexutil.Big {
return nil return nil
} }
switch tx.Type() { switch tx.Type() {
case types.DynamicFeeTxType, types.BlobTxType, types.SetCodeTxType: case types.DynamicFeeTxType, types.BlobTxType, types.SetCodeTxType, types.PoLTxType:
return (*hexutil.Big)(tx.GasTipCap()) return (*hexutil.Big)(tx.GasTipCap())
default: default:
return nil return nil

View file

@ -98,6 +98,14 @@ func Env() Environment {
isPR := os.Getenv("GITHUB_HEAD_REF") != "" isPR := os.Getenv("GITHUB_HEAD_REF") != ""
tag := "" tag := ""
branch := "" branch := ""
if reftype == "" {
gref := os.Getenv("GITHUB_REF")
if strings.HasPrefix(gref, "refs/heads/") {
reftype = "branch"
} else if strings.HasPrefix(gref, "refs/tags/") {
reftype = "tag"
}
}
switch { switch {
case isPR: case isPR:
branch = os.Getenv("GITHUB_BASE_REF") branch = os.Getenv("GITHUB_BASE_REF")
@ -106,6 +114,18 @@ func Env() Environment {
case reftype == "tag": case reftype == "tag":
tag = os.Getenv("GITHUB_REF_NAME") tag = os.Getenv("GITHUB_REF_NAME")
} }
// Fallback: If tag or branch is still empty, derive from GITHUB_REF.
if tag == "" && branch == "" {
if gref := os.Getenv("GITHUB_REF"); gref != "" {
if strings.HasPrefix(gref, "refs/tags/") {
tag = strings.TrimPrefix(gref, "refs/tags/")
} else if strings.HasPrefix(gref, "refs/heads/") {
branch = strings.TrimPrefix(gref, "refs/heads/")
}
}
}
return Environment{ return Environment{
CI: true, CI: true,
Name: "github-actions", Name: "github-actions",
@ -126,7 +146,7 @@ func Env() Environment {
// LocalEnv returns build environment metadata gathered from git. // LocalEnv returns build environment metadata gathered from git.
func LocalEnv() Environment { func LocalEnv() Environment {
env := applyEnvFlags(Environment{Name: "local", Repo: "ethereum/go-ethereum"}) env := applyEnvFlags(Environment{Name: "local", Repo: "berachain/bera-geth"})
head := readGitFile("HEAD") head := readGitFile("HEAD")
if fields := strings.Fields(head); len(fields) == 2 { if fields := strings.Fields(head); len(fields) == 2 {

View file

@ -696,7 +696,7 @@ func applyMessage(ctx context.Context, b Backend, args TransactionArgs, state *s
if err := args.CallDefaults(gp.Gas(), blockContext.BaseFee, b.ChainConfig().ChainID); err != nil { if err := args.CallDefaults(gp.Gas(), blockContext.BaseFee, b.ChainConfig().ChainID); err != nil {
return nil, err return nil, err
} }
msg := args.ToMessage(header.BaseFee, skipChecks, skipChecks) msg := args.ToMessage(header.BaseFee, skipChecks, skipChecks, b.ChainConfig().IsPrague1(header.Number, header.Time), b.ChainConfig().Berachain.Prague1.PoLDistributorAddress)
// Lower the basefee to 0 to avoid breaking EVM // Lower the basefee to 0 to avoid breaking EVM
// invariants (basefee < feecap). // invariants (basefee < feecap).
if msg.GasPrice.Sign() == 0 { if msg.GasPrice.Sign() == 0 {
@ -839,7 +839,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr
if err := args.CallDefaults(gasCap, header.BaseFee, b.ChainConfig().ChainID); err != nil { if err := args.CallDefaults(gasCap, header.BaseFee, b.ChainConfig().ChainID); err != nil {
return 0, err return 0, err
} }
call := args.ToMessage(header.BaseFee, true, true) call := args.ToMessage(header.BaseFee, true, true, b.ChainConfig().IsPrague1(header.Number, header.Time), b.ChainConfig().Berachain.Prague1.PoLDistributorAddress)
// Run the gas estimation and wrap any revertals into a custom return // Run the gas estimation and wrap any revertals into a custom return
estimate, revert, err := gasestimator.Estimate(ctx, call, opts, gasCap) estimate, revert, err := gasestimator.Estimate(ctx, call, opts, gasCap)
@ -904,6 +904,9 @@ func RPCMarshalHeader(head *types.Header) map[string]interface{} {
if head.RequestsHash != nil { if head.RequestsHash != nil {
result["requestsHash"] = head.RequestsHash result["requestsHash"] = head.RequestsHash
} }
if head.ParentProposerPubkey != nil {
result["parentProposerPubkey"] = head.ParentProposerPubkey
}
return result return result
} }
@ -1057,6 +1060,14 @@ func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber
result.GasPrice = (*hexutil.Big)(tx.GasFeeCap()) result.GasPrice = (*hexutil.Big)(tx.GasFeeCap())
} }
result.AuthorizationList = tx.SetCodeAuthorizations() result.AuthorizationList = tx.SetCodeAuthorizations()
case types.PoLTxType:
al := tx.AccessList()
result.Accesses = &al
result.ChainID = (*hexutil.Big)(tx.ChainId())
result.GasFeeCap = (*hexutil.Big)(tx.GasFeeCap())
result.GasTipCap = (*hexutil.Big)(tx.GasTipCap())
result.MaxFeePerBlobGas = (*hexutil.Big)(tx.BlobGasFeeCap())
} }
return result return result
} }
@ -1217,7 +1228,9 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
statedb := db.Copy() statedb := db.Copy()
// Set the accesslist to the last al // Set the accesslist to the last al
args.AccessList = &accessList args.AccessList = &accessList
msg := args.ToMessage(header.BaseFee, true, true) isPrague1 := b.ChainConfig().IsPrague1(header.Number, header.Time)
distributorAddress := b.ChainConfig().Berachain.Prague1.PoLDistributorAddress
msg := args.ToMessage(header.BaseFee, true, true, isPrague1, distributorAddress)
// Apply the transaction with the access list tracer // Apply the transaction with the access list tracer
tracer := logger.NewAccessListTracer(accessList, addressesToExclude) tracer := logger.NewAccessListTracer(accessList, addressesToExclude)
@ -1234,7 +1247,15 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
} }
res, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(msg.GasLimit)) res, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(msg.GasLimit))
if err != nil { if err != nil {
return nil, 0, nil, fmt.Errorf("failed to apply transaction: %v err: %v", args.ToTransaction(types.LegacyTxType).Hash(), err) return nil, 0, nil, fmt.Errorf(
"failed to apply transaction: %v err: %v",
args.ToTransaction(
types.LegacyTxType,
b.ChainConfig().IsPrague1(header.Number, header.Time),
b.ChainConfig().Berachain.Prague1.PoLDistributorAddress,
).Hash(),
err,
)
} }
if tracer.Equal(prevTracer) { if tracer.Equal(prevTracer) {
return accessList, res.UsedGas, res.Err, nil return accessList, res.UsedGas, res.Err, nil
@ -1503,7 +1524,13 @@ func (api *TransactionAPI) SendTransaction(ctx context.Context, args Transaction
return common.Hash{}, err return common.Hash{}, err
} }
// Assemble the transaction and sign with the wallet // Assemble the transaction and sign with the wallet
tx := args.ToTransaction(types.LegacyTxType) header := api.b.CurrentHeader()
tx := args.ToTransaction(
types.LegacyTxType,
api.b.ChainConfig().IsPrague1(header.Number, header.Time),
api.b.ChainConfig().Berachain.Prague1.PoLDistributorAddress,
)
signed, err := wallet.SignTx(account, tx, api.b.ChainConfig().ChainID) signed, err := wallet.SignTx(account, tx, api.b.ChainConfig().ChainID)
if err != nil { if err != nil {
@ -1523,7 +1550,12 @@ func (api *TransactionAPI) FillTransaction(ctx context.Context, args Transaction
return nil, err return nil, err
} }
// Assemble the transaction and obtain rlp // Assemble the transaction and obtain rlp
tx := args.ToTransaction(types.LegacyTxType) header := api.b.CurrentHeader()
tx := args.ToTransaction(
types.LegacyTxType,
api.b.ChainConfig().IsPrague1(header.Number, header.Time),
api.b.ChainConfig().Berachain.Prague1.PoLDistributorAddress,
)
data, err := tx.MarshalBinary() data, err := tx.MarshalBinary()
if err != nil { if err != nil {
return nil, err return nil, err
@ -1591,7 +1623,12 @@ func (api *TransactionAPI) SignTransaction(ctx context.Context, args Transaction
return nil, err return nil, err
} }
// Before actually sign the transaction, ensure the transaction fee is reasonable. // Before actually sign the transaction, ensure the transaction fee is reasonable.
tx := args.ToTransaction(types.LegacyTxType) header := api.b.CurrentHeader()
tx := args.ToTransaction(
types.LegacyTxType,
api.b.ChainConfig().IsPrague1(header.Number, header.Time),
api.b.ChainConfig().Berachain.Prague1.PoLDistributorAddress,
)
if err := checkTxFee(tx.GasPrice(), tx.Gas(), api.b.RPCTxFeeCap()); err != nil { if err := checkTxFee(tx.GasPrice(), tx.Gas(), api.b.RPCTxFeeCap()); err != nil {
return nil, err return nil, err
} }
@ -1645,7 +1682,12 @@ func (api *TransactionAPI) Resend(ctx context.Context, sendArgs TransactionArgs,
if err := sendArgs.setDefaults(ctx, api.b, false); err != nil { if err := sendArgs.setDefaults(ctx, api.b, false); err != nil {
return common.Hash{}, err return common.Hash{}, err
} }
matchTx := sendArgs.ToTransaction(types.LegacyTxType) header := api.b.CurrentHeader()
matchTx := sendArgs.ToTransaction(
types.LegacyTxType,
api.b.ChainConfig().IsPrague1(header.Number, header.Time),
api.b.ChainConfig().Berachain.Prague1.PoLDistributorAddress,
)
// Before replacing the old transaction, ensure the _new_ transaction fee is reasonable. // Before replacing the old transaction, ensure the _new_ transaction fee is reasonable.
price := matchTx.GasPrice() price := matchTx.GasPrice()
@ -1675,7 +1717,14 @@ func (api *TransactionAPI) Resend(ctx context.Context, sendArgs TransactionArgs,
if gasLimit != nil && *gasLimit != 0 { if gasLimit != nil && *gasLimit != 0 {
sendArgs.Gas = gasLimit sendArgs.Gas = gasLimit
} }
signedTx, err := api.sign(sendArgs.from(), sendArgs.ToTransaction(types.LegacyTxType)) header := api.b.CurrentHeader()
signedTx, err := api.sign(
sendArgs.from(), sendArgs.ToTransaction(
types.LegacyTxType,
api.b.ChainConfig().IsPrague1(header.Number, header.Time),
api.b.ChainConfig().Berachain.Prague1.PoLDistributorAddress,
),
)
if err != nil { if err != nil {
return common.Hash{}, err return common.Hash{}, err
} }

View file

@ -1217,6 +1217,15 @@ func TestCall(t *testing.T) {
}, },
expectErr: errors.New(`block override "withdrawals" is not supported for this RPC method`), expectErr: errors.New(`block override "withdrawals" is not supported for this RPC method`),
}, },
{
name: "unsupported block override proposerPubkey",
blockNumber: rpc.LatestBlockNumber,
call: TransactionArgs{},
blockOverrides: override.BlockOverrides{
ProposerPubkey: &common.Pubkey{},
},
expectErr: errors.New(`block override "proposerPubkey" is not supported for this RPC method`),
},
} }
for _, tc := range testSuite { for _, tc := range testSuite {
result, err := api.Call(context.Background(), tc.call, &rpc.BlockNumberOrHash{BlockNumber: &tc.blockNumber}, &tc.overrides, &tc.blockOverrides) result, err := api.Call(context.Background(), tc.call, &rpc.BlockNumberOrHash{BlockNumber: &tc.blockNumber}, &tc.overrides, &tc.blockOverrides)

View file

@ -121,16 +121,17 @@ func (diff *StateOverride) Apply(statedb *state.StateDB, precompiles vm.Precompi
// BlockOverrides is a set of header fields to override. // BlockOverrides is a set of header fields to override.
type BlockOverrides struct { type BlockOverrides struct {
Number *hexutil.Big Number *hexutil.Big
Difficulty *hexutil.Big // No-op if we're simulating post-merge calls. Difficulty *hexutil.Big // No-op if we're simulating post-merge calls.
Time *hexutil.Uint64 Time *hexutil.Uint64
GasLimit *hexutil.Uint64 GasLimit *hexutil.Uint64
FeeRecipient *common.Address FeeRecipient *common.Address
PrevRandao *common.Hash PrevRandao *common.Hash
BaseFeePerGas *hexutil.Big BaseFeePerGas *hexutil.Big
BlobBaseFee *hexutil.Big BlobBaseFee *hexutil.Big
BeaconRoot *common.Hash BeaconRoot *common.Hash
Withdrawals *types.Withdrawals Withdrawals *types.Withdrawals
ProposerPubkey *common.Pubkey
} }
// Apply overrides the given header fields into the given block context. // Apply overrides the given header fields into the given block context.
@ -144,6 +145,9 @@ func (o *BlockOverrides) Apply(blockCtx *vm.BlockContext) error {
if o.Withdrawals != nil { if o.Withdrawals != nil {
return errors.New(`block override "withdrawals" is not supported for this RPC method`) return errors.New(`block override "withdrawals" is not supported for this RPC method`)
} }
if o.ProposerPubkey != nil {
return errors.New(`block override "proposerPubkey" is not supported for this RPC method`)
}
if o.Number != nil { if o.Number != nil {
blockCtx.BlockNumber = o.Number.ToInt() blockCtx.BlockNumber = o.Number.ToInt()
} }

View file

@ -199,6 +199,37 @@ func (sim *simulator) execute(ctx context.Context, blocks []simBlock) ([]*simBlo
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Berachain: Pre-compute expected PoL tx hash when in Prague1.
isPrague1 := sim.chainConfig.IsPrague1(result.Number(), result.Time())
var expectedPoLHash common.Hash
if isPrague1 {
polTx, err := types.NewPoLTx(
sim.chainConfig.ChainID,
sim.chainConfig.Berachain.Prague1.PoLDistributorAddress,
new(big.Int).Sub(result.Number(), big.NewInt(1)),
params.PoLTxGasLimit,
result.BaseFee(),
result.ProposerPubkey(),
)
if err != nil {
return nil, fmt.Errorf("failed to create expected PoL tx: %w", err)
}
expectedPoLHash = polTx.Hash()
}
// Berachain: validate the PoL tx is only the first tx in the block.
for i, tx := range result.Transactions() {
switch {
case isPrague1 && i == 0:
if tx.Hash() != expectedPoLHash {
return nil, fmt.Errorf("PoL tx hash mismatch: have %v, want %v", tx.Hash(), expectedPoLHash)
}
case tx.Type() == types.PoLTxType:
return nil, fmt.Errorf("invalid block: tx at index %d is a PoL tx", i)
}
}
headers[bi] = result.Header() headers[bi] = result.Header()
results[bi] = &simBlockResult{fullTx: sim.fullTx, chainConfig: sim.chainConfig, Block: result, Calls: callResults, senders: senders} results[bi] = &simBlockResult{fullTx: sim.fullTx, chainConfig: sim.chainConfig, Block: result, Calls: callResults, senders: senders}
parent = result.Header() parent = result.Header()
@ -279,15 +310,17 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
return nil, nil, nil, err return nil, nil, nil, err
} }
var ( var (
tx = call.ToTransaction(types.DynamicFeeTxType) isPrague1 = sim.chainConfig.IsPrague1(header.Number, header.Time)
txHash = tx.Hash() distributorAddress = sim.chainConfig.Berachain.Prague1.PoLDistributorAddress
tx = call.ToTransaction(types.DynamicFeeTxType, isPrague1, distributorAddress)
txHash = tx.Hash()
) )
txes[i] = tx txes[i] = tx
senders[txHash] = call.from() senders[txHash] = call.from()
tracer.reset(txHash, uint(i)) tracer.reset(txHash, uint(i))
sim.state.SetTxContext(txHash, i) sim.state.SetTxContext(txHash, i)
// EoA check is always skipped, even in validation mode. // EoA check is always skipped, even in validation mode.
msg := call.ToMessage(header.BaseFee, !sim.validate, true) msg := call.ToMessage(header.BaseFee, !sim.validate, true, isPrague1, distributorAddress)
result, err := applyMessageWithEVM(ctx, evm, msg, timeout, sim.gp) result, err := applyMessageWithEVM(ctx, evm, msg, timeout, sim.gp)
if err != nil { if err != nil {
txErr := txValidationError(err) txErr := txValidationError(err)
@ -484,15 +517,24 @@ func (sim *simulator) makeHeaders(blocks []simBlock) ([]*types.Header, error) {
parentBeaconRoot = overrides.BeaconRoot parentBeaconRoot = overrides.BeaconRoot
} }
} }
var parentProposerPubkey *common.Pubkey
if sim.chainConfig.IsPrague1(overrides.Number.ToInt(), (uint64)(*overrides.Time)) {
parentProposerPubkey = new(common.Pubkey)
if overrides.ProposerPubkey == nil {
return nil, errors.New("proposer pubkey override is required for post Prague1 blocks")
}
*parentProposerPubkey = *overrides.ProposerPubkey
}
header = overrides.MakeHeader(&types.Header{ header = overrides.MakeHeader(&types.Header{
UncleHash: types.EmptyUncleHash, UncleHash: types.EmptyUncleHash,
ReceiptHash: types.EmptyReceiptsHash, ReceiptHash: types.EmptyReceiptsHash,
TxHash: types.EmptyTxsHash, TxHash: types.EmptyTxsHash,
Coinbase: header.Coinbase, Coinbase: header.Coinbase,
Difficulty: header.Difficulty, Difficulty: header.Difficulty,
GasLimit: header.GasLimit, GasLimit: header.GasLimit,
WithdrawalsHash: withdrawalsHash, WithdrawalsHash: withdrawalsHash,
ParentBeaconRoot: parentBeaconRoot, ParentBeaconRoot: parentBeaconRoot,
ParentProposerPubkey: parentProposerPubkey,
}) })
res[bi] = header res[bi] = header
} }

View file

@ -414,7 +414,7 @@ func (args *TransactionArgs) CallDefaults(globalGasCap uint64, baseFee *big.Int,
// core evm. This method is used in calls and traces that do not require a real // core evm. This method is used in calls and traces that do not require a real
// live transaction. // live transaction.
// Assumes that fields are not nil, i.e. setDefaults or CallDefaults has been called. // Assumes that fields are not nil, i.e. setDefaults or CallDefaults has been called.
func (args *TransactionArgs) ToMessage(baseFee *big.Int, skipNonceCheck, skipEoACheck bool) *core.Message { func (args *TransactionArgs) ToMessage(baseFee *big.Int, skipNonceCheck, skipEoACheck, isPrague1 bool, distributorAddress common.Address) *core.Message {
var ( var (
gasPrice *big.Int gasPrice *big.Int
gasFeeCap *big.Int gasFeeCap *big.Int
@ -463,14 +463,17 @@ func (args *TransactionArgs) ToMessage(baseFee *big.Int, skipNonceCheck, skipEoA
SetCodeAuthorizations: args.AuthorizationList, SetCodeAuthorizations: args.AuthorizationList,
SkipNonceChecks: skipNonceCheck, SkipNonceChecks: skipNonceCheck,
SkipFromEOACheck: skipEoACheck, SkipFromEOACheck: skipEoACheck,
IsPoLTx: isPrague1 && types.IsPoLDistribution(args.To, args.data(), distributorAddress),
} }
} }
// ToTransaction converts the arguments to a transaction. // ToTransaction converts the arguments to a transaction.
// This assumes that setDefaults has been called. // This assumes that setDefaults has been called.
func (args *TransactionArgs) ToTransaction(defaultType int) *types.Transaction { func (args *TransactionArgs) ToTransaction(defaultType int, isPrague1 bool, distributorAddress common.Address) *types.Transaction {
usedType := types.LegacyTxType usedType := types.LegacyTxType
switch { switch {
case isPrague1 && types.IsPoLDistribution(args.To, args.data(), distributorAddress):
usedType = types.PoLTxType
case args.AuthorizationList != nil || defaultType == types.SetCodeTxType: case args.AuthorizationList != nil || defaultType == types.SetCodeTxType:
usedType = types.SetCodeTxType usedType = types.SetCodeTxType
case args.BlobHashes != nil || defaultType == types.BlobTxType: case args.BlobHashes != nil || defaultType == types.BlobTxType:
@ -486,6 +489,20 @@ func (args *TransactionArgs) ToTransaction(defaultType int) *types.Transaction {
} }
var data types.TxData var data types.TxData
switch usedType { switch usedType {
case types.PoLTxType:
gasPrice := (*big.Int)(args.GasPrice)
if args.MaxFeePerGas != nil || defaultType == types.DynamicFeeTxType {
gasPrice = (*big.Int)(args.MaxFeePerGas)
}
data = &types.PoLTx{
ChainID: (*big.Int)(args.ChainID),
From: args.from(),
To: *args.To,
Nonce: uint64(*args.Nonce),
GasLimit: uint64(*args.Gas),
GasPrice: gasPrice,
Data: args.data(),
}
case types.SetCodeTxType: case types.SetCodeTxType:
al := types.AccessList{} al := types.AccessList{}
if args.AccessList != nil { if args.AccessList != nil {

View file

@ -57,8 +57,8 @@ func WithCommit(gitCommit, gitDate string) string {
// Archive holds the textual version string used for Geth archives. e.g. // Archive holds the textual version string used for Geth archives. e.g.
// "1.8.11-dea1ce05" for stable releases, or "1.8.13-unstable-21c059b6" for unstable // "1.8.11-dea1ce05" for stable releases, or "1.8.13-unstable-21c059b6" for unstable
// releases. // releases.
func Archive(gitCommit string) string { func Archive(tag, gitCommit string) string {
vsn := Semantic vsn := tag
if version.Meta != "stable" { if version.Meta != "stable" {
vsn += "-" + version.Meta vsn += "-" + version.Meta
} }

View file

@ -140,6 +140,11 @@ func (miner *Miner) BuildPayload(args *BuildPayloadArgs, witness bool) (*Payload
// getPending retrieves the pending block based on the current head block. // getPending retrieves the pending block based on the current head block.
// The result might be nil if pending generation is failed. // The result might be nil if pending generation is failed.
//
// Berachain: Note that the correct PoL tx is not included in the pending block as the
// parent proposer pubkey of the pending block is not made available here.
//
// TODO(BRIP-4): Include parent proposer pubkey as soon as it is made available by CL.
func (miner *Miner) getPending() *newPayloadResult { func (miner *Miner) getPending() *newPayloadResult {
header := miner.chain.CurrentHeader() header := miner.chain.CurrentHeader()
miner.pendingMu.Lock() miner.pendingMu.Lock()
@ -156,14 +161,15 @@ func (miner *Miner) getPending() *newPayloadResult {
withdrawal = []*types.Withdrawal{} withdrawal = []*types.Withdrawal{}
} }
ret := miner.generateWork(&generateParams{ ret := miner.generateWork(&generateParams{
timestamp: timestamp, timestamp: timestamp,
forceTime: false, forceTime: false,
parentHash: header.Hash(), parentHash: header.Hash(),
coinbase: miner.config.PendingFeeRecipient, coinbase: miner.config.PendingFeeRecipient,
random: common.Hash{}, random: common.Hash{},
withdrawals: withdrawal, withdrawals: withdrawal,
beaconRoot: nil, beaconRoot: nil,
noTxs: false, proposerPubkey: &common.Pubkey{},
noTxs: false,
}, false) // we will never make a witness for a pending block }, false) // we will never make a witness for a pending block
if ret.err != nil { if ret.err != nil {
return nil return nil

View file

@ -37,13 +37,14 @@ import (
// Check engine-api specification for more details. // Check engine-api specification for more details.
// https://github.com/ethereum/execution-apis/blob/main/src/engine/cancun.md#payloadattributesv3 // https://github.com/ethereum/execution-apis/blob/main/src/engine/cancun.md#payloadattributesv3
type BuildPayloadArgs struct { type BuildPayloadArgs struct {
Parent common.Hash // The parent block to build payload on top Parent common.Hash // The parent block to build payload on top
Timestamp uint64 // The provided timestamp of generated payload Timestamp uint64 // The provided timestamp of generated payload
FeeRecipient common.Address // The provided recipient address for collecting transaction fee FeeRecipient common.Address // The provided recipient address for collecting transaction fee
Random common.Hash // The provided randomness value Random common.Hash // The provided randomness value
Withdrawals types.Withdrawals // The provided withdrawals Withdrawals types.Withdrawals // The provided withdrawals
BeaconRoot *common.Hash // The provided beaconRoot (Cancun) BeaconRoot *common.Hash // The provided beaconRoot (Cancun)
Version engine.PayloadVersion // Versioning byte for payload id calculation. ProposerPubkey *common.Pubkey // Berachain BRIP-0004: The provided proposer pubkey (Prague1)
Version engine.PayloadVersion // Versioning byte for payload id calculation.
} }
// Id computes an 8-byte identifier by hashing the components of the payload arguments. // Id computes an 8-byte identifier by hashing the components of the payload arguments.
@ -57,6 +58,9 @@ func (args *BuildPayloadArgs) Id() engine.PayloadID {
if args.BeaconRoot != nil { if args.BeaconRoot != nil {
hasher.Write(args.BeaconRoot[:]) hasher.Write(args.BeaconRoot[:])
} }
if args.ProposerPubkey != nil {
hasher.Write(args.ProposerPubkey[:])
}
var out engine.PayloadID var out engine.PayloadID
copy(out[:], hasher.Sum(nil)[:8]) copy(out[:], hasher.Sum(nil)[:8])
out[0] = byte(args.Version) out[0] = byte(args.Version)
@ -211,14 +215,15 @@ func (miner *Miner) buildPayload(args *BuildPayloadArgs, witness bool) (*Payload
// enough to run. The empty payload can at least make sure there is something // enough to run. The empty payload can at least make sure there is something
// to deliver for not missing slot. // to deliver for not missing slot.
emptyParams := &generateParams{ emptyParams := &generateParams{
timestamp: args.Timestamp, timestamp: args.Timestamp,
forceTime: true, forceTime: true,
parentHash: args.Parent, parentHash: args.Parent,
coinbase: args.FeeRecipient, coinbase: args.FeeRecipient,
random: args.Random, random: args.Random,
withdrawals: args.Withdrawals, withdrawals: args.Withdrawals,
beaconRoot: args.BeaconRoot, beaconRoot: args.BeaconRoot,
noTxs: true, proposerPubkey: args.ProposerPubkey,
noTxs: true,
} }
empty := miner.generateWork(emptyParams, witness) empty := miner.generateWork(emptyParams, witness)
if empty.err != nil { if empty.err != nil {
@ -235,20 +240,24 @@ func (miner *Miner) buildPayload(args *BuildPayloadArgs, witness bool) (*Payload
timer := time.NewTimer(0) timer := time.NewTimer(0)
defer timer.Stop() defer timer.Stop()
// Setup the timer for terminating the process if SECONDS_PER_SLOT (12s in // Setup the timer for terminating the process if SECONDS_PER_SLOT (2s in
// the Mainnet configuration) have passed since the point in time identified // the Berachain configuration) have passed since the point in time identified
// by the timestamp parameter. // by the timestamp parameter.
endTimer := time.NewTimer(time.Second * 12) //
// TODO(Berachain): Using 4s for now to handle cases where block time exceeds 2s. To be
// verified further after stable block time is in effect.
endTimer := time.NewTimer(time.Second * 4)
fullParams := &generateParams{ fullParams := &generateParams{
timestamp: args.Timestamp, timestamp: args.Timestamp,
forceTime: true, forceTime: true,
parentHash: args.Parent, parentHash: args.Parent,
coinbase: args.FeeRecipient, coinbase: args.FeeRecipient,
random: args.Random, random: args.Random,
withdrawals: args.Withdrawals, withdrawals: args.Withdrawals,
beaconRoot: args.BeaconRoot, beaconRoot: args.BeaconRoot,
noTxs: false, proposerPubkey: args.ProposerPubkey,
noTxs: false,
} }
for { for {

View file

@ -94,14 +94,15 @@ type newPayloadResult struct {
// generateParams wraps various settings for generating sealing task. // generateParams wraps various settings for generating sealing task.
type generateParams struct { type generateParams struct {
timestamp uint64 // The timestamp for sealing task timestamp uint64 // The timestamp for sealing task
forceTime bool // Flag whether the given timestamp is immutable or not forceTime bool // Flag whether the given timestamp is immutable or not
parentHash common.Hash // Parent block hash, empty means the latest chain head parentHash common.Hash // Parent block hash, empty means the latest chain head
coinbase common.Address // The fee recipient address for including transaction coinbase common.Address // The fee recipient address for including transaction
random common.Hash // The randomness generated by beacon chain, empty before the merge random common.Hash // The randomness generated by beacon chain, empty before the merge
withdrawals types.Withdrawals // List of withdrawals to include in block (shanghai field) withdrawals types.Withdrawals // List of withdrawals to include in block (shanghai field)
beaconRoot *common.Hash // The beacon root (cancun field). beaconRoot *common.Hash // The beacon root (cancun field).
noTxs bool // Flag whether an empty block without any transaction is expected proposerPubkey *common.Pubkey // Berachain: The proposer pubkey (prague1 field).
noTxs bool // Flag whether an empty block without any transaction is expected
} }
// generateWork generates a sealing block based on the given parameters. // generateWork generates a sealing block based on the given parameters.
@ -242,6 +243,10 @@ func (miner *Miner) prepareWork(genParams *generateParams, witness bool) (*envir
header.ExcessBlobGas = &excessBlobGas header.ExcessBlobGas = &excessBlobGas
header.ParentBeaconRoot = genParams.beaconRoot header.ParentBeaconRoot = genParams.beaconRoot
} }
// Berachain: Apply BRIP-0004.
if miner.chainConfig.IsPrague1(header.Number, header.Time) {
header.ParentProposerPubkey = genParams.proposerPubkey
}
// Could potentially happen if starting to mine in an odd state. // Could potentially happen if starting to mine in an odd state.
// Note genParams.coinbase can be different with header.Coinbase // Note genParams.coinbase can be different with header.Coinbase
// since clique algorithm can modify the coinbase field in header. // since clique algorithm can modify the coinbase field in header.
@ -331,13 +336,23 @@ func (miner *Miner) commitBlobTransaction(env *environment, tx *types.Transactio
// applyTransaction runs the transaction. If execution fails, state and gas pool are reverted. // applyTransaction runs the transaction. If execution fails, state and gas pool are reverted.
func (miner *Miner) applyTransaction(env *environment, tx *types.Transaction) (*types.Receipt, error) { func (miner *Miner) applyTransaction(env *environment, tx *types.Transaction) (*types.Receipt, error) {
var ( var (
snap = env.state.Snapshot() gasPool = env.gasPool
gp = env.gasPool.Gas() blockGasUsed = &env.header.GasUsed
snap = env.state.Snapshot()
gasBefore uint64
) )
receipt, err := core.ApplyTransaction(env.evm, env.gasPool, env.state, env.header, tx, &env.header.GasUsed) // Berachain: PoL tx does not consume gas.
if tx.Type() == types.PoLTxType {
gasPool = nil
blockGasUsed = new(uint64)
}
if gasPool != nil {
gasBefore = gasPool.Gas()
}
receipt, err := core.ApplyTransaction(env.evm, env.gasPool, env.state, env.header, tx, blockGasUsed)
if err != nil { if err != nil {
env.state.RevertToSnapshot(snap) env.state.RevertToSnapshot(snap)
env.gasPool.SetGas(gp) env.gasPool.SetGas(gasBefore)
} }
return receipt, err return receipt, err
} }
@ -484,6 +499,29 @@ func (miner *Miner) fillTransactions(interrupt *atomic.Int32, env *environment)
prio := miner.prio prio := miner.prio
miner.confMu.RUnlock() miner.confMu.RUnlock()
// Berachain: Post-Prague1, add PoL tx to the block according to BRIP-0004.
if miner.chainConfig.IsPrague1(env.header.Number, env.header.Time) {
tx, err := types.NewPoLTx(
miner.chainConfig.ChainID,
miner.chainConfig.Berachain.Prague1.PoLDistributorAddress,
new(big.Int).Sub(env.header.Number, big.NewInt(1)),
params.PoLTxGasLimit,
env.header.BaseFee,
env.header.ParentProposerPubkey,
)
if err != nil {
return fmt.Errorf("failed to create PoL tx: %v", err)
}
// Start executing the PoL transaction, outside of miner commitTransactions loop.
// env.tcount must be at 0 at this point.
env.state.SetTxContext(tx.Hash(), env.tcount)
if err = miner.commitTransaction(env, tx); err != nil {
log.Trace("Failed to commit PoL transaction", "hash", tx.Hash(), "err", err)
return err
}
}
// Retrieve the pending transactions pre-filtered by the 1559/4844 dynamic fees // Retrieve the pending transactions pre-filtered by the 1559/4844 dynamic fees
filter := txpool.PendingFilter{ filter := txpool.PendingFilter{
MinTip: uint256.MustFromBig(tip), MinTip: uint256.MustFromBig(tip),

View file

@ -32,6 +32,10 @@ var (
HoleskyGenesisHash = common.HexToHash("0xb5f7f912443c940f21fd611f12828d75b534364ed9e95ca4e307729a4661bde4") HoleskyGenesisHash = common.HexToHash("0xb5f7f912443c940f21fd611f12828d75b534364ed9e95ca4e307729a4661bde4")
SepoliaGenesisHash = common.HexToHash("0x25a5cc106eea7138acab33231d7160d69cb777ee0c2c553fcddf5138993e6dd9") SepoliaGenesisHash = common.HexToHash("0x25a5cc106eea7138acab33231d7160d69cb777ee0c2c553fcddf5138993e6dd9")
HoodiGenesisHash = common.HexToHash("0xbbe312868b376a3001692a646dd2d7d1e4406380dfd86b98aa8a34d1557c971b") HoodiGenesisHash = common.HexToHash("0xbbe312868b376a3001692a646dd2d7d1e4406380dfd86b98aa8a34d1557c971b")
// Berachain
BerachainGenesisHash = common.HexToHash("0xd57819422128da1c44339fc7956662378c17e2213e669b427ac91cd11dfcfb38")
BepoliaGenesisHash = common.HexToHash("0x0207661de38f0e54ba91c8286096e72486784c79dc6a9681fc486b38335c042f")
) )
func newUint64(val uint64) *uint64 { return &val } func newUint64(val uint64) *uint64 { return &val }
@ -158,6 +162,80 @@ var (
Prague: DefaultPragueBlobConfig, Prague: DefaultPragueBlobConfig,
}, },
} }
// BerachainChainConfig is the chain parameters to run a node on the Berachain network.
BerachainChainConfig = &ChainConfig{
ChainID: big.NewInt(80094),
HomesteadBlock: big.NewInt(0),
DAOForkBlock: big.NewInt(0),
DAOForkSupport: true,
EIP150Block: big.NewInt(0),
EIP155Block: big.NewInt(0),
EIP158Block: big.NewInt(0),
ByzantiumBlock: big.NewInt(0),
ConstantinopleBlock: big.NewInt(0),
PetersburgBlock: big.NewInt(0),
IstanbulBlock: big.NewInt(0),
MuirGlacierBlock: big.NewInt(0),
BerlinBlock: big.NewInt(0),
LondonBlock: big.NewInt(0),
ArrowGlacierBlock: big.NewInt(0),
GrayGlacierBlock: big.NewInt(0),
TerminalTotalDifficulty: big.NewInt(0),
MergeNetsplitBlock: big.NewInt(0),
ShanghaiTime: newUint64(0),
CancunTime: newUint64(0),
PragueTime: newUint64(1749056400),
DepositContractAddress: common.HexToAddress("0x4242424242424242424242424242424242424242"),
Ethash: new(EthashConfig),
BlobScheduleConfig: &BlobScheduleConfig{
Cancun: DefaultCancunBlobConfig,
Prague: DefaultBerachainPragueBlobConfig,
},
Berachain: BerachainConfig{
Prague1: Prague1Config{
Time: newUint64(999999999999999),
MinimumBaseFeeWei: 1000000000, // 1 gwei
BaseFeeChangeDenominator: 48, // 6x increase from the default
},
},
}
// BepoliaChainConfig contains the chain parameters to run a node on the Bepolia test network.
BepoliaChainConfig = &ChainConfig{
ChainID: big.NewInt(80069),
HomesteadBlock: big.NewInt(0),
DAOForkBlock: big.NewInt(0),
DAOForkSupport: true,
EIP150Block: big.NewInt(0),
EIP155Block: big.NewInt(0),
EIP158Block: big.NewInt(0),
ByzantiumBlock: big.NewInt(0),
ConstantinopleBlock: big.NewInt(0),
PetersburgBlock: big.NewInt(0),
IstanbulBlock: big.NewInt(0),
MuirGlacierBlock: big.NewInt(0),
BerlinBlock: big.NewInt(0),
LondonBlock: big.NewInt(0),
ArrowGlacierBlock: big.NewInt(0),
GrayGlacierBlock: big.NewInt(0),
TerminalTotalDifficulty: big.NewInt(0),
MergeNetsplitBlock: big.NewInt(0),
ShanghaiTime: newUint64(0),
CancunTime: newUint64(0),
PragueTime: newUint64(1746633600),
DepositContractAddress: common.HexToAddress("0x4242424242424242424242424242424242424242"),
Ethash: new(EthashConfig),
BlobScheduleConfig: &BlobScheduleConfig{
Cancun: DefaultCancunBlobConfig,
Prague: DefaultBerachainPragueBlobConfig,
},
Berachain: BerachainConfig{
Prague1: Prague1Config{
Time: newUint64(999999999999999),
MinimumBaseFeeWei: 1000000000, // 1 gwei
BaseFeeChangeDenominator: 48, // 6x increase from the default
},
},
}
// AllEthashProtocolChanges contains every protocol change (EIPs) introduced // AllEthashProtocolChanges contains every protocol change (EIPs) introduced
// and accepted by the Ethereum core developers into the Ethash consensus. // and accepted by the Ethereum core developers into the Ethash consensus.
AllEthashProtocolChanges = &ChainConfig{ AllEthashProtocolChanges = &ChainConfig{
@ -353,6 +431,13 @@ var (
Max: 9, Max: 9,
UpdateFraction: 5007716, UpdateFraction: 5007716,
} }
// DefaultBerachainPragueBlobConfig is the default blob configuration for the Prague fork
// on Berachain networks.
DefaultBerachainPragueBlobConfig = &BlobConfig{
Target: 3,
Max: 6,
UpdateFraction: 3338477,
}
// DefaultOsakaBlobConfig is the default blob configuration for the Osaka fork. // DefaultOsakaBlobConfig is the default blob configuration for the Osaka fork.
DefaultOsakaBlobConfig = &BlobConfig{ DefaultOsakaBlobConfig = &BlobConfig{
Target: 6, Target: 6,
@ -440,6 +525,48 @@ type ChainConfig struct {
Ethash *EthashConfig `json:"ethash,omitempty"` Ethash *EthashConfig `json:"ethash,omitempty"`
Clique *CliqueConfig `json:"clique,omitempty"` Clique *CliqueConfig `json:"clique,omitempty"`
BlobScheduleConfig *BlobScheduleConfig `json:"blobSchedule,omitempty"` BlobScheduleConfig *BlobScheduleConfig `json:"blobSchedule,omitempty"`
// Berachain config
Berachain BerachainConfig `json:"berachain,omitempty"`
}
// BerachainConfig is the berachain config.
type BerachainConfig struct {
// Prague1 fork values.
Prague1 Prague1Config `json:"prague1,omitempty"`
}
// String implements the stringer interface.
func (o *BerachainConfig) String() string {
banner := "berachain"
if o.Prague1.Time != nil {
banner += fmt.Sprintf("(%s)", o.Prague1)
}
return banner
}
// Prague1Config is the config values for the Prague1 fork on Berachain.
type Prague1Config struct {
// Time is the time of the Prague1 fork.
Time *uint64 `json:"time,omitempty"` // Prague1 switch time (0 = already on prague1, nil = no fork)
// BaseFeeChangeDenominator is the base fee change denominator.
BaseFeeChangeDenominator uint64 `json:"baseFeeChangeDenominator,omitempty"`
// MinimumBaseFeeWei is the minimum base fee in wei.
MinimumBaseFeeWei uint64 `json:"minimumBaseFeeWei,omitempty"`
// PoLDistributorAddress is the address of the PoL distributor.
PoLDistributorAddress common.Address `json:"polDistributorAddress,omitempty"`
}
// String implements the stringer interface.
func (c Prague1Config) String() string {
banner := "prague1"
if c.Time != nil {
banner += fmt.Sprintf(
"(time: %v, baseFeeChangeDenominator: %v, minimumBaseFeeWei: %v, polDistributorAddress: %v)",
*c.Time, c.BaseFeeChangeDenominator, c.MinimumBaseFeeWei, c.PoLDistributorAddress,
)
}
return banner
} }
// EthashConfig is the consensus engine configs for proof-of-work based sealing. // EthashConfig is the consensus engine configs for proof-of-work based sealing.
@ -530,6 +657,9 @@ func (c *ChainConfig) Description() string {
if c.PragueTime != nil { if c.PragueTime != nil {
banner += fmt.Sprintf(" - Prague: @%-10v\n", *c.PragueTime) banner += fmt.Sprintf(" - Prague: @%-10v\n", *c.PragueTime)
} }
if c.Berachain.Prague1.Time != nil {
banner += fmt.Sprintf(" - Prague1: @%-10v\n", *c.Berachain.Prague1.Time)
}
if c.OsakaTime != nil { if c.OsakaTime != nil {
banner += fmt.Sprintf(" - Osaka: @%-10v\n", *c.OsakaTime) banner += fmt.Sprintf(" - Osaka: @%-10v\n", *c.OsakaTime)
} }
@ -669,6 +799,12 @@ func (c *ChainConfig) IsPrague(num *big.Int, time uint64) bool {
return c.IsLondon(num) && isTimestampForked(c.PragueTime, time) return c.IsLondon(num) && isTimestampForked(c.PragueTime, time)
} }
// IsPrague1 returns whether time is either equal to the Prague1 fork time or greater.
// NOTE: Prague1 is a Berachain fork and must be on Ethereum's Prague fork.
func (c *ChainConfig) IsPrague1(num *big.Int, time uint64) bool {
return c.IsPrague(num, time) && isTimestampForked(c.Berachain.Prague1.Time, time)
}
// IsOsaka returns whether time is either equal to the Osaka fork time or greater. // IsOsaka returns whether time is either equal to the Osaka fork time or greater.
func (c *ChainConfig) IsOsaka(num *big.Int, time uint64) bool { func (c *ChainConfig) IsOsaka(num *big.Int, time uint64) bool {
return c.IsLondon(num) && isTimestampForked(c.OsakaTime, time) return c.IsLondon(num) && isTimestampForked(c.OsakaTime, time)
@ -777,6 +913,7 @@ func (c *ChainConfig) CheckConfigForkOrder() error {
{name: "shanghaiTime", timestamp: c.ShanghaiTime}, {name: "shanghaiTime", timestamp: c.ShanghaiTime},
{name: "cancunTime", timestamp: c.CancunTime, optional: true}, {name: "cancunTime", timestamp: c.CancunTime, optional: true},
{name: "pragueTime", timestamp: c.PragueTime, optional: true}, {name: "pragueTime", timestamp: c.PragueTime, optional: true},
{name: "prague1Time", timestamp: c.Berachain.Prague1.Time, optional: true},
{name: "osakaTime", timestamp: c.OsakaTime, optional: true}, {name: "osakaTime", timestamp: c.OsakaTime, optional: true},
{name: "verkleTime", timestamp: c.VerkleTime, optional: true}, {name: "verkleTime", timestamp: c.VerkleTime, optional: true},
{name: "bpo1", timestamp: c.BPO1Time, optional: true}, {name: "bpo1", timestamp: c.BPO1Time, optional: true},
@ -932,6 +1069,9 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, headNumber *big.Int,
if isForkTimestampIncompatible(c.PragueTime, newcfg.PragueTime, headTimestamp) { if isForkTimestampIncompatible(c.PragueTime, newcfg.PragueTime, headTimestamp) {
return newTimestampCompatError("Prague fork timestamp", c.PragueTime, newcfg.PragueTime) return newTimestampCompatError("Prague fork timestamp", c.PragueTime, newcfg.PragueTime)
} }
if isForkTimestampIncompatible(c.Berachain.Prague1.Time, newcfg.Berachain.Prague1.Time, headTimestamp) {
return newTimestampCompatError("Prague1 fork timestamp", c.Berachain.Prague1.Time, newcfg.Berachain.Prague1.Time)
}
if isForkTimestampIncompatible(c.OsakaTime, newcfg.OsakaTime, headTimestamp) { if isForkTimestampIncompatible(c.OsakaTime, newcfg.OsakaTime, headTimestamp) {
return newTimestampCompatError("Osaka fork timestamp", c.OsakaTime, newcfg.OsakaTime) return newTimestampCompatError("Osaka fork timestamp", c.OsakaTime, newcfg.OsakaTime)
} }
@ -957,7 +1097,12 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, headNumber *big.Int,
} }
// BaseFeeChangeDenominator bounds the amount the base fee can change between blocks. // BaseFeeChangeDenominator bounds the amount the base fee can change between blocks.
func (c *ChainConfig) BaseFeeChangeDenominator() uint64 { func (c *ChainConfig) BaseFeeChangeDenominator(num *big.Int, time uint64) uint64 {
// Starting at the Prague1 fork, we use the Berachain base fee change denominator.
if c.IsPrague1(num, time) {
return c.Berachain.Prague1.BaseFeeChangeDenominator
}
return DefaultBaseFeeChangeDenominator return DefaultBaseFeeChangeDenominator
} }
@ -974,6 +1119,8 @@ func (c *ChainConfig) LatestFork(time uint64) forks.Fork {
switch { switch {
case c.IsOsaka(london, time): case c.IsOsaka(london, time):
return forks.Osaka return forks.Osaka
case c.IsPrague1(london, time):
return forks.Prague1
case c.IsPrague(london, time): case c.IsPrague(london, time):
return forks.Prague return forks.Prague
case c.IsCancun(london, time): case c.IsCancun(london, time):
@ -991,6 +1138,8 @@ func (c *ChainConfig) Timestamp(fork forks.Fork) *uint64 {
switch { switch {
case fork == forks.Osaka: case fork == forks.Osaka:
return c.OsakaTime return c.OsakaTime
case fork == forks.Prague1:
return c.Berachain.Prague1.Time
case fork == forks.Prague: case fork == forks.Prague:
return c.PragueTime return c.PragueTime
case fork == forks.Cancun: case fork == forks.Cancun:
@ -1137,13 +1286,13 @@ func (err *ConfigCompatError) Error() string {
// Rules is a one time interface meaning that it shouldn't be used in between transition // Rules is a one time interface meaning that it shouldn't be used in between transition
// phases. // phases.
type Rules struct { type Rules struct {
ChainID *big.Int ChainID *big.Int
IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool
IsEIP2929, IsEIP4762 bool IsEIP2929, IsEIP4762 bool
IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool
IsBerlin, IsLondon bool IsBerlin, IsLondon bool
IsMerge, IsShanghai, IsCancun, IsPrague, IsOsaka bool IsMerge, IsShanghai, IsCancun, IsPrague, IsPrague1, IsOsaka bool
IsVerkle bool IsVerkle bool
} }
// Rules ensures c's ChainID is not nil. // Rules ensures c's ChainID is not nil.
@ -1172,6 +1321,7 @@ func (c *ChainConfig) Rules(num *big.Int, isMerge bool, timestamp uint64) Rules
IsShanghai: isMerge && c.IsShanghai(num, timestamp), IsShanghai: isMerge && c.IsShanghai(num, timestamp),
IsCancun: isMerge && c.IsCancun(num, timestamp), IsCancun: isMerge && c.IsCancun(num, timestamp),
IsPrague: isMerge && c.IsPrague(num, timestamp), IsPrague: isMerge && c.IsPrague(num, timestamp),
IsPrague1: isMerge && c.IsPrague1(num, timestamp),
IsOsaka: isMerge && c.IsOsaka(num, timestamp), IsOsaka: isMerge && c.IsOsaka(num, timestamp),
IsVerkle: isVerkle, IsVerkle: isVerkle,
IsEIP4762: isVerkle, IsEIP4762: isVerkle,

View file

@ -39,6 +39,7 @@ const (
Shanghai Shanghai
Cancun Cancun
Prague Prague
Prague1
Osaka Osaka
) )
@ -71,5 +72,6 @@ var forkToString = map[Fork]string{
Shanghai: "Shanghai", Shanghai: "Shanghai",
Cancun: "Cancun", Cancun: "Cancun",
Prague: "Prague", Prague: "Prague",
Prague1: "Prague1",
Osaka: "Osaka", Osaka: "Osaka",
} }

View file

@ -201,7 +201,7 @@ var (
// System contracts. // System contracts.
var ( var (
// SystemAddress is where the system-transaction is sent from as per EIP-4788 // SystemAddress is where the system-transaction is sent from as per EIP-4788 and BRIP-0004.
SystemAddress = common.HexToAddress("0xfffffffffffffffffffffffffffffffffffffffe") SystemAddress = common.HexToAddress("0xfffffffffffffffffffffffffffffffffffffffe")
// EIP-4788 - Beacon block root in the EVM // EIP-4788 - Beacon block root in the EVM
@ -219,4 +219,7 @@ var (
// EIP-7251 - Increase the MAX_EFFECTIVE_BALANCE // EIP-7251 - Increase the MAX_EFFECTIVE_BALANCE
ConsolidationQueueAddress = common.HexToAddress("0x0000BBdDc7CE488642fb579F8B00f3a590007251") ConsolidationQueueAddress = common.HexToAddress("0x0000BBdDc7CE488642fb579F8B00f3a590007251")
ConsolidationQueueCode = common.FromHex("3373fffffffffffffffffffffffffffffffffffffffe1460d35760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1461019a57600182026001905f5b5f82111560685781019083028483029004916001019190604d565b9093900492505050366060146088573661019a573461019a575f5260205ff35b341061019a57600154600101600155600354806004026004013381556001015f358155600101602035815560010160403590553360601b5f5260605f60143760745fa0600101600355005b6003546002548082038060021160e7575060025b5f5b8181146101295782810160040260040181607402815460601b815260140181600101548152602001816002015481526020019060030154905260010160e9565b910180921461013b5790600255610146565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff141561017357505f5b6001546001828201116101885750505f61018e565b01600190035b5f555f6001556074025ff35b5f5ffd") ConsolidationQueueCode = common.FromHex("3373fffffffffffffffffffffffffffffffffffffffe1460d35760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1461019a57600182026001905f5b5f82111560685781019083028483029004916001019190604d565b9093900492505050366060146088573661019a573461019a575f5260205ff35b341061019a57600154600101600155600354806004026004013381556001015f358155600101602035815560010160403590553360601b5f5260605f60143760745fa0600101600355005b6003546002548082038060021160e7575060025b5f5b8181146101295782810160040260040181607402815460601b815260140181600101548152602001816002015481526020019060030154905260010160e9565b910180921461013b5790600255610146565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff141561017357505f5b6001546001828201116101885750505f61018e565b01600190035b5f555f6001556074025ff35b5f5ffd")
// BRIP-0004 - PoL Distributor
PoLTxGasLimit uint64 = 30_000_000
) )

View file

@ -97,6 +97,7 @@ type btHeader struct {
BlobGasUsed *uint64 BlobGasUsed *uint64
ExcessBlobGas *uint64 ExcessBlobGas *uint64
ParentBeaconBlockRoot *common.Hash ParentBeaconBlockRoot *common.Hash
ParentProposerPubkey *common.Pubkey
} }
type btHeaderMarshaling struct { type btHeaderMarshaling struct {
@ -336,6 +337,9 @@ func validateHeader(h *btHeader, h2 *types.Header) error {
if !reflect.DeepEqual(h.ParentBeaconBlockRoot, h2.ParentBeaconRoot) { if !reflect.DeepEqual(h.ParentBeaconBlockRoot, h2.ParentBeaconRoot) {
return fmt.Errorf("parentBeaconBlockRoot: want: %v have: %v", h.ParentBeaconBlockRoot, h2.ParentBeaconRoot) return fmt.Errorf("parentBeaconBlockRoot: want: %v have: %v", h.ParentBeaconBlockRoot, h2.ParentBeaconRoot)
} }
if !reflect.DeepEqual(h.ParentProposerPubkey, h2.ParentProposerPubkey) {
return fmt.Errorf("parentProposerPubkey: want: %v have: %v", h.ParentProposerPubkey, h2.ParentProposerPubkey)
}
return nil return nil
} }

View file

@ -38,6 +38,7 @@ func (b btHeader) MarshalJSON() ([]byte, error) {
BlobGasUsed *math.HexOrDecimal64 BlobGasUsed *math.HexOrDecimal64
ExcessBlobGas *math.HexOrDecimal64 ExcessBlobGas *math.HexOrDecimal64
ParentBeaconBlockRoot *common.Hash ParentBeaconBlockRoot *common.Hash
ParentProposerPubkey *common.Pubkey
} }
var enc btHeader var enc btHeader
enc.Bloom = b.Bloom enc.Bloom = b.Bloom
@ -61,6 +62,7 @@ func (b btHeader) MarshalJSON() ([]byte, error) {
enc.BlobGasUsed = (*math.HexOrDecimal64)(b.BlobGasUsed) enc.BlobGasUsed = (*math.HexOrDecimal64)(b.BlobGasUsed)
enc.ExcessBlobGas = (*math.HexOrDecimal64)(b.ExcessBlobGas) enc.ExcessBlobGas = (*math.HexOrDecimal64)(b.ExcessBlobGas)
enc.ParentBeaconBlockRoot = b.ParentBeaconBlockRoot enc.ParentBeaconBlockRoot = b.ParentBeaconBlockRoot
enc.ParentProposerPubkey = b.ParentProposerPubkey
return json.Marshal(&enc) return json.Marshal(&enc)
} }
@ -88,6 +90,7 @@ func (b *btHeader) UnmarshalJSON(input []byte) error {
BlobGasUsed *math.HexOrDecimal64 BlobGasUsed *math.HexOrDecimal64
ExcessBlobGas *math.HexOrDecimal64 ExcessBlobGas *math.HexOrDecimal64
ParentBeaconBlockRoot *common.Hash ParentBeaconBlockRoot *common.Hash
ParentProposerPubkey *common.Pubkey
} }
var dec btHeader var dec btHeader
if err := json.Unmarshal(input, &dec); err != nil { if err := json.Unmarshal(input, &dec); err != nil {
@ -156,5 +159,8 @@ func (b *btHeader) UnmarshalJSON(input []byte) error {
if dec.ParentBeaconBlockRoot != nil { if dec.ParentBeaconBlockRoot != nil {
b.ParentBeaconBlockRoot = dec.ParentBeaconBlockRoot b.ParentBeaconBlockRoot = dec.ParentBeaconBlockRoot
} }
if dec.ParentProposerPubkey != nil {
b.ParentProposerPubkey = dec.ParentProposerPubkey
}
return nil return nil
} }