Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: ethereum/go-ethereum
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: master
Choose a base ref
...
head repository: jwasinger/go-ethereum
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: master
Choose a head ref
Can’t automatically merge. Don’t worry, you can still create the pull request.
  • 7 commits
  • 7 files changed
  • 1 contributor

Commits on Jan 12, 2025

  1. core/txpool/legacypool, ethclient/simulated: ensure pending nonces ar…

    …e reset by subpool.Clear (TODO: add this for blobpool as well)
    jwasinger committed Jan 12, 2025
    Copy the full SHA
    978d392 View commit details

Commits on Jan 13, 2025

  1. Copy the full SHA
    768a29e View commit details
  2. Copy the full SHA
    1103cb3 View commit details
  3. fix comment

    jwasinger committed Jan 13, 2025
    Copy the full SHA
    d9639a5 View commit details
  4. gofmt

    jwasinger committed Jan 13, 2025
    Copy the full SHA
    5372700 View commit details

Commits on Jan 14, 2025

  1. Copy the full SHA
    b16e65f View commit details

Commits on Feb 19, 2025

  1. core/txpool,core/types,eth: when receiving blob txs via PooledTransac…

    …tions, validate that the 'blob_versioned_hashes' in the tx header is produced from the commitments in the sidecar.
    jwasinger committed Feb 19, 2025
    Copy the full SHA
    9903605 View commit details
Showing with 199 additions and 21 deletions.
  1. +1 −0 core/txpool/legacypool/legacypool.go
  2. +2 −12 core/txpool/validation.go
  3. +23 −0 core/types/tx_blob.go
  4. +10 −1 eth/handler_eth.go
  5. +12 −6 ethclient/simulated/backend.go
  6. +49 −2 ethclient/simulated/backend_test.go
  7. +102 −0 ethclient/simulated/rollback_test.go
1 change: 1 addition & 0 deletions core/txpool/legacypool/legacypool.go
Original file line number Diff line number Diff line change
@@ -1994,6 +1994,7 @@ func (pool *LegacyPool) Clear() {
pool.priced = newPricedList(pool.all)
pool.pending = make(map[common.Address]*list)
pool.queue = make(map[common.Address]*list)
pool.pendingNonces = newNoncer(pool.currentState)

if !pool.config.NoLocals && pool.config.Journal != "" {
pool.journal = newTxJournal(pool.config.Journal)
14 changes: 2 additions & 12 deletions core/txpool/validation.go
Original file line number Diff line number Diff line change
@@ -17,7 +17,6 @@
package txpool

import (
"crypto/sha256"
"errors"
"fmt"
"math/big"
@@ -149,20 +148,11 @@ func validateBlobSidecar(hashes []common.Hash, sidecar *types.BlobTxSidecar) err
if len(sidecar.Blobs) != len(hashes) {
return fmt.Errorf("invalid number of %d blobs compared to %d blob hashes", len(sidecar.Blobs), len(hashes))
}
if len(sidecar.Commitments) != len(hashes) {
return fmt.Errorf("invalid number of %d blob commitments compared to %d blob hashes", len(sidecar.Commitments), len(hashes))
}
if len(sidecar.Proofs) != len(hashes) {
return fmt.Errorf("invalid number of %d blob proofs compared to %d blob hashes", len(sidecar.Proofs), len(hashes))
}
// Blob quantities match up, validate that the provers match with the
// transaction hash before getting to the cryptography
hasher := sha256.New()
for i, vhash := range hashes {
computed := kzg4844.CalcBlobHashV1(hasher, &sidecar.Commitments[i])
if vhash != computed {
return fmt.Errorf("blob %d: computed hash %#x mismatches transaction one %#x", i, computed, vhash)
}
if err := sidecar.ValidateBlobCommitmentHashes(hashes); err != nil {
return err
}
// Blob commitments match with the hashes in the transaction, verify the
// blobs themselves via KZG
23 changes: 23 additions & 0 deletions core/types/tx_blob.go
Original file line number Diff line number Diff line change
@@ -19,6 +19,7 @@ package types
import (
"bytes"
"crypto/sha256"
"fmt"
"math/big"

"github.com/ethereum/go-ethereum/common"
@@ -85,6 +86,28 @@ func (sc *BlobTxSidecar) encodedSize() uint64 {
return rlp.ListSize(blobs) + rlp.ListSize(commitments) + rlp.ListSize(proofs)
}

func (sc *BlobTxSidecar) ValidateBlobCommitmentHashes(hashes []common.Hash) error {
if len(sc.Blobs) != len(hashes) {
return fmt.Errorf("invalid number of %d blobs compared to %d blob hashes", len(sc.Blobs), len(hashes))
}
if len(sc.Commitments) != len(hashes) {
return fmt.Errorf("invalid number of %d blob commitments compared to %d blob hashes", len(sc.Commitments), len(hashes))
}
if len(sc.Proofs) != len(hashes) {
return fmt.Errorf("invalid number of %d blob proofs compared to %d blob hashes", len(sc.Proofs), len(hashes))
}
// Blob quantities match up, validate that the provers match with the
// transaction hash before getting to the cryptography
hasher := sha256.New()
for i, vhash := range hashes {
computed := kzg4844.CalcBlobHashV1(hasher, &sc.Commitments[i])
if vhash != computed {
return fmt.Errorf("blob %d: computed hash %#x mismatches transaction one %#x", i, computed, vhash)
}
}
return nil
}

// blobTxWithBlobs is used for encoding of transactions when blobs are present.
type blobTxWithBlobs struct {
BlobTx *BlobTx
11 changes: 10 additions & 1 deletion eth/handler_eth.go
Original file line number Diff line number Diff line change
@@ -19,7 +19,6 @@ package eth
import (
"errors"
"fmt"

"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth/protocols/eth"
@@ -69,6 +68,16 @@ func (h *ethHandler) Handle(peer *eth.Peer, packet eth.Packet) error {
return h.txFetcher.Enqueue(peer.ID(), *packet, false)

case *eth.PooledTransactionsResponse:
for _, tx := range *packet {
if tx.Type() == types.BlobTxType {
if tx.BlobTxSidecar() == nil {
return errors.New("received sidecar-less blob transaction")
}
if err := tx.BlobTxSidecar().ValidateBlobCommitmentHashes(tx.BlobHashes()); err != nil {
return err
}
}
}
return h.txFetcher.Enqueue(peer.ID(), *packet, true)

default:
18 changes: 12 additions & 6 deletions ethclient/simulated/backend.go
Original file line number Diff line number Diff line change
@@ -18,6 +18,7 @@ package simulated

import (
"errors"
"github.com/ethereum/go-ethereum/log"
"time"

"github.com/ethereum/go-ethereum"
@@ -62,9 +63,10 @@ type simClient struct {
// Backend is a simulated blockchain. You can use it to test your contracts or
// other code that interacts with the Ethereum chain.
type Backend struct {
node *node.Node
beacon *catalyst.SimulatedBeacon
client simClient
node *node.Node
beacon *catalyst.SimulatedBeacon
ethBackend *eth.Ethereum
client simClient
}

// NewBackend creates a new simulated blockchain that can be used as a backend for
@@ -129,9 +131,10 @@ func newWithNode(stack *node.Node, conf *eth.Config, blockPeriod uint64) (*Backe
return nil, err
}
return &Backend{
node: stack,
beacon: beacon,
client: simClient{ethclient.NewClient(stack.Attach())},
node: stack,
beacon: beacon,
ethBackend: backend,
client: simClient{ethclient.NewClient(stack.Attach())},
}, nil
}

@@ -147,6 +150,9 @@ func (n *Backend) Close() error {
err = n.beacon.Stop()
n.beacon = nil
}
if err := n.ethBackend.Stop(); err != nil {
log.Error("error stopping eth backend", "err", err)
}
if n.node != nil {
err = errors.Join(err, n.node.Close())
n.node = nil
51 changes: 49 additions & 2 deletions ethclient/simulated/backend_test.go
Original file line number Diff line number Diff line change
@@ -19,11 +19,15 @@ package simulated
import (
"context"
"crypto/ecdsa"
"crypto/sha256"
"math/big"
"math/rand"
"testing"
"time"

"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/holiman/uint256"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
@@ -34,8 +38,10 @@ import (
var _ bind.ContractBackend = (Client)(nil)

var (
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
testAddr = crypto.PubkeyToAddress(testKey.PublicKey)
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
testAddr = crypto.PubkeyToAddress(testKey.PublicKey)
testKey2, _ = crypto.HexToECDSA("7ee346e3f7efc685250053bfbafbfc880d58dc6145247053d4fb3cb0f66dfcb2")
testAddr2 = crypto.PubkeyToAddress(testKey2.PublicKey)
)

func simTestBackend(testAddr common.Address) *Backend {
@@ -46,6 +52,46 @@ func simTestBackend(testAddr common.Address) *Backend {
)
}

func newBlobTx(sim *Backend, key *ecdsa.PrivateKey) (*types.Transaction, error) {
client := sim.Client()

testBlob := &kzg4844.Blob{0x00}
testBlobCommit, _ := kzg4844.BlobToCommitment(testBlob)
testBlobProof, _ := kzg4844.ComputeBlobProof(testBlob, testBlobCommit)
testBlobVHash := kzg4844.CalcBlobHashV1(sha256.New(), &testBlobCommit)

head, _ := client.HeaderByNumber(context.Background(), nil) // Should be child's, good enough
gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(params.GWei))
gasPriceU256, _ := uint256.FromBig(gasPrice)
gasTipCapU256, _ := uint256.FromBig(big.NewInt(params.GWei))

addr := crypto.PubkeyToAddress(key.PublicKey)
chainid, _ := client.ChainID(context.Background())
nonce, err := client.PendingNonceAt(context.Background(), addr)
if err != nil {
return nil, err
}

chainidU256, _ := uint256.FromBig(chainid)
tx := types.NewTx(&types.BlobTx{
ChainID: chainidU256,
GasTipCap: gasTipCapU256,
GasFeeCap: gasPriceU256,
BlobFeeCap: uint256.NewInt(1),
Gas: 21000,
Nonce: nonce,
To: addr,
AccessList: nil,
BlobHashes: []common.Hash{testBlobVHash},
Sidecar: &types.BlobTxSidecar{
Blobs: []kzg4844.Blob{*testBlob},
Commitments: []kzg4844.Commitment{testBlobCommit},
Proofs: []kzg4844.Proof{testBlobProof},
},
})
return types.SignTx(tx, types.LatestSignerForChainID(chainid), key)
}

func newTx(sim *Backend, key *ecdsa.PrivateKey) (*types.Transaction, error) {
client := sim.Client()

@@ -66,6 +112,7 @@ func newTx(sim *Backend, key *ecdsa.PrivateKey) (*types.Transaction, error) {
Gas: 21000,
To: &addr,
})

return types.SignTx(tx, types.LatestSignerForChainID(chainid), key)
}

102 changes: 102 additions & 0 deletions ethclient/simulated/rollback_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package simulated

import (
"context"
"crypto/ecdsa"
"math/big"
"testing"
"time"

"github.com/ethereum/go-ethereum/core/types"
)

// TestTransactionRollbackBehavior tests that calling Rollback on the simulated backend doesn't prevent subsequent
// addition of new transactions
func TestTransactionRollbackBehavior(t *testing.T) {
sim := NewBackend(
types.GenesisAlloc{
testAddr: {Balance: big.NewInt(10000000000000000)},
testAddr2: {Balance: big.NewInt(10000000000000000)},
},
)
defer sim.Close()
client := sim.Client()

btx0 := testSendSignedTx(t, testKey, sim, true)
tx0 := testSendSignedTx(t, testKey2, sim, false)
tx1 := testSendSignedTx(t, testKey2, sim, false)

sim.Rollback()

if pendingStateHasTx(client, btx0) || pendingStateHasTx(client, tx0) || pendingStateHasTx(client, tx1) {
t.Fatalf("all transactions were not rolled back")
}

btx2 := testSendSignedTx(t, testKey, sim, true)
tx2 := testSendSignedTx(t, testKey2, sim, false)
tx3 := testSendSignedTx(t, testKey2, sim, false)

sim.Commit()

if !pendingStateHasTx(client, btx2) || !pendingStateHasTx(client, tx2) || !pendingStateHasTx(client, tx3) {
t.Fatalf("all post-rollback transactions were not included")
}
}

// testSendSignedTx sends a signed transaction to the simulated backend.
// It does not commit the block.
func testSendSignedTx(t *testing.T, key *ecdsa.PrivateKey, sim *Backend, isBlobTx bool) *types.Transaction {
t.Helper()
client := sim.Client()
ctx := context.Background()

var (
err error
signedTx *types.Transaction
)
if isBlobTx {
signedTx, err = newBlobTx(sim, key)
} else {
signedTx, err = newTx(sim, key)
}
if err != nil {
t.Fatalf("failed to create transaction: %v", err)
}

if err = client.SendTransaction(ctx, signedTx); err != nil {
t.Fatalf("failed to send transaction: %v", err)
}

return signedTx
}

// pendingStateHasTx returns true if a given transaction was successfully included as of the latest pending state.
func pendingStateHasTx(client Client, tx *types.Transaction) bool {
ctx := context.Background()

var (
receipt *types.Receipt
err error
)

// Poll for receipt with timeout
deadline := time.Now().Add(2 * time.Second)
for time.Now().Before(deadline) {
receipt, err = client.TransactionReceipt(ctx, tx.Hash())
if err == nil && receipt != nil {
break
}
time.Sleep(100 * time.Millisecond)
}

if err != nil {
return false
}
if receipt == nil {
return false
}
if receipt.Status != types.ReceiptStatusSuccessful {
return false
}
return true
}