mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-16 20:01:37 +00:00
Merge 3b852842f9 into deda47f6a1
This commit is contained in:
commit
6dcf9d0d03
8 changed files with 462 additions and 64 deletions
|
|
@ -262,6 +262,18 @@ func ExecutableDataToBlock(data ExecutableData, versionedHashes []common.Hash, b
|
||||||
// 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) (*types.Block, error) {
|
||||||
|
var requestsHash *common.Hash
|
||||||
|
if requests != nil {
|
||||||
|
h := types.CalcRequestsHash(requests)
|
||||||
|
requestsHash = &h
|
||||||
|
}
|
||||||
|
|
||||||
|
return ExecutableDataToBlockNoHashWithRequestsHash(data, versionedHashes, beaconRoot, requestsHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecutableDataToBlockNoHashWithRequestsHash does the same thing as
|
||||||
|
// ExecutableDataToBlockNoHash, but it takes a hash of the requests.
|
||||||
|
func ExecutableDataToBlockNoHashWithRequestsHash(data ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, requestsHash *common.Hash) (*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
|
||||||
|
|
@ -297,12 +309,6 @@ func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.H
|
||||||
withdrawalsRoot = &h
|
withdrawalsRoot = &h
|
||||||
}
|
}
|
||||||
|
|
||||||
var requestsHash *common.Hash
|
|
||||||
if requests != nil {
|
|
||||||
h := types.CalcRequestsHash(requests)
|
|
||||||
requestsHash = &h
|
|
||||||
}
|
|
||||||
|
|
||||||
header := &types.Header{
|
header := &types.Header{
|
||||||
ParentHash: data.ParentHash,
|
ParentHash: data.ParentHash,
|
||||||
UncleHash: types.EmptyUncleHash,
|
UncleHash: types.EmptyUncleHash,
|
||||||
|
|
|
||||||
|
|
@ -75,5 +75,5 @@ func (api *testingAPI) BuildBlockV1(parentHash common.Hash, payloadAttributes en
|
||||||
Withdrawals: payloadAttributes.Withdrawals,
|
Withdrawals: payloadAttributes.Withdrawals,
|
||||||
BeaconRoot: payloadAttributes.BeaconRoot,
|
BeaconRoot: payloadAttributes.BeaconRoot,
|
||||||
}
|
}
|
||||||
return api.eth.Miner().BuildTestingPayload(args, txs, buildEmpty, extra)
|
return api.eth.Miner().BuildTestingPayload(args, txs, buildEmpty, extra, nil)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,19 @@ func New(eth Backend, config Config, engine consensus.Engine) *Miner {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewWithoutBackend creates a miner with the provided config. It is used in
|
||||||
|
// a testing context.
|
||||||
|
func NewWithoutBackend(chain *core.BlockChain, pool *txpool.TxPool, config Config, engine consensus.Engine) *Miner {
|
||||||
|
return &Miner{
|
||||||
|
config: &config,
|
||||||
|
chainConfig: chain.Config(),
|
||||||
|
engine: engine,
|
||||||
|
txpool: pool,
|
||||||
|
chain: chain,
|
||||||
|
pending: &pending{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Pending returns the currently pending block and associated receipts, logs
|
// Pending returns the currently pending block and associated receipts, logs
|
||||||
// and statedb. The returned values can be nil in case the pending block is
|
// and statedb. The returned values can be nil in case the pending block is
|
||||||
// not initialized.
|
// not initialized.
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -339,9 +340,7 @@ func (payload *Payload) updateSpanForDelivery(bSpan trace.Span) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuildTestingPayload is for testing_buildBlockV*. It creates a block with the exact content given
|
func (miner *Miner) buildPayloadWithOverrides(args *BuildPayloadArgs, transactions []*types.Transaction, empty bool, extraData []byte, gasLimit *uint64) (*newPayloadResult, error) {
|
||||||
// by the parameters instead of using the locally available transactions.
|
|
||||||
func (miner *Miner) BuildTestingPayload(args *BuildPayloadArgs, transactions []*types.Transaction, empty bool, extraData []byte) (*engine.ExecutionPayloadEnvelope, error) {
|
|
||||||
fullParams := &generateParams{
|
fullParams := &generateParams{
|
||||||
timestamp: args.Timestamp,
|
timestamp: args.Timestamp,
|
||||||
forceTime: true,
|
forceTime: true,
|
||||||
|
|
@ -355,10 +354,43 @@ func (miner *Miner) BuildTestingPayload(args *BuildPayloadArgs, transactions []*
|
||||||
forceOverrides: true,
|
forceOverrides: true,
|
||||||
overrideExtraData: extraData,
|
overrideExtraData: extraData,
|
||||||
overrideTxs: transactions,
|
overrideTxs: transactions,
|
||||||
|
overrideGasLimit: gasLimit,
|
||||||
}
|
}
|
||||||
res := miner.generateWork(context.Background(), fullParams, false)
|
res := miner.generateWork(context.Background(), fullParams, false)
|
||||||
if res.err != nil {
|
if res.err != nil {
|
||||||
return nil, res.err
|
return nil, res.err
|
||||||
}
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildPayloadWithOverrides creates a block with the content given by the parameters instead
|
||||||
|
// of using locally-available transactions. It attempts to include any transactions in the exact
|
||||||
|
// order they are specified.
|
||||||
|
func (miner *Miner) BuildPayloadWithOverrides(args *BuildPayloadArgs, transactions []*types.Transaction, empty bool, extraData []byte, gasLimit *uint64) (*engine.ExecutionPayloadEnvelope, error) {
|
||||||
|
res, err := miner.buildPayloadWithOverrides(args, transactions, empty, extraData, gasLimit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return engine.BlockToExecutableData(res.block, new(big.Int), res.sidecars, res.requests), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildTestingPayload is for testing_buildBlockV*. It creates a block with the exact content given
|
||||||
|
// by the parameters instead of using the locally available transactions.
|
||||||
|
// If a block cannot be constructed with the given parameters, an error is returned.
|
||||||
|
func (miner *Miner) BuildTestingPayload(args *BuildPayloadArgs, transactions []*types.Transaction, empty bool, extraData []byte, gasLimit *uint64) (*engine.ExecutionPayloadEnvelope, error) {
|
||||||
|
res, err := miner.buildPayloadWithOverrides(args, transactions, empty, extraData, gasLimit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// validate that the constructed block contains exactly the transactions specified, and in the specified order.
|
||||||
|
if len(res.block.Transactions()) != len(transactions) {
|
||||||
|
return nil, fmt.Errorf("constructed payload contained different number of txs than specified. want=%d, got=%d\n", len(transactions), len(res.block.Transactions()))
|
||||||
|
}
|
||||||
|
for i, expectedTx := range transactions {
|
||||||
|
if res.block.Transactions()[i].Hash() != expectedTx.Hash() {
|
||||||
|
return nil, fmt.Errorf("constructed block contained unexpected transaction at index %d: %x. expected %x\n", i, res.block.Transactions()[i].Hash(), expectedTx.Hash())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return engine.BlockToExecutableData(res.block, new(big.Int), res.sidecars, res.requests), nil
|
return engine.BlockToExecutableData(res.block, new(big.Int), res.sidecars, res.requests), nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
265
miner/tx_source.go
Normal file
265
miner/tx_source.go
Normal file
|
|
@ -0,0 +1,265 @@
|
||||||
|
// Copyright 2026 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package miner
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/txpool"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// resolveableTransaction represents a transaction of which some of the fields
|
||||||
|
// are available, but the full transaction will be resolved from some source.
|
||||||
|
type resolveableTransaction interface {
|
||||||
|
Hash() common.Hash
|
||||||
|
Resolve() *types.Transaction
|
||||||
|
Gas() uint64
|
||||||
|
BlobGas() uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// lazyTransaction wraps a txpool.LazyTransaction and implements resolveableTransaction.
|
||||||
|
type lazyTransaction struct {
|
||||||
|
*txpool.LazyTransaction
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lazyTransaction) Resolve() *types.Transaction {
|
||||||
|
return l.LazyTransaction.Resolve()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lazyTransaction) Gas() uint64 {
|
||||||
|
return l.LazyTransaction.Gas
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lazyTransaction) Hash() common.Hash {
|
||||||
|
return l.LazyTransaction.Hash
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lazyTransaction) BlobGas() uint64 {
|
||||||
|
return l.LazyTransaction.BlobGas
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolvedTransaction implements resolveableTransaction for a transaction
|
||||||
|
// that was resolved in the first place. used for building payloads with
|
||||||
|
// tx set overrides
|
||||||
|
type resolvedTransaction struct {
|
||||||
|
*types.Transaction
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *resolvedTransaction) Gas() uint64 {
|
||||||
|
return r.Transaction.Gas()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *resolvedTransaction) Resolve() *types.Transaction {
|
||||||
|
return r.Transaction
|
||||||
|
}
|
||||||
|
|
||||||
|
// transactionQueue represents a list of transactions
|
||||||
|
// which are queued for inclusion.
|
||||||
|
type transactionQueue interface {
|
||||||
|
// Shift removes all transactions from the sender of the
|
||||||
|
// current highest-priority transaction for inclusion
|
||||||
|
Shift()
|
||||||
|
// Pop removes the highest-priority transaction from
|
||||||
|
// the queue.
|
||||||
|
Pop()
|
||||||
|
// HasBlobTxs returns true if the sender of the current
|
||||||
|
// highest-priority transaction has queued blob transactions.
|
||||||
|
HasBlobTxs() bool
|
||||||
|
// ClearBlobTxs removes all blob txs from the sender of the
|
||||||
|
// current highest-priority transaction from the queue.
|
||||||
|
ClearBlobTxs()
|
||||||
|
}
|
||||||
|
|
||||||
|
// transactionSource is a source that the miner can
|
||||||
|
type transactionSource interface {
|
||||||
|
// Peek returns the next transaction which should be evaluated for inclusion
|
||||||
|
// and a transactionQueue for the sender account.
|
||||||
|
Peek() (resolveableTransaction, transactionQueue)
|
||||||
|
}
|
||||||
|
|
||||||
|
// plainTxQueue implements transactionQueue for a set of non-blob transactions
|
||||||
|
type plainTxQueue struct {
|
||||||
|
txs *transactionsByPriceAndNonce
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *plainTxQueue) Shift() { q.txs.Shift() }
|
||||||
|
func (q *plainTxQueue) Pop() { q.txs.Pop() }
|
||||||
|
|
||||||
|
// HasBlobTxs always returns false: plain queues never carry blob transactions.
|
||||||
|
func (q *plainTxQueue) HasBlobTxs() bool { return false }
|
||||||
|
|
||||||
|
// ClearBlobTxs is a no-op for plain queues and always returns false.
|
||||||
|
func (q *plainTxQueue) ClearBlobTxs() {}
|
||||||
|
|
||||||
|
// blobTxQueue implements transactionQueue for a set of blob transactions
|
||||||
|
type blobTxQueue struct {
|
||||||
|
txs *transactionsByPriceAndNonce
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *blobTxQueue) Shift() {
|
||||||
|
if q.txs != nil {
|
||||||
|
q.txs.Shift()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *blobTxQueue) Pop() {
|
||||||
|
if q.txs != nil {
|
||||||
|
q.txs.Pop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasBlobTxs always returns true: this queue exclusively holds blob transactions.
|
||||||
|
func (q *blobTxQueue) HasBlobTxs() bool { return true }
|
||||||
|
|
||||||
|
func (q *blobTxQueue) ClearBlobTxs() {
|
||||||
|
if q.txs != nil {
|
||||||
|
q.txs.Clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// feeOrderedTxSource implements transactionSource for the standard strategy
|
||||||
|
// of transaction inclusion: prioritize by profitability.
|
||||||
|
type feeOrderedTxSource struct {
|
||||||
|
plainQueue *plainTxQueue
|
||||||
|
blobQueue *blobTxQueue
|
||||||
|
}
|
||||||
|
|
||||||
|
// newFeeOrderedTxSource creates a feeOrderedTxSource from separate plain and blob
|
||||||
|
// transaction sets. Either argument may be nil (e.g. when there are no blob
|
||||||
|
// transactions pending).
|
||||||
|
func newFeeOrderedTxSource(plain, blob *transactionsByPriceAndNonce) *feeOrderedTxSource {
|
||||||
|
s := &feeOrderedTxSource{
|
||||||
|
plainQueue: &plainTxQueue{txs: plain},
|
||||||
|
blobQueue: &blobTxQueue{txs: blob},
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Peek returns the next most profitable queued transaction
|
||||||
|
func (s *feeOrderedTxSource) Peek() (resolveableTransaction, transactionQueue) {
|
||||||
|
pTx, pTip := s.plainQueue.txs.Peek()
|
||||||
|
bTx, bTip := s.blobQueue.txs.Peek()
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case pTx == nil && bTx == nil:
|
||||||
|
return nil, nil
|
||||||
|
case pTx == nil:
|
||||||
|
return &lazyTransaction{bTx}, s.blobQueue
|
||||||
|
case bTx == nil:
|
||||||
|
return &lazyTransaction{pTx}, s.plainQueue
|
||||||
|
default:
|
||||||
|
if bTip.Gt(pTip) {
|
||||||
|
return &lazyTransaction{bTx}, s.blobQueue
|
||||||
|
} else {
|
||||||
|
return &lazyTransaction{pTx}, s.plainQueue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// orderedTxSource implements transactionSource and transactionQueue.
|
||||||
|
// The transaction set and order is based on a pre-determined list.
|
||||||
|
type orderedTxSource struct {
|
||||||
|
// txs ordered as they are intended to be included in a payload
|
||||||
|
txs types.Transactions
|
||||||
|
// txs ordered by sender and nonce
|
||||||
|
orderedTxs map[common.Address][]*types.Transaction
|
||||||
|
signer types.Signer
|
||||||
|
}
|
||||||
|
|
||||||
|
func newOrderedTxSource(txs types.Transactions, signer types.Signer) (*orderedTxSource, error) {
|
||||||
|
orderedTxs := make(map[common.Address][]*types.Transaction)
|
||||||
|
for _, tx := range txs {
|
||||||
|
from, _ := signer.Sender(tx)
|
||||||
|
if _, ok := orderedTxs[from]; !ok {
|
||||||
|
orderedTxs[from] = []*types.Transaction{tx}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate that no transactions from the same sender have conflicting nonces.
|
||||||
|
senderTxs := orderedTxs[from]
|
||||||
|
for _, senderTx := range senderTxs {
|
||||||
|
if tx.Nonce() == senderTx.Nonce() {
|
||||||
|
return nil, fmt.Errorf("conflicting transaction nonces")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
insertionPoint int
|
||||||
|
senderTx *types.Transaction
|
||||||
|
)
|
||||||
|
for insertionPoint, senderTx = range senderTxs {
|
||||||
|
if senderTx.Nonce() > tx.Nonce() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
orderedTxs[from] = slices.Insert(senderTxs, insertionPoint, tx)
|
||||||
|
}
|
||||||
|
return &orderedTxSource{txs: txs, signer: signer, orderedTxs: orderedTxs}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *orderedTxSource) Pop() {
|
||||||
|
from, _ := s.signer.Sender(s.txs[0])
|
||||||
|
s.txs = slices.DeleteFunc(s.txs, func(tx *types.Transaction) bool {
|
||||||
|
txSender, _ := s.signer.Sender(tx)
|
||||||
|
return txSender == from
|
||||||
|
})
|
||||||
|
delete(s.orderedTxs, from)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *orderedTxSource) Shift() {
|
||||||
|
delTx := s.txs[0]
|
||||||
|
sender, _ := s.signer.Sender(delTx)
|
||||||
|
|
||||||
|
// application order of txs might not be by nonce,
|
||||||
|
// so deletion from the nonce-ordered set requires
|
||||||
|
// search
|
||||||
|
s.orderedTxs[sender] = slices.DeleteFunc(s.orderedTxs[sender], func(tx *types.Transaction) bool {
|
||||||
|
return delTx.Hash() == tx.Hash()
|
||||||
|
})
|
||||||
|
|
||||||
|
s.txs = s.txs[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *orderedTxSource) HasBlobTxs() bool {
|
||||||
|
for _, tx := range s.txs {
|
||||||
|
if tx.Type() == types.BlobTxType {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *orderedTxSource) ClearBlobTxs() {
|
||||||
|
var remainingTxs types.Transactions
|
||||||
|
for _, tx := range s.txs {
|
||||||
|
if tx.Type() != types.BlobTxType {
|
||||||
|
remainingTxs = append(remainingTxs, tx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.txs = remainingTxs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *orderedTxSource) Peek() (resolveableTransaction, transactionQueue) {
|
||||||
|
if len(s.txs) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return &resolvedTransaction{s.txs[0]}, s
|
||||||
|
}
|
||||||
|
|
@ -124,6 +124,7 @@ type generateParams struct {
|
||||||
forceOverrides bool // Flag whether we should overwrite extraData and transactions
|
forceOverrides bool // Flag whether we should overwrite extraData and transactions
|
||||||
overrideExtraData []byte
|
overrideExtraData []byte
|
||||||
overrideTxs []*types.Transaction
|
overrideTxs []*types.Transaction
|
||||||
|
overrideGasLimit *uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// generateWork generates a sealing block based on the given parameters.
|
// generateWork generates a sealing block based on the given parameters.
|
||||||
|
|
@ -160,15 +161,15 @@ func (miner *Miner) generateWork(ctx context.Context, genParam *generateParams,
|
||||||
work.size += uint64(genParam.withdrawals.Size())
|
work.size += uint64(genParam.withdrawals.Size())
|
||||||
|
|
||||||
if !genParam.noTxs {
|
if !genParam.noTxs {
|
||||||
// If forceOverrides is true and overrideTxs is not empty, commit the override transactions
|
// If forceOverrides is true, commit the override transactions
|
||||||
// otherwise, fill the block with the current transactions from the txpool
|
// otherwise, fill the block with the current transactions from the txpool
|
||||||
if genParam.forceOverrides && len(genParam.overrideTxs) > 0 {
|
if genParam.forceOverrides {
|
||||||
for _, tx := range genParam.overrideTxs {
|
overrideSource, err := newOrderedTxSource(genParam.overrideTxs, work.signer)
|
||||||
work.state.SetTxContext(tx.Hash(), work.tcount)
|
if err != nil {
|
||||||
if err := miner.commitTransaction(ctx, work, tx); err != nil {
|
return &newPayloadResult{err: err}
|
||||||
// all passed transactions HAVE to be valid at this point
|
}
|
||||||
return &newPayloadResult{err: err}
|
if err := miner.commitTransactions(ctx, work, overrideSource, new(atomic.Int32)); err != nil {
|
||||||
}
|
log.Warn("block building failed", "err", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
interrupt := new(atomic.Int32)
|
interrupt := new(atomic.Int32)
|
||||||
|
|
@ -281,6 +282,9 @@ func (miner *Miner) prepareWork(ctx context.Context, genParams *generateParams,
|
||||||
header.GasLimit = core.CalcGasLimit(parentGasLimit, miner.config.GasCeil)
|
header.GasLimit = core.CalcGasLimit(parentGasLimit, miner.config.GasCeil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if genParams.overrideGasLimit != nil {
|
||||||
|
header.GasLimit = *genParams.overrideGasLimit
|
||||||
|
}
|
||||||
// Run the consensus preparation with the default or customized consensus engine.
|
// Run the consensus preparation with the default or customized consensus engine.
|
||||||
// Note that the `header.Time` may be changed.
|
// Note that the `header.Time` may be changed.
|
||||||
if err := miner.engine.Prepare(miner.chain, header); err != nil {
|
if err := miner.engine.Prepare(miner.chain, header); err != nil {
|
||||||
|
|
@ -410,7 +414,12 @@ func (miner *Miner) applyTransaction(env *environment, tx *types.Transaction) (*
|
||||||
return receipt, nil
|
return receipt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (miner *Miner) commitTransactions(ctx context.Context, env *environment, plainTxs, blobTxs *transactionsByPriceAndNonce, interrupt *atomic.Int32) error {
|
func (miner *Miner) commitTransactions(
|
||||||
|
ctx context.Context,
|
||||||
|
env *environment,
|
||||||
|
allTxs transactionSource,
|
||||||
|
interrupt *atomic.Int32,
|
||||||
|
) error {
|
||||||
ctx, _, spanEnd := telemetry.StartSpan(ctx, "miner.commitTransactions")
|
ctx, _, spanEnd := telemetry.StartSpan(ctx, "miner.commitTransactions")
|
||||||
defer spanEnd(nil)
|
defer spanEnd(nil)
|
||||||
isCancun := miner.chainConfig.IsCancun(env.header.Number, env.header.Time)
|
isCancun := miner.chainConfig.IsCancun(env.header.Number, env.header.Time)
|
||||||
|
|
@ -426,39 +435,20 @@ func (miner *Miner) commitTransactions(ctx context.Context, env *environment, pl
|
||||||
log.Trace("Not enough gas for further transactions", "have", env.gasPool, "want", params.TxGas)
|
log.Trace("Not enough gas for further transactions", "have", env.gasPool, "want", params.TxGas)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
// If we don't have enough blob space for any further blob transactions,
|
|
||||||
// skip that list altogether
|
|
||||||
if !blobTxs.Empty() && env.blobs >= miner.maxBlobsPerBlock(env.header.Time) {
|
|
||||||
log.Trace("Not enough blob space for further blob transactions")
|
|
||||||
blobTxs.Clear()
|
|
||||||
// Fall though to pick up any plain txs
|
|
||||||
}
|
|
||||||
// Retrieve the next transaction and abort if all done.
|
|
||||||
var (
|
|
||||||
ltx *txpool.LazyTransaction
|
|
||||||
txs *transactionsByPriceAndNonce
|
|
||||||
)
|
|
||||||
pltx, ptip := plainTxs.Peek()
|
|
||||||
bltx, btip := blobTxs.Peek()
|
|
||||||
|
|
||||||
switch {
|
ltx, txs := allTxs.Peek()
|
||||||
case pltx == nil:
|
|
||||||
txs, ltx = blobTxs, bltx
|
|
||||||
case bltx == nil:
|
|
||||||
txs, ltx = plainTxs, pltx
|
|
||||||
default:
|
|
||||||
if ptip.Lt(btip) {
|
|
||||||
txs, ltx = blobTxs, bltx
|
|
||||||
} else {
|
|
||||||
txs, ltx = plainTxs, pltx
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ltx == nil {
|
if ltx == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if txs.HasBlobTxs() && env.blobs >= miner.maxBlobsPerBlock(env.header.Time) {
|
||||||
|
log.Trace("Not enough blob space for further blob transactions")
|
||||||
|
txs.ClearBlobTxs()
|
||||||
|
continue
|
||||||
|
}
|
||||||
// If we don't have enough space for the next transaction, skip the account.
|
// If we don't have enough space for the next transaction, skip the account.
|
||||||
if env.gasPool.Gas() < ltx.Gas {
|
if env.gasPool.Gas() < ltx.Gas() {
|
||||||
log.Trace("Not enough gas left for transaction", "hash", ltx.Hash, "left", env.gasPool.Gas(), "needed", ltx.Gas)
|
log.Trace("Not enough gas left for transaction", "hash", ltx.Hash(), "left", env.gasPool.Gas(), "needed", ltx.Gas())
|
||||||
txs.Pop()
|
txs.Pop()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -468,8 +458,8 @@ func (miner *Miner) commitTransactions(ctx context.Context, env *environment, pl
|
||||||
// a defined schedule, so we need to verify it's safe to call.
|
// a defined schedule, so we need to verify it's safe to call.
|
||||||
if isCancun {
|
if isCancun {
|
||||||
left := miner.maxBlobsPerBlock(env.header.Time) - env.blobs
|
left := miner.maxBlobsPerBlock(env.header.Time) - env.blobs
|
||||||
if left < int(ltx.BlobGas/params.BlobTxBlobGasPerBlob) {
|
if left < int(ltx.BlobGas()/params.BlobTxBlobGasPerBlob) {
|
||||||
log.Trace("Not enough blob space left for transaction", "hash", ltx.Hash, "left", left, "needed", ltx.BlobGas/params.BlobTxBlobGasPerBlob)
|
log.Trace("Not enough blob space left for transaction", "hash", ltx.Hash(), "left", left, "needed", ltx.BlobGas()/params.BlobTxBlobGasPerBlob)
|
||||||
txs.Pop()
|
txs.Pop()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -478,7 +468,7 @@ func (miner *Miner) commitTransactions(ctx context.Context, env *environment, pl
|
||||||
// Transaction seems to fit, pull it up from the pool
|
// Transaction seems to fit, pull it up from the pool
|
||||||
tx := ltx.Resolve()
|
tx := ltx.Resolve()
|
||||||
if tx == nil {
|
if tx == nil {
|
||||||
log.Trace("Ignoring evicted transaction", "hash", ltx.Hash)
|
log.Trace("Ignoring evicted transaction", "hash", ltx.Hash())
|
||||||
txs.Pop()
|
txs.Pop()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -495,7 +485,7 @@ func (miner *Miner) commitTransactions(ctx context.Context, env *environment, pl
|
||||||
// Check whether the tx is replay protected. If we're not in the EIP155 hf
|
// Check whether the tx is replay protected. If we're not in the EIP155 hf
|
||||||
// phase, start ignoring the sender until we do.
|
// phase, start ignoring the sender until we do.
|
||||||
if tx.Protected() && !miner.chainConfig.IsEIP155(env.header.Number) {
|
if tx.Protected() && !miner.chainConfig.IsEIP155(env.header.Number) {
|
||||||
log.Trace("Ignoring replay protected transaction", "hash", ltx.Hash, "eip155", miner.chainConfig.EIP155Block)
|
log.Trace("Ignoring replay protected transaction", "hash", ltx.Hash(), "eip155", miner.chainConfig.EIP155Block)
|
||||||
txs.Pop()
|
txs.Pop()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -506,7 +496,7 @@ func (miner *Miner) commitTransactions(ctx context.Context, env *environment, pl
|
||||||
switch {
|
switch {
|
||||||
case errors.Is(err, core.ErrNonceTooLow):
|
case errors.Is(err, core.ErrNonceTooLow):
|
||||||
// New head notification data race between the transaction pool and miner, shift
|
// New head notification data race between the transaction pool and miner, shift
|
||||||
log.Trace("Skipping transaction with low nonce", "hash", ltx.Hash, "sender", from, "nonce", tx.Nonce())
|
log.Trace("Skipping transaction with low nonce", "hash", ltx.Hash(), "sender", from, "nonce", tx.Nonce())
|
||||||
txs.Shift()
|
txs.Shift()
|
||||||
|
|
||||||
case errors.Is(err, nil):
|
case errors.Is(err, nil):
|
||||||
|
|
@ -516,7 +506,7 @@ func (miner *Miner) commitTransactions(ctx context.Context, env *environment, pl
|
||||||
default:
|
default:
|
||||||
// Transaction is regarded as invalid, drop all consecutive transactions from
|
// Transaction is regarded as invalid, drop all consecutive transactions from
|
||||||
// the same sender because of `nonce-too-high` clause.
|
// the same sender because of `nonce-too-high` clause.
|
||||||
log.Debug("Transaction failed, account skipped", "hash", ltx.Hash, "err", err)
|
log.Debug("Transaction failed, account skipped", "hash", ltx.Hash(), "err", err)
|
||||||
txs.Pop()
|
txs.Pop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -581,7 +571,7 @@ func (miner *Miner) fillTransactions(ctx context.Context, interrupt *atomic.Int3
|
||||||
plainTxs := newTransactionsByPriceAndNonce(env.signer, prioPlainTxs, env.header.BaseFee)
|
plainTxs := newTransactionsByPriceAndNonce(env.signer, prioPlainTxs, env.header.BaseFee)
|
||||||
blobTxs := newTransactionsByPriceAndNonce(env.signer, prioBlobTxs, env.header.BaseFee)
|
blobTxs := newTransactionsByPriceAndNonce(env.signer, prioBlobTxs, env.header.BaseFee)
|
||||||
|
|
||||||
if err := miner.commitTransactions(ctx, env, plainTxs, blobTxs, interrupt); err != nil {
|
if err := miner.commitTransactions(ctx, env, newFeeOrderedTxSource(plainTxs, blobTxs), interrupt); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -589,7 +579,7 @@ func (miner *Miner) fillTransactions(ctx context.Context, interrupt *atomic.Int3
|
||||||
plainTxs := newTransactionsByPriceAndNonce(env.signer, normalPlainTxs, env.header.BaseFee)
|
plainTxs := newTransactionsByPriceAndNonce(env.signer, normalPlainTxs, env.header.BaseFee)
|
||||||
blobTxs := newTransactionsByPriceAndNonce(env.signer, normalBlobTxs, env.header.BaseFee)
|
blobTxs := newTransactionsByPriceAndNonce(env.signer, normalBlobTxs, env.header.BaseFee)
|
||||||
|
|
||||||
if err := miner.commitTransactions(ctx, env, plainTxs, blobTxs, interrupt); err != nil {
|
if err := miner.commitTransactions(ctx, env, newFeeOrderedTxSource(plainTxs, blobTxs), interrupt); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,26 @@ func TestBlockchain(t *testing.T) {
|
||||||
bt.skipLoad(`.*bcFrontierToHomestead/blockChainFrontierWithLargerTDvsHomesteadBlockchain.json`)
|
bt.skipLoad(`.*bcFrontierToHomestead/blockChainFrontierWithLargerTDvsHomesteadBlockchain.json`)
|
||||||
bt.skipLoad(`.*bcFrontierToHomestead/blockChainFrontierWithLargerTDvsHomesteadBlockchain2.json`)
|
bt.skipLoad(`.*bcFrontierToHomestead/blockChainFrontierWithLargerTDvsHomesteadBlockchain2.json`)
|
||||||
|
|
||||||
|
/*
|
||||||
|
skip these for the payload building only:
|
||||||
|
--- FAIL: TestExecutionSpecBlocktests/osaka/eip7934_block_rlp_limit/test_block_at_rlp_limit_with_logs.json (0.45s)
|
||||||
|
block_test.go:110: test with config {snapshotter:false, scheme:hash} failed: mismatch in block hash. wanted 51a93fb2f038278424742e6ceaf476f83fd30ab84580216f08d124e258554b00, got 8dddbd82ec3e09b201230202207069fa64b6fb21a89a3b5e8ab2421adac933ea
|
||||||
|
--- FAIL: TestExecutionSpecBlocktests/osaka/eip7934_block_rlp_limit/test_block_at_rlp_size_limit_boundary.json (1.30s)
|
||||||
|
--- FAIL: TestExecutionSpecBlocktests/osaka/eip7934_block_rlp_limit/test_block_at_rlp_size_limit_boundary.json/tests/osaka/eip7934_block_rlp_limit/test_max_block_rlp_size.py::test_block_at_rlp_size_limit_boundary[fork_Osaka-blockchain_test-max_rlp_size] (0.10s)
|
||||||
|
block_test.go:110: test with config {snapshotter:false, scheme:hash} failed: mismatch in block hash. wanted bf2a96a93b4b519a36cc17ba7e5b9de7a2a8efef8f15f00e97d63c067fcae532, got 99be2c1ff6a5ab3b7c80eca8ed12e933417efc4c9fbc73a23873017a4888ac30
|
||||||
|
--- FAIL: TestExecutionSpecBlocktests/osaka/eip7934_block_rlp_limit/test_block_at_rlp_size_limit_boundary.json/tests/osaka/eip7934_block_rlp_limit/test_max_block_rlp_size.py::test_block_at_rlp_size_limit_boundary[fork_Osaka-blockchain_test-max_rlp_size_minus_1_byte] (0.09s)
|
||||||
|
block_test.go:110: test with config {snapshotter:false, scheme:hash} failed: mismatch in block hash. wanted f01227ceacf73b2fa7492cc36f307535d6734a0de468b42a136bd39561a613ec, got 5a5019a9d576c93e55ad94e2fe2b6ba8881d96c9df4b7d4ea8c4780520fa232e
|
||||||
|
--- FAIL: TestExecutionSpecBlocktests/osaka/eip7934_block_rlp_limit/test_block_rlp_size_at_limit_with_all_typed_transactions.json (2.51s)
|
||||||
|
--- FAIL: TestExecutionSpecBlocktests/osaka/eip7934_block_rlp_limit/test_block_rlp_size_at_limit_with_all_typed_transactions.json/tests/osaka/eip7934_block_rlp_limit/test_max_block_rlp_size.py::test_block_rlp_size_at_limit_with_all_typed_transactions[fork_Osaka-typed_transaction_0-blockchain_test] (0.09s)
|
||||||
|
block_test.go:110: test with config {snapshotter:false, scheme:hash} failed: mismatch in block hash. wanted e15a14788a8b960fa9951cdd8f059b9c53ac50ca12c2d63255a437fb2a10a93f, got 194fbad30f9fe3f0e60627a0fcc6eec78884ce117e9d13e04b7a44f8e14c0547
|
||||||
|
--- FAIL: TestExecutionSpecBlocktests/osaka/eip7934_block_rlp_limit/test_block_rlp_size_at_limit_with_all_typed_transactions.json/tests/osaka/eip7934_block_rlp_limit/test_max_block_rlp_size.py::test_block_rlp_size_at_limit_with_all_typed_transactions[fork_Osaka-typed_transaction_1-blockchain_test] (0.09s)
|
||||||
|
block_test.go:110: test with config {snapshotter:false, scheme:hash} failed: mismatch in block hash. wanted 5ad6568d4e64aa88faa2ad73a249bc7fcd9a36a23629ee6e914a05fa75a87293, got 0f9f047ddfc220c18b53cfc82871a67c4166eebda65bb0c137af87cf20e6c25a
|
||||||
|
--- FAIL: TestExecutionSpecBlocktests/osaka/eip7934_block_rlp_limit/test_block_rlp_size_at_limit_with_all_typed_transactions.json/tests/osaka/eip7934_block_rlp_limit/test_max_block_rlp_size.py::test_block_rlp_size_at_limit_with_all_typed_transactions[fork_Osaka-typed_transaction_2-blockchain_test] (0.09s)
|
||||||
|
block_test.go:110: test with config {snapshotter:false, scheme:hash} failed: mismatch in block hash. wanted 1570cd16ac3b096866bf5765fdb28571140f70392bda5ac7fbb034e5e9a93acb, got 0d254beb06fe3b4d4ffbe9cd6fa914f402a809e8f60ce311f849431e485ba8d9
|
||||||
|
--- FAIL: TestExecutionSpecBlocktests/osaka/eip7934_block_rlp_limit/test_block_rlp_size_at_limit_with_all_typed_transactions.json/tests/osaka/eip7934_block_rlp_limit/test_max_block_rlp_size.py::test_block_rlp_size_at_limit_with_all_typed_transactions[fork_Osaka-typed_transaction_4-blockchain_test] (0.11s)
|
||||||
|
block_test.go:110: test with config {snapshotter:false, scheme:hash} failed: mismatch in block hash. wanted 8a12b02aaf555c15e83ac9831205b35006c94e6c8aec7d2c99a135507fa4103b, got d65726606a9c28a68cea095b2682872d88cb535ff295b135d156f28d3c2c59f7
|
||||||
|
*/
|
||||||
|
|
||||||
// With chain history removal, TDs become unavailable, this transition tests based on TTD are unrunnable
|
// With chain history removal, TDs become unavailable, this transition tests based on TTD are unrunnable
|
||||||
bt.skipLoad(`.*bcArrowGlacierToParis/powToPosBlockRejection.json`)
|
bt.skipLoad(`.*bcArrowGlacierToParis/powToPosBlockRejection.json`)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,10 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/engine"
|
||||||
|
"github.com/ethereum/go-ethereum/core/txpool"
|
||||||
|
"github.com/ethereum/go-ethereum/miner"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/common/math"
|
"github.com/ethereum/go-ethereum/common/math"
|
||||||
|
|
@ -230,21 +234,48 @@ func (t *BlockTest) genesis(config *params.ChainConfig) *core.Genesis {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// return a payload version for post-merge hard-forks. otherwise return an error.
|
||||||
|
func getPayloadVersion(header *types.Header, chainConfig *params.ChainConfig) engine.PayloadVersion {
|
||||||
|
switch {
|
||||||
|
case chainConfig.IsPostMerge(header.Number.Uint64(), header.Time):
|
||||||
|
return engine.PayloadV1
|
||||||
|
case chainConfig.IsShanghai(header.Number, header.Time):
|
||||||
|
return engine.PayloadV2
|
||||||
|
case chainConfig.IsPrague(header.Number, header.Time):
|
||||||
|
case chainConfig.IsCancun(header.Number, header.Time):
|
||||||
|
return engine.PayloadV3
|
||||||
|
}
|
||||||
|
panic("invalid post-merge fork")
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
See https://ethereum-tests.readthedocs.io/en/latest/blockchain-ref.html
|
See https://ethereum-tests.readthedocs.io/en/latest/blockchain-ref.html
|
||||||
|
|
||||||
Whether a block is valid or not is a bit subtle, it's defined by presence of
|
Whether a block is valid or not is a bit subtle, it's defined by presence of
|
||||||
blockHeader, transactions and uncleHeaders fields. If they are missing, the block is
|
blockHeader, transactions and uncleHeaders fields. If they are missing, the block is
|
||||||
invalid and we must verify that we do not accept it.
|
invalid and we must verify that we do not accept it.
|
||||||
|
|
||||||
Since some tests mix valid and invalid blocks we need to check this for every block.
|
Since some tests mix valid and invalid blocks we need to check this for every block.
|
||||||
|
|
||||||
If a block is invalid it does not necessarily fail the test, if it's invalidness is
|
If a block is invalid it does not necessarily fail the test, if it's invalidness is
|
||||||
expected we are expected to ignore it and continue processing and then validate the
|
expected we are expected to ignore it and continue processing and then validate the
|
||||||
post state.
|
post state.
|
||||||
|
|
||||||
|
If the block is post-merge, insertBlocks will construct payloads via the miner,
|
||||||
|
validating that they match exactly blocks in the test which:
|
||||||
|
- are past the merge block
|
||||||
|
- are not specified as invalid by the test
|
||||||
|
- do not contain blob transactions
|
||||||
*/
|
*/
|
||||||
func (t *BlockTest) insertBlocks(blockchain *core.BlockChain) ([]btBlock, error) {
|
func (t *BlockTest) insertBlocks(blockchain *core.BlockChain) ([]btBlock, error) {
|
||||||
validBlocks := make([]btBlock, 0)
|
var (
|
||||||
|
pool *txpool.TxPool
|
||||||
|
config = miner.Config{}
|
||||||
|
consensusEngine = beacon.New(ethash.NewFaker())
|
||||||
|
builder = miner.NewWithoutBackend(blockchain, pool, config, consensusEngine)
|
||||||
|
validBlocks = make([]btBlock, 0)
|
||||||
|
)
|
||||||
|
|
||||||
// insert the test blocks, which will execute all transactions
|
// insert the test blocks, which will execute all transactions
|
||||||
for bi, b := range t.json.Blocks {
|
for bi, b := range t.json.Blocks {
|
||||||
cb, err := b.decode()
|
cb, err := b.decode()
|
||||||
|
|
@ -257,7 +288,48 @@ func (t *BlockTest) insertBlocks(blockchain *core.BlockChain) ([]btBlock, error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// RLP decoding worked, try to insert into chain:
|
// RLP decoding worked, try to insert into chain:
|
||||||
blocks := types.Blocks{cb}
|
var blocks types.Blocks
|
||||||
|
|
||||||
|
var blockHasBlobTxs bool
|
||||||
|
for _, tx := range cb.Transactions() {
|
||||||
|
if tx.BlobHashes() != nil {
|
||||||
|
blockHasBlobTxs = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// test payload building on post-merge blocks that are valid and don't contain blob transactions
|
||||||
|
if !blockHasBlobTxs && b.BlockHeader != nil && (b.BlockHeader.Difficulty == nil || b.BlockHeader.Difficulty.Cmp(common.Big0) == 0) {
|
||||||
|
payloadVersion := getPayloadVersion(cb.Header(), blockchain.Config())
|
||||||
|
|
||||||
|
gasLimit := cb.GasLimit()
|
||||||
|
args := miner.BuildPayloadArgs{
|
||||||
|
Parent: cb.ParentHash(),
|
||||||
|
Timestamp: cb.Time(),
|
||||||
|
FeeRecipient: cb.Coinbase(),
|
||||||
|
Random: cb.MixDigest(),
|
||||||
|
Withdrawals: cb.Withdrawals(),
|
||||||
|
BeaconRoot: cb.BeaconRoot(),
|
||||||
|
SlotNum: cb.SlotNumber(),
|
||||||
|
Version: payloadVersion,
|
||||||
|
}
|
||||||
|
envelope, err := builder.BuildPayloadWithOverrides(&args, cb.Transactions(), false, cb.Extra(), &gasLimit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
constructedBlock, err := engine.ExecutableDataToBlockNoHashWithRequestsHash(*envelope.ExecutionPayload, nil, cb.BeaconRoot(), cb.RequestsHash())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if constructedBlock.Hash() != cb.Hash() {
|
||||||
|
return nil, fmt.Errorf("mismatch in block hash. wanted %x, got %x", cb.Hash(), constructedBlock.Hash())
|
||||||
|
}
|
||||||
|
blocks = types.Blocks{constructedBlock}
|
||||||
|
} else {
|
||||||
|
blocks = types.Blocks{cb}
|
||||||
|
}
|
||||||
i, err := blockchain.InsertChain(blocks)
|
i, err := blockchain.InsertChain(blocks)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if b.BlockHeader == nil {
|
if b.BlockHeader == nil {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue