forked from cerc-io/plugeth
manual geth v1.11.1 merge
This commit is contained in:
@@ -1,87 +0,0 @@
|
||||
// Copyright 2022 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 beacon
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
// EngineAPIError is a standardized error message between consensus and execution
|
||||
// clients, also containing any custom error message Geth might include.
|
||||
type EngineAPIError struct {
|
||||
code int
|
||||
msg string
|
||||
err error
|
||||
}
|
||||
|
||||
func (e *EngineAPIError) ErrorCode() int { return e.code }
|
||||
func (e *EngineAPIError) Error() string { return e.msg }
|
||||
func (e *EngineAPIError) ErrorData() interface{} {
|
||||
if e.err == nil {
|
||||
return nil
|
||||
}
|
||||
return struct {
|
||||
Error string `json:"err"`
|
||||
}{e.err.Error()}
|
||||
}
|
||||
|
||||
// With returns a copy of the error with a new embedded custom data field.
|
||||
func (e *EngineAPIError) With(err error) *EngineAPIError {
|
||||
return &EngineAPIError{
|
||||
code: e.code,
|
||||
msg: e.msg,
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
_ rpc.Error = new(EngineAPIError)
|
||||
_ rpc.DataError = new(EngineAPIError)
|
||||
)
|
||||
|
||||
var (
|
||||
// VALID is returned by the engine API in the following calls:
|
||||
// - newPayloadV1: if the payload was already known or was just validated and executed
|
||||
// - forkchoiceUpdateV1: if the chain accepted the reorg (might ignore if it's stale)
|
||||
VALID = "VALID"
|
||||
|
||||
// INVALID is returned by the engine API in the following calls:
|
||||
// - newPayloadV1: if the payload failed to execute on top of the local chain
|
||||
// - forkchoiceUpdateV1: if the new head is unknown, pre-merge, or reorg to it fails
|
||||
INVALID = "INVALID"
|
||||
|
||||
// SYNCING is returned by the engine API in the following calls:
|
||||
// - newPayloadV1: if the payload was accepted on top of an active sync
|
||||
// - forkchoiceUpdateV1: if the new head was seen before, but not part of the chain
|
||||
SYNCING = "SYNCING"
|
||||
|
||||
// ACCEPTED is returned by the engine API in the following calls:
|
||||
// - newPayloadV1: if the payload was accepted, but not processed (side chain)
|
||||
ACCEPTED = "ACCEPTED"
|
||||
|
||||
INVALIDBLOCKHASH = "INVALID_BLOCK_HASH"
|
||||
|
||||
GenericServerError = &EngineAPIError{code: -32000, msg: "Server error"}
|
||||
UnknownPayload = &EngineAPIError{code: -38001, msg: "Unknown payload"}
|
||||
InvalidForkChoiceState = &EngineAPIError{code: -38002, msg: "Invalid forkchoice state"}
|
||||
InvalidPayloadAttributes = &EngineAPIError{code: -38003, msg: "Invalid payload attributes"}
|
||||
|
||||
STATUS_INVALID = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: INVALID}, PayloadID: nil}
|
||||
STATUS_SYNCING = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: SYNCING}, PayloadID: nil}
|
||||
INVALID_TERMINAL_BLOCK = PayloadStatusV1{Status: INVALID, LatestValidHash: &common.Hash{}}
|
||||
)
|
||||
@@ -1,53 +0,0 @@
|
||||
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
|
||||
|
||||
package beacon
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
)
|
||||
|
||||
var _ = (*payloadAttributesMarshaling)(nil)
|
||||
|
||||
// MarshalJSON marshals as JSON.
|
||||
func (p PayloadAttributesV1) MarshalJSON() ([]byte, error) {
|
||||
type PayloadAttributesV1 struct {
|
||||
Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"`
|
||||
Random common.Hash `json:"prevRandao" gencodec:"required"`
|
||||
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
|
||||
}
|
||||
var enc PayloadAttributesV1
|
||||
enc.Timestamp = hexutil.Uint64(p.Timestamp)
|
||||
enc.Random = p.Random
|
||||
enc.SuggestedFeeRecipient = p.SuggestedFeeRecipient
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals from JSON.
|
||||
func (p *PayloadAttributesV1) UnmarshalJSON(input []byte) error {
|
||||
type PayloadAttributesV1 struct {
|
||||
Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"`
|
||||
Random *common.Hash `json:"prevRandao" gencodec:"required"`
|
||||
SuggestedFeeRecipient *common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
|
||||
}
|
||||
var dec PayloadAttributesV1
|
||||
if err := json.Unmarshal(input, &dec); err != nil {
|
||||
return err
|
||||
}
|
||||
if dec.Timestamp == nil {
|
||||
return errors.New("missing required field 'timestamp' for PayloadAttributesV1")
|
||||
}
|
||||
p.Timestamp = uint64(*dec.Timestamp)
|
||||
if dec.Random == nil {
|
||||
return errors.New("missing required field 'prevRandao' for PayloadAttributesV1")
|
||||
}
|
||||
p.Random = *dec.Random
|
||||
if dec.SuggestedFeeRecipient == nil {
|
||||
return errors.New("missing required field 'suggestedFeeRecipient' for PayloadAttributesV1")
|
||||
}
|
||||
p.SuggestedFeeRecipient = *dec.SuggestedFeeRecipient
|
||||
return nil
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
|
||||
|
||||
package beacon
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
)
|
||||
|
||||
var _ = (*executableDataMarshaling)(nil)
|
||||
|
||||
// MarshalJSON marshals as JSON.
|
||||
func (e ExecutableDataV1) MarshalJSON() ([]byte, error) {
|
||||
type ExecutableDataV1 struct {
|
||||
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
|
||||
FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"`
|
||||
StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
|
||||
ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"`
|
||||
LogsBloom hexutil.Bytes `json:"logsBloom" gencodec:"required"`
|
||||
Random common.Hash `json:"prevRandao" gencodec:"required"`
|
||||
Number hexutil.Uint64 `json:"blockNumber" gencodec:"required"`
|
||||
GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
|
||||
GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
|
||||
Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"`
|
||||
ExtraData hexutil.Bytes `json:"extraData" gencodec:"required"`
|
||||
BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"`
|
||||
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
|
||||
Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
|
||||
}
|
||||
var enc ExecutableDataV1
|
||||
enc.ParentHash = e.ParentHash
|
||||
enc.FeeRecipient = e.FeeRecipient
|
||||
enc.StateRoot = e.StateRoot
|
||||
enc.ReceiptsRoot = e.ReceiptsRoot
|
||||
enc.LogsBloom = e.LogsBloom
|
||||
enc.Random = e.Random
|
||||
enc.Number = hexutil.Uint64(e.Number)
|
||||
enc.GasLimit = hexutil.Uint64(e.GasLimit)
|
||||
enc.GasUsed = hexutil.Uint64(e.GasUsed)
|
||||
enc.Timestamp = hexutil.Uint64(e.Timestamp)
|
||||
enc.ExtraData = e.ExtraData
|
||||
enc.BaseFeePerGas = (*hexutil.Big)(e.BaseFeePerGas)
|
||||
enc.BlockHash = e.BlockHash
|
||||
if e.Transactions != nil {
|
||||
enc.Transactions = make([]hexutil.Bytes, len(e.Transactions))
|
||||
for k, v := range e.Transactions {
|
||||
enc.Transactions[k] = v
|
||||
}
|
||||
}
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals from JSON.
|
||||
func (e *ExecutableDataV1) UnmarshalJSON(input []byte) error {
|
||||
type ExecutableDataV1 struct {
|
||||
ParentHash *common.Hash `json:"parentHash" gencodec:"required"`
|
||||
FeeRecipient *common.Address `json:"feeRecipient" gencodec:"required"`
|
||||
StateRoot *common.Hash `json:"stateRoot" gencodec:"required"`
|
||||
ReceiptsRoot *common.Hash `json:"receiptsRoot" gencodec:"required"`
|
||||
LogsBloom *hexutil.Bytes `json:"logsBloom" gencodec:"required"`
|
||||
Random *common.Hash `json:"prevRandao" gencodec:"required"`
|
||||
Number *hexutil.Uint64 `json:"blockNumber" gencodec:"required"`
|
||||
GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
|
||||
GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
|
||||
Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"`
|
||||
ExtraData *hexutil.Bytes `json:"extraData" gencodec:"required"`
|
||||
BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"`
|
||||
BlockHash *common.Hash `json:"blockHash" gencodec:"required"`
|
||||
Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
|
||||
}
|
||||
var dec ExecutableDataV1
|
||||
if err := json.Unmarshal(input, &dec); err != nil {
|
||||
return err
|
||||
}
|
||||
if dec.ParentHash == nil {
|
||||
return errors.New("missing required field 'parentHash' for ExecutableDataV1")
|
||||
}
|
||||
e.ParentHash = *dec.ParentHash
|
||||
if dec.FeeRecipient == nil {
|
||||
return errors.New("missing required field 'feeRecipient' for ExecutableDataV1")
|
||||
}
|
||||
e.FeeRecipient = *dec.FeeRecipient
|
||||
if dec.StateRoot == nil {
|
||||
return errors.New("missing required field 'stateRoot' for ExecutableDataV1")
|
||||
}
|
||||
e.StateRoot = *dec.StateRoot
|
||||
if dec.ReceiptsRoot == nil {
|
||||
return errors.New("missing required field 'receiptsRoot' for ExecutableDataV1")
|
||||
}
|
||||
e.ReceiptsRoot = *dec.ReceiptsRoot
|
||||
if dec.LogsBloom == nil {
|
||||
return errors.New("missing required field 'logsBloom' for ExecutableDataV1")
|
||||
}
|
||||
e.LogsBloom = *dec.LogsBloom
|
||||
if dec.Random == nil {
|
||||
return errors.New("missing required field 'prevRandao' for ExecutableDataV1")
|
||||
}
|
||||
e.Random = *dec.Random
|
||||
if dec.Number == nil {
|
||||
return errors.New("missing required field 'blockNumber' for ExecutableDataV1")
|
||||
}
|
||||
e.Number = uint64(*dec.Number)
|
||||
if dec.GasLimit == nil {
|
||||
return errors.New("missing required field 'gasLimit' for ExecutableDataV1")
|
||||
}
|
||||
e.GasLimit = uint64(*dec.GasLimit)
|
||||
if dec.GasUsed == nil {
|
||||
return errors.New("missing required field 'gasUsed' for ExecutableDataV1")
|
||||
}
|
||||
e.GasUsed = uint64(*dec.GasUsed)
|
||||
if dec.Timestamp == nil {
|
||||
return errors.New("missing required field 'timestamp' for ExecutableDataV1")
|
||||
}
|
||||
e.Timestamp = uint64(*dec.Timestamp)
|
||||
if dec.ExtraData == nil {
|
||||
return errors.New("missing required field 'extraData' for ExecutableDataV1")
|
||||
}
|
||||
e.ExtraData = *dec.ExtraData
|
||||
if dec.BaseFeePerGas == nil {
|
||||
return errors.New("missing required field 'baseFeePerGas' for ExecutableDataV1")
|
||||
}
|
||||
e.BaseFeePerGas = (*big.Int)(dec.BaseFeePerGas)
|
||||
if dec.BlockHash == nil {
|
||||
return errors.New("missing required field 'blockHash' for ExecutableDataV1")
|
||||
}
|
||||
e.BlockHash = *dec.BlockHash
|
||||
if dec.Transactions == nil {
|
||||
return errors.New("missing required field 'transactions' for ExecutableDataV1")
|
||||
}
|
||||
e.Transactions = make([][]byte, len(dec.Transactions))
|
||||
for k, v := range dec.Transactions {
|
||||
e.Transactions[k] = v
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,201 +0,0 @@
|
||||
// Copyright 2022 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 beacon
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
)
|
||||
|
||||
//go:generate go run github.com/fjl/gencodec -type PayloadAttributesV1 -field-override payloadAttributesMarshaling -out gen_blockparams.go
|
||||
|
||||
// PayloadAttributesV1 structure described at https://github.com/ethereum/execution-apis/pull/74
|
||||
type PayloadAttributesV1 struct {
|
||||
Timestamp uint64 `json:"timestamp" gencodec:"required"`
|
||||
Random common.Hash `json:"prevRandao" gencodec:"required"`
|
||||
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
|
||||
}
|
||||
|
||||
// JSON type overrides for PayloadAttributesV1.
|
||||
type payloadAttributesMarshaling struct {
|
||||
Timestamp hexutil.Uint64
|
||||
}
|
||||
|
||||
//go:generate go run github.com/fjl/gencodec -type ExecutableDataV1 -field-override executableDataMarshaling -out gen_ed.go
|
||||
|
||||
// ExecutableDataV1 structure described at https://github.com/ethereum/execution-apis/tree/main/src/engine/specification.md
|
||||
type ExecutableDataV1 struct {
|
||||
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
|
||||
FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"`
|
||||
StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
|
||||
ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"`
|
||||
LogsBloom []byte `json:"logsBloom" gencodec:"required"`
|
||||
Random common.Hash `json:"prevRandao" gencodec:"required"`
|
||||
Number uint64 `json:"blockNumber" gencodec:"required"`
|
||||
GasLimit uint64 `json:"gasLimit" gencodec:"required"`
|
||||
GasUsed uint64 `json:"gasUsed" gencodec:"required"`
|
||||
Timestamp uint64 `json:"timestamp" gencodec:"required"`
|
||||
ExtraData []byte `json:"extraData" gencodec:"required"`
|
||||
BaseFeePerGas *big.Int `json:"baseFeePerGas" gencodec:"required"`
|
||||
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
|
||||
Transactions [][]byte `json:"transactions" gencodec:"required"`
|
||||
}
|
||||
|
||||
// JSON type overrides for executableData.
|
||||
type executableDataMarshaling struct {
|
||||
Number hexutil.Uint64
|
||||
GasLimit hexutil.Uint64
|
||||
GasUsed hexutil.Uint64
|
||||
Timestamp hexutil.Uint64
|
||||
BaseFeePerGas *hexutil.Big
|
||||
ExtraData hexutil.Bytes
|
||||
LogsBloom hexutil.Bytes
|
||||
Transactions []hexutil.Bytes
|
||||
}
|
||||
|
||||
type PayloadStatusV1 struct {
|
||||
Status string `json:"status"`
|
||||
LatestValidHash *common.Hash `json:"latestValidHash"`
|
||||
ValidationError *string `json:"validationError"`
|
||||
}
|
||||
|
||||
type TransitionConfigurationV1 struct {
|
||||
TerminalTotalDifficulty *hexutil.Big `json:"terminalTotalDifficulty"`
|
||||
TerminalBlockHash common.Hash `json:"terminalBlockHash"`
|
||||
TerminalBlockNumber hexutil.Uint64 `json:"terminalBlockNumber"`
|
||||
}
|
||||
|
||||
// PayloadID is an identifier of the payload build process
|
||||
type PayloadID [8]byte
|
||||
|
||||
func (b PayloadID) String() string {
|
||||
return hexutil.Encode(b[:])
|
||||
}
|
||||
|
||||
func (b PayloadID) MarshalText() ([]byte, error) {
|
||||
return hexutil.Bytes(b[:]).MarshalText()
|
||||
}
|
||||
|
||||
func (b *PayloadID) UnmarshalText(input []byte) error {
|
||||
err := hexutil.UnmarshalFixedText("PayloadID", input, b[:])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid payload id %q: %w", input, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ForkChoiceResponse struct {
|
||||
PayloadStatus PayloadStatusV1 `json:"payloadStatus"`
|
||||
PayloadID *PayloadID `json:"payloadId"`
|
||||
}
|
||||
|
||||
type ForkchoiceStateV1 struct {
|
||||
HeadBlockHash common.Hash `json:"headBlockHash"`
|
||||
SafeBlockHash common.Hash `json:"safeBlockHash"`
|
||||
FinalizedBlockHash common.Hash `json:"finalizedBlockHash"`
|
||||
}
|
||||
|
||||
func encodeTransactions(txs []*types.Transaction) [][]byte {
|
||||
var enc = make([][]byte, len(txs))
|
||||
for i, tx := range txs {
|
||||
enc[i], _ = tx.MarshalBinary()
|
||||
}
|
||||
return enc
|
||||
}
|
||||
|
||||
func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) {
|
||||
var txs = make([]*types.Transaction, len(enc))
|
||||
for i, encTx := range enc {
|
||||
var tx types.Transaction
|
||||
if err := tx.UnmarshalBinary(encTx); err != nil {
|
||||
return nil, fmt.Errorf("invalid transaction %d: %v", i, err)
|
||||
}
|
||||
txs[i] = &tx
|
||||
}
|
||||
return txs, nil
|
||||
}
|
||||
|
||||
// ExecutableDataToBlock constructs a block from executable data.
|
||||
// It verifies that the following fields:
|
||||
// len(extraData) <= 32
|
||||
// uncleHash = emptyUncleHash
|
||||
// difficulty = 0
|
||||
// and that the blockhash of the constructed block matches the parameters.
|
||||
func ExecutableDataToBlock(params ExecutableDataV1) (*types.Block, error) {
|
||||
txs, err := decodeTransactions(params.Transactions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(params.ExtraData) > 32 {
|
||||
return nil, fmt.Errorf("invalid extradata length: %v", len(params.ExtraData))
|
||||
}
|
||||
if len(params.LogsBloom) != 256 {
|
||||
return nil, fmt.Errorf("invalid logsBloom length: %v", len(params.LogsBloom))
|
||||
}
|
||||
// Check that baseFeePerGas is not negative or too big
|
||||
if params.BaseFeePerGas != nil && (params.BaseFeePerGas.Sign() == -1 || params.BaseFeePerGas.BitLen() > 256) {
|
||||
return nil, fmt.Errorf("invalid baseFeePerGas: %v", params.BaseFeePerGas)
|
||||
}
|
||||
header := &types.Header{
|
||||
ParentHash: params.ParentHash,
|
||||
UncleHash: types.EmptyUncleHash,
|
||||
Coinbase: params.FeeRecipient,
|
||||
Root: params.StateRoot,
|
||||
TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)),
|
||||
ReceiptHash: params.ReceiptsRoot,
|
||||
Bloom: types.BytesToBloom(params.LogsBloom),
|
||||
Difficulty: common.Big0,
|
||||
Number: new(big.Int).SetUint64(params.Number),
|
||||
GasLimit: params.GasLimit,
|
||||
GasUsed: params.GasUsed,
|
||||
Time: params.Timestamp,
|
||||
BaseFee: params.BaseFeePerGas,
|
||||
Extra: params.ExtraData,
|
||||
MixDigest: params.Random,
|
||||
}
|
||||
block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */)
|
||||
if block.Hash() != params.BlockHash {
|
||||
return nil, fmt.Errorf("blockhash mismatch, want %x, got %x", params.BlockHash, block.Hash())
|
||||
}
|
||||
return block, nil
|
||||
}
|
||||
|
||||
// BlockToExecutableData constructs the executableDataV1 structure by filling the
|
||||
// fields from the given block. It assumes the given block is post-merge block.
|
||||
func BlockToExecutableData(block *types.Block) *ExecutableDataV1 {
|
||||
return &ExecutableDataV1{
|
||||
BlockHash: block.Hash(),
|
||||
ParentHash: block.ParentHash(),
|
||||
FeeRecipient: block.Coinbase(),
|
||||
StateRoot: block.Root(),
|
||||
Number: block.NumberU64(),
|
||||
GasLimit: block.GasLimit(),
|
||||
GasUsed: block.GasUsed(),
|
||||
BaseFeePerGas: block.BaseFee(),
|
||||
Timestamp: block.Time(),
|
||||
ReceiptsRoot: block.ReceiptHash(),
|
||||
LogsBloom: block.Bloom().Bytes(),
|
||||
Transactions: encodeTransactions(block.Transactions()),
|
||||
Random: block.MixDigest(),
|
||||
ExtraData: block.Extra(),
|
||||
}
|
||||
}
|
||||
+10
-6
@@ -83,7 +83,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) {
|
||||
return func(i int, gen *BlockGen) {
|
||||
toaddr := common.Address{}
|
||||
data := make([]byte, nbytes)
|
||||
gas, _ := IntrinsicGas(data, nil, false, false, false)
|
||||
gas, _ := IntrinsicGas(data, nil, false, false, false, false)
|
||||
signer := types.MakeSigner(gen.config, big.NewInt(int64(i)))
|
||||
gasPrice := big.NewInt(0)
|
||||
if gen.header.BaseFee != nil {
|
||||
@@ -187,16 +187,15 @@ func benchInsertChain(b *testing.B, disk bool, gen func(int, *BlockGen)) {
|
||||
|
||||
// Generate a chain of b.N blocks using the supplied block
|
||||
// generator function.
|
||||
gspec := Genesis{
|
||||
gspec := &Genesis{
|
||||
Config: params.TestChainConfig,
|
||||
Alloc: GenesisAlloc{benchRootAddr: {Balance: benchRootFunds}},
|
||||
}
|
||||
genesis := gspec.MustCommit(db)
|
||||
chain, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, b.N, gen)
|
||||
_, chain, _ := GenerateChainWithGenesis(gspec, ethash.NewFaker(), b.N, gen)
|
||||
|
||||
// Time the insertion of the new chain.
|
||||
// State and blocks are stored in the same DB.
|
||||
chainman, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil)
|
||||
chainman, _ := NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil)
|
||||
defer chainman.Stop()
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
@@ -262,6 +261,11 @@ func makeChainForBench(db ethdb.Database, full bool, count uint64) {
|
||||
rawdb.WriteCanonicalHash(db, hash, n)
|
||||
rawdb.WriteTd(db, hash, n, big.NewInt(int64(n+1)))
|
||||
|
||||
if n == 0 {
|
||||
rawdb.WriteChainConfig(db, hash, params.AllEthashProtocolChanges)
|
||||
}
|
||||
rawdb.WriteHeadHeaderHash(db, hash)
|
||||
|
||||
if full || n == 0 {
|
||||
block := types.NewBlockWithHeader(header)
|
||||
rawdb.WriteBody(db, hash, n, block.Body())
|
||||
@@ -303,7 +307,7 @@ func benchReadChain(b *testing.B, full bool, count uint64) {
|
||||
if err != nil {
|
||||
b.Fatalf("error opening database at %v: %v", dir, err)
|
||||
}
|
||||
chain, err := NewBlockChain(db, &cacheConfig, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, nil)
|
||||
chain, err := NewBlockChain(db, &cacheConfig, nil, nil, ethash.NewFaker(), vm.Config{}, nil, nil)
|
||||
if err != nil {
|
||||
b.Fatalf("error creating chain: %v", err)
|
||||
}
|
||||
|
||||
+20
-4
@@ -50,21 +50,37 @@ func NewBlockValidator(config *params.ChainConfig, blockchain *BlockChain, engin
|
||||
// header's transaction and uncle roots. The headers are assumed to be already
|
||||
// validated at this point.
|
||||
func (v *BlockValidator) ValidateBody(block *types.Block) error {
|
||||
// Check whether the block's known, and if not, that it's linkable
|
||||
// Check whether the block is already imported.
|
||||
if v.bc.HasBlockAndState(block.Hash(), block.NumberU64()) {
|
||||
return ErrKnownBlock
|
||||
}
|
||||
// Header validity is known at this point, check the uncles and transactions
|
||||
|
||||
// Header validity is known at this point. Here we verify that uncles, transactions
|
||||
// and withdrawals given in the block body match the header.
|
||||
header := block.Header()
|
||||
if err := v.engine.VerifyUncles(v.bc, block); err != nil {
|
||||
return err
|
||||
}
|
||||
if hash := types.CalcUncleHash(block.Uncles()); hash != header.UncleHash {
|
||||
return fmt.Errorf("uncle root hash mismatch: have %x, want %x", hash, header.UncleHash)
|
||||
return fmt.Errorf("uncle root hash mismatch (header value %x, calculated %x)", header.UncleHash, hash)
|
||||
}
|
||||
if hash := types.DeriveSha(block.Transactions(), trie.NewStackTrie(nil)); hash != header.TxHash {
|
||||
return fmt.Errorf("transaction root hash mismatch: have %x, want %x", hash, header.TxHash)
|
||||
return fmt.Errorf("transaction root hash mismatch (header value %x, calculated %x)", header.TxHash, hash)
|
||||
}
|
||||
// Withdrawals are present after the Shanghai fork.
|
||||
if header.WithdrawalsHash != nil {
|
||||
// Withdrawals list must be present in body after Shanghai.
|
||||
if block.Withdrawals() == nil {
|
||||
return fmt.Errorf("missing withdrawals in block body")
|
||||
}
|
||||
if hash := types.DeriveSha(block.Withdrawals(), trie.NewStackTrie(nil)); hash != *header.WithdrawalsHash {
|
||||
return fmt.Errorf("withdrawals root hash mismatch (header value %x, calculated %x)", *header.WithdrawalsHash, hash)
|
||||
}
|
||||
} else if block.Withdrawals() != nil {
|
||||
// Withdrawals are not allowed prior to shanghai fork
|
||||
return fmt.Errorf("withdrawals present in block body")
|
||||
}
|
||||
|
||||
if !v.bc.HasBlockAndState(block.ParentHash(), block.NumberU64()-1) {
|
||||
if !v.bc.HasBlock(block.ParentHash(), block.NumberU64()-1) {
|
||||
return consensus.ErrUnknownAncestor
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math/big"
|
||||
"runtime"
|
||||
"testing"
|
||||
@@ -39,17 +38,15 @@ import (
|
||||
func TestHeaderVerification(t *testing.T) {
|
||||
// Create a simple chain to verify
|
||||
var (
|
||||
testdb = rawdb.NewMemoryDatabase()
|
||||
gspec = &Genesis{Config: params.TestChainConfig}
|
||||
genesis = gspec.MustCommit(testdb)
|
||||
blocks, _ = GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), testdb, 8, nil)
|
||||
gspec = &Genesis{Config: params.TestChainConfig}
|
||||
_, blocks, _ = GenerateChainWithGenesis(gspec, ethash.NewFaker(), 8, nil)
|
||||
)
|
||||
headers := make([]*types.Header, len(blocks))
|
||||
for i, block := range blocks {
|
||||
headers[i] = block.Header()
|
||||
}
|
||||
// Run the header checker for blocks one-by-one, checking for both valid and invalid nonces
|
||||
chain, _ := NewBlockChain(testdb, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, nil)
|
||||
chain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil)
|
||||
defer chain.Stop()
|
||||
|
||||
for i := 0; i < len(blocks); i++ {
|
||||
@@ -89,20 +86,21 @@ func TestHeaderVerificationForMergingEthash(t *testing.T) { testHeaderVerificati
|
||||
// Tests the verification for eth1/2 merging, including pre-merge and post-merge
|
||||
func testHeaderVerificationForMerging(t *testing.T, isClique bool) {
|
||||
var (
|
||||
testdb = rawdb.NewMemoryDatabase()
|
||||
preBlocks []*types.Block
|
||||
postBlocks []*types.Block
|
||||
runEngine consensus.Engine
|
||||
chainConfig *params.ChainConfig
|
||||
merger = consensus.NewMerger(rawdb.NewMemoryDatabase())
|
||||
gspec *Genesis
|
||||
preBlocks []*types.Block
|
||||
postBlocks []*types.Block
|
||||
engine consensus.Engine
|
||||
merger = consensus.NewMerger(rawdb.NewMemoryDatabase())
|
||||
)
|
||||
if isClique {
|
||||
var (
|
||||
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
addr = crypto.PubkeyToAddress(key.PublicKey)
|
||||
engine = clique.New(params.AllCliqueProtocolChanges.Clique, testdb)
|
||||
config = *params.AllCliqueProtocolChanges
|
||||
)
|
||||
genspec := &Genesis{
|
||||
engine = beacon.New(clique.New(params.AllCliqueProtocolChanges.Clique, rawdb.NewMemoryDatabase()))
|
||||
gspec = &Genesis{
|
||||
Config: &config,
|
||||
ExtraData: make([]byte, 32+common.AddressLength+crypto.SignatureLength),
|
||||
Alloc: map[common.Address]GenesisAccount{
|
||||
addr: {Balance: big.NewInt(1)},
|
||||
@@ -110,84 +108,76 @@ func testHeaderVerificationForMerging(t *testing.T, isClique bool) {
|
||||
BaseFee: big.NewInt(params.InitialBaseFee),
|
||||
Difficulty: new(big.Int),
|
||||
}
|
||||
copy(genspec.ExtraData[32:], addr[:])
|
||||
genesis := genspec.MustCommit(testdb)
|
||||
copy(gspec.ExtraData[32:], addr[:])
|
||||
|
||||
genEngine := beacon.New(engine)
|
||||
preBlocks, _ = GenerateChain(params.AllCliqueProtocolChanges, genesis, genEngine, testdb, 8, nil)
|
||||
td := 0
|
||||
for i, block := range preBlocks {
|
||||
genDb, blocks, _ := GenerateChainWithGenesis(gspec, engine, 8, nil)
|
||||
for i, block := range blocks {
|
||||
header := block.Header()
|
||||
if i > 0 {
|
||||
header.ParentHash = preBlocks[i-1].Hash()
|
||||
header.ParentHash = blocks[i-1].Hash()
|
||||
}
|
||||
header.Extra = make([]byte, 32+crypto.SignatureLength)
|
||||
header.Difficulty = big.NewInt(2)
|
||||
|
||||
sig, _ := crypto.Sign(genEngine.SealHash(header).Bytes(), key)
|
||||
sig, _ := crypto.Sign(engine.SealHash(header).Bytes(), key)
|
||||
copy(header.Extra[len(header.Extra)-crypto.SignatureLength:], sig)
|
||||
preBlocks[i] = block.WithSeal(header)
|
||||
blocks[i] = block.WithSeal(header)
|
||||
|
||||
// calculate td
|
||||
td += int(block.Difficulty().Uint64())
|
||||
}
|
||||
config := *params.AllCliqueProtocolChanges
|
||||
config.TerminalTotalDifficulty = big.NewInt(int64(td))
|
||||
postBlocks, _ = GenerateChain(&config, preBlocks[len(preBlocks)-1], genEngine, testdb, 8, nil)
|
||||
chainConfig = &config
|
||||
runEngine = beacon.New(engine)
|
||||
preBlocks = blocks
|
||||
gspec.Config.TerminalTotalDifficulty = big.NewInt(int64(td))
|
||||
postBlocks, _ = GenerateChain(gspec.Config, preBlocks[len(preBlocks)-1], engine, genDb, 8, nil)
|
||||
} else {
|
||||
gspec := &Genesis{Config: params.TestChainConfig}
|
||||
genesis := gspec.MustCommit(testdb)
|
||||
genEngine := beacon.New(ethash.NewFaker())
|
||||
|
||||
preBlocks, _ = GenerateChain(params.TestChainConfig, genesis, genEngine, testdb, 8, nil)
|
||||
td := 0
|
||||
for _, block := range preBlocks {
|
||||
config := *params.TestChainConfig
|
||||
gspec = &Genesis{Config: &config}
|
||||
engine = beacon.New(ethash.NewFaker())
|
||||
td := int(params.GenesisDifficulty.Uint64())
|
||||
genDb, blocks, _ := GenerateChainWithGenesis(gspec, engine, 8, nil)
|
||||
for _, block := range blocks {
|
||||
// calculate td
|
||||
td += int(block.Difficulty().Uint64())
|
||||
}
|
||||
config := *params.TestChainConfig
|
||||
config.TerminalTotalDifficulty = big.NewInt(int64(td))
|
||||
postBlocks, _ = GenerateChain(params.TestChainConfig, preBlocks[len(preBlocks)-1], genEngine, testdb, 8, nil)
|
||||
|
||||
chainConfig = &config
|
||||
runEngine = beacon.New(ethash.NewFaker())
|
||||
preBlocks = blocks
|
||||
gspec.Config.TerminalTotalDifficulty = big.NewInt(int64(td))
|
||||
t.Logf("Set ttd to %v\n", gspec.Config.TerminalTotalDifficulty)
|
||||
postBlocks, _ = GenerateChain(gspec.Config, preBlocks[len(preBlocks)-1], engine, genDb, 8, func(i int, gen *BlockGen) {
|
||||
gen.SetPoS()
|
||||
})
|
||||
}
|
||||
|
||||
// Assemble header batch
|
||||
preHeaders := make([]*types.Header, len(preBlocks))
|
||||
for i, block := range preBlocks {
|
||||
preHeaders[i] = block.Header()
|
||||
|
||||
blob, _ := json.Marshal(block.Header())
|
||||
t.Logf("Log header before the merging %d: %v", block.NumberU64(), string(blob))
|
||||
t.Logf("Pre-merge header: %d", block.NumberU64())
|
||||
}
|
||||
postHeaders := make([]*types.Header, len(postBlocks))
|
||||
for i, block := range postBlocks {
|
||||
postHeaders[i] = block.Header()
|
||||
|
||||
blob, _ := json.Marshal(block.Header())
|
||||
t.Logf("Log header after the merging %d: %v", block.NumberU64(), string(blob))
|
||||
t.Logf("Post-merge header: %d", block.NumberU64())
|
||||
}
|
||||
// Run the header checker for blocks one-by-one, checking for both valid and invalid nonces
|
||||
chain, _ := NewBlockChain(testdb, nil, chainConfig, runEngine, vm.Config{}, nil, nil)
|
||||
chain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{}, nil, nil)
|
||||
defer chain.Stop()
|
||||
|
||||
// Verify the blocks before the merging
|
||||
for i := 0; i < len(preBlocks); i++ {
|
||||
_, results := runEngine.VerifyHeaders(chain, []*types.Header{preHeaders[i]}, []bool{true})
|
||||
_, results := engine.VerifyHeaders(chain, []*types.Header{preHeaders[i]}, []bool{true})
|
||||
// Wait for the verification result
|
||||
select {
|
||||
case result := <-results:
|
||||
if result != nil {
|
||||
t.Errorf("test %d: verification failed %v", i, result)
|
||||
t.Errorf("pre-block %d: verification failed %v", i, result)
|
||||
}
|
||||
case <-time.After(time.Second):
|
||||
t.Fatalf("test %d: verification timeout", i)
|
||||
t.Fatalf("pre-block %d: verification timeout", i)
|
||||
}
|
||||
// Make sure no more data is returned
|
||||
select {
|
||||
case result := <-results:
|
||||
t.Fatalf("test %d: unexpected result returned: %v", i, result)
|
||||
t.Fatalf("pre-block %d: unexpected result returned: %v", i, result)
|
||||
case <-time.After(25 * time.Millisecond):
|
||||
}
|
||||
chain.InsertChain(preBlocks[i : i+1])
|
||||
@@ -199,12 +189,12 @@ func testHeaderVerificationForMerging(t *testing.T, isClique bool) {
|
||||
|
||||
// Verify the blocks after the merging
|
||||
for i := 0; i < len(postBlocks); i++ {
|
||||
_, results := runEngine.VerifyHeaders(chain, []*types.Header{postHeaders[i]}, []bool{true})
|
||||
_, results := engine.VerifyHeaders(chain, []*types.Header{postHeaders[i]}, []bool{true})
|
||||
// Wait for the verification result
|
||||
select {
|
||||
case result := <-results:
|
||||
if result != nil {
|
||||
t.Errorf("test %d: verification failed %v", i, result)
|
||||
t.Errorf("post-block %d: verification failed %v", i, result)
|
||||
}
|
||||
case <-time.After(time.Second):
|
||||
t.Fatalf("test %d: verification timeout", i)
|
||||
@@ -212,7 +202,7 @@ func testHeaderVerificationForMerging(t *testing.T, isClique bool) {
|
||||
// Make sure no more data is returned
|
||||
select {
|
||||
case result := <-results:
|
||||
t.Fatalf("test %d: unexpected result returned: %v", i, result)
|
||||
t.Fatalf("post-block %d: unexpected result returned: %v", i, result)
|
||||
case <-time.After(25 * time.Millisecond):
|
||||
}
|
||||
chain.InsertBlockWithoutSetHead(postBlocks[i])
|
||||
@@ -231,7 +221,7 @@ func testHeaderVerificationForMerging(t *testing.T, isClique bool) {
|
||||
headers = append(headers, block.Header())
|
||||
seals = append(seals, true)
|
||||
}
|
||||
_, results := runEngine.VerifyHeaders(chain, headers, seals)
|
||||
_, results := engine.VerifyHeaders(chain, headers, seals)
|
||||
for i := 0; i < len(headers); i++ {
|
||||
select {
|
||||
case result := <-results:
|
||||
@@ -258,10 +248,8 @@ func TestHeaderConcurrentVerification32(t *testing.T) { testHeaderConcurrentVeri
|
||||
func testHeaderConcurrentVerification(t *testing.T, threads int) {
|
||||
// Create a simple chain to verify
|
||||
var (
|
||||
testdb = rawdb.NewMemoryDatabase()
|
||||
gspec = &Genesis{Config: params.TestChainConfig}
|
||||
genesis = gspec.MustCommit(testdb)
|
||||
blocks, _ = GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), testdb, 8, nil)
|
||||
gspec = &Genesis{Config: params.TestChainConfig}
|
||||
_, blocks, _ = GenerateChainWithGenesis(gspec, ethash.NewFaker(), 8, nil)
|
||||
)
|
||||
headers := make([]*types.Header, len(blocks))
|
||||
seals := make([]bool, len(blocks))
|
||||
@@ -280,11 +268,11 @@ func testHeaderConcurrentVerification(t *testing.T, threads int) {
|
||||
var results <-chan error
|
||||
|
||||
if valid {
|
||||
chain, _ := NewBlockChain(testdb, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, nil)
|
||||
chain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil)
|
||||
_, results = chain.engine.VerifyHeaders(chain, headers, seals)
|
||||
chain.Stop()
|
||||
} else {
|
||||
chain, _ := NewBlockChain(testdb, nil, params.TestChainConfig, ethash.NewFakeFailer(uint64(len(headers)-1)), vm.Config{}, nil, nil)
|
||||
chain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, ethash.NewFakeFailer(uint64(len(headers)-1)), vm.Config{}, nil, nil)
|
||||
_, results = chain.engine.VerifyHeaders(chain, headers, seals)
|
||||
chain.Stop()
|
||||
}
|
||||
@@ -330,10 +318,8 @@ func TestHeaderConcurrentAbortion32(t *testing.T) { testHeaderConcurrentAbortion
|
||||
func testHeaderConcurrentAbortion(t *testing.T, threads int) {
|
||||
// Create a simple chain to verify
|
||||
var (
|
||||
testdb = rawdb.NewMemoryDatabase()
|
||||
gspec = &Genesis{Config: params.TestChainConfig}
|
||||
genesis = gspec.MustCommit(testdb)
|
||||
blocks, _ = GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), testdb, 1024, nil)
|
||||
gspec = &Genesis{Config: params.TestChainConfig}
|
||||
_, blocks, _ = GenerateChainWithGenesis(gspec, ethash.NewFaker(), 1024, nil)
|
||||
)
|
||||
headers := make([]*types.Header, len(blocks))
|
||||
seals := make([]bool, len(blocks))
|
||||
@@ -347,7 +333,7 @@ func testHeaderConcurrentAbortion(t *testing.T, threads int) {
|
||||
defer runtime.GOMAXPROCS(old)
|
||||
|
||||
// Start the verifications and immediately abort
|
||||
chain, _ := NewBlockChain(testdb, nil, params.TestChainConfig, ethash.NewFakeDelayer(time.Millisecond), vm.Config{}, nil, nil)
|
||||
chain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, ethash.NewFakeDelayer(time.Millisecond), vm.Config{}, nil, nil)
|
||||
defer chain.Stop()
|
||||
|
||||
abort, results := chain.engine.VerifyHeaders(chain, headers, seals)
|
||||
|
||||
+322
-269
@@ -22,12 +22,15 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/lru"
|
||||
"github.com/ethereum/go-ethereum/common/mclock"
|
||||
"github.com/ethereum/go-ethereum/common/prque"
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
@@ -39,11 +42,12 @@ import (
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/internal/syncx"
|
||||
"github.com/ethereum/go-ethereum/internal/version"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -67,15 +71,16 @@ var (
|
||||
snapshotStorageReadTimer = metrics.NewRegisteredTimer("chain/snapshot/storage/reads", nil)
|
||||
snapshotCommitTimer = metrics.NewRegisteredTimer("chain/snapshot/commits", nil)
|
||||
|
||||
triedbCommitTimer = metrics.NewRegisteredTimer("chain/triedb/commits", nil)
|
||||
|
||||
blockInsertTimer = metrics.NewRegisteredTimer("chain/inserts", nil)
|
||||
blockValidationTimer = metrics.NewRegisteredTimer("chain/validation", nil)
|
||||
blockExecutionTimer = metrics.NewRegisteredTimer("chain/execution", nil)
|
||||
blockWriteTimer = metrics.NewRegisteredTimer("chain/write", nil)
|
||||
|
||||
blockReorgMeter = metrics.NewRegisteredMeter("chain/reorg/executes", nil)
|
||||
blockReorgAddMeter = metrics.NewRegisteredMeter("chain/reorg/add", nil)
|
||||
blockReorgDropMeter = metrics.NewRegisteredMeter("chain/reorg/drop", nil)
|
||||
blockReorgInvalidatedTx = metrics.NewRegisteredMeter("chain/reorg/invalidTx", nil)
|
||||
blockReorgMeter = metrics.NewRegisteredMeter("chain/reorg/executes", nil)
|
||||
blockReorgAddMeter = metrics.NewRegisteredMeter("chain/reorg/add", nil)
|
||||
blockReorgDropMeter = metrics.NewRegisteredMeter("chain/reorg/drop", nil)
|
||||
|
||||
blockPrefetchExecuteTimer = metrics.NewRegisteredTimer("chain/prefetch/executes", nil)
|
||||
blockPrefetchInterruptMeter = metrics.NewRegisteredMeter("chain/prefetch/interrupts", nil)
|
||||
@@ -119,7 +124,7 @@ const (
|
||||
BlockChainVersion uint64 = 8
|
||||
)
|
||||
|
||||
// CacheConfig contains the configuration values for the trie caching/pruning
|
||||
// CacheConfig contains the configuration values for the trie database
|
||||
// that's resident in a blockchain.
|
||||
type CacheConfig struct {
|
||||
TrieCleanLimit int // Memory allowance (MB) to use for caching trie nodes in memory
|
||||
@@ -132,7 +137,8 @@ type CacheConfig struct {
|
||||
SnapshotLimit int // Memory allowance (MB) to use for caching snapshot entries in memory
|
||||
Preimages bool // Whether to store preimage of trie key to the disk
|
||||
|
||||
SnapshotWait bool // Wait for snapshot construction on startup. TODO(karalabe): This is a dirty hack for testing, nuke it
|
||||
SnapshotNoBuild bool // Whether the background generation is allowed
|
||||
SnapshotWait bool // Wait for snapshot construction on startup. TODO(karalabe): This is a dirty hack for testing, nuke it
|
||||
}
|
||||
|
||||
// defaultCacheConfig are the default caching values if none are specified by the
|
||||
@@ -163,10 +169,14 @@ type BlockChain struct {
|
||||
chainConfig *params.ChainConfig // Chain & network configuration
|
||||
cacheConfig *CacheConfig // Cache configuration for pruning
|
||||
|
||||
db ethdb.Database // Low level persistent database to store final content in
|
||||
snaps *snapshot.Tree // Snapshot tree for fast trie leaf access
|
||||
triegc *prque.Prque // Priority queue mapping block numbers to tries to gc
|
||||
gcproc time.Duration // Accumulates canonical block processing for trie dumping
|
||||
db ethdb.Database // Low level persistent database to store final content in
|
||||
snaps *snapshot.Tree // Snapshot tree for fast trie leaf access
|
||||
triegc *prque.Prque[int64, common.Hash] // Priority queue mapping block numbers to tries to gc
|
||||
gcproc time.Duration // Accumulates canonical block processing for trie dumping
|
||||
lastWrite uint64 // Last block when the state was flushed
|
||||
flushInterval int64 // Time interval (processing time) after which to flush a state
|
||||
triedb *trie.Database // The database handler for maintaining trie nodes.
|
||||
stateCache state.Database // State database to reuse between imports (contains state cache)
|
||||
|
||||
// txLookupLimit is the maximum number of blocks from head whose tx indices
|
||||
// are reserved:
|
||||
@@ -194,13 +204,14 @@ type BlockChain struct {
|
||||
currentFinalizedBlock atomic.Value // Current finalized head
|
||||
currentSafeBlock atomic.Value // Current safe head
|
||||
|
||||
stateCache state.Database // State database to reuse between imports (contains state cache)
|
||||
bodyCache *lru.Cache // Cache for the most recent block bodies
|
||||
bodyRLPCache *lru.Cache // Cache for the most recent block bodies in RLP encoded format
|
||||
receiptsCache *lru.Cache // Cache for the most recent receipts per block
|
||||
blockCache *lru.Cache // Cache for the most recent entire blocks
|
||||
txLookupCache *lru.Cache // Cache for the most recent transaction lookup data.
|
||||
futureBlocks *lru.Cache // future blocks are blocks added for later processing
|
||||
bodyCache *lru.Cache[common.Hash, *types.Body]
|
||||
bodyRLPCache *lru.Cache[common.Hash, rlp.RawValue]
|
||||
receiptsCache *lru.Cache[common.Hash, []*types.Receipt]
|
||||
blockCache *lru.Cache[common.Hash, *types.Block]
|
||||
txLookupCache *lru.Cache[common.Hash, *rawdb.LegacyTxLookupEntry]
|
||||
|
||||
// future blocks are blocks added for later processing
|
||||
futureBlocks *lru.Cache[common.Hash, *types.Block]
|
||||
|
||||
wg sync.WaitGroup //
|
||||
quit chan struct{} // shutdown signal, closed in Stop.
|
||||
@@ -218,39 +229,52 @@ type BlockChain struct {
|
||||
// NewBlockChain returns a fully initialised block chain using information
|
||||
// available in the database. It initialises the default Ethereum Validator
|
||||
// and Processor.
|
||||
func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *params.ChainConfig, engine consensus.Engine, vmConfig vm.Config, shouldPreserve func(header *types.Header) bool, txLookupLimit *uint64) (*BlockChain, error) {
|
||||
func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis, overrides *ChainOverrides, engine consensus.Engine, vmConfig vm.Config, shouldPreserve func(header *types.Header) bool, txLookupLimit *uint64) (*BlockChain, error) {
|
||||
if cacheConfig == nil {
|
||||
cacheConfig = defaultCacheConfig
|
||||
}
|
||||
bodyCache, _ := lru.New(bodyCacheLimit)
|
||||
bodyRLPCache, _ := lru.New(bodyCacheLimit)
|
||||
receiptsCache, _ := lru.New(receiptsCacheLimit)
|
||||
blockCache, _ := lru.New(blockCacheLimit)
|
||||
txLookupCache, _ := lru.New(txLookupCacheLimit)
|
||||
futureBlocks, _ := lru.New(maxFutureBlocks)
|
||||
|
||||
// Open trie database with provided config
|
||||
triedb := trie.NewDatabaseWithConfig(db, &trie.Config{
|
||||
Cache: cacheConfig.TrieCleanLimit,
|
||||
Journal: cacheConfig.TrieCleanJournal,
|
||||
Preimages: cacheConfig.Preimages,
|
||||
})
|
||||
// Setup the genesis block, commit the provided genesis specification
|
||||
// to database if the genesis block is not present yet, or load the
|
||||
// stored one from database.
|
||||
chainConfig, genesisHash, genesisErr := SetupGenesisBlockWithOverride(db, triedb, genesis, overrides)
|
||||
if _, ok := genesisErr.(*params.ConfigCompatError); genesisErr != nil && !ok {
|
||||
return nil, genesisErr
|
||||
}
|
||||
log.Info("")
|
||||
log.Info(strings.Repeat("-", 153))
|
||||
for _, line := range strings.Split(chainConfig.Description(), "\n") {
|
||||
log.Info(line)
|
||||
}
|
||||
log.Info(strings.Repeat("-", 153))
|
||||
log.Info("")
|
||||
|
||||
bc := &BlockChain{
|
||||
chainConfig: chainConfig,
|
||||
cacheConfig: cacheConfig,
|
||||
db: db,
|
||||
triegc: prque.New(nil),
|
||||
stateCache: state.NewDatabaseWithConfig(db, &trie.Config{
|
||||
Cache: cacheConfig.TrieCleanLimit,
|
||||
Journal: cacheConfig.TrieCleanJournal,
|
||||
Preimages: cacheConfig.Preimages,
|
||||
}),
|
||||
chainConfig: chainConfig,
|
||||
cacheConfig: cacheConfig,
|
||||
db: db,
|
||||
triedb: triedb,
|
||||
flushInterval: int64(cacheConfig.TrieTimeLimit),
|
||||
triegc: prque.New[int64, common.Hash](nil),
|
||||
quit: make(chan struct{}),
|
||||
chainmu: syncx.NewClosableMutex(),
|
||||
bodyCache: bodyCache,
|
||||
bodyRLPCache: bodyRLPCache,
|
||||
receiptsCache: receiptsCache,
|
||||
blockCache: blockCache,
|
||||
txLookupCache: txLookupCache,
|
||||
futureBlocks: futureBlocks,
|
||||
bodyCache: lru.NewCache[common.Hash, *types.Body](bodyCacheLimit),
|
||||
bodyRLPCache: lru.NewCache[common.Hash, rlp.RawValue](bodyCacheLimit),
|
||||
receiptsCache: lru.NewCache[common.Hash, []*types.Receipt](receiptsCacheLimit),
|
||||
blockCache: lru.NewCache[common.Hash, *types.Block](blockCacheLimit),
|
||||
txLookupCache: lru.NewCache[common.Hash, *rawdb.LegacyTxLookupEntry](txLookupCacheLimit),
|
||||
futureBlocks: lru.NewCache[common.Hash, *types.Block](maxFutureBlocks),
|
||||
engine: engine,
|
||||
vmConfig: vmConfig,
|
||||
}
|
||||
bc.forker = NewForkChoice(bc, shouldPreserve)
|
||||
bc.stateCache = state.NewDatabaseWithNodeDB(bc.db, bc.triedb)
|
||||
bc.validator = NewBlockValidator(chainConfig, bc, engine)
|
||||
bc.prefetcher = newStatePrefetcher(chainConfig, bc, engine)
|
||||
bc.processor = NewStateProcessor(chainConfig, bc, engine)
|
||||
@@ -271,25 +295,19 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
|
||||
bc.currentFinalizedBlock.Store(nilBlock)
|
||||
bc.currentSafeBlock.Store(nilBlock)
|
||||
|
||||
// Initialize the chain with ancient data if it isn't empty.
|
||||
var txIndexBlock uint64
|
||||
|
||||
// If Geth is initialized with an external ancient store, re-initialize the
|
||||
// missing chain indexes and chain flags. This procedure can survive crash
|
||||
// and can be resumed in next restart since chain flags are updated in last step.
|
||||
if bc.empty() {
|
||||
rawdb.InitDatabaseFromFreezer(bc.db)
|
||||
// If ancient database is not empty, reconstruct all missing
|
||||
// indices in the background.
|
||||
frozen, _ := bc.db.Ancients()
|
||||
if frozen > 0 {
|
||||
txIndexBlock = frozen
|
||||
}
|
||||
}
|
||||
// Load blockchain states from disk
|
||||
if err := bc.loadLastState(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Make sure the state associated with the block is available
|
||||
head := bc.CurrentBlock()
|
||||
if _, err := state.New(head.Root(), bc.stateCache, bc.snaps); err != nil {
|
||||
if !bc.HasState(head.Root()) {
|
||||
// Head state is missing, before the state recovery, find out the
|
||||
// disk layer point of snapshot(if it's enabled). Make sure the
|
||||
// rewound point is lower than disk layer.
|
||||
@@ -300,7 +318,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
|
||||
if diskRoot != (common.Hash{}) {
|
||||
log.Warn("Head state missing, repairing", "number", head.Number(), "hash", head.Hash(), "snaproot", diskRoot)
|
||||
|
||||
snapDisk, err := bc.setHeadBeyondRoot(head.NumberU64(), diskRoot, true)
|
||||
snapDisk, err := bc.setHeadBeyondRoot(head.NumberU64(), 0, diskRoot, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -310,7 +328,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
|
||||
}
|
||||
} else {
|
||||
log.Warn("Head state missing, repairing", "number", head.Number(), "hash", head.Hash())
|
||||
if _, err := bc.setHeadBeyondRoot(head.NumberU64(), common.Hash{}, true); err != nil {
|
||||
if _, err := bc.setHeadBeyondRoot(head.NumberU64(), 0, common.Hash{}, true); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@@ -377,38 +395,52 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
|
||||
var recover bool
|
||||
|
||||
head := bc.CurrentBlock()
|
||||
if layer := rawdb.ReadSnapshotRecoveryNumber(bc.db); layer != nil && *layer > head.NumberU64() {
|
||||
if layer := rawdb.ReadSnapshotRecoveryNumber(bc.db); layer != nil && *layer >= head.NumberU64() {
|
||||
log.Warn("Enabling snapshot recovery", "chainhead", head.NumberU64(), "diskbase", *layer)
|
||||
recover = true
|
||||
}
|
||||
bc.snaps, _ = snapshot.New(bc.db, bc.stateCache.TrieDB(), bc.cacheConfig.SnapshotLimit, head.Root(), !bc.cacheConfig.SnapshotWait, true, recover)
|
||||
snapconfig := snapshot.Config{
|
||||
CacheSize: bc.cacheConfig.SnapshotLimit,
|
||||
Recovery: recover,
|
||||
NoBuild: bc.cacheConfig.SnapshotNoBuild,
|
||||
AsyncBuild: !bc.cacheConfig.SnapshotWait,
|
||||
}
|
||||
bc.snaps, _ = snapshot.New(snapconfig, bc.db, bc.triedb, head.Root())
|
||||
}
|
||||
|
||||
// Start future block processor.
|
||||
bc.wg.Add(1)
|
||||
go bc.updateFutureBlocks()
|
||||
|
||||
// Start tx indexer/unindexer.
|
||||
if txLookupLimit != nil {
|
||||
bc.txLookupLimit = *txLookupLimit
|
||||
|
||||
bc.wg.Add(1)
|
||||
go bc.maintainTxIndex(txIndexBlock)
|
||||
}
|
||||
|
||||
// If periodic cache journal is required, spin it up.
|
||||
if bc.cacheConfig.TrieCleanRejournal > 0 {
|
||||
if bc.cacheConfig.TrieCleanRejournal < time.Minute {
|
||||
log.Warn("Sanitizing invalid trie cache journal time", "provided", bc.cacheConfig.TrieCleanRejournal, "updated", time.Minute)
|
||||
bc.cacheConfig.TrieCleanRejournal = time.Minute
|
||||
}
|
||||
triedb := bc.stateCache.TrieDB()
|
||||
bc.wg.Add(1)
|
||||
go func() {
|
||||
defer bc.wg.Done()
|
||||
triedb.SaveCachePeriodically(bc.cacheConfig.TrieCleanJournal, bc.cacheConfig.TrieCleanRejournal, bc.quit)
|
||||
bc.triedb.SaveCachePeriodically(bc.cacheConfig.TrieCleanJournal, bc.cacheConfig.TrieCleanRejournal, bc.quit)
|
||||
}()
|
||||
}
|
||||
// Rewind the chain in case of an incompatible config upgrade.
|
||||
if compat, ok := genesisErr.(*params.ConfigCompatError); ok {
|
||||
log.Warn("Rewinding chain to upgrade configuration", "err", compat)
|
||||
if compat.RewindToTime > 0 {
|
||||
bc.SetHeadWithTimestamp(compat.RewindToTime)
|
||||
} else {
|
||||
bc.SetHead(compat.RewindToBlock)
|
||||
}
|
||||
rawdb.WriteChainConfig(db, genesisHash, chainConfig)
|
||||
}
|
||||
// Start tx indexer/unindexer if required.
|
||||
if txLookupLimit != nil {
|
||||
bc.txLookupLimit = *txLookupLimit
|
||||
|
||||
bc.wg.Add(1)
|
||||
go bc.maintainTxIndex()
|
||||
}
|
||||
return bc, nil
|
||||
}
|
||||
|
||||
@@ -504,8 +536,25 @@ func (bc *BlockChain) loadLastState() error {
|
||||
// was fast synced or full synced and in which state, the method will try to
|
||||
// delete minimal data from disk whilst retaining chain consistency.
|
||||
func (bc *BlockChain) SetHead(head uint64) error {
|
||||
_, err := bc.setHeadBeyondRoot(head, common.Hash{}, false)
|
||||
return err
|
||||
if _, err := bc.setHeadBeyondRoot(head, 0, common.Hash{}, false); err != nil {
|
||||
return err
|
||||
}
|
||||
// Send chain head event to update the transaction pool
|
||||
bc.chainHeadFeed.Send(ChainHeadEvent{Block: bc.CurrentBlock()})
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetHeadWithTimestamp rewinds the local chain to a new head that has at max
|
||||
// the given timestamp. Depending on whether the node was fast synced or full
|
||||
// synced and in which state, the method will try to delete minimal data from
|
||||
// disk whilst retaining chain consistency.
|
||||
func (bc *BlockChain) SetHeadWithTimestamp(timestamp uint64) error {
|
||||
if _, err := bc.setHeadBeyondRoot(0, timestamp, common.Hash{}, false); err != nil {
|
||||
return err
|
||||
}
|
||||
// Send chain head event to update the transaction pool
|
||||
bc.chainHeadFeed.Send(ChainHeadEvent{Block: bc.CurrentBlock()})
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetFinalized sets the finalized block.
|
||||
@@ -537,8 +586,12 @@ func (bc *BlockChain) SetSafe(block *types.Block) {
|
||||
// in which state, the method will try to delete minimal data from disk whilst
|
||||
// retaining chain consistency.
|
||||
//
|
||||
// The method also works in timestamp mode if `head == 0` but `time != 0`. In that
|
||||
// case blocks are rolled back until the new head becomes older or equal to the
|
||||
// requested time. If both `head` and `time` is 0, the chain is rewound to genesis.
|
||||
//
|
||||
// The method returns the block number where the requested root cap was found.
|
||||
func (bc *BlockChain) setHeadBeyondRoot(head uint64, root common.Hash, repair bool) (uint64, error) {
|
||||
func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Hash, repair bool) (uint64, error) {
|
||||
if !bc.chainmu.TryLock() {
|
||||
return 0, errChainStopped
|
||||
}
|
||||
@@ -552,7 +605,7 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, root common.Hash, repair bo
|
||||
pivot := rawdb.ReadLastPivotNumber(bc.db)
|
||||
frozen, _ := bc.db.Ancients()
|
||||
|
||||
updateFn := func(db ethdb.KeyValueWriter, header *types.Header) (uint64, bool) {
|
||||
updateFn := func(db ethdb.KeyValueWriter, header *types.Header) (*types.Header, bool) {
|
||||
// Rewind the blockchain, ensuring we don't end up with a stateless head
|
||||
// block. Note, depth equality is permitted to allow using SetHead as a
|
||||
// chain reparation mechanism without deleting any data!
|
||||
@@ -572,7 +625,7 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, root common.Hash, repair bo
|
||||
if root != (common.Hash{}) && !beyondRoot && newHeadBlock.Root() == root {
|
||||
beyondRoot, rootNumber = true, newHeadBlock.NumberU64()
|
||||
}
|
||||
if _, err := state.New(newHeadBlock.Root(), bc.stateCache, bc.snaps); err != nil {
|
||||
if !bc.HasState(newHeadBlock.Root()) {
|
||||
log.Trace("Block state missing, rewinding further", "number", newHeadBlock.NumberU64(), "hash", newHeadBlock.Hash())
|
||||
if pivot == nil || newHeadBlock.NumberU64() > *pivot {
|
||||
parent := bc.GetBlock(newHeadBlock.ParentHash(), newHeadBlock.NumberU64()-1)
|
||||
@@ -595,7 +648,7 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, root common.Hash, repair bo
|
||||
// if the historical chain pruning is enabled. In that case the logic
|
||||
// needs to be improved here.
|
||||
if !bc.HasState(bc.genesisBlock.Root()) {
|
||||
if err := CommitGenesisState(bc.db, bc.genesisBlock.Hash()); err != nil {
|
||||
if err := CommitGenesisState(bc.db, bc.triedb, bc.genesisBlock.Hash()); err != nil {
|
||||
log.Crit("Failed to commit genesis state", "err", err)
|
||||
}
|
||||
log.Debug("Recommitted genesis state to disk")
|
||||
@@ -633,16 +686,18 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, root common.Hash, repair bo
|
||||
bc.currentFastBlock.Store(newHeadFastBlock)
|
||||
headFastBlockGauge.Update(int64(newHeadFastBlock.NumberU64()))
|
||||
}
|
||||
head := bc.CurrentBlock().NumberU64()
|
||||
|
||||
var (
|
||||
headHeader = bc.CurrentBlock().Header()
|
||||
headNumber = headHeader.Number.Uint64()
|
||||
)
|
||||
// If setHead underflown the freezer threshold and the block processing
|
||||
// intent afterwards is full block importing, delete the chain segment
|
||||
// between the stateful-block and the sethead target.
|
||||
var wipe bool
|
||||
if head+1 < frozen {
|
||||
wipe = pivot == nil || head >= *pivot
|
||||
if headNumber+1 < frozen {
|
||||
wipe = pivot == nil || headNumber >= *pivot
|
||||
}
|
||||
return head, wipe // Only force wipe if full synced
|
||||
return headHeader, wipe // Only force wipe if full synced
|
||||
}
|
||||
// Rewind the header chain, deleting all block bodies until then
|
||||
delFn := func(db ethdb.KeyValueWriter, hash common.Hash, num uint64) {
|
||||
@@ -669,13 +724,18 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, root common.Hash, repair bo
|
||||
// touching the header chain altogether, unless the freezer is broken
|
||||
if repair {
|
||||
if target, force := updateFn(bc.db, bc.CurrentBlock().Header()); force {
|
||||
bc.hc.SetHead(target, updateFn, delFn)
|
||||
bc.hc.SetHead(target.Number.Uint64(), updateFn, delFn)
|
||||
}
|
||||
} else {
|
||||
// Rewind the chain to the requested head and keep going backwards until a
|
||||
// block with a state is found or fast sync pivot is passed
|
||||
log.Warn("Rewinding blockchain", "target", head)
|
||||
bc.hc.SetHead(head, updateFn, delFn)
|
||||
if time > 0 {
|
||||
log.Warn("Rewinding blockchain to timestamp", "target", time)
|
||||
bc.hc.SetHeadWithTimestamp(time, updateFn, delFn)
|
||||
} else {
|
||||
log.Warn("Rewinding blockchain to block", "target", head)
|
||||
bc.hc.SetHead(head, updateFn, delFn)
|
||||
}
|
||||
}
|
||||
// Clear out any stale content from the caches
|
||||
bc.bodyCache.Purge()
|
||||
@@ -706,10 +766,10 @@ func (bc *BlockChain) SnapSyncCommitHead(hash common.Hash) error {
|
||||
if block == nil {
|
||||
return fmt.Errorf("non existent block [%x..]", hash[:4])
|
||||
}
|
||||
if _, err := trie.NewStateTrie(common.Hash{}, block.Root(), bc.stateCache.TrieDB()); err != nil {
|
||||
return err
|
||||
root := block.Root()
|
||||
if !bc.HasState(root) {
|
||||
return fmt.Errorf("non existent state [%x..]", root[:4])
|
||||
}
|
||||
|
||||
// If all checks out, manually set the head block.
|
||||
if !bc.chainmu.TryLock() {
|
||||
return errChainStopped
|
||||
@@ -721,7 +781,7 @@ func (bc *BlockChain) SnapSyncCommitHead(hash common.Hash) error {
|
||||
// Destroy any existing state snapshot and regenerate it in the background,
|
||||
// also resuming the normal maintenance of any previously paused snapshot.
|
||||
if bc.snaps != nil {
|
||||
bc.snaps.Rebuild(block.Root())
|
||||
bc.snaps.Rebuild(root)
|
||||
}
|
||||
log.Info("Committed new head block", "number", block.Number(), "hash", hash)
|
||||
return nil
|
||||
@@ -830,9 +890,13 @@ func (bc *BlockChain) writeHeadBlock(block *types.Block) {
|
||||
headBlockGauge.Update(int64(block.NumberU64()))
|
||||
}
|
||||
|
||||
// Stop stops the blockchain service. If any imports are currently in progress
|
||||
// it will abort them using the procInterrupt.
|
||||
func (bc *BlockChain) Stop() {
|
||||
// stop stops the blockchain service. If any imports are currently in progress
|
||||
// it will abort them using the procInterrupt. This method stops all running
|
||||
// goroutines, but does not do all the post-stop work of persisting data.
|
||||
// OBS! It is generally recommended to use the Stop method!
|
||||
// This method has been exposed to allow tests to stop the blockchain while simulating
|
||||
// a crash.
|
||||
func (bc *BlockChain) stopWithoutSaving() {
|
||||
if !atomic.CompareAndSwapInt32(&bc.running, 0, 1) {
|
||||
return
|
||||
}
|
||||
@@ -852,6 +916,12 @@ func (bc *BlockChain) Stop() {
|
||||
// returned.
|
||||
bc.chainmu.Close()
|
||||
bc.wg.Wait()
|
||||
}
|
||||
|
||||
// Stop stops the blockchain service. If any imports are currently in progress
|
||||
// it will abort them using the procInterrupt.
|
||||
func (bc *BlockChain) Stop() {
|
||||
bc.stopWithoutSaving()
|
||||
|
||||
// Ensure that the entirety of the state snapshot is journalled to disk.
|
||||
var snapBase common.Hash
|
||||
@@ -868,26 +938,26 @@ func (bc *BlockChain) Stop() {
|
||||
// - HEAD-1: So we don't do large reorgs if our HEAD becomes an uncle
|
||||
// - HEAD-127: So we have a hard limit on the number of blocks reexecuted
|
||||
if !bc.cacheConfig.TrieDirtyDisabled {
|
||||
triedb := bc.stateCache.TrieDB()
|
||||
triedb := bc.triedb
|
||||
|
||||
for _, offset := range []uint64{0, 1, TriesInMemory - 1} {
|
||||
if number := bc.CurrentBlock().NumberU64(); number > offset {
|
||||
recent := bc.GetBlockByNumber(number - offset)
|
||||
|
||||
log.Info("Writing cached state to disk", "block", recent.Number(), "hash", recent.Hash(), "root", recent.Root())
|
||||
if err := triedb.Commit(recent.Root(), true, nil); err != nil {
|
||||
if err := triedb.Commit(recent.Root(), true); err != nil {
|
||||
log.Error("Failed to commit recent state trie", "err", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if snapBase != (common.Hash{}) {
|
||||
log.Info("Writing snapshot state to disk", "root", snapBase)
|
||||
if err := triedb.Commit(snapBase, true, nil); err != nil {
|
||||
if err := triedb.Commit(snapBase, true); err != nil {
|
||||
log.Error("Failed to commit recent state trie", "err", err)
|
||||
}
|
||||
}
|
||||
for !bc.triegc.Empty() {
|
||||
triedb.Dereference(bc.triegc.PopItem().(common.Hash))
|
||||
triedb.Dereference(bc.triegc.PopItem())
|
||||
}
|
||||
if size, _ := triedb.Size(); size != 0 {
|
||||
log.Error("Dangling trie nodes after full cleanup")
|
||||
@@ -900,8 +970,7 @@ func (bc *BlockChain) Stop() {
|
||||
// Ensure all live cached entries be saved into disk, so that we can skip
|
||||
// cache warmup when node restarts.
|
||||
if bc.cacheConfig.TrieCleanJournal != "" {
|
||||
triedb := bc.stateCache.TrieDB()
|
||||
triedb.SaveCache(bc.cacheConfig.TrieCleanJournal)
|
||||
bc.triedb.SaveCache(bc.cacheConfig.TrieCleanJournal)
|
||||
}
|
||||
log.Info("Blockchain stopped")
|
||||
}
|
||||
@@ -922,7 +991,7 @@ func (bc *BlockChain) procFutureBlocks() {
|
||||
blocks := make([]*types.Block, 0, bc.futureBlocks.Len())
|
||||
for _, hash := range bc.futureBlocks.Keys() {
|
||||
if block, exist := bc.futureBlocks.Peek(hash); exist {
|
||||
blocks = append(blocks, block.(*types.Block))
|
||||
blocks = append(blocks, block)
|
||||
}
|
||||
}
|
||||
if len(blocks) > 0 {
|
||||
@@ -1214,8 +1283,6 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
var lastWrite uint64
|
||||
|
||||
// writeBlockWithoutState writes only the block and its metadata to the database,
|
||||
// but does not write any state. This is used to construct competing side forks
|
||||
// up to the point where they exceed the canonical total difficulty.
|
||||
@@ -1274,58 +1341,58 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
triedb := bc.stateCache.TrieDB()
|
||||
|
||||
// If we're running an archive node, always flush
|
||||
if bc.cacheConfig.TrieDirtyDisabled {
|
||||
return triedb.Commit(root, false, nil)
|
||||
} else {
|
||||
// Full but not archive node, do proper garbage collection
|
||||
triedb.Reference(root, common.Hash{}) // metadata reference to keep trie alive
|
||||
bc.triegc.Push(root, -int64(block.NumberU64()))
|
||||
return bc.triedb.Commit(root, false)
|
||||
}
|
||||
// Full but not archive node, do proper garbage collection
|
||||
bc.triedb.Reference(root, common.Hash{}) // metadata reference to keep trie alive
|
||||
bc.triegc.Push(root, -int64(block.NumberU64()))
|
||||
|
||||
if current := block.NumberU64(); current > TriesInMemory {
|
||||
// If we exceeded our memory allowance, flush matured singleton nodes to disk
|
||||
var (
|
||||
nodes, imgs = triedb.Size()
|
||||
limit = common.StorageSize(bc.cacheConfig.TrieDirtyLimit) * 1024 * 1024
|
||||
)
|
||||
if nodes > limit || imgs > 4*1024*1024 {
|
||||
triedb.Cap(limit - ethdb.IdealBatchSize)
|
||||
}
|
||||
// Find the next state trie we need to commit
|
||||
chosen := current - TriesInMemory
|
||||
|
||||
// If we exceeded out time allowance, flush an entire trie to disk
|
||||
if bc.gcproc > bc.cacheConfig.TrieTimeLimit {
|
||||
// If the header is missing (canonical chain behind), we're reorging a low
|
||||
// diff sidechain. Suspend committing until this operation is completed.
|
||||
header := bc.GetHeaderByNumber(chosen)
|
||||
if header == nil {
|
||||
log.Warn("Reorg in progress, trie commit postponed", "number", chosen)
|
||||
} else {
|
||||
// If we're exceeding limits but haven't reached a large enough memory gap,
|
||||
// warn the user that the system is becoming unstable.
|
||||
if chosen < lastWrite+TriesInMemory && bc.gcproc >= 2*bc.cacheConfig.TrieTimeLimit {
|
||||
log.Info("State in memory for too long, committing", "time", bc.gcproc, "allowance", bc.cacheConfig.TrieTimeLimit, "optimum", float64(chosen-lastWrite)/TriesInMemory)
|
||||
}
|
||||
// Flush an entire trie and restart the counters
|
||||
triedb.Commit(header.Root, true, nil)
|
||||
lastWrite = chosen
|
||||
bc.gcproc = 0
|
||||
}
|
||||
}
|
||||
// Garbage collect anything below our required write retention
|
||||
for !bc.triegc.Empty() {
|
||||
root, number := bc.triegc.Pop()
|
||||
if uint64(-number) > chosen {
|
||||
bc.triegc.Push(root, number)
|
||||
break
|
||||
}
|
||||
triedb.Dereference(root.(common.Hash))
|
||||
current := block.NumberU64()
|
||||
// Flush limits are not considered for the first TriesInMemory blocks.
|
||||
if current <= TriesInMemory {
|
||||
return nil
|
||||
}
|
||||
// If we exceeded our memory allowance, flush matured singleton nodes to disk
|
||||
var (
|
||||
nodes, imgs = bc.triedb.Size()
|
||||
limit = common.StorageSize(bc.cacheConfig.TrieDirtyLimit) * 1024 * 1024
|
||||
)
|
||||
if nodes > limit || imgs > 4*1024*1024 {
|
||||
bc.triedb.Cap(limit - ethdb.IdealBatchSize)
|
||||
}
|
||||
// Find the next state trie we need to commit
|
||||
chosen := current - TriesInMemory
|
||||
flushInterval := time.Duration(atomic.LoadInt64(&bc.flushInterval))
|
||||
// If we exceeded time allowance, flush an entire trie to disk
|
||||
if bc.gcproc > flushInterval {
|
||||
// If the header is missing (canonical chain behind), we're reorging a low
|
||||
// diff sidechain. Suspend committing until this operation is completed.
|
||||
header := bc.GetHeaderByNumber(chosen)
|
||||
if header == nil {
|
||||
log.Warn("Reorg in progress, trie commit postponed", "number", chosen)
|
||||
} else {
|
||||
// If we're exceeding limits but haven't reached a large enough memory gap,
|
||||
// warn the user that the system is becoming unstable.
|
||||
if chosen < bc.lastWrite+TriesInMemory && bc.gcproc >= 2*flushInterval {
|
||||
log.Info("State in memory for too long, committing", "time", bc.gcproc, "allowance", flushInterval, "optimum", float64(chosen-bc.lastWrite)/TriesInMemory)
|
||||
}
|
||||
// Flush an entire trie and restart the counters
|
||||
bc.triedb.Commit(header.Root, true)
|
||||
bc.lastWrite = chosen
|
||||
bc.gcproc = 0
|
||||
}
|
||||
}
|
||||
// Garbage collect anything below our required write retention
|
||||
for !bc.triegc.Empty() {
|
||||
root, number := bc.triegc.Pop()
|
||||
if uint64(-number) > chosen {
|
||||
bc.triegc.Push(root, number)
|
||||
break
|
||||
}
|
||||
bc.triedb.Dereference(root)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1383,7 +1450,7 @@ func (bc *BlockChain) writeBlockAndSetHead(block *types.Block, receipts []*types
|
||||
if len(logs) > 0 {
|
||||
bc.logsFeed.Send(logs)
|
||||
}
|
||||
// In theory we should fire a ChainHeadEvent when we inject
|
||||
// In theory, we should fire a ChainHeadEvent when we inject
|
||||
// a canonical block, but sometimes we can insert a batch of
|
||||
// canonical blocks. Avoid firing too many ChainHeadEvents,
|
||||
// we will fire an accumulated ChainHeadEvent and disable fire
|
||||
@@ -1468,7 +1535,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals, setHead bool)
|
||||
}
|
||||
|
||||
// Start a parallel signature recovery (signer will fluke on fork transition, minimal perf loss)
|
||||
senderCacher.recoverFromBlocks(types.MakeSigner(bc.chainConfig, chain[0].Number()), chain)
|
||||
SenderCacher.RecoverFromBlocks(types.MakeSigner(bc.chainConfig, chain[0].Number()), chain)
|
||||
|
||||
var (
|
||||
stats = insertStats{startTime: mclock.Now()}
|
||||
@@ -1731,18 +1798,23 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals, setHead bool)
|
||||
accountCommitTimer.Update(statedb.AccountCommits) // Account commits are complete, we can mark them
|
||||
storageCommitTimer.Update(statedb.StorageCommits) // Storage commits are complete, we can mark them
|
||||
snapshotCommitTimer.Update(statedb.SnapshotCommits) // Snapshot commits are complete, we can mark them
|
||||
triedbCommitTimer.Update(statedb.TrieDBCommits) // Triedb commits are complete, we can mark them
|
||||
|
||||
blockWriteTimer.Update(time.Since(substart) - statedb.AccountCommits - statedb.StorageCommits - statedb.SnapshotCommits)
|
||||
blockWriteTimer.Update(time.Since(substart) - statedb.AccountCommits - statedb.StorageCommits - statedb.SnapshotCommits - statedb.TrieDBCommits)
|
||||
blockInsertTimer.UpdateSince(start)
|
||||
|
||||
// Report the import stats before returning the various results
|
||||
stats.processed++
|
||||
stats.usedGas += usedGas
|
||||
|
||||
dirty, _ := bc.stateCache.TrieDB().Size()
|
||||
dirty, _ := bc.triedb.Size()
|
||||
stats.report(chain, it.index, dirty, setHead)
|
||||
|
||||
if !setHead {
|
||||
// After merge we expect few side chains. Simply count
|
||||
// all blocks the CL gives us for GC processing time
|
||||
bc.gcproc += proctime
|
||||
|
||||
return it.index, nil // Direct block insertion of a single block
|
||||
}
|
||||
switch status {
|
||||
@@ -1889,7 +1961,7 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (i
|
||||
// Import all the pruned blocks to make the state available
|
||||
var (
|
||||
blocks []*types.Block
|
||||
memory common.StorageSize
|
||||
memory uint64
|
||||
)
|
||||
for i := len(hashes) - 1; i >= 0; i-- {
|
||||
// Append the next block to our batch
|
||||
@@ -1968,14 +2040,10 @@ func (bc *BlockChain) recoverAncestors(block *types.Block) (common.Hash, error)
|
||||
}
|
||||
|
||||
// collectLogs collects the logs that were generated or removed during
|
||||
// the processing of the block that corresponds with the given hash.
|
||||
// These logs are later announced as deleted or reborn.
|
||||
func (bc *BlockChain) collectLogs(hash common.Hash, removed bool) []*types.Log {
|
||||
number := bc.hc.GetBlockNumber(hash)
|
||||
if number == nil {
|
||||
return nil
|
||||
}
|
||||
receipts := rawdb.ReadReceipts(bc.db, hash, *number, bc.chainConfig)
|
||||
// the processing of a block. These logs are later announced as deleted or reborn.
|
||||
func (bc *BlockChain) collectLogs(b *types.Block, removed bool) []*types.Log {
|
||||
receipts := rawdb.ReadRawReceipts(bc.db, b.Hash(), b.NumberU64())
|
||||
receipts.DeriveFields(bc.chainConfig, b.Hash(), b.NumberU64(), b.Transactions())
|
||||
|
||||
var logs []*types.Log
|
||||
for _, receipt := range receipts {
|
||||
@@ -1990,21 +2058,6 @@ func (bc *BlockChain) collectLogs(hash common.Hash, removed bool) []*types.Log {
|
||||
return logs
|
||||
}
|
||||
|
||||
// mergeLogs returns a merged log slice with specified sort order.
|
||||
func mergeLogs(logs [][]*types.Log, reverse bool) []*types.Log {
|
||||
var ret []*types.Log
|
||||
if reverse {
|
||||
for i := len(logs) - 1; i >= 0; i-- {
|
||||
ret = append(ret, logs[i]...)
|
||||
}
|
||||
} else {
|
||||
for i := 0; i < len(logs); i++ {
|
||||
ret = append(ret, logs[i]...)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// reorg takes two blocks, an old chain and a new chain and will reconstruct the
|
||||
// blocks and inserts them to be part of the new canonical chain and accumulates
|
||||
// potential missing transactions and post an event about them.
|
||||
@@ -2018,9 +2071,6 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error {
|
||||
|
||||
deletedTxs []common.Hash
|
||||
addedTxs []common.Hash
|
||||
|
||||
deletedLogs [][]*types.Log
|
||||
rebirthLogs [][]*types.Log
|
||||
)
|
||||
// Reduce the longer chain to the same number as the shorter one
|
||||
if oldBlock.NumberU64() > newBlock.NumberU64() {
|
||||
@@ -2030,12 +2080,6 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error {
|
||||
for _, tx := range oldBlock.Transactions() {
|
||||
deletedTxs = append(deletedTxs, tx.Hash())
|
||||
}
|
||||
|
||||
// Collect deleted logs for notification
|
||||
logs := bc.collectLogs(oldBlock.Hash(), true)
|
||||
if len(logs) > 0 {
|
||||
deletedLogs = append(deletedLogs, logs)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// New chain is longer, stash all blocks away for subsequent insertion
|
||||
@@ -2062,12 +2106,6 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error {
|
||||
for _, tx := range oldBlock.Transactions() {
|
||||
deletedTxs = append(deletedTxs, tx.Hash())
|
||||
}
|
||||
|
||||
// Collect deleted logs for notification
|
||||
logs := bc.collectLogs(oldBlock.Hash(), true)
|
||||
if len(logs) > 0 {
|
||||
deletedLogs = append(deletedLogs, logs)
|
||||
}
|
||||
newChain = append(newChain, newBlock)
|
||||
|
||||
// Step back with both chains
|
||||
@@ -2144,28 +2182,42 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error {
|
||||
log.Crit("Failed to delete useless indexes", "err", err)
|
||||
}
|
||||
|
||||
// Collect the logs
|
||||
for i := len(newChain) - 1; i >= 1; i-- {
|
||||
// Collect reborn logs due to chain reorg
|
||||
logs := bc.collectLogs(newChain[i].Hash(), false)
|
||||
if len(logs) > 0 {
|
||||
rebirthLogs = append(rebirthLogs, logs)
|
||||
// Send out events for logs from the old canon chain, and 'reborn'
|
||||
// logs from the new canon chain. The number of logs can be very
|
||||
// high, so the events are sent in batches of size around 512.
|
||||
|
||||
// Deleted logs + blocks:
|
||||
var deletedLogs []*types.Log
|
||||
for i := len(oldChain) - 1; i >= 0; i-- {
|
||||
// Also send event for blocks removed from the canon chain.
|
||||
bc.chainSideFeed.Send(ChainSideEvent{Block: oldChain[i]})
|
||||
|
||||
// Collect deleted logs for notification
|
||||
if logs := bc.collectLogs(oldChain[i], true); len(logs) > 0 {
|
||||
deletedLogs = append(deletedLogs, logs...)
|
||||
}
|
||||
if len(deletedLogs) > 512 {
|
||||
bc.rmLogsFeed.Send(RemovedLogsEvent{deletedLogs})
|
||||
deletedLogs = nil
|
||||
}
|
||||
}
|
||||
// If any logs need to be fired, do it now. In theory we could avoid creating
|
||||
// this goroutine if there are no events to fire, but realistcally that only
|
||||
// ever happens if we're reorging empty blocks, which will only happen on idle
|
||||
// networks where performance is not an issue either way.
|
||||
if len(deletedLogs) > 0 {
|
||||
bc.rmLogsFeed.Send(RemovedLogsEvent{mergeLogs(deletedLogs, true)})
|
||||
bc.rmLogsFeed.Send(RemovedLogsEvent{deletedLogs})
|
||||
}
|
||||
|
||||
// New logs:
|
||||
var rebirthLogs []*types.Log
|
||||
for i := len(newChain) - 1; i >= 1; i-- {
|
||||
if logs := bc.collectLogs(newChain[i], false); len(logs) > 0 {
|
||||
rebirthLogs = append(rebirthLogs, logs...)
|
||||
}
|
||||
if len(rebirthLogs) > 512 {
|
||||
bc.logsFeed.Send(rebirthLogs)
|
||||
rebirthLogs = nil
|
||||
}
|
||||
}
|
||||
if len(rebirthLogs) > 0 {
|
||||
bc.logsFeed.Send(mergeLogs(rebirthLogs, false))
|
||||
}
|
||||
if len(oldChain) > 0 {
|
||||
for i := len(oldChain) - 1; i >= 0; i-- {
|
||||
bc.chainSideFeed.Send(ChainSideEvent{Block: oldChain[i]})
|
||||
}
|
||||
bc.logsFeed.Send(rebirthLogs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -2211,7 +2263,7 @@ func (bc *BlockChain) SetCanonical(head *types.Block) (common.Hash, error) {
|
||||
bc.writeHeadBlock(head)
|
||||
|
||||
// Emit events
|
||||
logs := bc.collectLogs(head.Hash(), false)
|
||||
logs := bc.collectLogs(head, false)
|
||||
bc.chainFeed.Send(ChainEvent{Block: head, Hash: head.Hash(), Logs: logs})
|
||||
if len(logs) > 0 {
|
||||
bc.logsFeed.Send(logs)
|
||||
@@ -2294,6 +2346,44 @@ func (bc *BlockChain) skipBlock(err error, it *insertIterator) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// indexBlocks reindexes or unindexes transactions depending on user configuration
|
||||
func (bc *BlockChain) indexBlocks(tail *uint64, head uint64, done chan struct{}) {
|
||||
defer func() { close(done) }()
|
||||
|
||||
// The tail flag is not existent, it means the node is just initialized
|
||||
// and all blocks(may from ancient store) are not indexed yet.
|
||||
if tail == nil {
|
||||
from := uint64(0)
|
||||
if bc.txLookupLimit != 0 && head >= bc.txLookupLimit {
|
||||
from = head - bc.txLookupLimit + 1
|
||||
}
|
||||
rawdb.IndexTransactions(bc.db, from, head+1, bc.quit)
|
||||
return
|
||||
}
|
||||
// The tail flag is existent, but the whole chain is required to be indexed.
|
||||
if bc.txLookupLimit == 0 || head < bc.txLookupLimit {
|
||||
if *tail > 0 {
|
||||
// It can happen when chain is rewound to a historical point which
|
||||
// is even lower than the indexes tail, recap the indexing target
|
||||
// to new head to avoid reading non-existent block bodies.
|
||||
end := *tail
|
||||
if end > head+1 {
|
||||
end = head + 1
|
||||
}
|
||||
rawdb.IndexTransactions(bc.db, 0, end, bc.quit)
|
||||
}
|
||||
return
|
||||
}
|
||||
// Update the transaction index to the new chain state
|
||||
if head-bc.txLookupLimit+1 < *tail {
|
||||
// Reindex a part of missing indices and rewind index tail to HEAD-limit
|
||||
rawdb.IndexTransactions(bc.db, head-bc.txLookupLimit+1, *tail, bc.quit)
|
||||
} else {
|
||||
// Unindex a part of stale indices and forward index tail to HEAD-limit
|
||||
rawdb.UnindexTransactions(bc.db, *tail, head-bc.txLookupLimit+1, bc.quit)
|
||||
}
|
||||
}
|
||||
|
||||
// maintainTxIndex is responsible for the construction and deletion of the
|
||||
// transaction index.
|
||||
//
|
||||
@@ -2301,65 +2391,13 @@ func (bc *BlockChain) skipBlock(err error, it *insertIterator) bool {
|
||||
// which ancient tx indices get deleted. If `txlookuplimit` is 0, it means
|
||||
// all tx indices will be reserved.
|
||||
//
|
||||
// The user can adjust the txlookuplimit value for each launch after fast
|
||||
// sync, Geth will automatically construct the missing indices and delete
|
||||
// the extra indices.
|
||||
func (bc *BlockChain) maintainTxIndex(ancients uint64) {
|
||||
// The user can adjust the txlookuplimit value for each launch after sync,
|
||||
// Geth will automatically construct the missing indices or delete the extra
|
||||
// indices.
|
||||
func (bc *BlockChain) maintainTxIndex() {
|
||||
defer bc.wg.Done()
|
||||
|
||||
// Before starting the actual maintenance, we need to handle a special case,
|
||||
// where user might init Geth with an external ancient database. If so, we
|
||||
// need to reindex all necessary transactions before starting to process any
|
||||
// pruning requests.
|
||||
if ancients > 0 {
|
||||
var from = uint64(0)
|
||||
if bc.txLookupLimit != 0 && ancients > bc.txLookupLimit {
|
||||
from = ancients - bc.txLookupLimit
|
||||
}
|
||||
rawdb.IndexTransactions(bc.db, from, ancients, bc.quit)
|
||||
}
|
||||
|
||||
// indexBlocks reindexes or unindexes transactions depending on user configuration
|
||||
indexBlocks := func(tail *uint64, head uint64, done chan struct{}) {
|
||||
defer func() { done <- struct{}{} }()
|
||||
|
||||
// If the user just upgraded Geth to a new version which supports transaction
|
||||
// index pruning, write the new tail and remove anything older.
|
||||
if tail == nil {
|
||||
if bc.txLookupLimit == 0 || head < bc.txLookupLimit {
|
||||
// Nothing to delete, write the tail and return
|
||||
rawdb.WriteTxIndexTail(bc.db, 0)
|
||||
} else {
|
||||
// Prune all stale tx indices and record the tx index tail
|
||||
rawdb.UnindexTransactions(bc.db, 0, head-bc.txLookupLimit+1, bc.quit)
|
||||
}
|
||||
return
|
||||
}
|
||||
// If a previous indexing existed, make sure that we fill in any missing entries
|
||||
if bc.txLookupLimit == 0 || head < bc.txLookupLimit {
|
||||
if *tail > 0 {
|
||||
// It can happen when chain is rewound to a historical point which
|
||||
// is even lower than the indexes tail, recap the indexing target
|
||||
// to new head to avoid reading non-existent block bodies.
|
||||
end := *tail
|
||||
if end > head+1 {
|
||||
end = head + 1
|
||||
}
|
||||
rawdb.IndexTransactions(bc.db, 0, end, bc.quit)
|
||||
}
|
||||
return
|
||||
}
|
||||
// Update the transaction index to the new chain state
|
||||
if head-bc.txLookupLimit+1 < *tail {
|
||||
// Reindex a part of missing indices and rewind index tail to HEAD-limit
|
||||
rawdb.IndexTransactions(bc.db, head-bc.txLookupLimit+1, *tail, bc.quit)
|
||||
} else {
|
||||
// Unindex a part of stale indices and forward index tail to HEAD-limit
|
||||
rawdb.UnindexTransactions(bc.db, *tail, head-bc.txLookupLimit+1, bc.quit)
|
||||
}
|
||||
}
|
||||
|
||||
// Any reindexing done, start listening to chain events and moving the index window
|
||||
// Listening to chain events and manipulate the transaction indexes.
|
||||
var (
|
||||
done chan struct{} // Non-nil if background unindexing or reindexing routine is active.
|
||||
headCh = make(chan ChainHeadEvent, 1) // Buffered to avoid locking up the event feed
|
||||
@@ -2375,7 +2413,7 @@ func (bc *BlockChain) maintainTxIndex(ancients uint64) {
|
||||
case head := <-headCh:
|
||||
if done == nil {
|
||||
done = make(chan struct{})
|
||||
go indexBlocks(rawdb.ReadTxIndexTail(bc.db), head.Block.NumberU64(), done)
|
||||
go bc.indexBlocks(rawdb.ReadTxIndexTail(bc.db), head.Block.NumberU64(), done)
|
||||
}
|
||||
case <-done:
|
||||
done = nil
|
||||
@@ -2392,24 +2430,32 @@ func (bc *BlockChain) maintainTxIndex(ancients uint64) {
|
||||
// reportBlock logs a bad block error.
|
||||
func (bc *BlockChain) reportBlock(block *types.Block, receipts types.Receipts, err error) {
|
||||
rawdb.WriteBadBlock(bc.db, block)
|
||||
log.Error(summarizeBadBlock(block, receipts, bc.Config(), err))
|
||||
}
|
||||
|
||||
// summarizeBadBlock returns a string summarizing the bad block and other
|
||||
// relevant information.
|
||||
func summarizeBadBlock(block *types.Block, receipts []*types.Receipt, config *params.ChainConfig, err error) string {
|
||||
var receiptString string
|
||||
for i, receipt := range receipts {
|
||||
receiptString += fmt.Sprintf("\t %d: cumulative: %v gas: %v contract: %v status: %v tx: %v logs: %v bloom: %x state: %x\n",
|
||||
receiptString += fmt.Sprintf("\n %d: cumulative: %v gas: %v contract: %v status: %v tx: %v logs: %v bloom: %x state: %x",
|
||||
i, receipt.CumulativeGasUsed, receipt.GasUsed, receipt.ContractAddress.Hex(),
|
||||
receipt.Status, receipt.TxHash.Hex(), receipt.Logs, receipt.Bloom, receipt.PostState)
|
||||
}
|
||||
log.Error(fmt.Sprintf(`
|
||||
version, vcs := version.Info()
|
||||
platform := fmt.Sprintf("%s %s %s %s", version, runtime.Version(), runtime.GOARCH, runtime.GOOS)
|
||||
if vcs != "" {
|
||||
vcs = fmt.Sprintf("\nVCS: %s", vcs)
|
||||
}
|
||||
return fmt.Sprintf(`
|
||||
########## BAD BLOCK #########
|
||||
Chain config: %v
|
||||
|
||||
Number: %v
|
||||
Hash: %#x
|
||||
%v
|
||||
|
||||
Block: %v (%#x)
|
||||
Error: %v
|
||||
Platform: %v%v
|
||||
Chain config: %#v
|
||||
Receipts: %v
|
||||
##############################
|
||||
`, bc.chainConfig, block.Number(), block.Hash(), receiptString, err))
|
||||
`, block.Number(), block.Hash(), err, platform, vcs, config, receiptString)
|
||||
}
|
||||
|
||||
// InsertHeaderChain attempts to insert the given header chain in to the local
|
||||
@@ -2444,3 +2490,10 @@ func (bc *BlockChain) SetBlockValidatorAndProcessorForTesting(v Validator, p Pro
|
||||
bc.validator = v
|
||||
bc.processor = p
|
||||
}
|
||||
|
||||
// SetTrieFlushInterval configures how often in-memory tries are persisted to disk.
|
||||
// The interval is in terms of block processing time, not wall clock.
|
||||
// It is thread-safe and can be called repeatedly without side effects.
|
||||
func (bc *BlockChain) SetTrieFlushInterval(interval time.Duration) {
|
||||
atomic.StoreInt64(&bc.flushInterval, int64(interval))
|
||||
}
|
||||
|
||||
@@ -56,9 +56,9 @@ func (st *insertStats) report(chain []*types.Block, index int, dirty common.Stor
|
||||
|
||||
// Assemble the log context and send it to the logger
|
||||
context := []interface{}{
|
||||
"number", end.Number(), "hash", end.Hash(),
|
||||
"blocks", st.processed, "txs", txs, "mgas", float64(st.usedGas) / 1000000,
|
||||
"elapsed", common.PrettyDuration(elapsed), "mgasps", float64(st.usedGas) * 1000 / float64(elapsed),
|
||||
"number", end.Number(), "hash", end.Hash(),
|
||||
}
|
||||
if timestamp := time.Unix(int64(end.Time()), 0); time.Since(timestamp) > time.Minute {
|
||||
context = append(context, []interface{}{"age", common.PrettyAge(timestamp)}...)
|
||||
|
||||
@@ -29,6 +29,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
)
|
||||
|
||||
// CurrentHeader retrieves the current head header of the canonical chain. The
|
||||
@@ -96,8 +97,7 @@ func (bc *BlockChain) GetHeadersFrom(number, count uint64) []rlp.RawValue {
|
||||
func (bc *BlockChain) GetBody(hash common.Hash) *types.Body {
|
||||
// Short circuit if the body's already in the cache, retrieve otherwise
|
||||
if cached, ok := bc.bodyCache.Get(hash); ok {
|
||||
body := cached.(*types.Body)
|
||||
return body
|
||||
return cached
|
||||
}
|
||||
number := bc.hc.GetBlockNumber(hash)
|
||||
if number == nil {
|
||||
@@ -117,7 +117,7 @@ func (bc *BlockChain) GetBody(hash common.Hash) *types.Body {
|
||||
func (bc *BlockChain) GetBodyRLP(hash common.Hash) rlp.RawValue {
|
||||
// Short circuit if the body's already in the cache, retrieve otherwise
|
||||
if cached, ok := bc.bodyRLPCache.Get(hash); ok {
|
||||
return cached.(rlp.RawValue)
|
||||
return cached
|
||||
}
|
||||
number := bc.hc.GetBlockNumber(hash)
|
||||
if number == nil {
|
||||
@@ -159,7 +159,7 @@ func (bc *BlockChain) HasFastBlock(hash common.Hash, number uint64) bool {
|
||||
func (bc *BlockChain) GetBlock(hash common.Hash, number uint64) *types.Block {
|
||||
// Short circuit if the block's already in the cache, retrieve otherwise
|
||||
if block, ok := bc.blockCache.Get(hash); ok {
|
||||
return block.(*types.Block)
|
||||
return block
|
||||
}
|
||||
block := rawdb.ReadBlock(bc.db, hash, number)
|
||||
if block == nil {
|
||||
@@ -211,7 +211,7 @@ func (bc *BlockChain) GetBlocksFromHash(hash common.Hash, n int) (blocks []*type
|
||||
// GetReceiptsByHash retrieves the receipts for all transactions in a given block.
|
||||
func (bc *BlockChain) GetReceiptsByHash(hash common.Hash) types.Receipts {
|
||||
if receipts, ok := bc.receiptsCache.Get(hash); ok {
|
||||
return receipts.(types.Receipts)
|
||||
return receipts
|
||||
}
|
||||
number := rawdb.ReadHeaderNumber(bc.db, hash)
|
||||
if number == nil {
|
||||
@@ -255,7 +255,7 @@ func (bc *BlockChain) GetAncestor(hash common.Hash, number, ancestor uint64, max
|
||||
func (bc *BlockChain) GetTransactionLookup(hash common.Hash) *rawdb.LegacyTxLookupEntry {
|
||||
// Short circuit if the txlookup already in the cache, retrieve otherwise
|
||||
if lookup, exist := bc.txLookupCache.Get(hash); exist {
|
||||
return lookup.(*rawdb.LegacyTxLookupEntry)
|
||||
return lookup
|
||||
}
|
||||
tx, blockHash, blockNumber, txIndex := rawdb.ReadTransaction(bc.db, hash)
|
||||
if tx == nil {
|
||||
@@ -376,6 +376,11 @@ func (bc *BlockChain) TxLookupLimit() uint64 {
|
||||
return bc.txLookupLimit
|
||||
}
|
||||
|
||||
// TrieDB retrieves the low level trie database used for data storage.
|
||||
func (bc *BlockChain) TrieDB() *trie.Database {
|
||||
return bc.triedb
|
||||
}
|
||||
|
||||
// SubscribeRemovedLogsEvent registers a subscription of RemovedLogsEvent.
|
||||
func (bc *BlockChain) SubscribeRemovedLogsEvent(ch chan<- RemovedLogsEvent) event.Subscription {
|
||||
return bc.scope.Track(bc.rmLogsFeed.Subscribe(ch))
|
||||
|
||||
@@ -1756,7 +1756,10 @@ func testRepair(t *testing.T, tt *rewindTest, snapshots bool) {
|
||||
// Create a temporary persistent database
|
||||
datadir := t.TempDir()
|
||||
|
||||
db, err := rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "", false)
|
||||
db, err := rawdb.Open(rawdb.OpenOptions{
|
||||
Directory: datadir,
|
||||
AncientsDirectory: datadir,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create persistent database: %v", err)
|
||||
}
|
||||
@@ -1764,9 +1767,12 @@ func testRepair(t *testing.T, tt *rewindTest, snapshots bool) {
|
||||
|
||||
// Initialize a fresh chain
|
||||
var (
|
||||
genesis = (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(db)
|
||||
engine = ethash.NewFullFaker()
|
||||
config = &CacheConfig{
|
||||
gspec = &Genesis{
|
||||
BaseFee: big.NewInt(params.InitialBaseFee),
|
||||
Config: params.AllEthashProtocolChanges,
|
||||
}
|
||||
engine = ethash.NewFullFaker()
|
||||
config = &CacheConfig{
|
||||
TrieCleanLimit: 256,
|
||||
TrieDirtyLimit: 256,
|
||||
TrieTimeLimit: 5 * time.Minute,
|
||||
@@ -1778,21 +1784,21 @@ func testRepair(t *testing.T, tt *rewindTest, snapshots bool) {
|
||||
config.SnapshotLimit = 256
|
||||
config.SnapshotWait = true
|
||||
}
|
||||
chain, err := NewBlockChain(db, config, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil)
|
||||
chain, err := NewBlockChain(db, config, gspec, nil, engine, vm.Config{}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create chain: %v", err)
|
||||
}
|
||||
// If sidechain blocks are needed, make a light chain and import it
|
||||
var sideblocks types.Blocks
|
||||
if tt.sidechainBlocks > 0 {
|
||||
sideblocks, _ = GenerateChain(params.TestChainConfig, genesis, engine, rawdb.NewMemoryDatabase(), tt.sidechainBlocks, func(i int, b *BlockGen) {
|
||||
sideblocks, _ = GenerateChain(gspec.Config, gspec.ToBlock(), engine, rawdb.NewMemoryDatabase(), tt.sidechainBlocks, func(i int, b *BlockGen) {
|
||||
b.SetCoinbase(common.Address{0x01})
|
||||
})
|
||||
if _, err := chain.InsertChain(sideblocks); err != nil {
|
||||
t.Fatalf("Failed to import side chain: %v", err)
|
||||
}
|
||||
}
|
||||
canonblocks, _ := GenerateChain(params.TestChainConfig, genesis, engine, rawdb.NewMemoryDatabase(), tt.canonicalBlocks, func(i int, b *BlockGen) {
|
||||
canonblocks, _ := GenerateChain(gspec.Config, gspec.ToBlock(), engine, rawdb.NewMemoryDatabase(), tt.canonicalBlocks, func(i int, b *BlockGen) {
|
||||
b.SetCoinbase(common.Address{0x02})
|
||||
b.SetDifficulty(big.NewInt(1000000))
|
||||
})
|
||||
@@ -1800,7 +1806,7 @@ func testRepair(t *testing.T, tt *rewindTest, snapshots bool) {
|
||||
t.Fatalf("Failed to import canonical chain start: %v", err)
|
||||
}
|
||||
if tt.commitBlock > 0 {
|
||||
chain.stateCache.TrieDB().Commit(canonblocks[tt.commitBlock-1].Root(), true, nil)
|
||||
chain.stateCache.TrieDB().Commit(canonblocks[tt.commitBlock-1].Root(), false)
|
||||
if snapshots {
|
||||
if err := chain.snaps.Cap(canonblocks[tt.commitBlock-1].Root(), 0); err != nil {
|
||||
t.Fatalf("Failed to flatten snapshots: %v", err)
|
||||
@@ -1823,15 +1829,20 @@ func testRepair(t *testing.T, tt *rewindTest, snapshots bool) {
|
||||
}
|
||||
// Pull the plug on the database, simulating a hard crash
|
||||
db.Close()
|
||||
chain.stopWithoutSaving()
|
||||
|
||||
// Start a new blockchain back up and see where the repair leads us
|
||||
db, err = rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "", false)
|
||||
db, err = rawdb.Open(rawdb.OpenOptions{
|
||||
Directory: datadir,
|
||||
AncientsDirectory: datadir,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to reopen persistent database: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
newChain, err := NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil)
|
||||
newChain, err := NewBlockChain(db, nil, gspec, nil, engine, vm.Config{}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to recreate chain: %v", err)
|
||||
}
|
||||
@@ -1880,7 +1891,11 @@ func TestIssue23496(t *testing.T) {
|
||||
// Create a temporary persistent database
|
||||
datadir := t.TempDir()
|
||||
|
||||
db, err := rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "", false)
|
||||
db, err := rawdb.Open(rawdb.OpenOptions{
|
||||
Directory: datadir,
|
||||
AncientsDirectory: datadir,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create persistent database: %v", err)
|
||||
}
|
||||
@@ -1888,9 +1903,12 @@ func TestIssue23496(t *testing.T) {
|
||||
|
||||
// Initialize a fresh chain
|
||||
var (
|
||||
genesis = (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(db)
|
||||
engine = ethash.NewFullFaker()
|
||||
config = &CacheConfig{
|
||||
gspec = &Genesis{
|
||||
Config: params.TestChainConfig,
|
||||
BaseFee: big.NewInt(params.InitialBaseFee),
|
||||
}
|
||||
engine = ethash.NewFullFaker()
|
||||
config = &CacheConfig{
|
||||
TrieCleanLimit: 256,
|
||||
TrieDirtyLimit: 256,
|
||||
TrieTimeLimit: 5 * time.Minute,
|
||||
@@ -1898,11 +1916,11 @@ func TestIssue23496(t *testing.T) {
|
||||
SnapshotWait: true,
|
||||
}
|
||||
)
|
||||
chain, err := NewBlockChain(db, config, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil)
|
||||
chain, err := NewBlockChain(db, config, gspec, nil, engine, vm.Config{}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create chain: %v", err)
|
||||
}
|
||||
blocks, _ := GenerateChain(params.TestChainConfig, genesis, engine, rawdb.NewMemoryDatabase(), 4, func(i int, b *BlockGen) {
|
||||
_, blocks, _ := GenerateChainWithGenesis(gspec, engine, 4, func(i int, b *BlockGen) {
|
||||
b.SetCoinbase(common.Address{0x02})
|
||||
b.SetDifficulty(big.NewInt(1000000))
|
||||
})
|
||||
@@ -1911,7 +1929,7 @@ func TestIssue23496(t *testing.T) {
|
||||
if _, err := chain.InsertChain(blocks[:1]); err != nil {
|
||||
t.Fatalf("Failed to import canonical chain start: %v", err)
|
||||
}
|
||||
chain.stateCache.TrieDB().Commit(blocks[0].Root(), true, nil)
|
||||
chain.stateCache.TrieDB().Commit(blocks[0].Root(), false)
|
||||
|
||||
// Insert block B2 and commit the snapshot into disk
|
||||
if _, err := chain.InsertChain(blocks[1:2]); err != nil {
|
||||
@@ -1925,7 +1943,7 @@ func TestIssue23496(t *testing.T) {
|
||||
if _, err := chain.InsertChain(blocks[2:3]); err != nil {
|
||||
t.Fatalf("Failed to import canonical chain start: %v", err)
|
||||
}
|
||||
chain.stateCache.TrieDB().Commit(blocks[2].Root(), true, nil)
|
||||
chain.stateCache.TrieDB().Commit(blocks[2].Root(), false)
|
||||
|
||||
// Insert the remaining blocks
|
||||
if _, err := chain.InsertChain(blocks[3:]); err != nil {
|
||||
@@ -1934,15 +1952,19 @@ func TestIssue23496(t *testing.T) {
|
||||
|
||||
// Pull the plug on the database, simulating a hard crash
|
||||
db.Close()
|
||||
chain.stopWithoutSaving()
|
||||
|
||||
// Start a new blockchain back up and see where the repair leads us
|
||||
db, err = rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "", false)
|
||||
db, err = rawdb.Open(rawdb.OpenOptions{
|
||||
Directory: datadir,
|
||||
AncientsDirectory: datadir,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to reopen persistent database: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
chain, err = NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil)
|
||||
chain, err = NewBlockChain(db, nil, gspec, nil, engine, vm.Config{}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to recreate chain: %v", err)
|
||||
}
|
||||
|
||||
@@ -1956,7 +1956,10 @@ func testSetHead(t *testing.T, tt *rewindTest, snapshots bool) {
|
||||
// Create a temporary persistent database
|
||||
datadir := t.TempDir()
|
||||
|
||||
db, err := rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "", false)
|
||||
db, err := rawdb.Open(rawdb.OpenOptions{
|
||||
Directory: datadir,
|
||||
AncientsDirectory: datadir,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create persistent database: %v", err)
|
||||
}
|
||||
@@ -1964,9 +1967,12 @@ func testSetHead(t *testing.T, tt *rewindTest, snapshots bool) {
|
||||
|
||||
// Initialize a fresh chain
|
||||
var (
|
||||
genesis = (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(db)
|
||||
engine = ethash.NewFullFaker()
|
||||
config = &CacheConfig{
|
||||
gspec = &Genesis{
|
||||
BaseFee: big.NewInt(params.InitialBaseFee),
|
||||
Config: params.AllEthashProtocolChanges,
|
||||
}
|
||||
engine = ethash.NewFullFaker()
|
||||
config = &CacheConfig{
|
||||
TrieCleanLimit: 256,
|
||||
TrieDirtyLimit: 256,
|
||||
TrieTimeLimit: 5 * time.Minute,
|
||||
@@ -1977,21 +1983,23 @@ func testSetHead(t *testing.T, tt *rewindTest, snapshots bool) {
|
||||
config.SnapshotLimit = 256
|
||||
config.SnapshotWait = true
|
||||
}
|
||||
chain, err := NewBlockChain(db, config, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil)
|
||||
chain, err := NewBlockChain(db, config, gspec, nil, engine, vm.Config{}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create chain: %v", err)
|
||||
}
|
||||
defer chain.Stop()
|
||||
|
||||
// If sidechain blocks are needed, make a light chain and import it
|
||||
var sideblocks types.Blocks
|
||||
if tt.sidechainBlocks > 0 {
|
||||
sideblocks, _ = GenerateChain(params.TestChainConfig, genesis, engine, rawdb.NewMemoryDatabase(), tt.sidechainBlocks, func(i int, b *BlockGen) {
|
||||
sideblocks, _ = GenerateChain(gspec.Config, gspec.ToBlock(), engine, rawdb.NewMemoryDatabase(), tt.sidechainBlocks, func(i int, b *BlockGen) {
|
||||
b.SetCoinbase(common.Address{0x01})
|
||||
})
|
||||
if _, err := chain.InsertChain(sideblocks); err != nil {
|
||||
t.Fatalf("Failed to import side chain: %v", err)
|
||||
}
|
||||
}
|
||||
canonblocks, _ := GenerateChain(params.TestChainConfig, genesis, engine, rawdb.NewMemoryDatabase(), tt.canonicalBlocks, func(i int, b *BlockGen) {
|
||||
canonblocks, _ := GenerateChain(gspec.Config, gspec.ToBlock(), engine, rawdb.NewMemoryDatabase(), tt.canonicalBlocks, func(i int, b *BlockGen) {
|
||||
b.SetCoinbase(common.Address{0x02})
|
||||
b.SetDifficulty(big.NewInt(1000000))
|
||||
})
|
||||
@@ -1999,7 +2007,7 @@ func testSetHead(t *testing.T, tt *rewindTest, snapshots bool) {
|
||||
t.Fatalf("Failed to import canonical chain start: %v", err)
|
||||
}
|
||||
if tt.commitBlock > 0 {
|
||||
chain.stateCache.TrieDB().Commit(canonblocks[tt.commitBlock-1].Root(), true, nil)
|
||||
chain.stateCache.TrieDB().Commit(canonblocks[tt.commitBlock-1].Root(), false)
|
||||
if snapshots {
|
||||
if err := chain.snaps.Cap(canonblocks[tt.commitBlock-1].Root(), 0); err != nil {
|
||||
t.Fatalf("Failed to flatten snapshots: %v", err)
|
||||
|
||||
@@ -52,34 +52,40 @@ type snapshotTestBasic struct {
|
||||
// share fields, set in runtime
|
||||
datadir string
|
||||
db ethdb.Database
|
||||
gendb ethdb.Database
|
||||
genDb ethdb.Database
|
||||
engine consensus.Engine
|
||||
gspec *Genesis
|
||||
}
|
||||
|
||||
func (basic *snapshotTestBasic) prepare(t *testing.T) (*BlockChain, []*types.Block) {
|
||||
// Create a temporary persistent database
|
||||
datadir := t.TempDir()
|
||||
|
||||
db, err := rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "", false)
|
||||
db, err := rawdb.Open(rawdb.OpenOptions{
|
||||
Directory: datadir,
|
||||
AncientsDirectory: datadir,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create persistent database: %v", err)
|
||||
}
|
||||
// Initialize a fresh chain
|
||||
var (
|
||||
genesis = (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(db)
|
||||
engine = ethash.NewFullFaker()
|
||||
gendb = rawdb.NewMemoryDatabase()
|
||||
gspec = &Genesis{
|
||||
BaseFee: big.NewInt(params.InitialBaseFee),
|
||||
Config: params.AllEthashProtocolChanges,
|
||||
}
|
||||
engine = ethash.NewFullFaker()
|
||||
|
||||
// Snapshot is enabled, the first snapshot is created from the Genesis.
|
||||
// The snapshot memory allowance is 256MB, it means no snapshot flush
|
||||
// will happen during the block insertion.
|
||||
cacheConfig = defaultCacheConfig
|
||||
)
|
||||
chain, err := NewBlockChain(db, cacheConfig, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil)
|
||||
chain, err := NewBlockChain(db, cacheConfig, gspec, nil, engine, vm.Config{}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create chain: %v", err)
|
||||
}
|
||||
blocks, _ := GenerateChain(params.TestChainConfig, genesis, engine, gendb, basic.chainBlocks, func(i int, b *BlockGen) {})
|
||||
genDb, blocks, _ := GenerateChainWithGenesis(gspec, engine, basic.chainBlocks, func(i int, b *BlockGen) {})
|
||||
|
||||
// Insert the blocks with configured settings.
|
||||
var breakpoints []uint64
|
||||
@@ -96,7 +102,7 @@ func (basic *snapshotTestBasic) prepare(t *testing.T) (*BlockChain, []*types.Blo
|
||||
startPoint = point
|
||||
|
||||
if basic.commitBlock > 0 && basic.commitBlock == point {
|
||||
chain.stateCache.TrieDB().Commit(blocks[point-1].Root(), true, nil)
|
||||
chain.stateCache.TrieDB().Commit(blocks[point-1].Root(), false)
|
||||
}
|
||||
if basic.snapshotBlock > 0 && basic.snapshotBlock == point {
|
||||
// Flushing the entire snap tree into the disk, the
|
||||
@@ -116,8 +122,9 @@ func (basic *snapshotTestBasic) prepare(t *testing.T) (*BlockChain, []*types.Blo
|
||||
// Set runtime fields
|
||||
basic.datadir = datadir
|
||||
basic.db = db
|
||||
basic.gendb = gendb
|
||||
basic.genDb = genDb
|
||||
basic.engine = engine
|
||||
basic.gspec = gspec
|
||||
return chain, blocks
|
||||
}
|
||||
|
||||
@@ -139,7 +146,7 @@ func (basic *snapshotTestBasic) verify(t *testing.T, chain *BlockChain, blocks [
|
||||
// Check the disk layer, ensure they are matched
|
||||
block := chain.GetBlockByNumber(basic.expSnapshotBottom)
|
||||
if block == nil {
|
||||
t.Errorf("The correspnding block[%d] of snapshot disk layer is missing", basic.expSnapshotBottom)
|
||||
t.Errorf("The corresponding block[%d] of snapshot disk layer is missing", basic.expSnapshotBottom)
|
||||
} else if !bytes.Equal(chain.snaps.DiskRoot().Bytes(), block.Root().Bytes()) {
|
||||
t.Errorf("The snapshot disk layer root is incorrect, want %x, get %x", block.Root(), chain.snaps.DiskRoot())
|
||||
}
|
||||
@@ -201,7 +208,7 @@ func (basic *snapshotTestBasic) dump() string {
|
||||
|
||||
func (basic *snapshotTestBasic) teardown() {
|
||||
basic.db.Close()
|
||||
basic.gendb.Close()
|
||||
basic.genDb.Close()
|
||||
os.RemoveAll(basic.datadir)
|
||||
}
|
||||
|
||||
@@ -219,7 +226,7 @@ func (snaptest *snapshotTest) test(t *testing.T) {
|
||||
|
||||
// Restart the chain normally
|
||||
chain.Stop()
|
||||
newchain, err := NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil)
|
||||
newchain, err := NewBlockChain(snaptest.db, nil, snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to recreate chain: %v", err)
|
||||
}
|
||||
@@ -243,9 +250,14 @@ func (snaptest *crashSnapshotTest) test(t *testing.T) {
|
||||
// Pull the plug on the database, simulating a hard crash
|
||||
db := chain.db
|
||||
db.Close()
|
||||
chain.stopWithoutSaving()
|
||||
|
||||
// Start a new blockchain back up and see where the repair leads us
|
||||
newdb, err := rawdb.NewLevelDBDatabaseWithFreezer(snaptest.datadir, 0, 0, snaptest.datadir, "", false)
|
||||
newdb, err := rawdb.Open(rawdb.OpenOptions{
|
||||
Directory: snaptest.datadir,
|
||||
AncientsDirectory: snaptest.datadir,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to reopen persistent database: %v", err)
|
||||
}
|
||||
@@ -255,13 +267,13 @@ func (snaptest *crashSnapshotTest) test(t *testing.T) {
|
||||
// the crash, we do restart twice here: one after the crash and one
|
||||
// after the normal stop. It's used to ensure the broken snapshot
|
||||
// can be detected all the time.
|
||||
newchain, err := NewBlockChain(newdb, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil)
|
||||
newchain, err := NewBlockChain(newdb, nil, snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to recreate chain: %v", err)
|
||||
}
|
||||
newchain.Stop()
|
||||
|
||||
newchain, err = NewBlockChain(newdb, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil)
|
||||
newchain, err = NewBlockChain(newdb, nil, snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to recreate chain: %v", err)
|
||||
}
|
||||
@@ -288,7 +300,7 @@ func (snaptest *gappedSnapshotTest) test(t *testing.T) {
|
||||
|
||||
// Insert blocks without enabling snapshot if gapping is required.
|
||||
chain.Stop()
|
||||
gappedBlocks, _ := GenerateChain(params.TestChainConfig, blocks[len(blocks)-1], snaptest.engine, snaptest.gendb, snaptest.gapped, func(i int, b *BlockGen) {})
|
||||
gappedBlocks, _ := GenerateChain(params.TestChainConfig, blocks[len(blocks)-1], snaptest.engine, snaptest.genDb, snaptest.gapped, func(i int, b *BlockGen) {})
|
||||
|
||||
// Insert a few more blocks without enabling snapshot
|
||||
var cacheConfig = &CacheConfig{
|
||||
@@ -297,7 +309,7 @@ func (snaptest *gappedSnapshotTest) test(t *testing.T) {
|
||||
TrieTimeLimit: 5 * time.Minute,
|
||||
SnapshotLimit: 0,
|
||||
}
|
||||
newchain, err := NewBlockChain(snaptest.db, cacheConfig, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil)
|
||||
newchain, err := NewBlockChain(snaptest.db, cacheConfig, snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to recreate chain: %v", err)
|
||||
}
|
||||
@@ -305,7 +317,7 @@ func (snaptest *gappedSnapshotTest) test(t *testing.T) {
|
||||
newchain.Stop()
|
||||
|
||||
// Restart the chain with enabling the snapshot
|
||||
newchain, err = NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil)
|
||||
newchain, err = NewBlockChain(snaptest.db, nil, snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to recreate chain: %v", err)
|
||||
}
|
||||
@@ -333,7 +345,7 @@ func (snaptest *setHeadSnapshotTest) test(t *testing.T) {
|
||||
chain.SetHead(snaptest.setHead)
|
||||
chain.Stop()
|
||||
|
||||
newchain, err := NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil)
|
||||
newchain, err := NewBlockChain(snaptest.db, nil, snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to recreate chain: %v", err)
|
||||
}
|
||||
@@ -368,11 +380,11 @@ func (snaptest *wipeCrashSnapshotTest) test(t *testing.T) {
|
||||
TrieTimeLimit: 5 * time.Minute,
|
||||
SnapshotLimit: 0,
|
||||
}
|
||||
newchain, err := NewBlockChain(snaptest.db, config, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil)
|
||||
newchain, err := NewBlockChain(snaptest.db, config, snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to recreate chain: %v", err)
|
||||
}
|
||||
newBlocks, _ := GenerateChain(params.TestChainConfig, blocks[len(blocks)-1], snaptest.engine, snaptest.gendb, snaptest.newBlocks, func(i int, b *BlockGen) {})
|
||||
newBlocks, _ := GenerateChain(params.TestChainConfig, blocks[len(blocks)-1], snaptest.engine, snaptest.genDb, snaptest.newBlocks, func(i int, b *BlockGen) {})
|
||||
newchain.InsertChain(newBlocks)
|
||||
newchain.Stop()
|
||||
|
||||
@@ -384,16 +396,19 @@ func (snaptest *wipeCrashSnapshotTest) test(t *testing.T) {
|
||||
SnapshotLimit: 256,
|
||||
SnapshotWait: false, // Don't wait rebuild
|
||||
}
|
||||
_, err = NewBlockChain(snaptest.db, config, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil)
|
||||
tmp, err := NewBlockChain(snaptest.db, config, snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to recreate chain: %v", err)
|
||||
}
|
||||
// Simulate the blockchain crash.
|
||||
|
||||
newchain, err = NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil)
|
||||
// Simulate the blockchain crash.
|
||||
tmp.stopWithoutSaving()
|
||||
|
||||
newchain, err = NewBlockChain(snaptest.db, nil, snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to recreate chain: %v", err)
|
||||
}
|
||||
defer newchain.Stop()
|
||||
snaptest.verify(t, newchain, blocks)
|
||||
}
|
||||
|
||||
|
||||
+937
-463
File diff suppressed because it is too large
Load Diff
@@ -18,6 +18,7 @@ package bloombits
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
crand "crypto/rand"
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
@@ -78,7 +79,7 @@ func BenchmarkGenerator(b *testing.B) {
|
||||
}
|
||||
})
|
||||
for i := 0; i < types.BloomBitLength; i++ {
|
||||
rand.Read(input[i][:])
|
||||
crand.Read(input[i][:])
|
||||
}
|
||||
b.Run("random", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
|
||||
@@ -612,7 +612,7 @@ func (s *MatcherSession) Multiplex(batch int, wait time.Duration, mux chan chan
|
||||
return
|
||||
|
||||
case <-time.After(wait):
|
||||
// Throttling up, fetch whatever's available
|
||||
// Throttling up, fetch whatever is available
|
||||
}
|
||||
}
|
||||
// Allocate as much as we can handle and request servicing
|
||||
|
||||
@@ -19,11 +19,9 @@ package bloombits
|
||||
import (
|
||||
"bytes"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Tests that the scheduler can deduplicate and forward retrieval requests to
|
||||
@@ -53,7 +51,6 @@ func testScheduler(t *testing.T, clients int, fetchers int, requests int) {
|
||||
defer fetchPend.Done()
|
||||
|
||||
for req := range fetch {
|
||||
time.Sleep(time.Duration(rand.Intn(int(100 * time.Microsecond))))
|
||||
atomic.AddUint32(&delivered, 1)
|
||||
|
||||
f.deliver([]uint64{
|
||||
|
||||
+103
-24
@@ -23,11 +23,13 @@ import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/consensus/misc"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
)
|
||||
|
||||
// BlockGen creates blocks for testing.
|
||||
@@ -39,10 +41,11 @@ type BlockGen struct {
|
||||
header *types.Header
|
||||
statedb *state.StateDB
|
||||
|
||||
gasPool *GasPool
|
||||
txs []*types.Transaction
|
||||
receipts []*types.Receipt
|
||||
uncles []*types.Header
|
||||
gasPool *GasPool
|
||||
txs []*types.Transaction
|
||||
receipts []*types.Receipt
|
||||
uncles []*types.Header
|
||||
withdrawals []*types.Withdrawal
|
||||
|
||||
config *params.ChainConfig
|
||||
engine consensus.Engine
|
||||
@@ -78,6 +81,31 @@ func (b *BlockGen) SetDifficulty(diff *big.Int) {
|
||||
b.header.Difficulty = diff
|
||||
}
|
||||
|
||||
// SetPos makes the header a PoS-header (0 difficulty)
|
||||
func (b *BlockGen) SetPoS() {
|
||||
b.header.Difficulty = new(big.Int)
|
||||
}
|
||||
|
||||
// addTx adds a transaction to the generated block. If no coinbase has
|
||||
// been set, the block's coinbase is set to the zero address.
|
||||
//
|
||||
// There are a few options can be passed as well in order to run some
|
||||
// customized rules.
|
||||
// - bc: enables the ability to query historical block hashes for BLOCKHASH
|
||||
// - vmConfig: extends the flexibility for customizing evm rules, e.g. enable extra EIPs
|
||||
func (b *BlockGen) addTx(bc *BlockChain, vmConfig vm.Config, tx *types.Transaction) {
|
||||
if b.gasPool == nil {
|
||||
b.SetCoinbase(common.Address{})
|
||||
}
|
||||
b.statedb.SetTxContext(tx.Hash(), len(b.txs))
|
||||
receipt, err := ApplyTransaction(b.config, bc, &b.header.Coinbase, b.gasPool, b.statedb, b.header, tx, &b.header.GasUsed, vmConfig)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
b.txs = append(b.txs, tx)
|
||||
b.receipts = append(b.receipts, receipt)
|
||||
}
|
||||
|
||||
// AddTx adds a transaction to the generated block. If no coinbase has
|
||||
// been set, the block's coinbase is set to the zero address.
|
||||
//
|
||||
@@ -87,7 +115,7 @@ func (b *BlockGen) SetDifficulty(diff *big.Int) {
|
||||
// added. Notably, contract code relying on the BLOCKHASH instruction
|
||||
// will panic during execution.
|
||||
func (b *BlockGen) AddTx(tx *types.Transaction) {
|
||||
b.AddTxWithChain(nil, tx)
|
||||
b.addTx(nil, vm.Config{}, tx)
|
||||
}
|
||||
|
||||
// AddTxWithChain adds a transaction to the generated block. If no coinbase has
|
||||
@@ -99,16 +127,14 @@ func (b *BlockGen) AddTx(tx *types.Transaction) {
|
||||
// added. If contract code relies on the BLOCKHASH instruction,
|
||||
// the block in chain will be returned.
|
||||
func (b *BlockGen) AddTxWithChain(bc *BlockChain, tx *types.Transaction) {
|
||||
if b.gasPool == nil {
|
||||
b.SetCoinbase(common.Address{})
|
||||
}
|
||||
b.statedb.Prepare(tx.Hash(), len(b.txs))
|
||||
receipt, err := ApplyTransaction(b.config, bc, &b.header.Coinbase, b.gasPool, b.statedb, b.header, tx, &b.header.GasUsed, vm.Config{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
b.txs = append(b.txs, tx)
|
||||
b.receipts = append(b.receipts, receipt)
|
||||
b.addTx(bc, vm.Config{}, tx)
|
||||
}
|
||||
|
||||
// AddTxWithVMConfig adds a transaction to the generated block. If no coinbase has
|
||||
// been set, the block's coinbase is set to the zero address.
|
||||
// The evm interpreter can be customized with the provided vm config.
|
||||
func (b *BlockGen) AddTxWithVMConfig(tx *types.Transaction, config vm.Config) {
|
||||
b.addTx(nil, config, tx)
|
||||
}
|
||||
|
||||
// GetBalance returns the balance of the given address at the generated block.
|
||||
@@ -173,13 +199,33 @@ func (b *BlockGen) AddUncle(h *types.Header) {
|
||||
if b.config.IsLondon(h.Number) {
|
||||
h.BaseFee = misc.CalcBaseFee(b.config, parent)
|
||||
if !b.config.IsLondon(parent.Number) {
|
||||
parentGasLimit := parent.GasLimit * params.ElasticityMultiplier
|
||||
parentGasLimit := parent.GasLimit * b.config.ElasticityMultiplier()
|
||||
h.GasLimit = CalcGasLimit(parentGasLimit, parentGasLimit)
|
||||
}
|
||||
}
|
||||
b.uncles = append(b.uncles, h)
|
||||
}
|
||||
|
||||
// AddWithdrawal adds a withdrawal to the generated block.
|
||||
func (b *BlockGen) AddWithdrawal(w *types.Withdrawal) {
|
||||
// The withdrawal will be assigned the next valid index.
|
||||
var idx uint64
|
||||
for i := b.i - 1; i >= 0; i-- {
|
||||
if wd := b.chain[i].Withdrawals(); len(wd) != 0 {
|
||||
idx = wd[len(wd)-1].Index + 1
|
||||
break
|
||||
}
|
||||
if i == 0 {
|
||||
// Correctly set the index if no parent had withdrawals
|
||||
if wd := b.parent.Withdrawals(); len(wd) != 0 {
|
||||
idx = wd[len(wd)-1].Index + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
w.Index = idx
|
||||
b.withdrawals = append(b.withdrawals, w)
|
||||
}
|
||||
|
||||
// PrevBlock returns a previously generated block by number. It panics if
|
||||
// num is greater or equal to the number of the block being generated.
|
||||
// For index -1, PrevBlock returns the parent block given to GenerateChain.
|
||||
@@ -256,15 +302,17 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
|
||||
gen(i, b)
|
||||
}
|
||||
if b.engine != nil {
|
||||
// Finalize and seal the block
|
||||
block, _ := b.engine.FinalizeAndAssemble(chainreader, b.header, statedb, b.txs, b.uncles, b.receipts)
|
||||
block, err := b.engine.FinalizeAndAssemble(chainreader, b.header, statedb, b.txs, b.uncles, b.receipts, b.withdrawals)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Write state changes to db
|
||||
root, err := statedb.Commit(config.IsEIP158(b.header.Number))
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("state write error: %v", err))
|
||||
}
|
||||
if err := statedb.Database().TrieDB().Commit(root, false, nil); err != nil {
|
||||
if err := statedb.Database().TrieDB().Commit(root, false); err != nil {
|
||||
panic(fmt.Sprintf("trie write error: %v", err))
|
||||
}
|
||||
return block, b.receipts
|
||||
@@ -284,6 +332,19 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
|
||||
return blocks, receipts
|
||||
}
|
||||
|
||||
// GenerateChainWithGenesis is a wrapper of GenerateChain which will initialize
|
||||
// genesis block to database first according to the provided genesis specification
|
||||
// then generate chain on top.
|
||||
func GenerateChainWithGenesis(genesis *Genesis, engine consensus.Engine, n int, gen func(int, *BlockGen)) (ethdb.Database, []*types.Block, []types.Receipts) {
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
_, err := genesis.Commit(db, trie.NewDatabase(db))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
blocks, receipts := GenerateChain(genesis.Config, genesis.ToBlock(), engine, db, n, gen)
|
||||
return db, blocks, receipts
|
||||
}
|
||||
|
||||
func makeHeader(chain consensus.ChainReader, parent *types.Block, state *state.StateDB, engine consensus.Engine) *types.Header {
|
||||
var time uint64
|
||||
if parent.Time() == 0 {
|
||||
@@ -308,7 +369,7 @@ func makeHeader(chain consensus.ChainReader, parent *types.Block, state *state.S
|
||||
if chain.Config().IsLondon(header.Number) {
|
||||
header.BaseFee = misc.CalcBaseFee(chain.Config(), parent.Header())
|
||||
if !chain.Config().IsLondon(parent.Number()) {
|
||||
parentGasLimit := parent.GasLimit() * params.ElasticityMultiplier
|
||||
parentGasLimit := parent.GasLimit() * chain.Config().ElasticityMultiplier()
|
||||
header.GasLimit = CalcGasLimit(parentGasLimit, parentGasLimit)
|
||||
}
|
||||
}
|
||||
@@ -316,8 +377,8 @@ func makeHeader(chain consensus.ChainReader, parent *types.Block, state *state.S
|
||||
}
|
||||
|
||||
// makeHeaderChain creates a deterministic chain of headers rooted at parent.
|
||||
func makeHeaderChain(parent *types.Header, n int, engine consensus.Engine, db ethdb.Database, seed int) []*types.Header {
|
||||
blocks := makeBlockChain(types.NewBlockWithHeader(parent), n, engine, db, seed)
|
||||
func makeHeaderChain(chainConfig *params.ChainConfig, parent *types.Header, n int, engine consensus.Engine, db ethdb.Database, seed int) []*types.Header {
|
||||
blocks := makeBlockChain(chainConfig, types.NewBlockWithHeader(parent), n, engine, db, seed)
|
||||
headers := make([]*types.Header, len(blocks))
|
||||
for i, block := range blocks {
|
||||
headers[i] = block.Header()
|
||||
@@ -325,14 +386,32 @@ func makeHeaderChain(parent *types.Header, n int, engine consensus.Engine, db et
|
||||
return headers
|
||||
}
|
||||
|
||||
// makeHeaderChainWithGenesis creates a deterministic chain of headers from genesis.
|
||||
func makeHeaderChainWithGenesis(genesis *Genesis, n int, engine consensus.Engine, seed int) (ethdb.Database, []*types.Header) {
|
||||
db, blocks := makeBlockChainWithGenesis(genesis, n, engine, seed)
|
||||
headers := make([]*types.Header, len(blocks))
|
||||
for i, block := range blocks {
|
||||
headers[i] = block.Header()
|
||||
}
|
||||
return db, headers
|
||||
}
|
||||
|
||||
// makeBlockChain creates a deterministic chain of blocks rooted at parent.
|
||||
func makeBlockChain(parent *types.Block, n int, engine consensus.Engine, db ethdb.Database, seed int) []*types.Block {
|
||||
blocks, _ := GenerateChain(params.TestChainConfig, parent, engine, db, n, func(i int, b *BlockGen) {
|
||||
func makeBlockChain(chainConfig *params.ChainConfig, parent *types.Block, n int, engine consensus.Engine, db ethdb.Database, seed int) []*types.Block {
|
||||
blocks, _ := GenerateChain(chainConfig, parent, engine, db, n, func(i int, b *BlockGen) {
|
||||
b.SetCoinbase(common.Address{0: byte(seed), 19: byte(i)})
|
||||
})
|
||||
return blocks
|
||||
}
|
||||
|
||||
// makeBlockChain creates a deterministic chain of blocks from genesis
|
||||
func makeBlockChainWithGenesis(genesis *Genesis, n int, engine consensus.Engine, seed int) (ethdb.Database, []*types.Block) {
|
||||
db, blocks, _ := GenerateChainWithGenesis(genesis, engine, n, func(i int, b *BlockGen) {
|
||||
b.SetCoinbase(common.Address{0: byte(seed), 19: byte(i)})
|
||||
})
|
||||
return db, blocks
|
||||
}
|
||||
|
||||
type fakeChainReader struct {
|
||||
config *params.ChainConfig
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ func ExampleGenerateChain() {
|
||||
})
|
||||
|
||||
// Import the chain. This runs all block validation rules.
|
||||
blockchain, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil)
|
||||
blockchain, _ := NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil)
|
||||
defer blockchain.Stop()
|
||||
|
||||
if i, err := blockchain.InsertChain(chain); err != nil {
|
||||
|
||||
+37
-38
@@ -30,32 +30,39 @@ import (
|
||||
// blocks based on their extradata fields.
|
||||
func TestDAOForkRangeExtradata(t *testing.T) {
|
||||
forkBlock := big.NewInt(32)
|
||||
chainConfig := *params.NonActivatedConfig
|
||||
chainConfig.HomesteadBlock = big.NewInt(0)
|
||||
|
||||
// Generate a common prefix for both pro-forkers and non-forkers
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
gspec := &Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}
|
||||
genesis := gspec.MustCommit(db)
|
||||
prefix, _ := GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), db, int(forkBlock.Int64()-1), func(i int, gen *BlockGen) {})
|
||||
gspec := &Genesis{
|
||||
BaseFee: big.NewInt(params.InitialBaseFee),
|
||||
Config: &chainConfig,
|
||||
}
|
||||
genDb, prefix, _ := GenerateChainWithGenesis(gspec, ethash.NewFaker(), int(forkBlock.Int64()-1), func(i int, gen *BlockGen) {})
|
||||
|
||||
// Create the concurrent, conflicting two nodes
|
||||
proDb := rawdb.NewMemoryDatabase()
|
||||
gspec.MustCommit(proDb)
|
||||
|
||||
proConf := *params.TestChainConfig
|
||||
proConf := *params.NonActivatedConfig
|
||||
proConf.HomesteadBlock = big.NewInt(0)
|
||||
proConf.DAOForkBlock = forkBlock
|
||||
proConf.DAOForkSupport = true
|
||||
|
||||
proBc, _ := NewBlockChain(proDb, nil, &proConf, ethash.NewFaker(), vm.Config{}, nil, nil)
|
||||
progspec := &Genesis{
|
||||
BaseFee: big.NewInt(params.InitialBaseFee),
|
||||
Config: &proConf,
|
||||
}
|
||||
proBc, _ := NewBlockChain(proDb, nil, progspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil)
|
||||
defer proBc.Stop()
|
||||
|
||||
conDb := rawdb.NewMemoryDatabase()
|
||||
gspec.MustCommit(conDb)
|
||||
|
||||
conConf := *params.TestChainConfig
|
||||
conConf := *params.NonActivatedConfig
|
||||
conConf.HomesteadBlock = big.NewInt(0)
|
||||
conConf.DAOForkBlock = forkBlock
|
||||
conConf.DAOForkSupport = false
|
||||
|
||||
conBc, _ := NewBlockChain(conDb, nil, &conConf, ethash.NewFaker(), vm.Config{}, nil, nil)
|
||||
congspec := &Genesis{
|
||||
BaseFee: big.NewInt(params.InitialBaseFee),
|
||||
Config: &conConf,
|
||||
}
|
||||
conBc, _ := NewBlockChain(conDb, nil, congspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil)
|
||||
defer conBc.Stop()
|
||||
|
||||
if _, err := proBc.InsertChain(prefix); err != nil {
|
||||
@@ -67,10 +74,7 @@ func TestDAOForkRangeExtradata(t *testing.T) {
|
||||
// Try to expand both pro-fork and non-fork chains iteratively with other camp's blocks
|
||||
for i := int64(0); i < params.DAOForkExtraRange.Int64(); i++ {
|
||||
// Create a pro-fork block, and try to feed into the no-fork chain
|
||||
db = rawdb.NewMemoryDatabase()
|
||||
gspec.MustCommit(db)
|
||||
bc, _ := NewBlockChain(db, nil, &conConf, ethash.NewFaker(), vm.Config{}, nil, nil)
|
||||
defer bc.Stop()
|
||||
bc, _ := NewBlockChain(rawdb.NewMemoryDatabase(), nil, congspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil)
|
||||
|
||||
blocks := conBc.GetBlocksFromHash(conBc.CurrentBlock().Hash(), int(conBc.CurrentBlock().NumberU64()))
|
||||
for j := 0; j < len(blocks)/2; j++ {
|
||||
@@ -79,23 +83,21 @@ func TestDAOForkRangeExtradata(t *testing.T) {
|
||||
if _, err := bc.InsertChain(blocks); err != nil {
|
||||
t.Fatalf("failed to import contra-fork chain for expansion: %v", err)
|
||||
}
|
||||
if err := bc.stateCache.TrieDB().Commit(bc.CurrentHeader().Root, true, nil); err != nil {
|
||||
if err := bc.stateCache.TrieDB().Commit(bc.CurrentHeader().Root, false); err != nil {
|
||||
t.Fatalf("failed to commit contra-fork head for expansion: %v", err)
|
||||
}
|
||||
blocks, _ = GenerateChain(&proConf, conBc.CurrentBlock(), ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {})
|
||||
bc.Stop()
|
||||
blocks, _ = GenerateChain(&proConf, conBc.CurrentBlock(), ethash.NewFaker(), genDb, 1, func(i int, gen *BlockGen) {})
|
||||
if _, err := conBc.InsertChain(blocks); err == nil {
|
||||
t.Fatalf("contra-fork chain accepted pro-fork block: %v", blocks[0])
|
||||
}
|
||||
// Create a proper no-fork block for the contra-forker
|
||||
blocks, _ = GenerateChain(&conConf, conBc.CurrentBlock(), ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {})
|
||||
blocks, _ = GenerateChain(&conConf, conBc.CurrentBlock(), ethash.NewFaker(), genDb, 1, func(i int, gen *BlockGen) {})
|
||||
if _, err := conBc.InsertChain(blocks); err != nil {
|
||||
t.Fatalf("contra-fork chain didn't accepted no-fork block: %v", err)
|
||||
}
|
||||
// Create a no-fork block, and try to feed into the pro-fork chain
|
||||
db = rawdb.NewMemoryDatabase()
|
||||
gspec.MustCommit(db)
|
||||
bc, _ = NewBlockChain(db, nil, &proConf, ethash.NewFaker(), vm.Config{}, nil, nil)
|
||||
defer bc.Stop()
|
||||
bc, _ = NewBlockChain(rawdb.NewMemoryDatabase(), nil, progspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil)
|
||||
|
||||
blocks = proBc.GetBlocksFromHash(proBc.CurrentBlock().Hash(), int(proBc.CurrentBlock().NumberU64()))
|
||||
for j := 0; j < len(blocks)/2; j++ {
|
||||
@@ -104,23 +106,22 @@ func TestDAOForkRangeExtradata(t *testing.T) {
|
||||
if _, err := bc.InsertChain(blocks); err != nil {
|
||||
t.Fatalf("failed to import pro-fork chain for expansion: %v", err)
|
||||
}
|
||||
if err := bc.stateCache.TrieDB().Commit(bc.CurrentHeader().Root, true, nil); err != nil {
|
||||
if err := bc.stateCache.TrieDB().Commit(bc.CurrentHeader().Root, false); err != nil {
|
||||
t.Fatalf("failed to commit pro-fork head for expansion: %v", err)
|
||||
}
|
||||
blocks, _ = GenerateChain(&conConf, proBc.CurrentBlock(), ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {})
|
||||
bc.Stop()
|
||||
blocks, _ = GenerateChain(&conConf, proBc.CurrentBlock(), ethash.NewFaker(), genDb, 1, func(i int, gen *BlockGen) {})
|
||||
if _, err := proBc.InsertChain(blocks); err == nil {
|
||||
t.Fatalf("pro-fork chain accepted contra-fork block: %v", blocks[0])
|
||||
}
|
||||
// Create a proper pro-fork block for the pro-forker
|
||||
blocks, _ = GenerateChain(&proConf, proBc.CurrentBlock(), ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {})
|
||||
blocks, _ = GenerateChain(&proConf, proBc.CurrentBlock(), ethash.NewFaker(), genDb, 1, func(i int, gen *BlockGen) {})
|
||||
if _, err := proBc.InsertChain(blocks); err != nil {
|
||||
t.Fatalf("pro-fork chain didn't accepted pro-fork block: %v", err)
|
||||
}
|
||||
}
|
||||
// Verify that contra-forkers accept pro-fork extra-datas after forking finishes
|
||||
db = rawdb.NewMemoryDatabase()
|
||||
gspec.MustCommit(db)
|
||||
bc, _ := NewBlockChain(db, nil, &conConf, ethash.NewFaker(), vm.Config{}, nil, nil)
|
||||
bc, _ := NewBlockChain(rawdb.NewMemoryDatabase(), nil, congspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil)
|
||||
defer bc.Stop()
|
||||
|
||||
blocks := conBc.GetBlocksFromHash(conBc.CurrentBlock().Hash(), int(conBc.CurrentBlock().NumberU64()))
|
||||
@@ -130,17 +131,15 @@ func TestDAOForkRangeExtradata(t *testing.T) {
|
||||
if _, err := bc.InsertChain(blocks); err != nil {
|
||||
t.Fatalf("failed to import contra-fork chain for expansion: %v", err)
|
||||
}
|
||||
if err := bc.stateCache.TrieDB().Commit(bc.CurrentHeader().Root, true, nil); err != nil {
|
||||
if err := bc.stateCache.TrieDB().Commit(bc.CurrentHeader().Root, false); err != nil {
|
||||
t.Fatalf("failed to commit contra-fork head for expansion: %v", err)
|
||||
}
|
||||
blocks, _ = GenerateChain(&proConf, conBc.CurrentBlock(), ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {})
|
||||
blocks, _ = GenerateChain(&proConf, conBc.CurrentBlock(), ethash.NewFaker(), genDb, 1, func(i int, gen *BlockGen) {})
|
||||
if _, err := conBc.InsertChain(blocks); err != nil {
|
||||
t.Fatalf("contra-fork chain didn't accept pro-fork block post-fork: %v", err)
|
||||
}
|
||||
// Verify that pro-forkers accept contra-fork extra-datas after forking finishes
|
||||
db = rawdb.NewMemoryDatabase()
|
||||
gspec.MustCommit(db)
|
||||
bc, _ = NewBlockChain(db, nil, &proConf, ethash.NewFaker(), vm.Config{}, nil, nil)
|
||||
bc, _ = NewBlockChain(rawdb.NewMemoryDatabase(), nil, progspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil)
|
||||
defer bc.Stop()
|
||||
|
||||
blocks = proBc.GetBlocksFromHash(proBc.CurrentBlock().Hash(), int(proBc.CurrentBlock().NumberU64()))
|
||||
@@ -150,10 +149,10 @@ func TestDAOForkRangeExtradata(t *testing.T) {
|
||||
if _, err := bc.InsertChain(blocks); err != nil {
|
||||
t.Fatalf("failed to import pro-fork chain for expansion: %v", err)
|
||||
}
|
||||
if err := bc.stateCache.TrieDB().Commit(bc.CurrentHeader().Root, true, nil); err != nil {
|
||||
if err := bc.stateCache.TrieDB().Commit(bc.CurrentHeader().Root, false); err != nil {
|
||||
t.Fatalf("failed to commit pro-fork head for expansion: %v", err)
|
||||
}
|
||||
blocks, _ = GenerateChain(&conConf, proBc.CurrentBlock(), ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {})
|
||||
blocks, _ = GenerateChain(&conConf, proBc.CurrentBlock(), ethash.NewFaker(), genDb, 1, func(i int, gen *BlockGen) {})
|
||||
if _, err := proBc.InsertChain(blocks); err != nil {
|
||||
t.Fatalf("pro-fork chain didn't accept contra-fork block post-fork: %v", err)
|
||||
}
|
||||
|
||||
+5
-1
@@ -63,6 +63,10 @@ var (
|
||||
// have enough funds for transfer(topmost call only).
|
||||
ErrInsufficientFundsForTransfer = errors.New("insufficient funds for transfer")
|
||||
|
||||
// ErrMaxInitCodeSizeExceeded is returned if creation transaction provides the init code bigger
|
||||
// than init code size limit.
|
||||
ErrMaxInitCodeSizeExceeded = errors.New("max initcode size exceeded")
|
||||
|
||||
// ErrInsufficientFunds is returned if the total cost of executing a transaction
|
||||
// is higher than the balance of the user's account.
|
||||
ErrInsufficientFunds = errors.New("insufficient funds for gas * price + value")
|
||||
@@ -91,7 +95,7 @@ var (
|
||||
ErrFeeCapVeryHigh = errors.New("max fee per gas higher than 2^256-1")
|
||||
|
||||
// ErrFeeCapTooLow is returned if the transaction fee cap is less than the
|
||||
// the base fee of the block.
|
||||
// base fee of the block.
|
||||
ErrFeeCapTooLow = errors.New("max fee per gas less than block base fee")
|
||||
|
||||
// ErrSenderNoEOA is returned if the sender of a transaction is a contract.
|
||||
|
||||
+1
-1
@@ -61,7 +61,7 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common
|
||||
GetHash: GetHashFn(header, chain),
|
||||
Coinbase: beneficiary,
|
||||
BlockNumber: new(big.Int).Set(header.Number),
|
||||
Time: new(big.Int).SetUint64(header.Time),
|
||||
Time: header.Time,
|
||||
Difficulty: new(big.Int).Set(header.Difficulty),
|
||||
BaseFee: baseFee,
|
||||
GasLimit: header.GasLimit,
|
||||
|
||||
+18
-13
@@ -74,10 +74,10 @@ func NewForkChoice(chainReader ChainReader, preserve func(header *types.Header)
|
||||
// In the td mode, the new head is chosen if the corresponding
|
||||
// total difficulty is higher. In the extern mode, the trusted
|
||||
// header is always selected as the head.
|
||||
func (f *ForkChoice) ReorgNeeded(current *types.Header, header *types.Header) (bool, error) {
|
||||
func (f *ForkChoice) ReorgNeeded(current *types.Header, extern *types.Header) (bool, error) {
|
||||
var (
|
||||
localTD = f.chain.GetTd(current.Hash(), current.Number.Uint64())
|
||||
externTd = f.chain.GetTd(header.Hash(), header.Number.Uint64())
|
||||
externTd = f.chain.GetTd(extern.Hash(), extern.Number.Uint64())
|
||||
)
|
||||
if localTD == nil || externTd == nil {
|
||||
return false, errors.New("missing td")
|
||||
@@ -88,21 +88,26 @@ func (f *ForkChoice) ReorgNeeded(current *types.Header, header *types.Header) (b
|
||||
if ttd := f.chain.Config().TerminalTotalDifficulty; ttd != nil && ttd.Cmp(externTd) <= 0 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// If the total difficulty is higher than our known, add it to the canonical chain
|
||||
if diff := externTd.Cmp(localTD); diff > 0 {
|
||||
return true, nil
|
||||
} else if diff < 0 {
|
||||
return false, nil
|
||||
}
|
||||
// Local and external difficulty is identical.
|
||||
// Second clause in the if statement reduces the vulnerability to selfish mining.
|
||||
// Please refer to http://www.cs.cornell.edu/~ie53/publications/btcProcFC.pdf
|
||||
reorg := externTd.Cmp(localTD) > 0
|
||||
if !reorg && externTd.Cmp(localTD) == 0 {
|
||||
number, headNumber := header.Number.Uint64(), current.Number.Uint64()
|
||||
if number < headNumber {
|
||||
reorg = true
|
||||
} else if number == headNumber {
|
||||
var currentPreserve, externPreserve bool
|
||||
if f.preserve != nil {
|
||||
currentPreserve, externPreserve = f.preserve(current), f.preserve(header)
|
||||
}
|
||||
reorg = !currentPreserve && (externPreserve || f.rand.Float64() < 0.5)
|
||||
reorg := false
|
||||
externNum, localNum := extern.Number.Uint64(), current.Number.Uint64()
|
||||
if externNum < localNum {
|
||||
reorg = true
|
||||
} else if externNum == localNum {
|
||||
var currentPreserve, externPreserve bool
|
||||
if f.preserve != nil {
|
||||
currentPreserve, externPreserve = f.preserve(current), f.preserve(extern)
|
||||
}
|
||||
reorg = !currentPreserve && (externPreserve || f.rand.Float64() < 0.5)
|
||||
}
|
||||
return reorg, nil
|
||||
}
|
||||
|
||||
+83
-42
@@ -24,6 +24,7 @@ import (
|
||||
"math"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
@@ -44,6 +45,12 @@ var (
|
||||
ErrLocalIncompatibleOrStale = errors.New("local incompatible or needs update")
|
||||
)
|
||||
|
||||
// timestampThreshold is the Ethereum mainnet genesis timestamp. It is used to
|
||||
// differentiate if a forkid.next field is a block number or a timestamp. Whilst
|
||||
// very hacky, something's needed to split the validation during the transition
|
||||
// period (block forks -> time forks).
|
||||
const timestampThreshold = 1438269973
|
||||
|
||||
// Blockchain defines all necessary method to build a forkID.
|
||||
type Blockchain interface {
|
||||
// Config retrieves the chain's fork configuration.
|
||||
@@ -65,31 +72,41 @@ type ID struct {
|
||||
// Filter is a fork id filter to validate a remotely advertised ID.
|
||||
type Filter func(id ID) error
|
||||
|
||||
// NewID calculates the Ethereum fork ID from the chain config, genesis hash, and head.
|
||||
func NewID(config *params.ChainConfig, genesis common.Hash, head uint64) ID {
|
||||
// NewID calculates the Ethereum fork ID from the chain config, genesis hash, head and time.
|
||||
func NewID(config *params.ChainConfig, genesis common.Hash, head, time uint64) ID {
|
||||
// Calculate the starting checksum from the genesis hash
|
||||
hash := crc32.ChecksumIEEE(genesis[:])
|
||||
|
||||
// Calculate the current fork checksum and the next fork block
|
||||
var next uint64
|
||||
for _, fork := range gatherForks(config) {
|
||||
forksByBlock, forksByTime := gatherForks(config)
|
||||
for _, fork := range forksByBlock {
|
||||
if fork <= head {
|
||||
// Fork already passed, checksum the previous hash and the fork number
|
||||
hash = checksumUpdate(hash, fork)
|
||||
continue
|
||||
}
|
||||
next = fork
|
||||
break
|
||||
return ID{Hash: checksumToBytes(hash), Next: fork}
|
||||
}
|
||||
return ID{Hash: checksumToBytes(hash), Next: next}
|
||||
for _, fork := range forksByTime {
|
||||
if fork <= time {
|
||||
// Fork already passed, checksum the previous hash and fork timestamp
|
||||
hash = checksumUpdate(hash, fork)
|
||||
continue
|
||||
}
|
||||
return ID{Hash: checksumToBytes(hash), Next: fork}
|
||||
}
|
||||
return ID{Hash: checksumToBytes(hash), Next: 0}
|
||||
}
|
||||
|
||||
// NewIDWithChain calculates the Ethereum fork ID from an existing chain instance.
|
||||
func NewIDWithChain(chain Blockchain) ID {
|
||||
head := chain.CurrentHeader()
|
||||
|
||||
return NewID(
|
||||
chain.Config(),
|
||||
chain.Genesis().Hash(),
|
||||
chain.CurrentHeader().Number.Uint64(),
|
||||
head.Number.Uint64(),
|
||||
head.Time,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -99,26 +116,28 @@ func NewFilter(chain Blockchain) Filter {
|
||||
return newFilter(
|
||||
chain.Config(),
|
||||
chain.Genesis().Hash(),
|
||||
func() uint64 {
|
||||
return chain.CurrentHeader().Number.Uint64()
|
||||
func() (uint64, uint64) {
|
||||
head := chain.CurrentHeader()
|
||||
return head.Number.Uint64(), head.Time
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// NewStaticFilter creates a filter at block zero.
|
||||
func NewStaticFilter(config *params.ChainConfig, genesis common.Hash) Filter {
|
||||
head := func() uint64 { return 0 }
|
||||
head := func() (uint64, uint64) { return 0, 0 }
|
||||
return newFilter(config, genesis, head)
|
||||
}
|
||||
|
||||
// newFilter is the internal version of NewFilter, taking closures as its arguments
|
||||
// instead of a chain. The reason is to allow testing it without having to simulate
|
||||
// an entire blockchain.
|
||||
func newFilter(config *params.ChainConfig, genesis common.Hash, headfn func() uint64) Filter {
|
||||
func newFilter(config *params.ChainConfig, genesis common.Hash, headfn func() (uint64, uint64)) Filter {
|
||||
// Calculate the all the valid fork hash and fork next combos
|
||||
var (
|
||||
forks = gatherForks(config)
|
||||
sums = make([][4]byte, len(forks)+1) // 0th is the genesis
|
||||
forksByBlock, forksByTime = gatherForks(config)
|
||||
forks = append(append([]uint64{}, forksByBlock...), forksByTime...)
|
||||
sums = make([][4]byte, len(forks)+1) // 0th is the genesis
|
||||
)
|
||||
hash := crc32.ChecksumIEEE(genesis[:])
|
||||
sums[0] = checksumToBytes(hash)
|
||||
@@ -129,7 +148,10 @@ func newFilter(config *params.ChainConfig, genesis common.Hash, headfn func() ui
|
||||
// Add two sentries to simplify the fork checks and don't require special
|
||||
// casing the last one.
|
||||
forks = append(forks, math.MaxUint64) // Last fork will never be passed
|
||||
|
||||
if len(forksByTime) == 0 {
|
||||
// In purely block based forks, avoid the sentry spilling into timestapt territory
|
||||
forksByBlock = append(forksByBlock, math.MaxUint64) // Last fork will never be passed
|
||||
}
|
||||
// Create a validator that will filter out incompatible chains
|
||||
return func(id ID) error {
|
||||
// Run the fork checksum validation ruleset:
|
||||
@@ -151,8 +173,13 @@ func newFilter(config *params.ChainConfig, genesis common.Hash, headfn func() ui
|
||||
// the remote, but at this current point in time we don't have enough
|
||||
// information.
|
||||
// 4. Reject in all other cases.
|
||||
head := headfn()
|
||||
block, time := headfn()
|
||||
for i, fork := range forks {
|
||||
// Pick the head comparison based on fork progression
|
||||
head := block
|
||||
if i >= len(forksByBlock) {
|
||||
head = time
|
||||
}
|
||||
// If our head is beyond this fork, continue to the next (we have a dummy
|
||||
// fork of maxuint64 as the last item to always fail this check eventually).
|
||||
if head >= fork {
|
||||
@@ -163,7 +190,7 @@ func newFilter(config *params.ChainConfig, genesis common.Hash, headfn func() ui
|
||||
if sums[i] == id.Hash {
|
||||
// Fork checksum matched, check if a remote future fork block already passed
|
||||
// locally without the local node being aware of it (rule #1a).
|
||||
if id.Next > 0 && head >= id.Next {
|
||||
if id.Next > 0 && (head >= id.Next || (id.Next > timestampThreshold && time >= id.Next)) {
|
||||
return ErrLocalIncompatibleOrStale
|
||||
}
|
||||
// Haven't passed locally a remote-only fork, accept the connection (rule #1b).
|
||||
@@ -211,46 +238,60 @@ func checksumToBytes(hash uint32) [4]byte {
|
||||
return blob
|
||||
}
|
||||
|
||||
// gatherForks gathers all the known forks and creates a sorted list out of them.
|
||||
func gatherForks(config *params.ChainConfig) []uint64 {
|
||||
// gatherForks gathers all the known forks and creates two sorted lists out of
|
||||
// them, one for the block number based forks and the second for the timestamps.
|
||||
func gatherForks(config *params.ChainConfig) ([]uint64, []uint64) {
|
||||
// Gather all the fork block numbers via reflection
|
||||
kind := reflect.TypeOf(params.ChainConfig{})
|
||||
conf := reflect.ValueOf(config).Elem()
|
||||
|
||||
var forks []uint64
|
||||
x := uint64(0)
|
||||
var (
|
||||
forksByBlock []uint64
|
||||
forksByTime []uint64
|
||||
)
|
||||
for i := 0; i < kind.NumField(); i++ {
|
||||
// Fetch the next field and skip non-fork rules
|
||||
field := kind.Field(i)
|
||||
if !strings.HasSuffix(field.Name, "Block") {
|
||||
|
||||
time := strings.HasSuffix(field.Name, "Time")
|
||||
if !time && !strings.HasSuffix(field.Name, "Block") {
|
||||
continue
|
||||
}
|
||||
if field.Type != reflect.TypeOf(new(big.Int)) {
|
||||
continue
|
||||
|
||||
// Extract the fork rule block number or timestamp and aggregate it
|
||||
if field.Type == reflect.TypeOf(&x) {
|
||||
if rule := conf.Field(i).Interface().(*uint64); rule != nil {
|
||||
forksByTime = append(forksByTime, *rule)
|
||||
}
|
||||
}
|
||||
// Extract the fork rule block number and aggregate it
|
||||
rule := conf.Field(i).Interface().(*big.Int)
|
||||
if rule != nil {
|
||||
forks = append(forks, rule.Uint64())
|
||||
}
|
||||
}
|
||||
// Sort the fork block numbers to permit chronological XOR
|
||||
for i := 0; i < len(forks); i++ {
|
||||
for j := i + 1; j < len(forks); j++ {
|
||||
if forks[i] > forks[j] {
|
||||
forks[i], forks[j] = forks[j], forks[i]
|
||||
if field.Type == reflect.TypeOf(new(big.Int)) {
|
||||
if rule := conf.Field(i).Interface().(*big.Int); rule != nil {
|
||||
forksByBlock = append(forksByBlock, rule.Uint64())
|
||||
}
|
||||
}
|
||||
}
|
||||
// Deduplicate block numbers applying multiple forks
|
||||
for i := 1; i < len(forks); i++ {
|
||||
if forks[i] == forks[i-1] {
|
||||
forks = append(forks[:i], forks[i+1:]...)
|
||||
sort.Slice(forksByBlock, func(i, j int) bool { return forksByBlock[i] < forksByBlock[j] })
|
||||
sort.Slice(forksByTime, func(i, j int) bool { return forksByTime[i] < forksByTime[j] })
|
||||
|
||||
// Deduplicate fork identifiers applying multiple forks
|
||||
for i := 1; i < len(forksByBlock); i++ {
|
||||
if forksByBlock[i] == forksByBlock[i-1] {
|
||||
forksByBlock = append(forksByBlock[:i], forksByBlock[i+1:]...)
|
||||
i--
|
||||
}
|
||||
}
|
||||
for i := 1; i < len(forksByTime); i++ {
|
||||
if forksByTime[i] == forksByTime[i-1] {
|
||||
forksByTime = append(forksByTime[:i], forksByTime[i+1:]...)
|
||||
i--
|
||||
}
|
||||
}
|
||||
// Skip any forks in block 0, that's the genesis ruleset
|
||||
if len(forks) > 0 && forks[0] == 0 {
|
||||
forks = forks[1:]
|
||||
if len(forksByBlock) > 0 && forksByBlock[0] == 0 {
|
||||
forksByBlock = forksByBlock[1:]
|
||||
}
|
||||
return forks
|
||||
if len(forksByTime) > 0 && forksByTime[0] == 0 {
|
||||
forksByTime = forksByTime[1:]
|
||||
}
|
||||
return forksByBlock, forksByTime
|
||||
}
|
||||
|
||||
+283
-136
@@ -19,7 +19,6 @@ package forkid
|
||||
import (
|
||||
"bytes"
|
||||
"math"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
@@ -27,13 +26,18 @@ import (
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
func u64(val uint64) *uint64 { return &val }
|
||||
|
||||
// TestCreation tests that different genesis and fork rule combinations result in
|
||||
// the correct fork ID.
|
||||
func TestCreation(t *testing.T) {
|
||||
mergeConfig := *params.MainnetChainConfig
|
||||
mergeConfig.MergeNetsplitBlock = big.NewInt(18000000)
|
||||
// Temporary non-existent scenario TODO(karalabe): delete when Shanghai is enabled
|
||||
timestampedConfig := *params.MainnetChainConfig
|
||||
timestampedConfig.ShanghaiTime = u64(1668000000)
|
||||
|
||||
type testcase struct {
|
||||
head uint64
|
||||
time uint64
|
||||
want ID
|
||||
}
|
||||
tests := []struct {
|
||||
@@ -46,57 +50,32 @@ func TestCreation(t *testing.T) {
|
||||
params.MainnetChainConfig,
|
||||
params.MainnetGenesisHash,
|
||||
[]testcase{
|
||||
{0, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}}, // Unsynced
|
||||
{1149999, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}}, // Last Frontier block
|
||||
{1150000, ID{Hash: checksumToBytes(0x97c2c34c), Next: 1920000}}, // First Homestead block
|
||||
{1919999, ID{Hash: checksumToBytes(0x97c2c34c), Next: 1920000}}, // Last Homestead block
|
||||
{1920000, ID{Hash: checksumToBytes(0x91d1f948), Next: 2463000}}, // First DAO block
|
||||
{2462999, ID{Hash: checksumToBytes(0x91d1f948), Next: 2463000}}, // Last DAO block
|
||||
{2463000, ID{Hash: checksumToBytes(0x7a64da13), Next: 2675000}}, // First Tangerine block
|
||||
{2674999, ID{Hash: checksumToBytes(0x7a64da13), Next: 2675000}}, // Last Tangerine block
|
||||
{2675000, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}}, // First Spurious block
|
||||
{4369999, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}}, // Last Spurious block
|
||||
{4370000, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}}, // First Byzantium block
|
||||
{7279999, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}}, // Last Byzantium block
|
||||
{7280000, ID{Hash: checksumToBytes(0x668db0af), Next: 9069000}}, // First and last Constantinople, first Petersburg block
|
||||
{9068999, ID{Hash: checksumToBytes(0x668db0af), Next: 9069000}}, // Last Petersburg block
|
||||
{9069000, ID{Hash: checksumToBytes(0x879d6e30), Next: 9200000}}, // First Istanbul and first Muir Glacier block
|
||||
{9199999, ID{Hash: checksumToBytes(0x879d6e30), Next: 9200000}}, // Last Istanbul and first Muir Glacier block
|
||||
{9200000, ID{Hash: checksumToBytes(0xe029e991), Next: 12244000}}, // First Muir Glacier block
|
||||
{12243999, ID{Hash: checksumToBytes(0xe029e991), Next: 12244000}}, // Last Muir Glacier block
|
||||
{12244000, ID{Hash: checksumToBytes(0x0eb440f6), Next: 12965000}}, // First Berlin block
|
||||
{12964999, ID{Hash: checksumToBytes(0x0eb440f6), Next: 12965000}}, // Last Berlin block
|
||||
{12965000, ID{Hash: checksumToBytes(0xb715077d), Next: 13773000}}, // First London block
|
||||
{13772999, ID{Hash: checksumToBytes(0xb715077d), Next: 13773000}}, // Last London block
|
||||
{13773000, ID{Hash: checksumToBytes(0x20c327fc), Next: 15050000}}, // First Arrow Glacier block
|
||||
{15049999, ID{Hash: checksumToBytes(0x20c327fc), Next: 15050000}}, // Last Arrow Glacier block
|
||||
{15050000, ID{Hash: checksumToBytes(0xf0afd0e3), Next: 0}}, // First Gray Glacier block
|
||||
{20000000, ID{Hash: checksumToBytes(0xf0afd0e3), Next: 0}}, // Future Gray Glacier block
|
||||
},
|
||||
},
|
||||
// Ropsten test cases
|
||||
{
|
||||
params.RopstenChainConfig,
|
||||
params.RopstenGenesisHash,
|
||||
[]testcase{
|
||||
{0, ID{Hash: checksumToBytes(0x30c7ddbc), Next: 10}}, // Unsynced, last Frontier, Homestead and first Tangerine block
|
||||
{9, ID{Hash: checksumToBytes(0x30c7ddbc), Next: 10}}, // Last Tangerine block
|
||||
{10, ID{Hash: checksumToBytes(0x63760190), Next: 1700000}}, // First Spurious block
|
||||
{1699999, ID{Hash: checksumToBytes(0x63760190), Next: 1700000}}, // Last Spurious block
|
||||
{1700000, ID{Hash: checksumToBytes(0x3ea159c7), Next: 4230000}}, // First Byzantium block
|
||||
{4229999, ID{Hash: checksumToBytes(0x3ea159c7), Next: 4230000}}, // Last Byzantium block
|
||||
{4230000, ID{Hash: checksumToBytes(0x97b544f3), Next: 4939394}}, // First Constantinople block
|
||||
{4939393, ID{Hash: checksumToBytes(0x97b544f3), Next: 4939394}}, // Last Constantinople block
|
||||
{4939394, ID{Hash: checksumToBytes(0xd6e2149b), Next: 6485846}}, // First Petersburg block
|
||||
{6485845, ID{Hash: checksumToBytes(0xd6e2149b), Next: 6485846}}, // Last Petersburg block
|
||||
{6485846, ID{Hash: checksumToBytes(0x4bc66396), Next: 7117117}}, // First Istanbul block
|
||||
{7117116, ID{Hash: checksumToBytes(0x4bc66396), Next: 7117117}}, // Last Istanbul block
|
||||
{7117117, ID{Hash: checksumToBytes(0x6727ef90), Next: 9812189}}, // First Muir Glacier block
|
||||
{9812188, ID{Hash: checksumToBytes(0x6727ef90), Next: 9812189}}, // Last Muir Glacier block
|
||||
{9812189, ID{Hash: checksumToBytes(0xa157d377), Next: 10499401}}, // First Berlin block
|
||||
{10499400, ID{Hash: checksumToBytes(0xa157d377), Next: 10499401}}, // Last Berlin block
|
||||
{10499401, ID{Hash: checksumToBytes(0x7119b6b3), Next: 0}}, // First London block
|
||||
{11000000, ID{Hash: checksumToBytes(0x7119b6b3), Next: 0}}, // Future London block
|
||||
{0, 0, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}}, // Unsynced
|
||||
{1149999, 0, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}}, // Last Frontier block
|
||||
{1150000, 0, ID{Hash: checksumToBytes(0x97c2c34c), Next: 1920000}}, // First Homestead block
|
||||
{1919999, 0, ID{Hash: checksumToBytes(0x97c2c34c), Next: 1920000}}, // Last Homestead block
|
||||
{1920000, 0, ID{Hash: checksumToBytes(0x91d1f948), Next: 2463000}}, // First DAO block
|
||||
{2462999, 0, ID{Hash: checksumToBytes(0x91d1f948), Next: 2463000}}, // Last DAO block
|
||||
{2463000, 0, ID{Hash: checksumToBytes(0x7a64da13), Next: 2675000}}, // First Tangerine block
|
||||
{2674999, 0, ID{Hash: checksumToBytes(0x7a64da13), Next: 2675000}}, // Last Tangerine block
|
||||
{2675000, 0, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}}, // First Spurious block
|
||||
{4369999, 0, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}}, // Last Spurious block
|
||||
{4370000, 0, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}}, // First Byzantium block
|
||||
{7279999, 0, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}}, // Last Byzantium block
|
||||
{7280000, 0, ID{Hash: checksumToBytes(0x668db0af), Next: 9069000}}, // First and last Constantinople, first Petersburg block
|
||||
{9068999, 0, ID{Hash: checksumToBytes(0x668db0af), Next: 9069000}}, // Last Petersburg block
|
||||
{9069000, 0, ID{Hash: checksumToBytes(0x879d6e30), Next: 9200000}}, // First Istanbul and first Muir Glacier block
|
||||
{9199999, 0, ID{Hash: checksumToBytes(0x879d6e30), Next: 9200000}}, // Last Istanbul and first Muir Glacier block
|
||||
{9200000, 0, ID{Hash: checksumToBytes(0xe029e991), Next: 12244000}}, // First Muir Glacier block
|
||||
{12243999, 0, ID{Hash: checksumToBytes(0xe029e991), Next: 12244000}}, // Last Muir Glacier block
|
||||
{12244000, 0, ID{Hash: checksumToBytes(0x0eb440f6), Next: 12965000}}, // First Berlin block
|
||||
{12964999, 0, ID{Hash: checksumToBytes(0x0eb440f6), Next: 12965000}}, // Last Berlin block
|
||||
{12965000, 0, ID{Hash: checksumToBytes(0xb715077d), Next: 13773000}}, // First London block
|
||||
{13772999, 0, ID{Hash: checksumToBytes(0xb715077d), Next: 13773000}}, // Last London block
|
||||
{13773000, 0, ID{Hash: checksumToBytes(0x20c327fc), Next: 15050000}}, // First Arrow Glacier block
|
||||
{15049999, 0, ID{Hash: checksumToBytes(0x20c327fc), Next: 15050000}}, // Last Arrow Glacier block
|
||||
{15050000, 0, ID{Hash: checksumToBytes(0xf0afd0e3), Next: 0}}, // First Gray Glacier block
|
||||
{20000000, 0, ID{Hash: checksumToBytes(0xf0afd0e3), Next: 0}}, // Future Gray Glacier block
|
||||
},
|
||||
},
|
||||
// Rinkeby test cases
|
||||
@@ -104,23 +83,23 @@ func TestCreation(t *testing.T) {
|
||||
params.RinkebyChainConfig,
|
||||
params.RinkebyGenesisHash,
|
||||
[]testcase{
|
||||
{0, ID{Hash: checksumToBytes(0x3b8e0691), Next: 1}}, // Unsynced, last Frontier block
|
||||
{1, ID{Hash: checksumToBytes(0x60949295), Next: 2}}, // First and last Homestead block
|
||||
{2, ID{Hash: checksumToBytes(0x8bde40dd), Next: 3}}, // First and last Tangerine block
|
||||
{3, ID{Hash: checksumToBytes(0xcb3a64bb), Next: 1035301}}, // First Spurious block
|
||||
{1035300, ID{Hash: checksumToBytes(0xcb3a64bb), Next: 1035301}}, // Last Spurious block
|
||||
{1035301, ID{Hash: checksumToBytes(0x8d748b57), Next: 3660663}}, // First Byzantium block
|
||||
{3660662, ID{Hash: checksumToBytes(0x8d748b57), Next: 3660663}}, // Last Byzantium block
|
||||
{3660663, ID{Hash: checksumToBytes(0xe49cab14), Next: 4321234}}, // First Constantinople block
|
||||
{4321233, ID{Hash: checksumToBytes(0xe49cab14), Next: 4321234}}, // Last Constantinople block
|
||||
{4321234, ID{Hash: checksumToBytes(0xafec6b27), Next: 5435345}}, // First Petersburg block
|
||||
{5435344, ID{Hash: checksumToBytes(0xafec6b27), Next: 5435345}}, // Last Petersburg block
|
||||
{5435345, ID{Hash: checksumToBytes(0xcbdb8838), Next: 8290928}}, // First Istanbul block
|
||||
{8290927, ID{Hash: checksumToBytes(0xcbdb8838), Next: 8290928}}, // Last Istanbul block
|
||||
{8290928, ID{Hash: checksumToBytes(0x6910c8bd), Next: 8897988}}, // First Berlin block
|
||||
{8897987, ID{Hash: checksumToBytes(0x6910c8bd), Next: 8897988}}, // Last Berlin block
|
||||
{8897988, ID{Hash: checksumToBytes(0x8E29F2F3), Next: 0}}, // First London block
|
||||
{10000000, ID{Hash: checksumToBytes(0x8E29F2F3), Next: 0}}, // Future London block
|
||||
{0, 0, ID{Hash: checksumToBytes(0x3b8e0691), Next: 1}}, // Unsynced, last Frontier block
|
||||
{1, 0, ID{Hash: checksumToBytes(0x60949295), Next: 2}}, // First and last Homestead block
|
||||
{2, 0, ID{Hash: checksumToBytes(0x8bde40dd), Next: 3}}, // First and last Tangerine block
|
||||
{3, 0, ID{Hash: checksumToBytes(0xcb3a64bb), Next: 1035301}}, // First Spurious block
|
||||
{1035300, 0, ID{Hash: checksumToBytes(0xcb3a64bb), Next: 1035301}}, // Last Spurious block
|
||||
{1035301, 0, ID{Hash: checksumToBytes(0x8d748b57), Next: 3660663}}, // First Byzantium block
|
||||
{3660662, 0, ID{Hash: checksumToBytes(0x8d748b57), Next: 3660663}}, // Last Byzantium block
|
||||
{3660663, 0, ID{Hash: checksumToBytes(0xe49cab14), Next: 4321234}}, // First Constantinople block
|
||||
{4321233, 0, ID{Hash: checksumToBytes(0xe49cab14), Next: 4321234}}, // Last Constantinople block
|
||||
{4321234, 0, ID{Hash: checksumToBytes(0xafec6b27), Next: 5435345}}, // First Petersburg block
|
||||
{5435344, 0, ID{Hash: checksumToBytes(0xafec6b27), Next: 5435345}}, // Last Petersburg block
|
||||
{5435345, 0, ID{Hash: checksumToBytes(0xcbdb8838), Next: 8290928}}, // First Istanbul block
|
||||
{8290927, 0, ID{Hash: checksumToBytes(0xcbdb8838), Next: 8290928}}, // Last Istanbul block
|
||||
{8290928, 0, ID{Hash: checksumToBytes(0x6910c8bd), Next: 8897988}}, // First Berlin block
|
||||
{8897987, 0, ID{Hash: checksumToBytes(0x6910c8bd), Next: 8897988}}, // Last Berlin block
|
||||
{8897988, 0, ID{Hash: checksumToBytes(0x8E29F2F3), Next: 0}}, // First London block
|
||||
{10000000, 0, ID{Hash: checksumToBytes(0x8E29F2F3), Next: 0}}, // Future London block
|
||||
},
|
||||
},
|
||||
// Goerli test cases
|
||||
@@ -128,14 +107,14 @@ func TestCreation(t *testing.T) {
|
||||
params.GoerliChainConfig,
|
||||
params.GoerliGenesisHash,
|
||||
[]testcase{
|
||||
{0, ID{Hash: checksumToBytes(0xa3f5ab08), Next: 1561651}}, // Unsynced, last Frontier, Homestead, Tangerine, Spurious, Byzantium, Constantinople and first Petersburg block
|
||||
{1561650, ID{Hash: checksumToBytes(0xa3f5ab08), Next: 1561651}}, // Last Petersburg block
|
||||
{1561651, ID{Hash: checksumToBytes(0xc25efa5c), Next: 4460644}}, // First Istanbul block
|
||||
{4460643, ID{Hash: checksumToBytes(0xc25efa5c), Next: 4460644}}, // Last Istanbul block
|
||||
{4460644, ID{Hash: checksumToBytes(0x757a1c47), Next: 5062605}}, // First Berlin block
|
||||
{5000000, ID{Hash: checksumToBytes(0x757a1c47), Next: 5062605}}, // Last Berlin block
|
||||
{5062605, ID{Hash: checksumToBytes(0xB8C6299D), Next: 0}}, // First London block
|
||||
{6000000, ID{Hash: checksumToBytes(0xB8C6299D), Next: 0}}, // Future London block
|
||||
{0, 0, ID{Hash: checksumToBytes(0xa3f5ab08), Next: 1561651}}, // Unsynced, last Frontier, Homestead, Tangerine, Spurious, Byzantium, Constantinople and first Petersburg block
|
||||
{1561650, 0, ID{Hash: checksumToBytes(0xa3f5ab08), Next: 1561651}}, // Last Petersburg block
|
||||
{1561651, 0, ID{Hash: checksumToBytes(0xc25efa5c), Next: 4460644}}, // First Istanbul block
|
||||
{4460643, 0, ID{Hash: checksumToBytes(0xc25efa5c), Next: 4460644}}, // Last Istanbul block
|
||||
{4460644, 0, ID{Hash: checksumToBytes(0x757a1c47), Next: 5062605}}, // First Berlin block
|
||||
{5000000, 0, ID{Hash: checksumToBytes(0x757a1c47), Next: 5062605}}, // Last Berlin block
|
||||
{5062605, 0, ID{Hash: checksumToBytes(0xB8C6299D), Next: 0}}, // First London block
|
||||
{6000000, 0, ID{Hash: checksumToBytes(0xB8C6299D), Next: 0}}, // Future London block
|
||||
},
|
||||
},
|
||||
// Sepolia test cases
|
||||
@@ -143,49 +122,52 @@ func TestCreation(t *testing.T) {
|
||||
params.SepoliaChainConfig,
|
||||
params.SepoliaGenesisHash,
|
||||
[]testcase{
|
||||
{0, ID{Hash: checksumToBytes(0xfe3366e7), Next: 1735371}}, // Unsynced, last Frontier, Homestead, Tangerine, Spurious, Byzantium, Constantinople, Petersburg, Istanbul, Berlin and first London block
|
||||
{1735370, ID{Hash: checksumToBytes(0xfe3366e7), Next: 1735371}}, // Last London block
|
||||
{1735371, ID{Hash: checksumToBytes(0xb96cbd13), Next: 0}}, // First MergeNetsplit block
|
||||
{0, 0, ID{Hash: checksumToBytes(0xfe3366e7), Next: 1735371}}, // Unsynced, last Frontier, Homestead, Tangerine, Spurious, Byzantium, Constantinople, Petersburg, Istanbul, Berlin and first London block
|
||||
{1735370, 0, ID{Hash: checksumToBytes(0xfe3366e7), Next: 1735371}}, // Last London block
|
||||
{1735371, 0, ID{Hash: checksumToBytes(0xb96cbd13), Next: 1677557088}}, // First MergeNetsplit block
|
||||
{1735372, 1677557087, ID{Hash: checksumToBytes(0xb96cbd13), Next: 1677557088}}, // Last MergeNetsplit block
|
||||
{1735372, 1677557088, ID{Hash: checksumToBytes(0xf7f9bc08), Next: 0}}, // First Shanghai block
|
||||
},
|
||||
},
|
||||
// Merge test cases
|
||||
// Temporary timestamped test cases
|
||||
{
|
||||
&mergeConfig,
|
||||
×tampedConfig,
|
||||
params.MainnetGenesisHash,
|
||||
[]testcase{
|
||||
{0, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}}, // Unsynced
|
||||
{1149999, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}}, // Last Frontier block
|
||||
{1150000, ID{Hash: checksumToBytes(0x97c2c34c), Next: 1920000}}, // First Homestead block
|
||||
{1919999, ID{Hash: checksumToBytes(0x97c2c34c), Next: 1920000}}, // Last Homestead block
|
||||
{1920000, ID{Hash: checksumToBytes(0x91d1f948), Next: 2463000}}, // First DAO block
|
||||
{2462999, ID{Hash: checksumToBytes(0x91d1f948), Next: 2463000}}, // Last DAO block
|
||||
{2463000, ID{Hash: checksumToBytes(0x7a64da13), Next: 2675000}}, // First Tangerine block
|
||||
{2674999, ID{Hash: checksumToBytes(0x7a64da13), Next: 2675000}}, // Last Tangerine block
|
||||
{2675000, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}}, // First Spurious block
|
||||
{4369999, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}}, // Last Spurious block
|
||||
{4370000, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}}, // First Byzantium block
|
||||
{7279999, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}}, // Last Byzantium block
|
||||
{7280000, ID{Hash: checksumToBytes(0x668db0af), Next: 9069000}}, // First and last Constantinople, first Petersburg block
|
||||
{9068999, ID{Hash: checksumToBytes(0x668db0af), Next: 9069000}}, // Last Petersburg block
|
||||
{9069000, ID{Hash: checksumToBytes(0x879d6e30), Next: 9200000}}, // First Istanbul and first Muir Glacier block
|
||||
{9199999, ID{Hash: checksumToBytes(0x879d6e30), Next: 9200000}}, // Last Istanbul and first Muir Glacier block
|
||||
{9200000, ID{Hash: checksumToBytes(0xe029e991), Next: 12244000}}, // First Muir Glacier block
|
||||
{12243999, ID{Hash: checksumToBytes(0xe029e991), Next: 12244000}}, // Last Muir Glacier block
|
||||
{12244000, ID{Hash: checksumToBytes(0x0eb440f6), Next: 12965000}}, // First Berlin block
|
||||
{12964999, ID{Hash: checksumToBytes(0x0eb440f6), Next: 12965000}}, // Last Berlin block
|
||||
{12965000, ID{Hash: checksumToBytes(0xb715077d), Next: 13773000}}, // First London block
|
||||
{13772999, ID{Hash: checksumToBytes(0xb715077d), Next: 13773000}}, // Last London block
|
||||
{13773000, ID{Hash: checksumToBytes(0x20c327fc), Next: 15050000}}, // First Arrow Glacier block
|
||||
{15049999, ID{Hash: checksumToBytes(0x20c327fc), Next: 15050000}}, // Last Arrow Glacier block
|
||||
{15050000, ID{Hash: checksumToBytes(0xf0afd0e3), Next: 18000000}}, // First Gray Glacier block
|
||||
{18000000, ID{Hash: checksumToBytes(0x4fb8a872), Next: 0}}, // First Merge Start block
|
||||
{20000000, ID{Hash: checksumToBytes(0x4fb8a872), Next: 0}}, // Future Merge Start block
|
||||
{0, 0, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}}, // Unsynced
|
||||
{1149999, 0, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}}, // Last Frontier block
|
||||
{1150000, 0, ID{Hash: checksumToBytes(0x97c2c34c), Next: 1920000}}, // First Homestead block
|
||||
{1919999, 0, ID{Hash: checksumToBytes(0x97c2c34c), Next: 1920000}}, // Last Homestead block
|
||||
{1920000, 0, ID{Hash: checksumToBytes(0x91d1f948), Next: 2463000}}, // First DAO block
|
||||
{2462999, 0, ID{Hash: checksumToBytes(0x91d1f948), Next: 2463000}}, // Last DAO block
|
||||
{2463000, 0, ID{Hash: checksumToBytes(0x7a64da13), Next: 2675000}}, // First Tangerine block
|
||||
{2674999, 0, ID{Hash: checksumToBytes(0x7a64da13), Next: 2675000}}, // Last Tangerine block
|
||||
{2675000, 0, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}}, // First Spurious block
|
||||
{4369999, 0, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}}, // Last Spurious block
|
||||
{4370000, 0, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}}, // First Byzantium block
|
||||
{7279999, 0, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}}, // Last Byzantium block
|
||||
{7280000, 0, ID{Hash: checksumToBytes(0x668db0af), Next: 9069000}}, // First and last Constantinople, first Petersburg block
|
||||
{9068999, 0, ID{Hash: checksumToBytes(0x668db0af), Next: 9069000}}, // Last Petersburg block
|
||||
{9069000, 0, ID{Hash: checksumToBytes(0x879d6e30), Next: 9200000}}, // First Istanbul and first Muir Glacier block
|
||||
{9199999, 0, ID{Hash: checksumToBytes(0x879d6e30), Next: 9200000}}, // Last Istanbul and first Muir Glacier block
|
||||
{9200000, 0, ID{Hash: checksumToBytes(0xe029e991), Next: 12244000}}, // First Muir Glacier block
|
||||
{12243999, 0, ID{Hash: checksumToBytes(0xe029e991), Next: 12244000}}, // Last Muir Glacier block
|
||||
{12244000, 0, ID{Hash: checksumToBytes(0x0eb440f6), Next: 12965000}}, // First Berlin block
|
||||
{12964999, 0, ID{Hash: checksumToBytes(0x0eb440f6), Next: 12965000}}, // Last Berlin block
|
||||
{12965000, 0, ID{Hash: checksumToBytes(0xb715077d), Next: 13773000}}, // First London block
|
||||
{13772999, 0, ID{Hash: checksumToBytes(0xb715077d), Next: 13773000}}, // Last London block
|
||||
{13773000, 0, ID{Hash: checksumToBytes(0x20c327fc), Next: 15050000}}, // First Arrow Glacier block
|
||||
{15049999, 0, ID{Hash: checksumToBytes(0x20c327fc), Next: 15050000}}, // Last Arrow Glacier block
|
||||
{15050000, 0, ID{Hash: checksumToBytes(0xf0afd0e3), Next: 1668000000}}, // First Gray Glacier block
|
||||
{19999999, 1667999999, ID{Hash: checksumToBytes(0xf0afd0e3), Next: 1668000000}}, // Last Gray Glacier block
|
||||
{20000000, 1668000000, ID{Hash: checksumToBytes(0x71147644), Next: 0}}, // First Shanghai block
|
||||
{20000000, 2668000000, ID{Hash: checksumToBytes(0x71147644), Next: 0}}, // Future Shanghai block
|
||||
},
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
for j, ttt := range tt.cases {
|
||||
if have := NewID(tt.config, tt.genesis, ttt.head); have != ttt.want {
|
||||
if have := NewID(tt.config, tt.genesis, ttt.head, ttt.time); have != ttt.want {
|
||||
t.Errorf("test %d, case %d: fork ID mismatch: have %x, want %x", i, j, have, ttt.want)
|
||||
}
|
||||
}
|
||||
@@ -195,79 +177,244 @@ func TestCreation(t *testing.T) {
|
||||
// TestValidation tests that a local peer correctly validates and accepts a remote
|
||||
// fork ID.
|
||||
func TestValidation(t *testing.T) {
|
||||
tests := []struct {
|
||||
head uint64
|
||||
id ID
|
||||
err error
|
||||
}{
|
||||
// Local is mainnet Petersburg, remote announces the same. No future fork is announced.
|
||||
{7987396, ID{Hash: checksumToBytes(0x668db0af), Next: 0}, nil},
|
||||
// Temporary non-existent scenario TODO(karalabe): delete when Shanghai is enabled
|
||||
timestampedConfig := *params.MainnetChainConfig
|
||||
timestampedConfig.ShanghaiTime = u64(1668000000)
|
||||
|
||||
// Local is mainnet Petersburg, remote announces the same. Remote also announces a next fork
|
||||
tests := []struct {
|
||||
config *params.ChainConfig
|
||||
head uint64
|
||||
time uint64
|
||||
id ID
|
||||
err error
|
||||
}{
|
||||
//------------------
|
||||
// Block based tests
|
||||
//------------------
|
||||
|
||||
// Local is mainnet Gray Glacier, remote announces the same. No future fork is announced.
|
||||
{params.MainnetChainConfig, 15050000, 0, ID{Hash: checksumToBytes(0xf0afd0e3), Next: 0}, nil},
|
||||
|
||||
// Local is mainnet Gray Glacier, remote announces the same. Remote also announces a next fork
|
||||
// at block 0xffffffff, but that is uncertain.
|
||||
{7987396, ID{Hash: checksumToBytes(0x668db0af), Next: math.MaxUint64}, nil},
|
||||
{params.MainnetChainConfig, 15050000, 0, ID{Hash: checksumToBytes(0xf0afd0e3), Next: math.MaxUint64}, nil},
|
||||
|
||||
// Local is mainnet currently in Byzantium only (so it's aware of Petersburg), remote announces
|
||||
// also Byzantium, but it's not yet aware of Petersburg (e.g. non updated node before the fork).
|
||||
// In this case we don't know if Petersburg passed yet or not.
|
||||
{7279999, ID{Hash: checksumToBytes(0xa00bc324), Next: 0}, nil},
|
||||
{params.MainnetChainConfig, 7279999, 0, ID{Hash: checksumToBytes(0xa00bc324), Next: 0}, nil},
|
||||
|
||||
// Local is mainnet currently in Byzantium only (so it's aware of Petersburg), remote announces
|
||||
// also Byzantium, and it's also aware of Petersburg (e.g. updated node before the fork). We
|
||||
// don't know if Petersburg passed yet (will pass) or not.
|
||||
{7279999, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}, nil},
|
||||
{params.MainnetChainConfig, 7279999, 0, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}, nil},
|
||||
|
||||
// Local is mainnet currently in Byzantium only (so it's aware of Petersburg), remote announces
|
||||
// also Byzantium, and it's also aware of some random fork (e.g. misconfigured Petersburg). As
|
||||
// neither forks passed at neither nodes, they may mismatch, but we still connect for now.
|
||||
{7279999, ID{Hash: checksumToBytes(0xa00bc324), Next: math.MaxUint64}, nil},
|
||||
{params.MainnetChainConfig, 7279999, 0, ID{Hash: checksumToBytes(0xa00bc324), Next: math.MaxUint64}, nil},
|
||||
|
||||
// Local is mainnet exactly on Petersburg, remote announces Byzantium + knowledge about Petersburg. Remote
|
||||
// is simply out of sync, accept.
|
||||
{7280000, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}, nil},
|
||||
{params.MainnetChainConfig, 7280000, 0, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}, nil},
|
||||
|
||||
// Local is mainnet Petersburg, remote announces Byzantium + knowledge about Petersburg. Remote
|
||||
// is simply out of sync, accept.
|
||||
{7987396, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}, nil},
|
||||
{params.MainnetChainConfig, 7987396, 0, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}, nil},
|
||||
|
||||
// Local is mainnet Petersburg, remote announces Spurious + knowledge about Byzantium. Remote
|
||||
// is definitely out of sync. It may or may not need the Petersburg update, we don't know yet.
|
||||
{7987396, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}, nil},
|
||||
{params.MainnetChainConfig, 7987396, 0, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}, nil},
|
||||
|
||||
// Local is mainnet Byzantium, remote announces Petersburg. Local is out of sync, accept.
|
||||
{7279999, ID{Hash: checksumToBytes(0x668db0af), Next: 0}, nil},
|
||||
{params.MainnetChainConfig, 7279999, 0, ID{Hash: checksumToBytes(0x668db0af), Next: 0}, nil},
|
||||
|
||||
// Local is mainnet Spurious, remote announces Byzantium, but is not aware of Petersburg. Local
|
||||
// out of sync. Local also knows about a future fork, but that is uncertain yet.
|
||||
{4369999, ID{Hash: checksumToBytes(0xa00bc324), Next: 0}, nil},
|
||||
{params.MainnetChainConfig, 4369999, 0, ID{Hash: checksumToBytes(0xa00bc324), Next: 0}, nil},
|
||||
|
||||
// Local is mainnet Petersburg. remote announces Byzantium but is not aware of further forks.
|
||||
// Remote needs software update.
|
||||
{7987396, ID{Hash: checksumToBytes(0xa00bc324), Next: 0}, ErrRemoteStale},
|
||||
{params.MainnetChainConfig, 7987396, 0, ID{Hash: checksumToBytes(0xa00bc324), Next: 0}, ErrRemoteStale},
|
||||
|
||||
// Local is mainnet Petersburg, and isn't aware of more forks. Remote announces Petersburg +
|
||||
// 0xffffffff. Local needs software update, reject.
|
||||
{7987396, ID{Hash: checksumToBytes(0x5cddc0e1), Next: 0}, ErrLocalIncompatibleOrStale},
|
||||
{params.MainnetChainConfig, 7987396, 0, ID{Hash: checksumToBytes(0x5cddc0e1), Next: 0}, ErrLocalIncompatibleOrStale},
|
||||
|
||||
// Local is mainnet Byzantium, and is aware of Petersburg. Remote announces Petersburg +
|
||||
// 0xffffffff. Local needs software update, reject.
|
||||
{7279999, ID{Hash: checksumToBytes(0x5cddc0e1), Next: 0}, ErrLocalIncompatibleOrStale},
|
||||
{params.MainnetChainConfig, 7279999, 0, ID{Hash: checksumToBytes(0x5cddc0e1), Next: 0}, ErrLocalIncompatibleOrStale},
|
||||
|
||||
// Local is mainnet Petersburg, remote is Rinkeby Petersburg.
|
||||
{7987396, ID{Hash: checksumToBytes(0xafec6b27), Next: 0}, ErrLocalIncompatibleOrStale},
|
||||
{params.MainnetChainConfig, 7987396, 0, ID{Hash: checksumToBytes(0xafec6b27), Next: 0}, ErrLocalIncompatibleOrStale},
|
||||
|
||||
// Local is mainnet Gray Glacier, far in the future. Remote announces Gopherium (non existing fork)
|
||||
// at some future block 88888888, for itself, but past block for local. Local is incompatible.
|
||||
//
|
||||
// This case detects non-upgraded nodes with majority hash power (typical Ropsten mess).
|
||||
{88888888, ID{Hash: checksumToBytes(0xf0afd0e3), Next: 88888888}, ErrLocalIncompatibleOrStale},
|
||||
//
|
||||
// TODO(karalabe): This testcase will fail once mainnet gets timestamped forks, make legacy chain config
|
||||
{params.MainnetChainConfig, 88888888, 0, ID{Hash: checksumToBytes(0xf0afd0e3), Next: 88888888}, ErrLocalIncompatibleOrStale},
|
||||
|
||||
// Local is mainnet Byzantium. Remote is also in Byzantium, but announces Gopherium (non existing
|
||||
// fork) at block 7279999, before Petersburg. Local is incompatible.
|
||||
{7279999, ID{Hash: checksumToBytes(0xa00bc324), Next: 7279999}, ErrLocalIncompatibleOrStale},
|
||||
//
|
||||
// TODO(karalabe): This testcase will fail once mainnet gets timestamped forks, make legacy chain config
|
||||
{params.MainnetChainConfig, 7279999, 0, ID{Hash: checksumToBytes(0xa00bc324), Next: 7279999}, ErrLocalIncompatibleOrStale},
|
||||
|
||||
//------------------------------------
|
||||
// Block to timestamp transition tests
|
||||
//------------------------------------
|
||||
|
||||
// Local is mainnet currently in Gray Glacier only (so it's aware of Shanghai), remote announces
|
||||
// also Gray Glacier, but it's not yet aware of Shanghai (e.g. non updated node before the fork).
|
||||
// In this case we don't know if Shanghai passed yet or not.
|
||||
{×tampedConfig, 15050000, 0, ID{Hash: checksumToBytes(0xf0afd0e3), Next: 0}, nil},
|
||||
|
||||
// Local is mainnet currently in Gray Glacier only (so it's aware of Shanghai), remote announces
|
||||
// also Gray Glacier, and it's also aware of Shanghai (e.g. updated node before the fork). We
|
||||
// don't know if Shanghai passed yet (will pass) or not.
|
||||
{×tampedConfig, 15050000, 0, ID{Hash: checksumToBytes(0xf0afd0e3), Next: 1668000000}, nil},
|
||||
|
||||
// Local is mainnet currently in Gray Glacier only (so it's aware of Shanghai), remote announces
|
||||
// also Gray Glacier, and it's also aware of some random fork (e.g. misconfigured Shanghai). As
|
||||
// neither forks passed at neither nodes, they may mismatch, but we still connect for now.
|
||||
{×tampedConfig, 15050000, 0, ID{Hash: checksumToBytes(0xf0afd0e3), Next: math.MaxUint64}, nil},
|
||||
|
||||
// Local is mainnet exactly on Shanghai, remote announces Gray Glacier + knowledge about Shanghai. Remote
|
||||
// is simply out of sync, accept.
|
||||
{×tampedConfig, 20000000, 1668000000, ID{Hash: checksumToBytes(0xf0afd0e3), Next: 1668000000}, nil},
|
||||
|
||||
// Local is mainnet Shanghai, remote announces Gray Glacier + knowledge about Shanghai. Remote
|
||||
// is simply out of sync, accept.
|
||||
{×tampedConfig, 20123456, 1668123456, ID{Hash: checksumToBytes(0xf0afd0e3), Next: 1668000000}, nil},
|
||||
|
||||
// Local is mainnet Shanghai, remote announces Arrow Glacier + knowledge about Gray Glacier. Remote
|
||||
// is definitely out of sync. It may or may not need the Shanghai update, we don't know yet.
|
||||
{×tampedConfig, 20000000, 1668000000, ID{Hash: checksumToBytes(0x20c327fc), Next: 15050000}, nil},
|
||||
|
||||
// Local is mainnet Gray Glacier, remote announces Shanghai. Local is out of sync, accept.
|
||||
{×tampedConfig, 15050000, 0, ID{Hash: checksumToBytes(0x71147644), Next: 0}, nil},
|
||||
|
||||
// Local is mainnet Arrow Glacier, remote announces Gray Glacier, but is not aware of Shanghai. Local
|
||||
// out of sync. Local also knows about a future fork, but that is uncertain yet.
|
||||
{×tampedConfig, 13773000, 0, ID{Hash: checksumToBytes(0xf0afd0e3), Next: 0}, nil},
|
||||
|
||||
// Local is mainnet Shanghai. remote announces Gray Glacier but is not aware of further forks.
|
||||
// Remote needs software update.
|
||||
{×tampedConfig, 20000000, 1668000000, ID{Hash: checksumToBytes(0xf0afd0e3), Next: 0}, ErrRemoteStale},
|
||||
|
||||
// Local is mainnet Gray Glacier, and isn't aware of more forks. Remote announces Gray Glacier +
|
||||
// 0xffffffff. Local needs software update, reject.
|
||||
{×tampedConfig, 15050000, 0, ID{Hash: checksumToBytes(checksumUpdate(0xf0afd0e3, math.MaxUint64)), Next: 0}, ErrLocalIncompatibleOrStale},
|
||||
|
||||
// Local is mainnet Gray Glacier, and is aware of Shanghai. Remote announces Shanghai +
|
||||
// 0xffffffff. Local needs software update, reject.
|
||||
{×tampedConfig, 15050000, 0, ID{Hash: checksumToBytes(checksumUpdate(0x71147644, math.MaxUint64)), Next: 0}, ErrLocalIncompatibleOrStale},
|
||||
|
||||
// Local is mainnet Gray Glacier, far in the future. Remote announces Gopherium (non existing fork)
|
||||
// at some future timestamp 8888888888, for itself, but past block for local. Local is incompatible.
|
||||
//
|
||||
// This case detects non-upgraded nodes with majority hash power (typical Ropsten mess).
|
||||
{params.MainnetChainConfig, 888888888, 1660000000, ID{Hash: checksumToBytes(0xf0afd0e3), Next: 1660000000}, ErrLocalIncompatibleOrStale},
|
||||
|
||||
// Local is mainnet Gray Glacier. Remote is also in Gray Glacier, but announces Gopherium (non existing
|
||||
// fork) at block 7279999, before Shanghai. Local is incompatible.
|
||||
{×tampedConfig, 19999999, 1667999999, ID{Hash: checksumToBytes(0xf0afd0e3), Next: 1667999999}, ErrLocalIncompatibleOrStale},
|
||||
|
||||
//----------------------
|
||||
// Timestamp based tests
|
||||
//----------------------
|
||||
|
||||
// Local is mainnet Shanghai, remote announces the same. No future fork is announced.
|
||||
{×tampedConfig, 20000000, 1668000000, ID{Hash: checksumToBytes(0x71147644), Next: 0}, nil},
|
||||
|
||||
// Local is mainnet Shanghai, remote announces the same. Remote also announces a next fork
|
||||
// at time 0xffffffff, but that is uncertain.
|
||||
{×tampedConfig, 20000000, 1668000000, ID{Hash: checksumToBytes(0x71147644), Next: math.MaxUint64}, nil},
|
||||
|
||||
// Local is mainnet currently in Shanghai only (so it's aware of Cancun), remote announces
|
||||
// also Shanghai, but it's not yet aware of Cancun (e.g. non updated node before the fork).
|
||||
// In this case we don't know if Cancun passed yet or not.
|
||||
//
|
||||
// TODO(karalabe): Enable this when Cancun is specced
|
||||
//{×tampedConfig, 20000000, 1668000000, ID{Hash: checksumToBytes(0x71147644), Next: 0}, nil},
|
||||
|
||||
// Local is mainnet currently in Shanghai only (so it's aware of Cancun), remote announces
|
||||
// also Shanghai, and it's also aware of Cancun (e.g. updated node before the fork). We
|
||||
// don't know if Cancun passed yet (will pass) or not.
|
||||
//
|
||||
// TODO(karalabe): Enable this when Cancun is specced and update next timestamp
|
||||
//{×tampedConfig, 20000000, 1668000000, ID{Hash: checksumToBytes(0x71147644), Next: 1678000000}, nil},
|
||||
|
||||
// Local is mainnet currently in Shanghai only (so it's aware of Cancun), remote announces
|
||||
// also Shanghai, and it's also aware of some random fork (e.g. misconfigured Cancun). As
|
||||
// neither forks passed at neither nodes, they may mismatch, but we still connect for now.
|
||||
//
|
||||
// TODO(karalabe): Enable this when Cancun is specced
|
||||
//{×tampedConfig, 20000000, 1668000000, ID{Hash: checksumToBytes(0x71147644), Next: math.MaxUint64}, nil},
|
||||
|
||||
// Local is mainnet exactly on Cancun, remote announces Shanghai + knowledge about Cancun. Remote
|
||||
// is simply out of sync, accept.
|
||||
//
|
||||
// TODO(karalabe): Enable this when Cancun is specced, update local head and time, next timestamp
|
||||
// {×tampedConfig, 21000000, 1678000000, ID{Hash: checksumToBytes(0x71147644), Next: 1678000000}, nil},
|
||||
|
||||
// Local is mainnet Cancun, remote announces Shanghai + knowledge about Cancun. Remote
|
||||
// is simply out of sync, accept.
|
||||
// TODO(karalabe): Enable this when Cancun is specced, update local head and time, next timestamp
|
||||
//{×tampedConfig, 21123456, 1678123456, ID{Hash: checksumToBytes(0x71147644), Next: 1678000000}, nil},
|
||||
|
||||
// Local is mainnet Prague, remote announces Shanghai + knowledge about Cancun. Remote
|
||||
// is definitely out of sync. It may or may not need the Prague update, we don't know yet.
|
||||
//
|
||||
// TODO(karalabe): Enable this when Cancun **and** Prague is specced, update all the numbers
|
||||
//{×tampedConfig, 0, 0, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}, nil},
|
||||
|
||||
// Local is mainnet Shanghai, remote announces Cancun. Local is out of sync, accept.
|
||||
//
|
||||
// TODO(karalabe): Enable this when Cancun is specced, update remote checksum
|
||||
//{×tampedConfig, 21000000, 1678000000, ID{Hash: checksumToBytes(0x00000000), Next: 0}, nil},
|
||||
|
||||
// Local is mainnet Shanghai, remote announces Cancun, but is not aware of Prague. Local
|
||||
// out of sync. Local also knows about a future fork, but that is uncertain yet.
|
||||
//
|
||||
// TODO(karalabe): Enable this when Cancun **and** Prague is specced, update remote checksum
|
||||
//{×tampedConfig, 21000000, 1678000000, ID{Hash: checksumToBytes(0x00000000), Next: 0}, nil},
|
||||
|
||||
// Local is mainnet Cancun. remote announces Shanghai but is not aware of further forks.
|
||||
// Remote needs software update.
|
||||
//
|
||||
// TODO(karalabe): Enable this when Cancun is specced, update local head and time
|
||||
//{×tampedConfig, 21000000, 1678000000, ID{Hash: checksumToBytes(0x71147644), Next: 0}, ErrRemoteStale},
|
||||
|
||||
// Local is mainnet Shanghai, and isn't aware of more forks. Remote announces Shanghai +
|
||||
// 0xffffffff. Local needs software update, reject.
|
||||
{×tampedConfig, 20000000, 1668000000, ID{Hash: checksumToBytes(checksumUpdate(0x71147644, math.MaxUint64)), Next: 0}, ErrLocalIncompatibleOrStale},
|
||||
|
||||
// Local is mainnet Shanghai, and is aware of Cancun. Remote announces Cancun +
|
||||
// 0xffffffff. Local needs software update, reject.
|
||||
//
|
||||
// TODO(karalabe): Enable this when Cancun is specced, update remote checksum
|
||||
//{×tampedConfig, 20000000, 1668000000, ID{Hash: checksumToBytes(checksumUpdate(0x00000000, math.MaxUint64)), Next: 0}, ErrLocalIncompatibleOrStale},
|
||||
|
||||
// Local is mainnet Shanghai, remote is random Shanghai.
|
||||
{×tampedConfig, 20000000, 1668000000, ID{Hash: checksumToBytes(0x12345678), Next: 0}, ErrLocalIncompatibleOrStale},
|
||||
|
||||
// Local is mainnet Shanghai, far in the future. Remote announces Gopherium (non existing fork)
|
||||
// at some future timestamp 8888888888, for itself, but past block for local. Local is incompatible.
|
||||
//
|
||||
// This case detects non-upgraded nodes with majority hash power (typical Ropsten mess).
|
||||
{×tampedConfig, 88888888, 8888888888, ID{Hash: checksumToBytes(0x71147644), Next: 8888888888}, ErrLocalIncompatibleOrStale},
|
||||
|
||||
// Local is mainnet Shanghai. Remote is also in Shanghai, but announces Gopherium (non existing
|
||||
// fork) at timestamp 1668000000, before Cancun. Local is incompatible.
|
||||
//
|
||||
// TODO(karalabe): Enable this when Cancun is specced
|
||||
//{params.MainnetChainConfig, 20999999, 1677999999, ID{Hash: checksumToBytes(0x71147644), Next: 1678000000}, ErrLocalIncompatibleOrStale},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
filter := newFilter(params.MainnetChainConfig, params.MainnetGenesisHash, func() uint64 { return tt.head })
|
||||
filter := newFilter(tt.config, params.MainnetGenesisHash, func() (uint64, uint64) { return tt.head, tt.time })
|
||||
if err := filter(tt.id); err != tt.err {
|
||||
t.Errorf("test %d: validation error mismatch: have %v, want %v", i, err, tt.err)
|
||||
}
|
||||
|
||||
+119
-64
@@ -65,6 +65,41 @@ type Genesis struct {
|
||||
BaseFee *big.Int `json:"baseFeePerGas"`
|
||||
}
|
||||
|
||||
func ReadGenesis(db ethdb.Database) (*Genesis, error) {
|
||||
var genesis Genesis
|
||||
stored := rawdb.ReadCanonicalHash(db, 0)
|
||||
if (stored == common.Hash{}) {
|
||||
return nil, fmt.Errorf("invalid genesis hash in database: %x", stored)
|
||||
}
|
||||
blob := rawdb.ReadGenesisStateSpec(db, stored)
|
||||
if blob == nil {
|
||||
return nil, fmt.Errorf("genesis state missing from db")
|
||||
}
|
||||
if len(blob) != 0 {
|
||||
if err := genesis.Alloc.UnmarshalJSON(blob); err != nil {
|
||||
return nil, fmt.Errorf("could not unmarshal genesis state json: %s", err)
|
||||
}
|
||||
}
|
||||
genesis.Config = rawdb.ReadChainConfig(db, stored)
|
||||
if genesis.Config == nil {
|
||||
return nil, fmt.Errorf("genesis config missing from db")
|
||||
}
|
||||
genesisBlock := rawdb.ReadBlock(db, stored, 0)
|
||||
if genesisBlock == nil {
|
||||
return nil, fmt.Errorf("genesis block missing from db")
|
||||
}
|
||||
genesisHeader := genesisBlock.Header()
|
||||
genesis.Nonce = genesisHeader.Nonce.Uint64()
|
||||
genesis.Timestamp = genesisHeader.Time
|
||||
genesis.ExtraData = genesisHeader.Extra
|
||||
genesis.GasLimit = genesisHeader.GasLimit
|
||||
genesis.Difficulty = genesisHeader.Difficulty
|
||||
genesis.Mixhash = genesisHeader.MixDigest
|
||||
genesis.Coinbase = genesisHeader.Coinbase
|
||||
|
||||
return &genesis, nil
|
||||
}
|
||||
|
||||
// GenesisAlloc specifies the initial state that is part of the genesis block.
|
||||
type GenesisAlloc map[common.Address]GenesisAccount
|
||||
|
||||
@@ -103,8 +138,8 @@ func (ga *GenesisAlloc) deriveHash() (common.Hash, error) {
|
||||
// flush is very similar with deriveHash, but the main difference is
|
||||
// all the generated states will be persisted into the given database.
|
||||
// Also, the genesis state specification will be flushed as well.
|
||||
func (ga *GenesisAlloc) flush(db ethdb.Database) error {
|
||||
statedb, err := state.New(common.Hash{}, state.NewDatabaseWithConfig(db, &trie.Config{Preimages: true}), nil)
|
||||
func (ga *GenesisAlloc) flush(db ethdb.Database, triedb *trie.Database) error {
|
||||
statedb, err := state.New(common.Hash{}, state.NewDatabaseWithNodeDB(db, triedb), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -120,9 +155,11 @@ func (ga *GenesisAlloc) flush(db ethdb.Database) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = statedb.Database().TrieDB().Commit(root, true, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
// Commit newly generated states into disk if it's not empty.
|
||||
if root != types.EmptyRootHash {
|
||||
if err := triedb.Commit(root, true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Marshal the genesis state specification and persist.
|
||||
blob, err := json.Marshal(ga)
|
||||
@@ -134,8 +171,8 @@ func (ga *GenesisAlloc) flush(db ethdb.Database) error {
|
||||
}
|
||||
|
||||
// CommitGenesisState loads the stored genesis state with the given block
|
||||
// hash and commits them into the given database handler.
|
||||
func CommitGenesisState(db ethdb.Database, hash common.Hash) error {
|
||||
// hash and commits it into the provided trie database.
|
||||
func CommitGenesisState(db ethdb.Database, triedb *trie.Database, hash common.Hash) error {
|
||||
var alloc GenesisAlloc
|
||||
blob := rawdb.ReadGenesisStateSpec(db, hash)
|
||||
if len(blob) != 0 {
|
||||
@@ -152,8 +189,6 @@ func CommitGenesisState(db ethdb.Database, hash common.Hash) error {
|
||||
switch hash {
|
||||
case params.MainnetGenesisHash:
|
||||
genesis = DefaultGenesisBlock()
|
||||
case params.RopstenGenesisHash:
|
||||
genesis = DefaultRopstenGenesisBlock()
|
||||
case params.RinkebyGenesisHash:
|
||||
genesis = DefaultRinkebyGenesisBlock()
|
||||
case params.GoerliGenesisHash:
|
||||
@@ -167,7 +202,7 @@ func CommitGenesisState(db ethdb.Database, hash common.Hash) error {
|
||||
return errors.New("not found")
|
||||
}
|
||||
}
|
||||
return alloc.flush(db)
|
||||
return alloc.flush(db, triedb)
|
||||
}
|
||||
|
||||
// GenesisAccount is an account in the state of the genesis block.
|
||||
@@ -211,7 +246,6 @@ func (h *storageJSON) UnmarshalText(text []byte) error {
|
||||
}
|
||||
offset := len(h) - len(text)/2 // pad on the left
|
||||
if _, err := hex.Decode(h[offset:], text); err != nil {
|
||||
fmt.Println(err)
|
||||
return fmt.Errorf("invalid hex storage key/value %q", text)
|
||||
}
|
||||
return nil
|
||||
@@ -231,39 +265,39 @@ func (e *GenesisMismatchError) Error() string {
|
||||
return fmt.Sprintf("database contains incompatible genesis (have %x, new %x)", e.Stored, e.New)
|
||||
}
|
||||
|
||||
// ChainOverrides contains the changes to chain config.
|
||||
type ChainOverrides struct {
|
||||
OverrideShanghai *uint64
|
||||
}
|
||||
|
||||
// SetupGenesisBlock writes or updates the genesis block in db.
|
||||
// The block that will be used is:
|
||||
//
|
||||
// genesis == nil genesis != nil
|
||||
// +------------------------------------------
|
||||
// db has no genesis | main-net default | genesis
|
||||
// db has genesis | from DB | genesis (if compatible)
|
||||
// genesis == nil genesis != nil
|
||||
// +------------------------------------------
|
||||
// db has no genesis | main-net default | genesis
|
||||
// db has genesis | from DB | genesis (if compatible)
|
||||
//
|
||||
// The stored chain configuration will be updated if it is compatible (i.e. does not
|
||||
// specify a fork block below the local head block). In case of a conflict, the
|
||||
// error is a *params.ConfigCompatError and the new, unwritten config is returned.
|
||||
//
|
||||
// The returned chain configuration is never nil.
|
||||
func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig, common.Hash, error) {
|
||||
return SetupGenesisBlockWithOverride(db, genesis, nil, nil)
|
||||
func SetupGenesisBlock(db ethdb.Database, triedb *trie.Database, genesis *Genesis) (*params.ChainConfig, common.Hash, error) {
|
||||
return SetupGenesisBlockWithOverride(db, triedb, genesis, nil)
|
||||
}
|
||||
|
||||
func SetupGenesisBlockWithOverride(db ethdb.Database, genesis *Genesis, overrideTerminalTotalDifficulty *big.Int, overrideTerminalTotalDifficultyPassed *bool) (*params.ChainConfig, common.Hash, error) {
|
||||
func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *trie.Database, genesis *Genesis, overrides *ChainOverrides) (*params.ChainConfig, common.Hash, error) {
|
||||
if genesis != nil && genesis.Config == nil {
|
||||
return params.AllEthashProtocolChanges, common.Hash{}, errGenesisNoConfig
|
||||
}
|
||||
|
||||
applyOverrides := func(config *params.ChainConfig) {
|
||||
if config != nil {
|
||||
if overrideTerminalTotalDifficulty != nil {
|
||||
config.TerminalTotalDifficulty = overrideTerminalTotalDifficulty
|
||||
}
|
||||
if overrideTerminalTotalDifficultyPassed != nil {
|
||||
config.TerminalTotalDifficultyPassed = *overrideTerminalTotalDifficultyPassed
|
||||
if overrides != nil && overrides.OverrideShanghai != nil {
|
||||
config.ShanghaiTime = overrides.OverrideShanghai
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Just commit the new block if there is no stored genesis block.
|
||||
stored := rawdb.ReadCanonicalHash(db, 0)
|
||||
if (stored == common.Hash{}) {
|
||||
@@ -273,7 +307,7 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, genesis *Genesis, override
|
||||
} else {
|
||||
log.Info("Writing custom genesis block")
|
||||
}
|
||||
block, err := genesis.Commit(db)
|
||||
block, err := genesis.Commit(db, triedb)
|
||||
if err != nil {
|
||||
return genesis.Config, common.Hash{}, err
|
||||
}
|
||||
@@ -283,7 +317,7 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, genesis *Genesis, override
|
||||
// We have the genesis block in database(perhaps in ancient database)
|
||||
// but the corresponding state is missing.
|
||||
header := rawdb.ReadHeader(db, stored, 0)
|
||||
if _, err := state.New(header.Root, state.NewDatabaseWithConfig(db, nil), nil); err != nil {
|
||||
if header.Root != types.EmptyRootHash && !rawdb.HasLegacyTrieNode(db, header.Root) {
|
||||
if genesis == nil {
|
||||
genesis = DefaultGenesisBlock()
|
||||
}
|
||||
@@ -292,7 +326,7 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, genesis *Genesis, override
|
||||
if hash != stored {
|
||||
return genesis.Config, hash, &GenesisMismatchError{stored, hash}
|
||||
}
|
||||
block, err := genesis.Commit(db)
|
||||
block, err := genesis.Commit(db, triedb)
|
||||
if err != nil {
|
||||
return genesis.Config, hash, err
|
||||
}
|
||||
@@ -318,6 +352,7 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, genesis *Genesis, override
|
||||
rawdb.WriteChainConfig(db, stored, newcfg)
|
||||
return newcfg, stored, nil
|
||||
}
|
||||
storedData, _ := json.Marshal(storedcfg)
|
||||
// Special case: if a private network is being used (no genesis and also no
|
||||
// mainnet hash in the database), we must not apply the `configOrDefault`
|
||||
// chain config as that would be AllProtocolChanges (applying any new fork
|
||||
@@ -329,34 +364,69 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, genesis *Genesis, override
|
||||
}
|
||||
// Check config compatibility and write the config. Compatibility errors
|
||||
// are returned to the caller unless we're already at block zero.
|
||||
height := rawdb.ReadHeaderNumber(db, rawdb.ReadHeadHeaderHash(db))
|
||||
if height == nil {
|
||||
return newcfg, stored, fmt.Errorf("missing block number for head header hash")
|
||||
head := rawdb.ReadHeadHeader(db)
|
||||
if head == nil {
|
||||
return newcfg, stored, fmt.Errorf("missing head header")
|
||||
}
|
||||
compatErr := storedcfg.CheckCompatible(newcfg, *height)
|
||||
if compatErr != nil && *height != 0 && compatErr.RewindTo != 0 {
|
||||
compatErr := storedcfg.CheckCompatible(newcfg, head.Number.Uint64(), head.Time)
|
||||
if compatErr != nil && ((head.Number.Uint64() != 0 && compatErr.RewindToBlock != 0) || (head.Time != 0 && compatErr.RewindToTime != 0)) {
|
||||
return newcfg, stored, compatErr
|
||||
}
|
||||
rawdb.WriteChainConfig(db, stored, newcfg)
|
||||
// Don't overwrite if the old is identical to the new
|
||||
if newData, _ := json.Marshal(newcfg); !bytes.Equal(storedData, newData) {
|
||||
rawdb.WriteChainConfig(db, stored, newcfg)
|
||||
}
|
||||
return newcfg, stored, nil
|
||||
}
|
||||
|
||||
// LoadCliqueConfig loads the stored clique config if the chain config
|
||||
// is already present in database, otherwise, return the config in the
|
||||
// provided genesis specification. Note the returned clique config can
|
||||
// be nil if we are not in the clique network.
|
||||
func LoadCliqueConfig(db ethdb.Database, genesis *Genesis) (*params.CliqueConfig, error) {
|
||||
// Load the stored chain config from the database. It can be nil
|
||||
// in case the database is empty. Notably, we only care about the
|
||||
// chain config corresponds to the canonical chain.
|
||||
stored := rawdb.ReadCanonicalHash(db, 0)
|
||||
if stored != (common.Hash{}) {
|
||||
storedcfg := rawdb.ReadChainConfig(db, stored)
|
||||
if storedcfg != nil {
|
||||
return storedcfg.Clique, nil
|
||||
}
|
||||
}
|
||||
// Load the clique config from the provided genesis specification.
|
||||
if genesis != nil {
|
||||
// Reject invalid genesis spec without valid chain config
|
||||
if genesis.Config == nil {
|
||||
return nil, errGenesisNoConfig
|
||||
}
|
||||
// If the canonical genesis header is present, but the chain
|
||||
// config is missing(initialize the empty leveldb with an
|
||||
// external ancient chain segment), ensure the provided genesis
|
||||
// is matched.
|
||||
if stored != (common.Hash{}) && genesis.ToBlock().Hash() != stored {
|
||||
return nil, &GenesisMismatchError{stored, genesis.ToBlock().Hash()}
|
||||
}
|
||||
return genesis.Config.Clique, nil
|
||||
}
|
||||
// There is no stored chain config and no new config provided,
|
||||
// In this case the default chain config(mainnet) will be used,
|
||||
// namely ethash is the specified consensus engine, return nil.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (g *Genesis) configOrDefault(ghash common.Hash) *params.ChainConfig {
|
||||
switch {
|
||||
case g != nil:
|
||||
return g.Config
|
||||
case ghash == params.MainnetGenesisHash:
|
||||
return params.MainnetChainConfig
|
||||
case ghash == params.RopstenGenesisHash:
|
||||
return params.RopstenChainConfig
|
||||
case ghash == params.SepoliaGenesisHash:
|
||||
return params.SepoliaChainConfig
|
||||
case ghash == params.RinkebyGenesisHash:
|
||||
return params.RinkebyChainConfig
|
||||
case ghash == params.GoerliGenesisHash:
|
||||
return params.GoerliChainConfig
|
||||
case ghash == params.KilnGenesisHash:
|
||||
return DefaultKilnGenesisBlock().Config
|
||||
default:
|
||||
return params.AllEthashProtocolChanges
|
||||
}
|
||||
@@ -395,12 +465,17 @@ func (g *Genesis) ToBlock() *types.Block {
|
||||
head.BaseFee = new(big.Int).SetUint64(params.InitialBaseFee)
|
||||
}
|
||||
}
|
||||
return types.NewBlock(head, nil, nil, nil, trie.NewStackTrie(nil))
|
||||
var withdrawals []*types.Withdrawal
|
||||
if g.Config != nil && g.Config.IsShanghai(g.Timestamp) {
|
||||
head.WithdrawalsHash = &types.EmptyRootHash
|
||||
withdrawals = make([]*types.Withdrawal, 0)
|
||||
}
|
||||
return types.NewBlock(head, nil, nil, nil, trie.NewStackTrie(nil)).WithWithdrawals(withdrawals)
|
||||
}
|
||||
|
||||
// Commit writes the block and state of a genesis specification to the database.
|
||||
// The block is committed as the canonical head block.
|
||||
func (g *Genesis) Commit(db ethdb.Database) (*types.Block, error) {
|
||||
func (g *Genesis) Commit(db ethdb.Database, triedb *trie.Database) (*types.Block, error) {
|
||||
block := g.ToBlock()
|
||||
if block.Number().Sign() != 0 {
|
||||
return nil, errors.New("can't commit genesis block with number > 0")
|
||||
@@ -418,7 +493,7 @@ func (g *Genesis) Commit(db ethdb.Database) (*types.Block, error) {
|
||||
// All the checks has passed, flush the states derived from the genesis
|
||||
// specification as well as the specification itself into the provided
|
||||
// database.
|
||||
if err := g.Alloc.flush(db); err != nil {
|
||||
if err := g.Alloc.flush(db, triedb); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rawdb.WriteTd(db, block.Hash(), block.NumberU64(), block.Difficulty())
|
||||
@@ -434,8 +509,10 @@ func (g *Genesis) Commit(db ethdb.Database) (*types.Block, error) {
|
||||
|
||||
// MustCommit writes the genesis block and state to db, panicking on error.
|
||||
// The block is committed as the canonical head block.
|
||||
// Note the state changes will be committed in hash-based scheme, use Commit
|
||||
// if path-scheme is preferred.
|
||||
func (g *Genesis) MustCommit(db ethdb.Database) *types.Block {
|
||||
block, err := g.Commit(db)
|
||||
block, err := g.Commit(db, trie.NewDatabase(db))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -454,18 +531,6 @@ func DefaultGenesisBlock() *Genesis {
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultRopstenGenesisBlock returns the Ropsten network genesis block.
|
||||
func DefaultRopstenGenesisBlock() *Genesis {
|
||||
return &Genesis{
|
||||
Config: params.RopstenChainConfig,
|
||||
Nonce: 66,
|
||||
ExtraData: hexutil.MustDecode("0x3535353535353535353535353535353535353535353535353535353535353535"),
|
||||
GasLimit: 16777216,
|
||||
Difficulty: big.NewInt(1048576),
|
||||
Alloc: decodePrealloc(ropstenAllocData),
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultRinkebyGenesisBlock returns the Rinkeby network genesis block.
|
||||
func DefaultRinkebyGenesisBlock() *Genesis {
|
||||
return &Genesis{
|
||||
@@ -503,16 +568,6 @@ func DefaultSepoliaGenesisBlock() *Genesis {
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultKilnGenesisBlock returns the kiln network genesis block.
|
||||
func DefaultKilnGenesisBlock() *Genesis {
|
||||
g := new(Genesis)
|
||||
reader := strings.NewReader(KilnAllocData)
|
||||
if err := json.NewDecoder(reader).Decode(g); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return g
|
||||
}
|
||||
|
||||
// DeveloperGenesisBlock returns the 'geth --dev' genesis block.
|
||||
func DeveloperGenesisBlock(period uint64, gasLimit uint64, faucet common.Address) *Genesis {
|
||||
// Override the default period to the user requested one
|
||||
|
||||
File diff suppressed because one or more lines are too long
+24
-20
@@ -17,6 +17,7 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"testing"
|
||||
@@ -28,12 +29,14 @@ import (
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
)
|
||||
|
||||
func TestInvalidCliqueConfig(t *testing.T) {
|
||||
block := DefaultGoerliGenesisBlock()
|
||||
block.ExtraData = []byte{}
|
||||
if _, err := block.Commit(nil); err == nil {
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
if _, err := block.Commit(db, trie.NewDatabase(db)); err == nil {
|
||||
t.Fatal("Expected error on invalid clique config")
|
||||
}
|
||||
}
|
||||
@@ -60,7 +63,7 @@ func TestSetupGenesis(t *testing.T) {
|
||||
{
|
||||
name: "genesis without ChainConfig",
|
||||
fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) {
|
||||
return SetupGenesisBlock(db, new(Genesis))
|
||||
return SetupGenesisBlock(db, trie.NewDatabase(db), new(Genesis))
|
||||
},
|
||||
wantErr: errGenesisNoConfig,
|
||||
wantConfig: params.AllEthashProtocolChanges,
|
||||
@@ -68,7 +71,7 @@ func TestSetupGenesis(t *testing.T) {
|
||||
{
|
||||
name: "no block in DB, genesis == nil",
|
||||
fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) {
|
||||
return SetupGenesisBlock(db, nil)
|
||||
return SetupGenesisBlock(db, trie.NewDatabase(db), nil)
|
||||
},
|
||||
wantHash: params.MainnetGenesisHash,
|
||||
wantConfig: params.MainnetChainConfig,
|
||||
@@ -77,7 +80,7 @@ func TestSetupGenesis(t *testing.T) {
|
||||
name: "mainnet block in DB, genesis == nil",
|
||||
fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) {
|
||||
DefaultGenesisBlock().MustCommit(db)
|
||||
return SetupGenesisBlock(db, nil)
|
||||
return SetupGenesisBlock(db, trie.NewDatabase(db), nil)
|
||||
},
|
||||
wantHash: params.MainnetGenesisHash,
|
||||
wantConfig: params.MainnetChainConfig,
|
||||
@@ -86,26 +89,26 @@ func TestSetupGenesis(t *testing.T) {
|
||||
name: "custom block in DB, genesis == nil",
|
||||
fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) {
|
||||
customg.MustCommit(db)
|
||||
return SetupGenesisBlock(db, nil)
|
||||
return SetupGenesisBlock(db, trie.NewDatabase(db), nil)
|
||||
},
|
||||
wantHash: customghash,
|
||||
wantConfig: customg.Config,
|
||||
},
|
||||
{
|
||||
name: "custom block in DB, genesis == ropsten",
|
||||
name: "custom block in DB, genesis == goerli",
|
||||
fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) {
|
||||
customg.MustCommit(db)
|
||||
return SetupGenesisBlock(db, DefaultRopstenGenesisBlock())
|
||||
return SetupGenesisBlock(db, trie.NewDatabase(db), DefaultGoerliGenesisBlock())
|
||||
},
|
||||
wantErr: &GenesisMismatchError{Stored: customghash, New: params.RopstenGenesisHash},
|
||||
wantHash: params.RopstenGenesisHash,
|
||||
wantConfig: params.RopstenChainConfig,
|
||||
wantErr: &GenesisMismatchError{Stored: customghash, New: params.GoerliGenesisHash},
|
||||
wantHash: params.GoerliGenesisHash,
|
||||
wantConfig: params.GoerliChainConfig,
|
||||
},
|
||||
{
|
||||
name: "compatible config in DB",
|
||||
fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) {
|
||||
oldcustomg.MustCommit(db)
|
||||
return SetupGenesisBlock(db, &customg)
|
||||
return SetupGenesisBlock(db, trie.NewDatabase(db), &customg)
|
||||
},
|
||||
wantHash: customghash,
|
||||
wantConfig: customg.Config,
|
||||
@@ -117,22 +120,22 @@ func TestSetupGenesis(t *testing.T) {
|
||||
// Advance to block #4, past the homestead transition block of customg.
|
||||
genesis := oldcustomg.MustCommit(db)
|
||||
|
||||
bc, _ := NewBlockChain(db, nil, oldcustomg.Config, ethash.NewFullFaker(), vm.Config{}, nil, nil)
|
||||
bc, _ := NewBlockChain(db, nil, &oldcustomg, nil, ethash.NewFullFaker(), vm.Config{}, nil, nil)
|
||||
defer bc.Stop()
|
||||
|
||||
blocks, _ := GenerateChain(oldcustomg.Config, genesis, ethash.NewFaker(), db, 4, nil)
|
||||
bc.InsertChain(blocks)
|
||||
bc.CurrentBlock()
|
||||
|
||||
// This should return a compatibility error.
|
||||
return SetupGenesisBlock(db, &customg)
|
||||
return SetupGenesisBlock(db, trie.NewDatabase(db), &customg)
|
||||
},
|
||||
wantHash: customghash,
|
||||
wantConfig: customg.Config,
|
||||
wantErr: ¶ms.ConfigCompatError{
|
||||
What: "Homestead fork block",
|
||||
StoredConfig: big.NewInt(2),
|
||||
NewConfig: big.NewInt(3),
|
||||
RewindTo: 1,
|
||||
What: "Homestead fork block",
|
||||
StoredBlock: big.NewInt(2),
|
||||
NewBlock: big.NewInt(3),
|
||||
RewindToBlock: 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -169,7 +172,6 @@ func TestGenesisHashes(t *testing.T) {
|
||||
}{
|
||||
{DefaultGenesisBlock(), params.MainnetGenesisHash},
|
||||
{DefaultGoerliGenesisBlock(), params.GoerliGenesisHash},
|
||||
{DefaultRopstenGenesisBlock(), params.RopstenGenesisHash},
|
||||
{DefaultRinkebyGenesisBlock(), params.RinkebyGenesisHash},
|
||||
{DefaultSepoliaGenesisBlock(), params.SepoliaGenesisHash},
|
||||
} {
|
||||
@@ -193,6 +195,7 @@ func TestGenesis_Commit(t *testing.T) {
|
||||
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
genesisBlock := genesis.MustCommit(db)
|
||||
|
||||
if genesis.Difficulty != nil {
|
||||
t.Fatalf("assumption wrong")
|
||||
}
|
||||
@@ -219,7 +222,8 @@ func TestReadWriteGenesisAlloc(t *testing.T) {
|
||||
}
|
||||
hash, _ = alloc.deriveHash()
|
||||
)
|
||||
alloc.flush(db)
|
||||
blob, _ := json.Marshal(alloc)
|
||||
rawdb.WriteGenesisStateSpec(db, hash, blob)
|
||||
|
||||
var reload GenesisAlloc
|
||||
err := reload.UnmarshalJSON(rawdb.ReadGenesisStateSpec(db, hash))
|
||||
|
||||
+49
-24
@@ -27,6 +27,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/lru"
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
@@ -34,7 +35,6 @@ import (
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -64,9 +64,9 @@ type HeaderChain struct {
|
||||
currentHeader atomic.Value // Current head of the header chain (may be above the block chain!)
|
||||
currentHeaderHash common.Hash // Hash of the current head of the header chain (prevent recomputing all the time)
|
||||
|
||||
headerCache *lru.Cache // Cache for the most recent block headers
|
||||
tdCache *lru.Cache // Cache for the most recent block total difficulties
|
||||
numberCache *lru.Cache // Cache for the most recent block numbers
|
||||
headerCache *lru.Cache[common.Hash, *types.Header]
|
||||
tdCache *lru.Cache[common.Hash, *big.Int] // most recent total difficulties
|
||||
numberCache *lru.Cache[common.Hash, uint64] // most recent block numbers
|
||||
|
||||
procInterrupt func() bool
|
||||
|
||||
@@ -77,10 +77,6 @@ type HeaderChain struct {
|
||||
// NewHeaderChain creates a new HeaderChain structure. ProcInterrupt points
|
||||
// to the parent's interrupt semaphore.
|
||||
func NewHeaderChain(chainDb ethdb.Database, config *params.ChainConfig, engine consensus.Engine, procInterrupt func() bool) (*HeaderChain, error) {
|
||||
headerCache, _ := lru.New(headerCacheLimit)
|
||||
tdCache, _ := lru.New(tdCacheLimit)
|
||||
numberCache, _ := lru.New(numberCacheLimit)
|
||||
|
||||
// Seed a fast but crypto originating random generator
|
||||
seed, err := crand.Int(crand.Reader, big.NewInt(math.MaxInt64))
|
||||
if err != nil {
|
||||
@@ -89,9 +85,9 @@ func NewHeaderChain(chainDb ethdb.Database, config *params.ChainConfig, engine c
|
||||
hc := &HeaderChain{
|
||||
config: config,
|
||||
chainDb: chainDb,
|
||||
headerCache: headerCache,
|
||||
tdCache: tdCache,
|
||||
numberCache: numberCache,
|
||||
headerCache: lru.NewCache[common.Hash, *types.Header](headerCacheLimit),
|
||||
tdCache: lru.NewCache[common.Hash, *big.Int](tdCacheLimit),
|
||||
numberCache: lru.NewCache[common.Hash, uint64](numberCacheLimit),
|
||||
procInterrupt: procInterrupt,
|
||||
rand: mrand.New(mrand.NewSource(seed.Int64())),
|
||||
engine: engine,
|
||||
@@ -115,8 +111,7 @@ func NewHeaderChain(chainDb ethdb.Database, config *params.ChainConfig, engine c
|
||||
// from the cache or database
|
||||
func (hc *HeaderChain) GetBlockNumber(hash common.Hash) *uint64 {
|
||||
if cached, ok := hc.numberCache.Get(hash); ok {
|
||||
number := cached.(uint64)
|
||||
return &number
|
||||
return &cached
|
||||
}
|
||||
number := rawdb.ReadHeaderNumber(hc.chainDb, hash)
|
||||
if number != nil {
|
||||
@@ -442,7 +437,7 @@ func (hc *HeaderChain) GetAncestor(hash common.Hash, number, ancestor uint64, ma
|
||||
func (hc *HeaderChain) GetTd(hash common.Hash, number uint64) *big.Int {
|
||||
// Short circuit if the td's already in the cache, retrieve otherwise
|
||||
if cached, ok := hc.tdCache.Get(hash); ok {
|
||||
return cached.(*big.Int)
|
||||
return cached
|
||||
}
|
||||
td := rawdb.ReadTd(hc.chainDb, hash, number)
|
||||
if td == nil {
|
||||
@@ -458,7 +453,7 @@ func (hc *HeaderChain) GetTd(hash common.Hash, number uint64) *big.Int {
|
||||
func (hc *HeaderChain) GetHeader(hash common.Hash, number uint64) *types.Header {
|
||||
// Short circuit if the header's already in the cache, retrieve otherwise
|
||||
if header, ok := hc.headerCache.Get(hash); ok {
|
||||
return header.(*types.Header)
|
||||
return header
|
||||
}
|
||||
header := rawdb.ReadHeader(hc.chainDb, hash, number)
|
||||
if header == nil {
|
||||
@@ -525,10 +520,9 @@ func (hc *HeaderChain) GetHeadersFrom(number, count uint64) []rlp.RawValue {
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
h := header.(*types.Header)
|
||||
rlpData, _ := rlp.EncodeToBytes(h)
|
||||
rlpData, _ := rlp.EncodeToBytes(header)
|
||||
headers = append(headers, rlpData)
|
||||
hash = h.ParentHash
|
||||
hash = header.ParentHash
|
||||
count--
|
||||
number--
|
||||
}
|
||||
@@ -562,7 +556,7 @@ type (
|
||||
// before head header is updated. The method will return the actual block it
|
||||
// updated the head to (missing state) and a flag if setHead should continue
|
||||
// rewinding till that forcefully (exceeded ancient limits)
|
||||
UpdateHeadBlocksCallback func(ethdb.KeyValueWriter, *types.Header) (uint64, bool)
|
||||
UpdateHeadBlocksCallback func(ethdb.KeyValueWriter, *types.Header) (*types.Header, bool)
|
||||
|
||||
// DeleteBlockContentCallback is a callback function that is called by SetHead
|
||||
// before each header is deleted.
|
||||
@@ -572,15 +566,46 @@ type (
|
||||
// SetHead rewinds the local chain to a new head. Everything above the new head
|
||||
// will be deleted and the new one set.
|
||||
func (hc *HeaderChain) SetHead(head uint64, updateFn UpdateHeadBlocksCallback, delFn DeleteBlockContentCallback) {
|
||||
hc.setHead(head, 0, updateFn, delFn)
|
||||
}
|
||||
|
||||
// SetHeadWithTimestamp rewinds the local chain to a new head timestamp. Everything
|
||||
// above the new head will be deleted and the new one set.
|
||||
func (hc *HeaderChain) SetHeadWithTimestamp(time uint64, updateFn UpdateHeadBlocksCallback, delFn DeleteBlockContentCallback) {
|
||||
hc.setHead(0, time, updateFn, delFn)
|
||||
}
|
||||
|
||||
// setHead rewinds the local chain to a new head block or a head timestamp.
|
||||
// Everything above the new head will be deleted and the new one set.
|
||||
func (hc *HeaderChain) setHead(headBlock uint64, headTime uint64, updateFn UpdateHeadBlocksCallback, delFn DeleteBlockContentCallback) {
|
||||
// Sanity check that there's no attempt to undo the genesis block. This is
|
||||
// a fairly synthetic case where someone enables a timestamp based fork
|
||||
// below the genesis timestamp. It's nice to not allow that instead of the
|
||||
// entire chain getting deleted.
|
||||
if headTime > 0 && hc.genesisHeader.Time > headTime {
|
||||
// Note, a critical error is quite brutal, but we should really not reach
|
||||
// this point. Since pre-timestamp based forks it was impossible to have
|
||||
// a fork before block 0, the setHead would always work. With timestamp
|
||||
// forks it becomes possible to specify below the genesis. That said, the
|
||||
// only time we setHead via timestamp is with chain config changes on the
|
||||
// startup, so failing hard there is ok.
|
||||
log.Crit("Rejecting genesis rewind via timestamp", "target", headTime, "genesis", hc.genesisHeader.Time)
|
||||
}
|
||||
var (
|
||||
parentHash common.Hash
|
||||
batch = hc.chainDb.NewBatch()
|
||||
origin = true
|
||||
)
|
||||
for hdr := hc.CurrentHeader(); hdr != nil && hdr.Number.Uint64() > head; hdr = hc.CurrentHeader() {
|
||||
done := func(header *types.Header) bool {
|
||||
if headTime > 0 {
|
||||
return header.Time <= headTime
|
||||
}
|
||||
return header.Number.Uint64() <= headBlock
|
||||
}
|
||||
for hdr := hc.CurrentHeader(); hdr != nil && !done(hdr); hdr = hc.CurrentHeader() {
|
||||
num := hdr.Number.Uint64()
|
||||
|
||||
// Rewind block chain to new head.
|
||||
// Rewind chain to new head
|
||||
parent := hc.GetHeader(hdr.ParentHash, num-1)
|
||||
if parent == nil {
|
||||
parent = hc.genesisHeader
|
||||
@@ -597,9 +622,9 @@ func (hc *HeaderChain) SetHead(head uint64, updateFn UpdateHeadBlocksCallback, d
|
||||
markerBatch := hc.chainDb.NewBatch()
|
||||
if updateFn != nil {
|
||||
newHead, force := updateFn(markerBatch, parent)
|
||||
if force && newHead < head {
|
||||
log.Warn("Force rewinding till ancient limit", "head", newHead)
|
||||
head = newHead
|
||||
if force && ((headTime > 0 && newHead.Time < headTime) || (headTime == 0 && newHead.Number.Uint64() < headBlock)) {
|
||||
log.Warn("Force rewinding till ancient limit", "head", newHead.Number.Uint64())
|
||||
headBlock, headTime = newHead.Number.Uint64(), 0 // Target timestamp passed, continue rewind in block mode (cleaner)
|
||||
}
|
||||
}
|
||||
// Update head header then.
|
||||
|
||||
@@ -27,8 +27,8 @@ import (
|
||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
)
|
||||
|
||||
func verifyUnbrokenCanonchain(hc *HeaderChain) error {
|
||||
@@ -70,19 +70,18 @@ func testInsert(t *testing.T, hc *HeaderChain, chain []*types.Header, wantStatus
|
||||
// This test checks status reporting of InsertHeaderChain.
|
||||
func TestHeaderInsertion(t *testing.T) {
|
||||
var (
|
||||
db = rawdb.NewMemoryDatabase()
|
||||
genesis = (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(db)
|
||||
db = rawdb.NewMemoryDatabase()
|
||||
gspec = &Genesis{BaseFee: big.NewInt(params.InitialBaseFee), Config: params.AllEthashProtocolChanges}
|
||||
)
|
||||
|
||||
hc, err := NewHeaderChain(db, params.AllEthashProtocolChanges, ethash.NewFaker(), func() bool { return false })
|
||||
gspec.Commit(db, trie.NewDatabase(db))
|
||||
hc, err := NewHeaderChain(db, gspec.Config, ethash.NewFaker(), func() bool { return false })
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// chain A: G->A1->A2...A128
|
||||
chainA := makeHeaderChain(genesis.Header(), 128, ethash.NewFaker(), db, 10)
|
||||
genDb, chainA := makeHeaderChainWithGenesis(gspec, 128, ethash.NewFaker(), 10)
|
||||
// chain B: G->A1->B1...B128
|
||||
chainB := makeHeaderChain(chainA[0], 128, ethash.NewFaker(), db, 10)
|
||||
log.Root().SetHandler(log.StdoutHandler)
|
||||
chainB := makeHeaderChain(gspec.Config, chainA[0], 128, ethash.NewFaker(), genDb, 10)
|
||||
|
||||
forker := NewForkChoice(hc, nil)
|
||||
// Inserting 64 headers on an empty chain, expecting
|
||||
|
||||
+3
-5
@@ -18,12 +18,10 @@
|
||||
// +build none
|
||||
|
||||
/*
|
||||
The mkalloc tool creates the genesis allocation constants in genesis_alloc.go
|
||||
It outputs a const declaration that contains an RLP-encoded list of (address, balance) tuples.
|
||||
|
||||
The mkalloc tool creates the genesis allocation constants in genesis_alloc.go
|
||||
It outputs a const declaration that contains an RLP-encoded list of (address, balance) tuples.
|
||||
|
||||
go run mkalloc.go genesis.json
|
||||
|
||||
go run mkalloc.go genesis.json
|
||||
*/
|
||||
package main
|
||||
|
||||
|
||||
@@ -259,8 +259,7 @@ func WriteLastPivotNumber(db ethdb.KeyValueWriter, pivot uint64) {
|
||||
}
|
||||
|
||||
// ReadTxIndexTail retrieves the number of oldest indexed block
|
||||
// whose transaction indices has been indexed. If the corresponding entry
|
||||
// is non-existent in database it means the indexing has been finished.
|
||||
// whose transaction indices has been indexed.
|
||||
func ReadTxIndexTail(db ethdb.KeyValueReader) *uint64 {
|
||||
data, _ := db.Get(txIndexTailKey)
|
||||
if len(data) != 8 {
|
||||
@@ -670,10 +669,11 @@ func DeleteReceipts(db ethdb.KeyValueWriter, hash common.Hash, number uint64) {
|
||||
|
||||
// storedReceiptRLP is the storage encoding of a receipt.
|
||||
// Re-definition in core/types/receipt.go.
|
||||
// TODO: Re-use the existing definition.
|
||||
type storedReceiptRLP struct {
|
||||
PostStateOrStatus []byte
|
||||
CumulativeGasUsed uint64
|
||||
Logs []*types.LogForStorage
|
||||
Logs []*types.Log
|
||||
}
|
||||
|
||||
// ReceiptLogs is a barebone version of ReceiptForStorage which only keeps
|
||||
@@ -689,10 +689,7 @@ func (r *receiptLogs) DecodeRLP(s *rlp.Stream) error {
|
||||
if err := s.Decode(&stored); err != nil {
|
||||
return err
|
||||
}
|
||||
r.Logs = make([]*types.Log, len(stored.Logs))
|
||||
for i, log := range stored.Logs {
|
||||
r.Logs[i] = (*types.Log)(log)
|
||||
}
|
||||
r.Logs = stored.Logs
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -717,9 +714,9 @@ func deriveLogFields(receipts []*receiptLogs, hash common.Hash, number uint64, t
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadLogs retrieves the logs for all transactions in a block. The log fields
|
||||
// are populated with metadata. In case the receipts or the block body
|
||||
// are not found, a nil is returned.
|
||||
// ReadLogs retrieves the logs for all transactions in a block. In case
|
||||
// receipts is not found, a nil is returned.
|
||||
// Note: ReadLogs does not derive unstored log fields.
|
||||
func ReadLogs(db ethdb.Reader, hash common.Hash, number uint64, config *params.ChainConfig) [][]*types.Log {
|
||||
// Retrieve the flattened receipt slice
|
||||
data := ReadReceiptsRLP(db, hash, number)
|
||||
@@ -728,39 +725,10 @@ func ReadLogs(db ethdb.Reader, hash common.Hash, number uint64, config *params.C
|
||||
}
|
||||
receipts := []*receiptLogs{}
|
||||
if err := rlp.DecodeBytes(data, &receipts); err != nil {
|
||||
// Receipts might be in the legacy format, try decoding that.
|
||||
// TODO: to be removed after users migrated
|
||||
if logs := readLegacyLogs(db, hash, number, config); logs != nil {
|
||||
return logs
|
||||
}
|
||||
log.Error("Invalid receipt array RLP", "hash", hash, "err", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
body := ReadBody(db, hash, number)
|
||||
if body == nil {
|
||||
log.Error("Missing body but have receipt", "hash", hash, "number", number)
|
||||
return nil
|
||||
}
|
||||
if err := deriveLogFields(receipts, hash, number, body.Transactions); err != nil {
|
||||
log.Error("Failed to derive block receipts fields", "hash", hash, "number", number, "err", err)
|
||||
return nil
|
||||
}
|
||||
logs := make([][]*types.Log, len(receipts))
|
||||
for i, receipt := range receipts {
|
||||
logs[i] = receipt.Logs
|
||||
}
|
||||
return logs
|
||||
}
|
||||
|
||||
// readLegacyLogs is a temporary workaround for when trying to read logs
|
||||
// from a block which has its receipt stored in the legacy format. It'll
|
||||
// be removed after users have migrated their freezer databases.
|
||||
func readLegacyLogs(db ethdb.Reader, hash common.Hash, number uint64, config *params.ChainConfig) [][]*types.Log {
|
||||
receipts := ReadReceipts(db, hash, number, config)
|
||||
if receipts == nil {
|
||||
return nil
|
||||
}
|
||||
logs := make([][]*types.Log, len(receipts))
|
||||
for i, receipt := range receipts {
|
||||
logs[i] = receipt.Logs
|
||||
@@ -783,7 +751,7 @@ func ReadBlock(db ethdb.Reader, hash common.Hash, number uint64) *types.Block {
|
||||
if body == nil {
|
||||
return nil
|
||||
}
|
||||
return types.NewBlockWithHeader(header).WithBody(body.Transactions, body.Uncles)
|
||||
return types.NewBlockWithHeader(header).WithBody(body.Transactions, body.Uncles).WithWithdrawals(body.Withdrawals)
|
||||
}
|
||||
|
||||
// WriteBlock serializes a block into the database, header and body separately.
|
||||
@@ -883,7 +851,7 @@ func ReadBadBlock(db ethdb.Reader, hash common.Hash) *types.Block {
|
||||
}
|
||||
for _, bad := range badBlocks {
|
||||
if bad.Header.Hash() == hash {
|
||||
return types.NewBlockWithHeader(bad.Header).WithBody(bad.Body.Transactions, bad.Body.Uncles)
|
||||
return types.NewBlockWithHeader(bad.Header).WithBody(bad.Body.Transactions, bad.Body.Uncles).WithWithdrawals(bad.Body.Withdrawals)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -902,7 +870,7 @@ func ReadAllBadBlocks(db ethdb.Reader) []*types.Block {
|
||||
}
|
||||
var blocks []*types.Block
|
||||
for _, bad := range badBlocks {
|
||||
blocks = append(blocks, types.NewBlockWithHeader(bad.Header).WithBody(bad.Body.Transactions, bad.Body.Uncles))
|
||||
blocks = append(blocks, types.NewBlockWithHeader(bad.Header).WithBody(bad.Body.Transactions, bad.Body.Uncles).WithWithdrawals(bad.Body.Withdrawals))
|
||||
}
|
||||
return blocks
|
||||
}
|
||||
|
||||
@@ -750,10 +750,6 @@ func TestReadLogs(t *testing.T) {
|
||||
t.Fatalf("unexpected number of logs[1] returned, have %d want %d", have, want)
|
||||
}
|
||||
|
||||
// Fill in log fields so we can compare their rlp encoding
|
||||
if err := types.Receipts(receipts).DeriveFields(params.TestChainConfig, hash, 0, body.Transactions); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for i, pr := range receipts {
|
||||
for j, pl := range pr.Logs {
|
||||
rlpHave, err := rlp.EncodeToBytes(newFullLogRLP(logs[i][j]))
|
||||
|
||||
@@ -28,6 +28,17 @@ func ReadPreimage(db ethdb.KeyValueReader, hash common.Hash) []byte {
|
||||
return data
|
||||
}
|
||||
|
||||
// WritePreimages writes the provided set of preimages to the database.
|
||||
func WritePreimages(db ethdb.KeyValueWriter, preimages map[common.Hash][]byte) {
|
||||
for hash, preimage := range preimages {
|
||||
if err := db.Put(preimageKey(hash), preimage); err != nil {
|
||||
log.Crit("Failed to store trie preimage", "err", err)
|
||||
}
|
||||
}
|
||||
preimageCounter.Inc(int64(len(preimages)))
|
||||
preimageHitCounter.Inc(int64(len(preimages)))
|
||||
}
|
||||
|
||||
// ReadCode retrieves the contract code of the provided code hash.
|
||||
func ReadCode(db ethdb.KeyValueReader, hash common.Hash) []byte {
|
||||
// Try with the prefixed code scheme first, if not then try with legacy
|
||||
@@ -48,12 +59,6 @@ func ReadCodeWithPrefix(db ethdb.KeyValueReader, hash common.Hash) []byte {
|
||||
return data
|
||||
}
|
||||
|
||||
// ReadTrieNode retrieves the trie node of the provided hash.
|
||||
func ReadTrieNode(db ethdb.KeyValueReader, hash common.Hash) []byte {
|
||||
data, _ := db.Get(hash.Bytes())
|
||||
return data
|
||||
}
|
||||
|
||||
// HasCode checks if the contract code corresponding to the
|
||||
// provided code hash is present in the db.
|
||||
func HasCode(db ethdb.KeyValueReader, hash common.Hash) bool {
|
||||
@@ -74,23 +79,6 @@ func HasCodeWithPrefix(db ethdb.KeyValueReader, hash common.Hash) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
// HasTrieNode checks if the trie node with the provided hash is present in db.
|
||||
func HasTrieNode(db ethdb.KeyValueReader, hash common.Hash) bool {
|
||||
ok, _ := db.Has(hash.Bytes())
|
||||
return ok
|
||||
}
|
||||
|
||||
// WritePreimages writes the provided set of preimages to the database.
|
||||
func WritePreimages(db ethdb.KeyValueWriter, preimages map[common.Hash][]byte) {
|
||||
for hash, preimage := range preimages {
|
||||
if err := db.Put(preimageKey(hash), preimage); err != nil {
|
||||
log.Crit("Failed to store trie preimage", "err", err)
|
||||
}
|
||||
}
|
||||
preimageCounter.Inc(int64(len(preimages)))
|
||||
preimageHitCounter.Inc(int64(len(preimages)))
|
||||
}
|
||||
|
||||
// WriteCode writes the provided contract code database.
|
||||
func WriteCode(db ethdb.KeyValueWriter, hash common.Hash, code []byte) {
|
||||
if err := db.Put(codeKey(hash), code); err != nil {
|
||||
@@ -98,23 +86,9 @@ func WriteCode(db ethdb.KeyValueWriter, hash common.Hash, code []byte) {
|
||||
}
|
||||
}
|
||||
|
||||
// WriteTrieNode writes the provided trie node database.
|
||||
func WriteTrieNode(db ethdb.KeyValueWriter, hash common.Hash, node []byte) {
|
||||
if err := db.Put(hash.Bytes(), node); err != nil {
|
||||
log.Crit("Failed to store trie node", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteCode deletes the specified contract code from the database.
|
||||
func DeleteCode(db ethdb.KeyValueWriter, hash common.Hash) {
|
||||
if err := db.Delete(codeKey(hash)); err != nil {
|
||||
log.Crit("Failed to delete contract code", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteTrieNode deletes the specified trie node from the database.
|
||||
func DeleteTrieNode(db ethdb.KeyValueWriter, hash common.Hash) {
|
||||
if err := db.Delete(hash.Bytes()); err != nil {
|
||||
log.Crit("Failed to delete trie node", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
// Copyright 2022 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
package rawdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
// HashScheme is the legacy hash-based state scheme with which trie nodes are
|
||||
// stored in the disk with node hash as the database key. The advantage of this
|
||||
// scheme is that different versions of trie nodes can be stored in disk, which
|
||||
// is very beneficial for constructing archive nodes. The drawback is it will
|
||||
// store different trie nodes on the same path to different locations on the disk
|
||||
// with no data locality, and it's unfriendly for designing state pruning.
|
||||
//
|
||||
// Now this scheme is still kept for backward compatibility, and it will be used
|
||||
// for archive node and some other tries(e.g. light trie).
|
||||
const HashScheme = "hashScheme"
|
||||
|
||||
// PathScheme is the new path-based state scheme with which trie nodes are stored
|
||||
// in the disk with node path as the database key. This scheme will only store one
|
||||
// version of state data in the disk, which means that the state pruning operation
|
||||
// is native. At the same time, this scheme will put adjacent trie nodes in the same
|
||||
// area of the disk with good data locality property. But this scheme needs to rely
|
||||
// on extra state diffs to survive deep reorg.
|
||||
const PathScheme = "pathScheme"
|
||||
|
||||
// nodeHasher used to derive the hash of trie node.
|
||||
type nodeHasher struct{ sha crypto.KeccakState }
|
||||
|
||||
var hasherPool = sync.Pool{
|
||||
New: func() interface{} { return &nodeHasher{sha: sha3.NewLegacyKeccak256().(crypto.KeccakState)} },
|
||||
}
|
||||
|
||||
func newNodeHasher() *nodeHasher { return hasherPool.Get().(*nodeHasher) }
|
||||
func returnHasherToPool(h *nodeHasher) { hasherPool.Put(h) }
|
||||
|
||||
func (h *nodeHasher) hashData(data []byte) (n common.Hash) {
|
||||
h.sha.Reset()
|
||||
h.sha.Write(data)
|
||||
h.sha.Read(n[:])
|
||||
return n
|
||||
}
|
||||
|
||||
// ReadAccountTrieNode retrieves the account trie node and the associated node
|
||||
// hash with the specified node path.
|
||||
func ReadAccountTrieNode(db ethdb.KeyValueReader, path []byte) ([]byte, common.Hash) {
|
||||
data, err := db.Get(accountTrieNodeKey(path))
|
||||
if err != nil {
|
||||
return nil, common.Hash{}
|
||||
}
|
||||
hasher := newNodeHasher()
|
||||
defer returnHasherToPool(hasher)
|
||||
return data, hasher.hashData(data)
|
||||
}
|
||||
|
||||
// HasAccountTrieNode checks the account trie node presence with the specified
|
||||
// node path and the associated node hash.
|
||||
func HasAccountTrieNode(db ethdb.KeyValueReader, path []byte, hash common.Hash) bool {
|
||||
data, err := db.Get(accountTrieNodeKey(path))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
hasher := newNodeHasher()
|
||||
defer returnHasherToPool(hasher)
|
||||
return hasher.hashData(data) == hash
|
||||
}
|
||||
|
||||
// WriteAccountTrieNode writes the provided account trie node into database.
|
||||
func WriteAccountTrieNode(db ethdb.KeyValueWriter, path []byte, node []byte) {
|
||||
if err := db.Put(accountTrieNodeKey(path), node); err != nil {
|
||||
log.Crit("Failed to store account trie node", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteAccountTrieNode deletes the specified account trie node from the database.
|
||||
func DeleteAccountTrieNode(db ethdb.KeyValueWriter, path []byte) {
|
||||
if err := db.Delete(accountTrieNodeKey(path)); err != nil {
|
||||
log.Crit("Failed to delete account trie node", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ReadStorageTrieNode retrieves the storage trie node and the associated node
|
||||
// hash with the specified node path.
|
||||
func ReadStorageTrieNode(db ethdb.KeyValueReader, accountHash common.Hash, path []byte) ([]byte, common.Hash) {
|
||||
data, err := db.Get(storageTrieNodeKey(accountHash, path))
|
||||
if err != nil {
|
||||
return nil, common.Hash{}
|
||||
}
|
||||
hasher := newNodeHasher()
|
||||
defer returnHasherToPool(hasher)
|
||||
return data, hasher.hashData(data)
|
||||
}
|
||||
|
||||
// HasStorageTrieNode checks the storage trie node presence with the provided
|
||||
// node path and the associated node hash.
|
||||
func HasStorageTrieNode(db ethdb.KeyValueReader, accountHash common.Hash, path []byte, hash common.Hash) bool {
|
||||
data, err := db.Get(storageTrieNodeKey(accountHash, path))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
hasher := newNodeHasher()
|
||||
defer returnHasherToPool(hasher)
|
||||
return hasher.hashData(data) == hash
|
||||
}
|
||||
|
||||
// WriteStorageTrieNode writes the provided storage trie node into database.
|
||||
func WriteStorageTrieNode(db ethdb.KeyValueWriter, accountHash common.Hash, path []byte, node []byte) {
|
||||
if err := db.Put(storageTrieNodeKey(accountHash, path), node); err != nil {
|
||||
log.Crit("Failed to store storage trie node", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteStorageTrieNode deletes the specified storage trie node from the database.
|
||||
func DeleteStorageTrieNode(db ethdb.KeyValueWriter, accountHash common.Hash, path []byte) {
|
||||
if err := db.Delete(storageTrieNodeKey(accountHash, path)); err != nil {
|
||||
log.Crit("Failed to delete storage trie node", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ReadLegacyTrieNode retrieves the legacy trie node with the given
|
||||
// associated node hash.
|
||||
func ReadLegacyTrieNode(db ethdb.KeyValueReader, hash common.Hash) []byte {
|
||||
data, err := db.Get(hash.Bytes())
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// HasLegacyTrieNode checks if the trie node with the provided hash is present in db.
|
||||
func HasLegacyTrieNode(db ethdb.KeyValueReader, hash common.Hash) bool {
|
||||
ok, _ := db.Has(hash.Bytes())
|
||||
return ok
|
||||
}
|
||||
|
||||
// WriteLegacyTrieNode writes the provided legacy trie node to database.
|
||||
func WriteLegacyTrieNode(db ethdb.KeyValueWriter, hash common.Hash, node []byte) {
|
||||
if err := db.Put(hash.Bytes(), node); err != nil {
|
||||
log.Crit("Failed to store legacy trie node", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteLegacyTrieNode deletes the specified legacy trie node from database.
|
||||
func DeleteLegacyTrieNode(db ethdb.KeyValueWriter, hash common.Hash) {
|
||||
if err := db.Delete(hash.Bytes()); err != nil {
|
||||
log.Crit("Failed to delete legacy trie node", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// HasTrieNode checks the trie node presence with the provided node info and
|
||||
// the associated node hash.
|
||||
func HasTrieNode(db ethdb.KeyValueReader, owner common.Hash, path []byte, hash common.Hash, scheme string) bool {
|
||||
switch scheme {
|
||||
case HashScheme:
|
||||
return HasLegacyTrieNode(db, hash)
|
||||
case PathScheme:
|
||||
if owner == (common.Hash{}) {
|
||||
return HasAccountTrieNode(db, path, hash)
|
||||
}
|
||||
return HasStorageTrieNode(db, owner, path, hash)
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown scheme %v", scheme))
|
||||
}
|
||||
}
|
||||
|
||||
// ReadTrieNode retrieves the trie node from database with the provided node info
|
||||
// and associated node hash.
|
||||
// hashScheme-based lookup requires the following:
|
||||
// - hash
|
||||
//
|
||||
// pathScheme-based lookup requires the following:
|
||||
// - owner
|
||||
// - path
|
||||
func ReadTrieNode(db ethdb.KeyValueReader, owner common.Hash, path []byte, hash common.Hash, scheme string) []byte {
|
||||
switch scheme {
|
||||
case HashScheme:
|
||||
return ReadLegacyTrieNode(db, hash)
|
||||
case PathScheme:
|
||||
var (
|
||||
blob []byte
|
||||
nHash common.Hash
|
||||
)
|
||||
if owner == (common.Hash{}) {
|
||||
blob, nHash = ReadAccountTrieNode(db, path)
|
||||
} else {
|
||||
blob, nHash = ReadStorageTrieNode(db, owner, path)
|
||||
}
|
||||
if nHash != hash {
|
||||
return nil
|
||||
}
|
||||
return blob
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown scheme %v", scheme))
|
||||
}
|
||||
}
|
||||
|
||||
// WriteTrieNode writes the trie node into database with the provided node info
|
||||
// and associated node hash.
|
||||
// hashScheme-based lookup requires the following:
|
||||
// - hash
|
||||
//
|
||||
// pathScheme-based lookup requires the following:
|
||||
// - owner
|
||||
// - path
|
||||
func WriteTrieNode(db ethdb.KeyValueWriter, owner common.Hash, path []byte, hash common.Hash, node []byte, scheme string) {
|
||||
switch scheme {
|
||||
case HashScheme:
|
||||
WriteLegacyTrieNode(db, hash, node)
|
||||
case PathScheme:
|
||||
if owner == (common.Hash{}) {
|
||||
WriteAccountTrieNode(db, path, node)
|
||||
} else {
|
||||
WriteStorageTrieNode(db, owner, path, node)
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown scheme %v", scheme))
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteTrieNode deletes the trie node from database with the provided node info
|
||||
// and associated node hash.
|
||||
// hashScheme-based lookup requires the following:
|
||||
// - hash
|
||||
//
|
||||
// pathScheme-based lookup requires the following:
|
||||
// - owner
|
||||
// - path
|
||||
func DeleteTrieNode(db ethdb.KeyValueWriter, owner common.Hash, path []byte, hash common.Hash, scheme string) {
|
||||
switch scheme {
|
||||
case HashScheme:
|
||||
DeleteLegacyTrieNode(db, hash)
|
||||
case PathScheme:
|
||||
if owner == (common.Hash{}) {
|
||||
DeleteAccountTrieNode(db, path)
|
||||
} else {
|
||||
DeleteStorageTrieNode(db, owner, path)
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown scheme %v", scheme))
|
||||
}
|
||||
}
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
package rawdb
|
||||
|
||||
import "fmt"
|
||||
|
||||
// The list of table names of chain freezer.
|
||||
const (
|
||||
// chainFreezerHeaderTable indicates the name of the freezer header table.
|
||||
@@ -53,34 +51,3 @@ var (
|
||||
|
||||
// freezers the collections of all builtin freezers.
|
||||
var freezers = []string{chainFreezerName}
|
||||
|
||||
// InspectFreezerTable dumps out the index of a specific freezer table. The passed
|
||||
// ancient indicates the path of root ancient directory where the chain freezer can
|
||||
// be opened. Start and end specify the range for dumping out indexes.
|
||||
// Note this function can only be used for debugging purposes.
|
||||
func InspectFreezerTable(ancient string, freezerName string, tableName string, start, end int64) error {
|
||||
var (
|
||||
path string
|
||||
tables map[string]bool
|
||||
)
|
||||
switch freezerName {
|
||||
case chainFreezerName:
|
||||
path, tables = resolveChainFreezerDir(ancient), chainFreezerNoSnappy
|
||||
default:
|
||||
return fmt.Errorf("unknown freezer, supported ones: %v", freezers)
|
||||
}
|
||||
noSnappy, exist := tables[tableName]
|
||||
if !exist {
|
||||
var names []string
|
||||
for name := range tables {
|
||||
names = append(names, name)
|
||||
}
|
||||
return fmt.Errorf("unknown table, supported ones: %v", names)
|
||||
}
|
||||
table, err := newFreezerTable(path, tableName, noSnappy, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
table.dumpIndexStdout(start, end)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
// Copyright 2022 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package rawdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
)
|
||||
|
||||
type tableSize struct {
|
||||
name string
|
||||
size common.StorageSize
|
||||
}
|
||||
|
||||
// freezerInfo contains the basic information of the freezer.
|
||||
type freezerInfo struct {
|
||||
name string // The identifier of freezer
|
||||
head uint64 // The number of last stored item in the freezer
|
||||
tail uint64 // The number of first stored item in the freezer
|
||||
sizes []tableSize // The storage size per table
|
||||
}
|
||||
|
||||
// count returns the number of stored items in the freezer.
|
||||
func (info *freezerInfo) count() uint64 {
|
||||
return info.head - info.tail + 1
|
||||
}
|
||||
|
||||
// size returns the storage size of the entire freezer.
|
||||
func (info *freezerInfo) size() common.StorageSize {
|
||||
var total common.StorageSize
|
||||
for _, table := range info.sizes {
|
||||
total += table.size
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
// inspectFreezers inspects all freezers registered in the system.
|
||||
func inspectFreezers(db ethdb.Database) ([]freezerInfo, error) {
|
||||
var infos []freezerInfo
|
||||
for _, freezer := range freezers {
|
||||
switch freezer {
|
||||
case chainFreezerName:
|
||||
// Chain ancient store is a bit special. It's always opened along
|
||||
// with the key-value store, inspect the chain store directly.
|
||||
info := freezerInfo{name: freezer}
|
||||
// Retrieve storage size of every contained table.
|
||||
for table := range chainFreezerNoSnappy {
|
||||
size, err := db.AncientSize(table)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info.sizes = append(info.sizes, tableSize{name: table, size: common.StorageSize(size)})
|
||||
}
|
||||
// Retrieve the number of last stored item
|
||||
ancients, err := db.Ancients()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info.head = ancients - 1
|
||||
|
||||
// Retrieve the number of first stored item
|
||||
tail, err := db.Tail()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info.tail = tail
|
||||
infos = append(infos, info)
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown freezer, supported ones: %v", freezers)
|
||||
}
|
||||
}
|
||||
return infos, nil
|
||||
}
|
||||
|
||||
// InspectFreezerTable dumps out the index of a specific freezer table. The passed
|
||||
// ancient indicates the path of root ancient directory where the chain freezer can
|
||||
// be opened. Start and end specify the range for dumping out indexes.
|
||||
// Note this function can only be used for debugging purposes.
|
||||
func InspectFreezerTable(ancient string, freezerName string, tableName string, start, end int64) error {
|
||||
var (
|
||||
path string
|
||||
tables map[string]bool
|
||||
)
|
||||
switch freezerName {
|
||||
case chainFreezerName:
|
||||
path, tables = resolveChainFreezerDir(ancient), chainFreezerNoSnappy
|
||||
default:
|
||||
return fmt.Errorf("unknown freezer, supported ones: %v", freezers)
|
||||
}
|
||||
noSnappy, exist := tables[tableName]
|
||||
if !exist {
|
||||
var names []string
|
||||
for name := range tables {
|
||||
names = append(names, name)
|
||||
}
|
||||
return fmt.Errorf("unknown table, supported ones: %v", names)
|
||||
}
|
||||
table, err := newFreezerTable(path, tableName, noSnappy, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
table.dumpIndexStdout(start, end)
|
||||
return nil
|
||||
}
|
||||
@@ -70,14 +70,13 @@ func newChainFreezer(datadir string, namespace string, readonly bool, maxTableSi
|
||||
|
||||
// Close closes the chain freezer instance and terminates the background thread.
|
||||
func (f *chainFreezer) Close() error {
|
||||
err := f.Freezer.Close()
|
||||
select {
|
||||
case <-f.quit:
|
||||
default:
|
||||
close(f.quit)
|
||||
}
|
||||
f.wg.Wait()
|
||||
return err
|
||||
return f.Freezer.Close()
|
||||
}
|
||||
|
||||
// freeze is a background thread that periodically checks the blockchain for any
|
||||
@@ -86,12 +85,14 @@ func (f *chainFreezer) Close() error {
|
||||
// This functionality is deliberately broken off from block importing to avoid
|
||||
// incurring additional data shuffling delays on block propagation.
|
||||
func (f *chainFreezer) freeze(db ethdb.KeyValueStore) {
|
||||
nfdb := &nofreezedb{KeyValueStore: db}
|
||||
|
||||
var (
|
||||
backoff bool
|
||||
triggered chan struct{} // Used in tests
|
||||
nfdb = &nofreezedb{KeyValueStore: db}
|
||||
)
|
||||
timer := time.NewTimer(freezerRecheckInterval)
|
||||
defer timer.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-f.quit:
|
||||
@@ -106,8 +107,9 @@ func (f *chainFreezer) freeze(db ethdb.KeyValueStore) {
|
||||
triggered = nil
|
||||
}
|
||||
select {
|
||||
case <-time.NewTimer(freezerRecheckInterval).C:
|
||||
case <-timer.C:
|
||||
backoff = false
|
||||
timer.Reset(freezerRecheckInterval)
|
||||
case triggered = <-f.trigger:
|
||||
backoff = false
|
||||
case <-f.quit:
|
||||
|
||||
@@ -191,7 +191,7 @@ func indexTransactions(db ethdb.Database, from uint64, to uint64, interrupt chan
|
||||
// in to be [to-1]. Therefore, setting lastNum to means that the
|
||||
// prqueue gap-evaluation will work correctly
|
||||
lastNum = to
|
||||
queue = prque.New(nil)
|
||||
queue = prque.New[int64, *blockTxHashes](nil)
|
||||
// for stats reporting
|
||||
blocks, txs = 0, 0
|
||||
)
|
||||
@@ -210,7 +210,7 @@ func indexTransactions(db ethdb.Database, from uint64, to uint64, interrupt chan
|
||||
break
|
||||
}
|
||||
// Next block available, pop it off and index it
|
||||
delivery := queue.PopItem().(*blockTxHashes)
|
||||
delivery := queue.PopItem()
|
||||
lastNum = delivery.number
|
||||
WriteTxLookupEntries(batch, delivery.number, delivery.hashes)
|
||||
blocks++
|
||||
@@ -243,7 +243,7 @@ func indexTransactions(db ethdb.Database, from uint64, to uint64, interrupt chan
|
||||
case <-interrupt:
|
||||
log.Debug("Transaction indexing interrupted", "blocks", blocks, "txs", txs, "tail", lastNum, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||
default:
|
||||
log.Info("Indexed transactions", "blocks", blocks, "txs", txs, "tail", lastNum, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||
log.Debug("Indexed transactions", "blocks", blocks, "txs", txs, "tail", lastNum, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,7 +282,7 @@ func unindexTransactions(db ethdb.Database, from uint64, to uint64, interrupt ch
|
||||
// we expect the first number to come in to be [from]. Therefore, setting
|
||||
// nextNum to from means that the prqueue gap-evaluation will work correctly
|
||||
nextNum = from
|
||||
queue = prque.New(nil)
|
||||
queue = prque.New[int64, *blockTxHashes](nil)
|
||||
// for stats reporting
|
||||
blocks, txs = 0, 0
|
||||
)
|
||||
@@ -299,7 +299,7 @@ func unindexTransactions(db ethdb.Database, from uint64, to uint64, interrupt ch
|
||||
if hook != nil && !hook(nextNum) {
|
||||
break
|
||||
}
|
||||
delivery := queue.PopItem().(*blockTxHashes)
|
||||
delivery := queue.PopItem()
|
||||
nextNum = delivery.number + 1
|
||||
DeleteTxLookupEntries(batch, delivery.hashes)
|
||||
txs += len(delivery.hashes)
|
||||
@@ -335,7 +335,7 @@ func unindexTransactions(db ethdb.Database, from uint64, to uint64, interrupt ch
|
||||
case <-interrupt:
|
||||
log.Debug("Transaction unindexing interrupted", "blocks", blocks, "txs", txs, "tail", to, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||
default:
|
||||
log.Info("Unindexed transactions", "blocks", blocks, "txs", txs, "tail", to, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||
log.Debug("Unindexed transactions", "blocks", blocks, "txs", txs, "tail", to, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+100
-43
@@ -22,6 +22,8 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
@@ -240,8 +242,8 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, ancient string, namespace st
|
||||
if kvhash, _ := db.Get(headerHashKey(frozen)); len(kvhash) == 0 {
|
||||
// Subsequent header after the freezer limit is missing from the database.
|
||||
// Reject startup if the database has a more recent head.
|
||||
if *ReadHeaderNumber(db, ReadHeadHeaderHash(db)) > frozen-1 {
|
||||
return nil, fmt.Errorf("gap (#%d) in the chain between ancients and leveldb", frozen)
|
||||
if ldbNum := *ReadHeaderNumber(db, ReadHeadHeaderHash(db)); ldbNum > frozen-1 {
|
||||
return nil, fmt.Errorf("gap in the chain between ancients (#%d) and leveldb (#%d) ", frozen, ldbNum)
|
||||
}
|
||||
// Database contains only older data than the freezer, this happens if the
|
||||
// state was wiped and reinited from an existing freezer.
|
||||
@@ -301,19 +303,84 @@ func NewLevelDBDatabase(file string, cache int, handles int, namespace string, r
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Info("Using LevelDB as the backing database")
|
||||
return NewDatabase(db), nil
|
||||
}
|
||||
|
||||
// NewLevelDBDatabaseWithFreezer creates a persistent key-value database with a
|
||||
// freezer moving immutable chain segments into cold storage. The passed ancient
|
||||
// indicates the path of root ancient directory where the chain freezer can be
|
||||
// opened.
|
||||
func NewLevelDBDatabaseWithFreezer(file string, cache int, handles int, ancient string, namespace string, readonly bool) (ethdb.Database, error) {
|
||||
kvdb, err := leveldb.New(file, cache, handles, namespace, readonly)
|
||||
const (
|
||||
dbPebble = "pebble"
|
||||
dbLeveldb = "leveldb"
|
||||
)
|
||||
|
||||
// hasPreexistingDb checks the given data directory whether a database is already
|
||||
// instantiated at that location, and if so, returns the type of database (or the
|
||||
// empty string).
|
||||
func hasPreexistingDb(path string) string {
|
||||
if _, err := os.Stat(filepath.Join(path, "CURRENT")); err != nil {
|
||||
return "" // No pre-existing db
|
||||
}
|
||||
if matches, err := filepath.Glob(filepath.Join(path, "OPTIONS*")); len(matches) > 0 || err != nil {
|
||||
if err != nil {
|
||||
panic(err) // only possible if the pattern is malformed
|
||||
}
|
||||
return dbPebble
|
||||
}
|
||||
return dbLeveldb
|
||||
}
|
||||
|
||||
// OpenOptions contains the options to apply when opening a database.
|
||||
// OBS: If AncientsDirectory is empty, it indicates that no freezer is to be used.
|
||||
type OpenOptions struct {
|
||||
Type string // "leveldb" | "pebble"
|
||||
Directory string // the datadir
|
||||
AncientsDirectory string // the ancients-dir
|
||||
Namespace string // the namespace for database relevant metrics
|
||||
Cache int // the capacity(in megabytes) of the data caching
|
||||
Handles int // number of files to be open simultaneously
|
||||
ReadOnly bool
|
||||
}
|
||||
|
||||
// openKeyValueDatabase opens a disk-based key-value database, e.g. leveldb or pebble.
|
||||
//
|
||||
// type == null type != null
|
||||
// +----------------------------------------
|
||||
// db is non-existent | leveldb default | specified type
|
||||
// db is existent | from db | specified type (if compatible)
|
||||
func openKeyValueDatabase(o OpenOptions) (ethdb.Database, error) {
|
||||
existingDb := hasPreexistingDb(o.Directory)
|
||||
if len(existingDb) != 0 && len(o.Type) != 0 && o.Type != existingDb {
|
||||
return nil, fmt.Errorf("db.engine choice was %v but found pre-existing %v database in specified data directory", o.Type, existingDb)
|
||||
}
|
||||
if o.Type == dbPebble || existingDb == dbPebble {
|
||||
if PebbleEnabled {
|
||||
log.Info("Using pebble as the backing database")
|
||||
return NewPebbleDBDatabase(o.Directory, o.Cache, o.Handles, o.Namespace, o.ReadOnly)
|
||||
} else {
|
||||
return nil, errors.New("db.engine 'pebble' not supported on this platform")
|
||||
}
|
||||
}
|
||||
if len(o.Type) != 0 && o.Type != dbLeveldb {
|
||||
return nil, fmt.Errorf("unknown db.engine %v", o.Type)
|
||||
}
|
||||
log.Info("Using leveldb as the backing database")
|
||||
// Use leveldb, either as default (no explicit choice), or pre-existing, or chosen explicitly
|
||||
return NewLevelDBDatabase(o.Directory, o.Cache, o.Handles, o.Namespace, o.ReadOnly)
|
||||
}
|
||||
|
||||
// Open opens both a disk-based key-value database such as leveldb or pebble, but also
|
||||
// integrates it with a freezer database -- if the AncientDir option has been
|
||||
// set on the provided OpenOptions.
|
||||
// The passed o.AncientDir indicates the path of root ancient directory where
|
||||
// the chain freezer can be opened.
|
||||
func Open(o OpenOptions) (ethdb.Database, error) {
|
||||
kvdb, err := openKeyValueDatabase(o)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
frdb, err := NewDatabaseWithFreezer(kvdb, ancient, namespace, readonly)
|
||||
if len(o.AncientsDirectory) == 0 {
|
||||
return kvdb, nil
|
||||
}
|
||||
frdb, err := NewDatabaseWithFreezer(kvdb, o.AncientsDirectory, o.Namespace, o.ReadOnly)
|
||||
if err != nil {
|
||||
kvdb.Close()
|
||||
return nil, err
|
||||
@@ -379,13 +446,6 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
|
||||
beaconHeaders stat
|
||||
cliqueSnaps stat
|
||||
|
||||
// Ancient store statistics
|
||||
ancientHeadersSize common.StorageSize
|
||||
ancientBodiesSize common.StorageSize
|
||||
ancientReceiptsSize common.StorageSize
|
||||
ancientTdsSize common.StorageSize
|
||||
ancientHashesSize common.StorageSize
|
||||
|
||||
// Les statistic
|
||||
chtTrieNodes stat
|
||||
bloomTrieNodes stat
|
||||
@@ -439,15 +499,15 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
|
||||
bloomBits.Add(size)
|
||||
case bytes.HasPrefix(key, skeletonHeaderPrefix) && len(key) == (len(skeletonHeaderPrefix)+8):
|
||||
beaconHeaders.Add(size)
|
||||
case bytes.HasPrefix(key, []byte("clique-")) && len(key) == 7+common.HashLength:
|
||||
case bytes.HasPrefix(key, CliqueSnapshotPrefix) && len(key) == 7+common.HashLength:
|
||||
cliqueSnaps.Add(size)
|
||||
case bytes.HasPrefix(key, []byte("cht-")) ||
|
||||
bytes.HasPrefix(key, []byte("chtIndexV2-")) ||
|
||||
bytes.HasPrefix(key, []byte("chtRootV2-")): // Canonical hash trie
|
||||
case bytes.HasPrefix(key, ChtTablePrefix) ||
|
||||
bytes.HasPrefix(key, ChtIndexTablePrefix) ||
|
||||
bytes.HasPrefix(key, ChtPrefix): // Canonical hash trie
|
||||
chtTrieNodes.Add(size)
|
||||
case bytes.HasPrefix(key, []byte("blt-")) ||
|
||||
bytes.HasPrefix(key, []byte("bltIndex-")) ||
|
||||
bytes.HasPrefix(key, []byte("bltRoot-")): // Bloomtrie sub
|
||||
case bytes.HasPrefix(key, BloomTrieTablePrefix) ||
|
||||
bytes.HasPrefix(key, BloomTrieIndexPrefix) ||
|
||||
bytes.HasPrefix(key, BloomTriePrefix): // Bloomtrie sub
|
||||
bloomTrieNodes.Add(size)
|
||||
default:
|
||||
var accounted bool
|
||||
@@ -473,20 +533,7 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
|
||||
logged = time.Now()
|
||||
}
|
||||
}
|
||||
// Inspect append-only file store then.
|
||||
ancientSizes := []*common.StorageSize{&ancientHeadersSize, &ancientBodiesSize, &ancientReceiptsSize, &ancientHashesSize, &ancientTdsSize}
|
||||
for i, category := range []string{chainFreezerHeaderTable, chainFreezerBodiesTable, chainFreezerReceiptTable, chainFreezerHashTable, chainFreezerDifficultyTable} {
|
||||
if size, err := db.AncientSize(category); err == nil {
|
||||
*ancientSizes[i] += common.StorageSize(size)
|
||||
total += common.StorageSize(size)
|
||||
}
|
||||
}
|
||||
// Get number of ancient rows inside the freezer
|
||||
ancients := counter(0)
|
||||
if count, err := db.Ancients(); err == nil {
|
||||
ancients = counter(count)
|
||||
}
|
||||
// Display the database statistic.
|
||||
// Display the database statistic of key-value store.
|
||||
stats := [][]string{
|
||||
{"Key-Value store", "Headers", headers.Size(), headers.Count()},
|
||||
{"Key-Value store", "Bodies", bodies.Size(), bodies.Count()},
|
||||
@@ -504,14 +551,25 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
|
||||
{"Key-Value store", "Beacon sync headers", beaconHeaders.Size(), beaconHeaders.Count()},
|
||||
{"Key-Value store", "Clique snapshots", cliqueSnaps.Size(), cliqueSnaps.Count()},
|
||||
{"Key-Value store", "Singleton metadata", metadata.Size(), metadata.Count()},
|
||||
{"Ancient store", "Headers", ancientHeadersSize.String(), ancients.String()},
|
||||
{"Ancient store", "Bodies", ancientBodiesSize.String(), ancients.String()},
|
||||
{"Ancient store", "Receipt lists", ancientReceiptsSize.String(), ancients.String()},
|
||||
{"Ancient store", "Difficulties", ancientTdsSize.String(), ancients.String()},
|
||||
{"Ancient store", "Block number->hash", ancientHashesSize.String(), ancients.String()},
|
||||
{"Light client", "CHT trie nodes", chtTrieNodes.Size(), chtTrieNodes.Count()},
|
||||
{"Light client", "Bloom trie nodes", bloomTrieNodes.Size(), bloomTrieNodes.Count()},
|
||||
}
|
||||
// Inspect all registered append-only file store then.
|
||||
ancients, err := inspectFreezers(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, ancient := range ancients {
|
||||
for _, table := range ancient.sizes {
|
||||
stats = append(stats, []string{
|
||||
fmt.Sprintf("Ancient store (%s)", strings.Title(ancient.name)),
|
||||
strings.Title(table.name),
|
||||
table.size.String(),
|
||||
fmt.Sprintf("%d", ancient.count()),
|
||||
})
|
||||
}
|
||||
total += ancient.size()
|
||||
}
|
||||
table := tablewriter.NewWriter(os.Stdout)
|
||||
table.SetHeader([]string{"Database", "Category", "Size", "Items"})
|
||||
table.SetFooter([]string{"", "Total", total.String(), " "})
|
||||
@@ -521,6 +579,5 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
|
||||
if unaccounted.size > 0 {
|
||||
log.Error("Database contains unaccounted data", "size", unaccounted.size, "count", unaccounted.count)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright 2023 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/>
|
||||
|
||||
//go:build arm64 || amd64
|
||||
|
||||
package rawdb
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/ethdb/pebble"
|
||||
)
|
||||
|
||||
// Pebble is unsuported on 32bit architecture
|
||||
const PebbleEnabled = true
|
||||
|
||||
// NewPebbleDBDatabase creates a persistent key-value database without a freezer
|
||||
// moving immutable chain segments into cold storage.
|
||||
func NewPebbleDBDatabase(file string, cache int, handles int, namespace string, readonly bool) (ethdb.Database, error) {
|
||||
db, err := pebble.New(file, cache, handles, namespace, readonly)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewDatabase(db), nil
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright 2023 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/>.
|
||||
|
||||
//go:build !(arm64 || amd64)
|
||||
|
||||
package rawdb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
)
|
||||
|
||||
// Pebble is unsuported on 32bit architecture
|
||||
const PebbleEnabled = false
|
||||
|
||||
// NewPebbleDBDatabase creates a persistent key-value database without a freezer
|
||||
// moving immutable chain segments into cold storage.
|
||||
func NewPebbleDBDatabase(file string, cache int, handles int, namespace string, readonly bool) (ethdb.Database, error) {
|
||||
return nil, errors.New("pebble is not supported on this platform")
|
||||
}
|
||||
+22
-17
@@ -57,10 +57,10 @@ const freezerTableSize = 2 * 1000 * 1000 * 1000
|
||||
// Freezer is a memory mapped append-only database to store immutable ordered
|
||||
// data into flat files:
|
||||
//
|
||||
// - The append-only nature ensures that disk writes are minimized.
|
||||
// - The memory mapping ensures we can max out system memory for caching without
|
||||
// reserving it for go-ethereum. This would also reduce the memory requirements
|
||||
// of Geth, and thus also GC overhead.
|
||||
// - The append-only nature ensures that disk writes are minimized.
|
||||
// - The memory mapping ensures we can max out system memory for caching without
|
||||
// reserving it for go-ethereum. This would also reduce the memory requirements
|
||||
// of Geth, and thus also GC overhead.
|
||||
type Freezer struct {
|
||||
// WARNING: The `frozen` and `tail` fields are accessed atomically. On 32 bit platforms, only
|
||||
// 64-bit aligned fields can be atomic. The struct is guaranteed to be so aligned,
|
||||
@@ -188,9 +188,9 @@ func (f *Freezer) Ancient(kind string, number uint64) ([]byte, error) {
|
||||
|
||||
// AncientRange retrieves multiple items in sequence, starting from the index 'start'.
|
||||
// It will return
|
||||
// - at most 'max' items,
|
||||
// - at least 1 item (even if exceeding the maxByteSize), but will otherwise
|
||||
// return as many items as fit into maxByteSize.
|
||||
// - at most 'max' items,
|
||||
// - at least 1 item (even if exceeding the maxByteSize), but will otherwise
|
||||
// return as many items as fit into maxByteSize.
|
||||
func (f *Freezer) AncientRange(kind string, start, count, maxBytes uint64) ([][]byte, error) {
|
||||
if table := f.tables[kind]; table != nil {
|
||||
return table.RetrieveItems(start, count, maxBytes)
|
||||
@@ -321,30 +321,35 @@ func (f *Freezer) Sync() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// validate checks that every table has the same length.
|
||||
// validate checks that every table has the same boundary.
|
||||
// Used instead of `repair` in readonly mode.
|
||||
func (f *Freezer) validate() error {
|
||||
if len(f.tables) == 0 {
|
||||
return nil
|
||||
}
|
||||
var (
|
||||
length uint64
|
||||
name string
|
||||
head uint64
|
||||
tail uint64
|
||||
name string
|
||||
)
|
||||
// Hack to get length of any table
|
||||
// Hack to get boundary of any table
|
||||
for kind, table := range f.tables {
|
||||
length = atomic.LoadUint64(&table.items)
|
||||
head = atomic.LoadUint64(&table.items)
|
||||
tail = atomic.LoadUint64(&table.itemHidden)
|
||||
name = kind
|
||||
break
|
||||
}
|
||||
// Now check every table against that length
|
||||
// Now check every table against those boundaries.
|
||||
for kind, table := range f.tables {
|
||||
items := atomic.LoadUint64(&table.items)
|
||||
if length != items {
|
||||
return fmt.Errorf("freezer tables %s and %s have differing lengths: %d != %d", kind, name, items, length)
|
||||
if head != atomic.LoadUint64(&table.items) {
|
||||
return fmt.Errorf("freezer tables %s and %s have differing head: %d != %d", kind, name, atomic.LoadUint64(&table.items), head)
|
||||
}
|
||||
if tail != atomic.LoadUint64(&table.itemHidden) {
|
||||
return fmt.Errorf("freezer tables %s and %s have differing tail: %d != %d", kind, name, atomic.LoadUint64(&table.itemHidden), tail)
|
||||
}
|
||||
}
|
||||
atomic.StoreUint64(&f.frozen, length)
|
||||
atomic.StoreUint64(&f.frozen, head)
|
||||
atomic.StoreUint64(&f.tail, tail)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
// Copyright 2022 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package rawdb
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
)
|
||||
|
||||
const tmpSuffix = ".tmp"
|
||||
|
||||
// freezerOpenFunc is the function used to open/create a freezer.
|
||||
type freezerOpenFunc = func() (*Freezer, error)
|
||||
|
||||
// ResettableFreezer is a wrapper of the freezer which makes the
|
||||
// freezer resettable.
|
||||
type ResettableFreezer struct {
|
||||
freezer *Freezer
|
||||
opener freezerOpenFunc
|
||||
datadir string
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
// NewResettableFreezer creates a resettable freezer, note freezer is
|
||||
// only resettable if the passed file directory is exclusively occupied
|
||||
// by the freezer. And also the user-configurable ancient root directory
|
||||
// is **not** supported for reset since it might be a mount and rename
|
||||
// will cause a copy of hundreds of gigabyte into local directory. It
|
||||
// needs some other file based solutions.
|
||||
//
|
||||
// The reset function will delete directory atomically and re-create the
|
||||
// freezer from scratch.
|
||||
func NewResettableFreezer(datadir string, namespace string, readonly bool, maxTableSize uint32, tables map[string]bool) (*ResettableFreezer, error) {
|
||||
if err := cleanup(datadir); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opener := func() (*Freezer, error) {
|
||||
return NewFreezer(datadir, namespace, readonly, maxTableSize, tables)
|
||||
}
|
||||
freezer, err := opener()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ResettableFreezer{
|
||||
freezer: freezer,
|
||||
opener: opener,
|
||||
datadir: datadir,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Reset deletes the file directory exclusively occupied by the freezer and
|
||||
// recreate the freezer from scratch. The atomicity of directory deletion
|
||||
// is guaranteed by the rename operation, the leftover directory will be
|
||||
// cleaned up in next startup in case crash happens after rename.
|
||||
func (f *ResettableFreezer) Reset() error {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
|
||||
if err := f.freezer.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
tmp := tmpName(f.datadir)
|
||||
if err := os.Rename(f.datadir, tmp); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.RemoveAll(tmp); err != nil {
|
||||
return err
|
||||
}
|
||||
freezer, err := f.opener()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.freezer = freezer
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close terminates the chain freezer, unmapping all the data files.
|
||||
func (f *ResettableFreezer) Close() error {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
|
||||
return f.freezer.Close()
|
||||
}
|
||||
|
||||
// HasAncient returns an indicator whether the specified ancient data exists
|
||||
// in the freezer
|
||||
func (f *ResettableFreezer) HasAncient(kind string, number uint64) (bool, error) {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
|
||||
return f.freezer.HasAncient(kind, number)
|
||||
}
|
||||
|
||||
// Ancient retrieves an ancient binary blob from the append-only immutable files.
|
||||
func (f *ResettableFreezer) Ancient(kind string, number uint64) ([]byte, error) {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
|
||||
return f.freezer.Ancient(kind, number)
|
||||
}
|
||||
|
||||
// AncientRange retrieves multiple items in sequence, starting from the index 'start'.
|
||||
// It will return
|
||||
// - at most 'max' items,
|
||||
// - at least 1 item (even if exceeding the maxByteSize), but will otherwise
|
||||
// return as many items as fit into maxByteSize
|
||||
func (f *ResettableFreezer) AncientRange(kind string, start, count, maxBytes uint64) ([][]byte, error) {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
|
||||
return f.freezer.AncientRange(kind, start, count, maxBytes)
|
||||
}
|
||||
|
||||
// Ancients returns the length of the frozen items.
|
||||
func (f *ResettableFreezer) Ancients() (uint64, error) {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
|
||||
return f.freezer.Ancients()
|
||||
}
|
||||
|
||||
// Tail returns the number of first stored item in the freezer.
|
||||
func (f *ResettableFreezer) Tail() (uint64, error) {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
|
||||
return f.freezer.Tail()
|
||||
}
|
||||
|
||||
// AncientSize returns the ancient size of the specified category.
|
||||
func (f *ResettableFreezer) AncientSize(kind string) (uint64, error) {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
|
||||
return f.freezer.AncientSize(kind)
|
||||
}
|
||||
|
||||
// ReadAncients runs the given read operation while ensuring that no writes take place
|
||||
// on the underlying freezer.
|
||||
func (f *ResettableFreezer) ReadAncients(fn func(ethdb.AncientReaderOp) error) (err error) {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
|
||||
return f.freezer.ReadAncients(fn)
|
||||
}
|
||||
|
||||
// ModifyAncients runs the given write operation.
|
||||
func (f *ResettableFreezer) ModifyAncients(fn func(ethdb.AncientWriteOp) error) (writeSize int64, err error) {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
|
||||
return f.freezer.ModifyAncients(fn)
|
||||
}
|
||||
|
||||
// TruncateHead discards any recent data above the provided threshold number.
|
||||
func (f *ResettableFreezer) TruncateHead(items uint64) error {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
|
||||
return f.freezer.TruncateHead(items)
|
||||
}
|
||||
|
||||
// TruncateTail discards any recent data below the provided threshold number.
|
||||
func (f *ResettableFreezer) TruncateTail(tail uint64) error {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
|
||||
return f.freezer.TruncateTail(tail)
|
||||
}
|
||||
|
||||
// Sync flushes all data tables to disk.
|
||||
func (f *ResettableFreezer) Sync() error {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
|
||||
return f.freezer.Sync()
|
||||
}
|
||||
|
||||
// MigrateTable processes the entries in a given table in sequence
|
||||
// converting them to a new format if they're of an old format.
|
||||
func (f *ResettableFreezer) MigrateTable(kind string, convert convertLegacyFn) error {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
|
||||
return f.freezer.MigrateTable(kind, convert)
|
||||
}
|
||||
|
||||
// cleanup removes the directory located in the specified path
|
||||
// has the name with deletion marker suffix.
|
||||
func cleanup(path string) error {
|
||||
parent := filepath.Dir(path)
|
||||
if _, err := os.Lstat(parent); os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
dir, err := os.Open(parent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
names, err := dir.Readdirnames(0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cerr := dir.Close(); cerr != nil {
|
||||
return cerr
|
||||
}
|
||||
for _, name := range names {
|
||||
if name == filepath.Base(path)+tmpSuffix {
|
||||
return os.RemoveAll(filepath.Join(parent, name))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func tmpName(path string) string {
|
||||
return filepath.Join(filepath.Dir(path), filepath.Base(path)+tmpSuffix)
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
// Copyright 2022 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package rawdb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
)
|
||||
|
||||
func TestResetFreezer(t *testing.T) {
|
||||
items := []struct {
|
||||
id uint64
|
||||
blob []byte
|
||||
}{
|
||||
{0, bytes.Repeat([]byte{0}, 2048)},
|
||||
{1, bytes.Repeat([]byte{1}, 2048)},
|
||||
{2, bytes.Repeat([]byte{2}, 2048)},
|
||||
}
|
||||
f, _ := NewResettableFreezer(t.TempDir(), "", false, 2048, freezerTestTableDef)
|
||||
defer f.Close()
|
||||
|
||||
f.ModifyAncients(func(op ethdb.AncientWriteOp) error {
|
||||
for _, item := range items {
|
||||
op.AppendRaw("test", item.id, item.blob)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
for _, item := range items {
|
||||
blob, _ := f.Ancient("test", item.id)
|
||||
if !bytes.Equal(blob, item.blob) {
|
||||
t.Fatal("Unexpected blob")
|
||||
}
|
||||
}
|
||||
|
||||
// Reset freezer
|
||||
f.Reset()
|
||||
count, _ := f.Ancients()
|
||||
if count != 0 {
|
||||
t.Fatal("Failed to reset freezer")
|
||||
}
|
||||
for _, item := range items {
|
||||
blob, _ := f.Ancient("test", item.id)
|
||||
if len(blob) != 0 {
|
||||
t.Fatal("Unexpected blob")
|
||||
}
|
||||
}
|
||||
|
||||
// Fill the freezer
|
||||
f.ModifyAncients(func(op ethdb.AncientWriteOp) error {
|
||||
for _, item := range items {
|
||||
op.AppendRaw("test", item.id, item.blob)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
for _, item := range items {
|
||||
blob, _ := f.Ancient("test", item.id)
|
||||
if !bytes.Equal(blob, item.blob) {
|
||||
t.Fatal("Unexpected blob")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFreezerCleanup(t *testing.T) {
|
||||
items := []struct {
|
||||
id uint64
|
||||
blob []byte
|
||||
}{
|
||||
{0, bytes.Repeat([]byte{0}, 2048)},
|
||||
{1, bytes.Repeat([]byte{1}, 2048)},
|
||||
{2, bytes.Repeat([]byte{2}, 2048)},
|
||||
}
|
||||
datadir := t.TempDir()
|
||||
f, _ := NewResettableFreezer(datadir, "", false, 2048, freezerTestTableDef)
|
||||
f.ModifyAncients(func(op ethdb.AncientWriteOp) error {
|
||||
for _, item := range items {
|
||||
op.AppendRaw("test", item.id, item.blob)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
f.Close()
|
||||
os.Rename(datadir, tmpName(datadir))
|
||||
|
||||
// Open the freezer again, trigger cleanup operation
|
||||
f, _ = NewResettableFreezer(datadir, "", false, 2048, freezerTestTableDef)
|
||||
f.Close()
|
||||
|
||||
if _, err := os.Lstat(tmpName(datadir)); !os.IsNotExist(err) {
|
||||
t.Fatal("Failed to cleanup leftover directory")
|
||||
}
|
||||
}
|
||||
+56
-35
@@ -148,20 +148,12 @@ func newTable(path string, name string, readMeter metrics.Meter, writeMeter metr
|
||||
meta *os.File
|
||||
)
|
||||
if readonly {
|
||||
// Will fail if table doesn't exist
|
||||
// Will fail if table index file or meta file is not existent
|
||||
index, err = openFreezerFileForReadOnly(filepath.Join(path, idxName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO(rjl493456442) change it to read-only mode. Open the metadata file
|
||||
// in rw mode. It's a temporary solution for now and should be changed
|
||||
// whenever the tail deletion is actually used. The reason for this hack is
|
||||
// the additional meta file for each freezer table is added in order to support
|
||||
// tail deletion, but for most legacy nodes this file is missing. This check
|
||||
// will suddenly break lots of database relevant commands. So the metadata file
|
||||
// is always opened for mutation and nothing else will be written except
|
||||
// the initialization.
|
||||
meta, err = openFreezerFileForAppend(filepath.Join(path, fmt.Sprintf("%s.meta", name)))
|
||||
meta, err = openFreezerFileForReadOnly(filepath.Join(path, fmt.Sprintf("%s.meta", name)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -237,6 +229,7 @@ func (t *freezerTable) repair() error {
|
||||
lastIndex indexEntry
|
||||
contentSize int64
|
||||
contentExp int64
|
||||
verbose bool
|
||||
)
|
||||
// Read index zero, determine what file is the earliest
|
||||
// and what item offset to use
|
||||
@@ -280,9 +273,10 @@ func (t *freezerTable) repair() error {
|
||||
// Keep truncating both files until they come in sync
|
||||
contentExp = int64(lastIndex.offset)
|
||||
for contentExp != contentSize {
|
||||
verbose = true
|
||||
// Truncate the head file to the last offset pointer
|
||||
if contentExp < contentSize {
|
||||
t.logger.Warn("Truncating dangling head", "indexed", common.StorageSize(contentExp), "stored", common.StorageSize(contentSize))
|
||||
t.logger.Warn("Truncating dangling head", "indexed", contentExp, "stored", contentSize)
|
||||
if err := truncateFreezerFile(t.head, contentExp); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -290,7 +284,7 @@ func (t *freezerTable) repair() error {
|
||||
}
|
||||
// Truncate the index to point within the head file
|
||||
if contentExp > contentSize {
|
||||
t.logger.Warn("Truncating dangling indexes", "indexed", common.StorageSize(contentExp), "stored", common.StorageSize(contentSize))
|
||||
t.logger.Warn("Truncating dangling indexes", "indexes", offsetsSize/indexEntrySize, "indexed", contentExp, "stored", contentSize)
|
||||
if err := truncateFreezerFile(t.index, offsetsSize-indexEntrySize); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -351,7 +345,11 @@ func (t *freezerTable) repair() error {
|
||||
if err := t.preopen(); err != nil {
|
||||
return err
|
||||
}
|
||||
t.logger.Debug("Chain freezer table opened", "items", t.items, "size", common.StorageSize(t.headBytes))
|
||||
if verbose {
|
||||
t.logger.Info("Chain freezer table opened", "items", t.items, "size", t.headBytes)
|
||||
} else {
|
||||
t.logger.Debug("Chain freezer table opened", "items", t.items, "size", common.StorageSize(t.headBytes))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -561,21 +559,31 @@ func (t *freezerTable) Close() error {
|
||||
defer t.lock.Unlock()
|
||||
|
||||
var errs []error
|
||||
if err := t.index.Close(); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
t.index = nil
|
||||
|
||||
if err := t.meta.Close(); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
t.meta = nil
|
||||
|
||||
for _, f := range t.files {
|
||||
if err := f.Close(); err != nil {
|
||||
errs = append(errs, err)
|
||||
doClose := func(f *os.File, sync bool, close bool) {
|
||||
if sync && !t.readonly {
|
||||
if err := f.Sync(); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
if close {
|
||||
if err := f.Close(); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Trying to fsync a file opened in rdonly causes "Access denied"
|
||||
// error on Windows.
|
||||
doClose(t.index, true, true)
|
||||
doClose(t.meta, true, true)
|
||||
// The preopened non-head data-files are all opened in readonly.
|
||||
// The head is opened in rw-mode, so we sync it here - but since it's also
|
||||
// part of t.files, it will be closed in the loop below.
|
||||
doClose(t.head, true, false) // sync but do not close
|
||||
for _, f := range t.files {
|
||||
doClose(f, false, true) // close but do not sync
|
||||
}
|
||||
t.index = nil
|
||||
t.meta = nil
|
||||
t.head = nil
|
||||
|
||||
if errs != nil {
|
||||
@@ -732,7 +740,7 @@ func (t *freezerTable) retrieveItems(start, count, maxBytes uint64) ([]byte, []i
|
||||
defer t.lock.RUnlock()
|
||||
|
||||
// Ensure the table and the item are accessible
|
||||
if t.index == nil || t.head == nil {
|
||||
if t.index == nil || t.head == nil || t.meta == nil {
|
||||
return nil, nil, errClosed
|
||||
}
|
||||
var (
|
||||
@@ -860,8 +868,11 @@ func (t *freezerTable) advanceHead() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Close old file, and reopen in RDONLY mode.
|
||||
// Commit the contents of the old file to stable storage and
|
||||
// tear it down. It will be re-opened in read-only mode.
|
||||
if err := t.head.Sync(); err != nil {
|
||||
return err
|
||||
}
|
||||
t.releaseFile(t.headId)
|
||||
t.openFile(t.headId, openFreezerFileForReadOnly)
|
||||
|
||||
@@ -875,13 +886,22 @@ func (t *freezerTable) advanceHead() error {
|
||||
// Sync pushes any pending data from memory out to disk. This is an expensive
|
||||
// operation, so use it with care.
|
||||
func (t *freezerTable) Sync() error {
|
||||
if err := t.index.Sync(); err != nil {
|
||||
return err
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
if t.index == nil || t.head == nil || t.meta == nil {
|
||||
return errClosed
|
||||
}
|
||||
if err := t.meta.Sync(); err != nil {
|
||||
return err
|
||||
var err error
|
||||
trackError := func(e error) {
|
||||
if e != nil && err == nil {
|
||||
err = e
|
||||
}
|
||||
}
|
||||
return t.head.Sync()
|
||||
|
||||
trackError(t.index.Sync())
|
||||
trackError(t.meta.Sync())
|
||||
trackError(t.head.Sync())
|
||||
return err
|
||||
}
|
||||
|
||||
func (t *freezerTable) dumpIndexStdout(start, stop int64) {
|
||||
@@ -901,7 +921,8 @@ func (t *freezerTable) dumpIndex(w io.Writer, start, stop int64) {
|
||||
fmt.Fprintf(w, "Failed to decode freezer table %v\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(w, "Version %d deleted %d, hidden %d\n", meta.Version, atomic.LoadUint64(&t.itemOffset), atomic.LoadUint64(&t.itemHidden))
|
||||
fmt.Fprintf(w, "Version %d count %d, deleted %d, hidden %d\n", meta.Version,
|
||||
atomic.LoadUint64(&t.items), atomic.LoadUint64(&t.itemOffset), atomic.LoadUint64(&t.itemHidden))
|
||||
|
||||
buf := make([]byte, indexEntrySize)
|
||||
|
||||
|
||||
@@ -27,17 +27,12 @@ import (
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"testing/quick"
|
||||
"time"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().Unix())
|
||||
}
|
||||
|
||||
// TestFreezerBasics test initializing a freezertable from scratch, writing to the table,
|
||||
// and reading it back.
|
||||
func TestFreezerBasics(t *testing.T) {
|
||||
|
||||
@@ -190,7 +190,7 @@ func TestFreezerConcurrentModifyTruncate(t *testing.T) {
|
||||
|
||||
var item = make([]byte, 256)
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
for i := 0; i < 10; i++ {
|
||||
// First reset and write 100 items.
|
||||
if err := f.TruncateHead(0); err != nil {
|
||||
t.Fatal("truncate failed:", err)
|
||||
@@ -407,3 +407,25 @@ func TestRenameWindows(t *testing.T) {
|
||||
t.Errorf("unexpected file contents. Got %v\n", buf)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFreezerCloseSync(t *testing.T) {
|
||||
t.Parallel()
|
||||
f, _ := newFreezerForTesting(t, map[string]bool{"a": true, "b": true})
|
||||
defer f.Close()
|
||||
|
||||
// Now, close and sync. This mimics the behaviour if the node is shut down,
|
||||
// just as the chain freezer is writing.
|
||||
// 1: thread-1: chain treezer writes, via freezeRange (holds lock)
|
||||
// 2: thread-2: Close called, waits for write to finish
|
||||
// 3: thread-1: finishes writing, releases lock
|
||||
// 4: thread-2: obtains lock, completes Close()
|
||||
// 5: thread-1: calls f.Sync()
|
||||
if err := f.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := f.Sync(); err == nil {
|
||||
t.Fatalf("want error, have nil")
|
||||
} else if have, want := err.Error(), "[closed closed]"; have != want {
|
||||
t.Fatalf("want %v, have %v", have, want)
|
||||
}
|
||||
}
|
||||
|
||||
+26
-2
@@ -100,12 +100,26 @@ var (
|
||||
CodePrefix = []byte("c") // CodePrefix + code hash -> account code
|
||||
skeletonHeaderPrefix = []byte("S") // skeletonHeaderPrefix + num (uint64 big endian) -> header
|
||||
|
||||
// Path-based trie node scheme.
|
||||
trieNodeAccountPrefix = []byte("A") // trieNodeAccountPrefix + hexPath -> trie node
|
||||
trieNodeStoragePrefix = []byte("O") // trieNodeStoragePrefix + accountHash + hexPath -> trie node
|
||||
|
||||
PreimagePrefix = []byte("secure-key-") // PreimagePrefix + hash -> preimage
|
||||
configPrefix = []byte("ethereum-config-") // config prefix for the db
|
||||
genesisPrefix = []byte("ethereum-genesis-") // genesis state prefix for the db
|
||||
|
||||
// Chain index prefixes (use `i` + single byte to avoid mixing data types).
|
||||
BloomBitsIndexPrefix = []byte("iB") // BloomBitsIndexPrefix is the data table of a chain indexer to track its progress
|
||||
// BloomBitsIndexPrefix is the data table of a chain indexer to track its progress
|
||||
BloomBitsIndexPrefix = []byte("iB")
|
||||
|
||||
ChtPrefix = []byte("chtRootV2-") // ChtPrefix + chtNum (uint64 big endian) -> trie root hash
|
||||
ChtTablePrefix = []byte("cht-")
|
||||
ChtIndexTablePrefix = []byte("chtIndexV2-")
|
||||
|
||||
BloomTriePrefix = []byte("bltRoot-") // BloomTriePrefix + bloomTrieNum (uint64 big endian) -> trie root hash
|
||||
BloomTrieTablePrefix = []byte("blt-")
|
||||
BloomTrieIndexPrefix = []byte("bltIndex-")
|
||||
|
||||
CliqueSnapshotPrefix = []byte("clique-")
|
||||
|
||||
preimageCounter = metrics.NewRegisteredCounter("db/preimage/total", nil)
|
||||
preimageHitCounter = metrics.NewRegisteredCounter("db/preimage/hits", nil)
|
||||
@@ -224,3 +238,13 @@ func configKey(hash common.Hash) []byte {
|
||||
func genesisStateSpecKey(hash common.Hash) []byte {
|
||||
return append(genesisPrefix, hash.Bytes()...)
|
||||
}
|
||||
|
||||
// accountTrieNodeKey = trieNodeAccountPrefix + nodePath.
|
||||
func accountTrieNodeKey(path []byte) []byte {
|
||||
return append(trieNodeAccountPrefix, path...)
|
||||
}
|
||||
|
||||
// storageTrieNodeKey = trieNodeStoragePrefix + accountHash + nodePath.
|
||||
func storageTrieNodeKey(accountHash common.Hash, path []byte) []byte {
|
||||
return append(append(trieNodeStoragePrefix, accountHash.Bytes()...), path...)
|
||||
}
|
||||
|
||||
+3
-7
@@ -23,7 +23,6 @@ import (
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
@@ -33,10 +32,9 @@ import (
|
||||
|
||||
func getBlock(transactions int, uncles int, dataSize int) *types.Block {
|
||||
var (
|
||||
aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa")
|
||||
// Generate a canonical chain to act as the main dataset
|
||||
aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa")
|
||||
engine = ethash.NewFaker()
|
||||
db = rawdb.NewMemoryDatabase()
|
||||
|
||||
// A sender who makes transactions, has some funds
|
||||
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
address = crypto.PubkeyToAddress(key.PublicKey)
|
||||
@@ -45,11 +43,9 @@ func getBlock(transactions int, uncles int, dataSize int) *types.Block {
|
||||
Config: params.TestChainConfig,
|
||||
Alloc: GenesisAlloc{address: {Balance: funds}},
|
||||
}
|
||||
genesis = gspec.MustCommit(db)
|
||||
)
|
||||
|
||||
// We need to generate as many blocks +1 as uncles
|
||||
blocks, _ := GenerateChain(params.TestChainConfig, genesis, engine, db, uncles+1,
|
||||
_, blocks, _ := GenerateChainWithGenesis(gspec, engine, uncles+1,
|
||||
func(n int, b *BlockGen) {
|
||||
if n == uncles {
|
||||
// Add transactions and stuff on the last block
|
||||
|
||||
@@ -22,8 +22,8 @@ import (
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
)
|
||||
|
||||
// senderCacher is a concurrent transaction sender recoverer and cacher.
|
||||
var senderCacher = newTxSenderCacher(runtime.NumCPU())
|
||||
// SenderCacher is a concurrent transaction sender recoverer and cacher.
|
||||
var SenderCacher = newTxSenderCacher(runtime.NumCPU())
|
||||
|
||||
// txSenderCacherRequest is a request for recovering transaction senders with a
|
||||
// specific signature scheme and caching it into the transactions themselves.
|
||||
@@ -67,10 +67,10 @@ func (cacher *txSenderCacher) cache() {
|
||||
}
|
||||
}
|
||||
|
||||
// recover recovers the senders from a batch of transactions and caches them
|
||||
// Recover recovers the senders from a batch of transactions and caches them
|
||||
// back into the same data structures. There is no validation being done, nor
|
||||
// any reaction to invalid signatures. That is up to calling code later.
|
||||
func (cacher *txSenderCacher) recover(signer types.Signer, txs []*types.Transaction) {
|
||||
func (cacher *txSenderCacher) Recover(signer types.Signer, txs []*types.Transaction) {
|
||||
// If there's nothing to recover, abort
|
||||
if len(txs) == 0 {
|
||||
return
|
||||
@@ -89,10 +89,10 @@ func (cacher *txSenderCacher) recover(signer types.Signer, txs []*types.Transact
|
||||
}
|
||||
}
|
||||
|
||||
// recoverFromBlocks recovers the senders from a batch of blocks and caches them
|
||||
// RecoverFromBlocks recovers the senders from a batch of blocks and caches them
|
||||
// back into the same data structures. There is no validation being done, nor
|
||||
// any reaction to invalid signatures. That is up to calling code later.
|
||||
func (cacher *txSenderCacher) recoverFromBlocks(signer types.Signer, blocks []*types.Block) {
|
||||
func (cacher *txSenderCacher) RecoverFromBlocks(signer types.Signer, blocks []*types.Block) {
|
||||
count := 0
|
||||
for _, block := range blocks {
|
||||
count += len(block.Transactions())
|
||||
@@ -101,5 +101,5 @@ func (cacher *txSenderCacher) recoverFromBlocks(signer types.Signer, blocks []*t
|
||||
for _, block := range blocks {
|
||||
txs = append(txs, block.Transactions()...)
|
||||
}
|
||||
cacher.recover(signer, txs)
|
||||
cacher.Recover(signer, txs)
|
||||
}
|
||||
+54
-27
@@ -20,13 +20,12 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/VictoriaMetrics/fastcache"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/lru"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -43,7 +42,7 @@ type Database interface {
|
||||
OpenTrie(root common.Hash) (Trie, error)
|
||||
|
||||
// OpenStorageTrie opens the storage trie of an account.
|
||||
OpenStorageTrie(addrHash, root common.Hash) (Trie, error)
|
||||
OpenStorageTrie(stateRoot common.Hash, addrHash, root common.Hash) (Trie, error)
|
||||
|
||||
// CopyTrie returns an independent copy of the given trie.
|
||||
CopyTrie(Trie) Trie
|
||||
@@ -54,6 +53,9 @@ type Database interface {
|
||||
// ContractCodeSize retrieves a particular contracts code's size.
|
||||
ContractCodeSize(addrHash, codeHash common.Hash) (int, error)
|
||||
|
||||
// DiskDB returns the underlying key-value disk database.
|
||||
DiskDB() ethdb.KeyValueStore
|
||||
|
||||
// TrieDB retrieves the low level trie database used for data storage.
|
||||
TrieDB() *trie.Database
|
||||
}
|
||||
@@ -71,8 +73,13 @@ type Trie interface {
|
||||
// trie.MissingNodeError is returned.
|
||||
TryGet(key []byte) ([]byte, error)
|
||||
|
||||
// TryGetAccount abstract an account read from the trie.
|
||||
TryGetAccount(key []byte) (*types.StateAccount, error)
|
||||
// TryGetAccount abstracts an account read from the trie. It retrieves the
|
||||
// account blob from the trie with provided account address and decodes it
|
||||
// with associated decoding algorithm. If the specified account is not in
|
||||
// the trie, nil will be returned. If the trie is corrupted(e.g. some nodes
|
||||
// are missing or the account blob is incorrect for decoding), an error will
|
||||
// be returned.
|
||||
TryGetAccount(address common.Address) (*types.StateAccount, error)
|
||||
|
||||
// TryUpdate associates key with value in the trie. If value has length zero, any
|
||||
// existing value is deleted from the trie. The value bytes must not be modified
|
||||
@@ -80,15 +87,17 @@ type Trie interface {
|
||||
// database, a trie.MissingNodeError is returned.
|
||||
TryUpdate(key, value []byte) error
|
||||
|
||||
// TryUpdateAccount abstract an account write to the trie.
|
||||
TryUpdateAccount(key []byte, account *types.StateAccount) error
|
||||
// TryUpdateAccount abstracts an account write to the trie. It encodes the
|
||||
// provided account object with associated algorithm and then updates it
|
||||
// in the trie with provided address.
|
||||
TryUpdateAccount(address common.Address, account *types.StateAccount) error
|
||||
|
||||
// TryDelete removes any existing value for key from the trie. If a node was not
|
||||
// found in the database, a trie.MissingNodeError is returned.
|
||||
TryDelete(key []byte) error
|
||||
|
||||
// TryDeleteAccount abstracts an account deletion from the trie.
|
||||
TryDeleteAccount(key []byte) error
|
||||
TryDeleteAccount(address common.Address) error
|
||||
|
||||
// Hash returns the root hash of the trie. It does not write to the database and
|
||||
// can be used even if the trie doesn't have one.
|
||||
@@ -100,7 +109,7 @@ type Trie interface {
|
||||
// The returned nodeset can be nil if the trie is clean(nothing to commit).
|
||||
// Once the trie is committed, it's not usable anymore. A new trie must
|
||||
// be created with new root and updated trie database for following usage
|
||||
Commit(collectLeaf bool) (common.Hash, *trie.NodeSet, error)
|
||||
Commit(collectLeaf bool) (common.Hash, *trie.NodeSet)
|
||||
|
||||
// NodeIterator returns an iterator that returns nodes of the trie. Iteration
|
||||
// starts at the key after the given start key.
|
||||
@@ -127,23 +136,34 @@ func NewDatabase(db ethdb.Database) Database {
|
||||
// is safe for concurrent use and retains a lot of collapsed RLP trie nodes in a
|
||||
// large memory cache.
|
||||
func NewDatabaseWithConfig(db ethdb.Database, config *trie.Config) Database {
|
||||
csc, _ := lru.New(codeSizeCacheSize)
|
||||
return &cachingDB{
|
||||
db: trie.NewDatabaseWithConfig(db, config),
|
||||
codeSizeCache: csc,
|
||||
codeCache: fastcache.New(codeCacheSize),
|
||||
disk: db,
|
||||
codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize),
|
||||
codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize),
|
||||
triedb: trie.NewDatabaseWithConfig(db, config),
|
||||
}
|
||||
}
|
||||
|
||||
// NewDatabaseWithNodeDB creates a state database with an already initialized node database.
|
||||
func NewDatabaseWithNodeDB(db ethdb.Database, triedb *trie.Database) Database {
|
||||
return &cachingDB{
|
||||
disk: db,
|
||||
codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize),
|
||||
codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize),
|
||||
triedb: triedb,
|
||||
}
|
||||
}
|
||||
|
||||
type cachingDB struct {
|
||||
db *trie.Database
|
||||
codeSizeCache *lru.Cache
|
||||
codeCache *fastcache.Cache
|
||||
disk ethdb.KeyValueStore
|
||||
codeSizeCache *lru.Cache[common.Hash, int]
|
||||
codeCache *lru.SizeConstrainedCache[common.Hash, []byte]
|
||||
triedb *trie.Database
|
||||
}
|
||||
|
||||
// OpenTrie opens the main account trie at a specific root hash.
|
||||
func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) {
|
||||
tr, err := trie.NewStateTrie(common.Hash{}, root, db.db)
|
||||
tr, err := trie.NewStateTrie(trie.StateTrieID(root), db.triedb)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -151,8 +171,8 @@ func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) {
|
||||
}
|
||||
|
||||
// OpenStorageTrie opens the storage trie of an account.
|
||||
func (db *cachingDB) OpenStorageTrie(addrHash, root common.Hash) (Trie, error) {
|
||||
tr, err := trie.NewStateTrie(addrHash, root, db.db)
|
||||
func (db *cachingDB) OpenStorageTrie(stateRoot common.Hash, addrHash, root common.Hash) (Trie, error) {
|
||||
tr, err := trie.NewStateTrie(trie.StorageTrieID(stateRoot, addrHash, root), db.triedb)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -171,12 +191,13 @@ func (db *cachingDB) CopyTrie(t Trie) Trie {
|
||||
|
||||
// ContractCode retrieves a particular contract's code.
|
||||
func (db *cachingDB) ContractCode(addrHash, codeHash common.Hash) ([]byte, error) {
|
||||
if code := db.codeCache.Get(nil, codeHash.Bytes()); len(code) > 0 {
|
||||
code, _ := db.codeCache.Get(codeHash)
|
||||
if len(code) > 0 {
|
||||
return code, nil
|
||||
}
|
||||
code := rawdb.ReadCode(db.db.DiskDB(), codeHash)
|
||||
code = rawdb.ReadCode(db.disk, codeHash)
|
||||
if len(code) > 0 {
|
||||
db.codeCache.Set(codeHash.Bytes(), code)
|
||||
db.codeCache.Add(codeHash, code)
|
||||
db.codeSizeCache.Add(codeHash, len(code))
|
||||
return code, nil
|
||||
}
|
||||
@@ -187,12 +208,13 @@ func (db *cachingDB) ContractCode(addrHash, codeHash common.Hash) ([]byte, error
|
||||
// code can't be found in the cache, then check the existence with **new**
|
||||
// db scheme.
|
||||
func (db *cachingDB) ContractCodeWithPrefix(addrHash, codeHash common.Hash) ([]byte, error) {
|
||||
if code := db.codeCache.Get(nil, codeHash.Bytes()); len(code) > 0 {
|
||||
code, _ := db.codeCache.Get(codeHash)
|
||||
if len(code) > 0 {
|
||||
return code, nil
|
||||
}
|
||||
code := rawdb.ReadCodeWithPrefix(db.db.DiskDB(), codeHash)
|
||||
code = rawdb.ReadCodeWithPrefix(db.disk, codeHash)
|
||||
if len(code) > 0 {
|
||||
db.codeCache.Set(codeHash.Bytes(), code)
|
||||
db.codeCache.Add(codeHash, code)
|
||||
db.codeSizeCache.Add(codeHash, len(code))
|
||||
return code, nil
|
||||
}
|
||||
@@ -202,13 +224,18 @@ func (db *cachingDB) ContractCodeWithPrefix(addrHash, codeHash common.Hash) ([]b
|
||||
// ContractCodeSize retrieves a particular contracts code's size.
|
||||
func (db *cachingDB) ContractCodeSize(addrHash, codeHash common.Hash) (int, error) {
|
||||
if cached, ok := db.codeSizeCache.Get(codeHash); ok {
|
||||
return cached.(int), nil
|
||||
return cached, nil
|
||||
}
|
||||
code, err := db.ContractCode(addrHash, codeHash)
|
||||
return len(code), err
|
||||
}
|
||||
|
||||
// DiskDB returns the underlying key-value disk database.
|
||||
func (db *cachingDB) DiskDB() ethdb.KeyValueStore {
|
||||
return db.disk
|
||||
}
|
||||
|
||||
// TrieDB retrieves any intermediate trie-node caching layer.
|
||||
func (db *cachingDB) TrieDB() *trie.Database {
|
||||
return db.db
|
||||
return db.triedb
|
||||
}
|
||||
|
||||
+7
-2
@@ -29,7 +29,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
)
|
||||
|
||||
// DumpConfig is a set of options to control what portions of the statewill be
|
||||
// DumpConfig is a set of options to control what portions of the state will be
|
||||
// iterated and collected.
|
||||
type DumpConfig struct {
|
||||
SkipCode bool
|
||||
@@ -168,7 +168,12 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
|
||||
}
|
||||
if !conf.SkipStorage {
|
||||
account.Storage = make(map[common.Hash]string)
|
||||
storageIt := trie.NewIterator(obj.getTrie(s.db).NodeIterator(nil))
|
||||
tr, err := obj.getTrie(s.db)
|
||||
if err != nil {
|
||||
log.Error("Failed to load storage trie", "err", err)
|
||||
continue
|
||||
}
|
||||
storageIt := trie.NewIterator(tr.NodeIterator(nil))
|
||||
for storageIt.Next() {
|
||||
_, content, _, err := rlp.Split(storageIt.Value)
|
||||
if err != nil {
|
||||
|
||||
@@ -109,7 +109,7 @@ func (it *NodeIterator) step() error {
|
||||
if err := rlp.Decode(bytes.NewReader(it.stateIt.LeafBlob()), &account); err != nil {
|
||||
return err
|
||||
}
|
||||
dataTrie, err := it.state.db.OpenStorageTrie(common.BytesToHash(it.stateIt.LeafKey()), account.Root)
|
||||
dataTrie, err := it.state.db.OpenStorageTrie(it.state.originalRoot, common.BytesToHash(it.stateIt.LeafKey()), account.Root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+46
-22
@@ -17,20 +17,19 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
)
|
||||
|
||||
// Tests that the node iterator indeed walks over the entire database contents.
|
||||
func TestNodeIteratorCoverage(t *testing.T) {
|
||||
// Create some arbitrary test state to iterate
|
||||
db, root, _ := makeTestState()
|
||||
db.TrieDB().Commit(root, false, nil)
|
||||
db, sdb, root, _ := makeTestState()
|
||||
sdb.TrieDB().Commit(root, false)
|
||||
|
||||
state, err := New(root, db, nil)
|
||||
state, err := New(root, sdb, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create state trie at %x: %v", root, err)
|
||||
}
|
||||
@@ -41,29 +40,54 @@ func TestNodeIteratorCoverage(t *testing.T) {
|
||||
hashes[it.Hash] = struct{}{}
|
||||
}
|
||||
}
|
||||
// Check in-disk nodes
|
||||
var (
|
||||
seenNodes = make(map[common.Hash]struct{})
|
||||
seenCodes = make(map[common.Hash]struct{})
|
||||
)
|
||||
it := db.NewIterator(nil, nil)
|
||||
for it.Next() {
|
||||
ok, hash := isTrieNode(sdb.TrieDB().Scheme(), it.Key(), it.Value())
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
seenNodes[hash] = struct{}{}
|
||||
}
|
||||
it.Release()
|
||||
|
||||
// Check in-disk codes
|
||||
it = db.NewIterator(nil, nil)
|
||||
for it.Next() {
|
||||
ok, hash := rawdb.IsCodeKey(it.Key())
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if _, ok := hashes[common.BytesToHash(hash)]; !ok {
|
||||
t.Errorf("state entry not reported %x", it.Key())
|
||||
}
|
||||
seenCodes[common.BytesToHash(hash)] = struct{}{}
|
||||
}
|
||||
it.Release()
|
||||
|
||||
// Cross check the iterated hashes and the database/nodepool content
|
||||
for hash := range hashes {
|
||||
if _, err = db.TrieDB().Node(hash); err != nil {
|
||||
_, err = db.ContractCode(common.Hash{}, hash)
|
||||
_, ok := seenNodes[hash]
|
||||
if !ok {
|
||||
_, ok = seenCodes[hash]
|
||||
}
|
||||
if err != nil {
|
||||
if !ok {
|
||||
t.Errorf("failed to retrieve reported node %x", hash)
|
||||
}
|
||||
}
|
||||
for _, hash := range db.TrieDB().Nodes() {
|
||||
if _, ok := hashes[hash]; !ok {
|
||||
t.Errorf("state entry not reported %x", hash)
|
||||
}
|
||||
|
||||
// isTrieNode is a helper function which reports if the provided
|
||||
// database entry belongs to a trie node or not.
|
||||
func isTrieNode(scheme string, key, val []byte) (bool, common.Hash) {
|
||||
if scheme == rawdb.HashScheme {
|
||||
if len(key) == common.HashLength {
|
||||
return true, common.BytesToHash(key)
|
||||
}
|
||||
}
|
||||
it := db.TrieDB().DiskDB().(ethdb.Database).NewIterator(nil, nil)
|
||||
for it.Next() {
|
||||
key := it.Key()
|
||||
if bytes.HasPrefix(key, []byte("secure-key-")) {
|
||||
continue
|
||||
}
|
||||
if _, ok := hashes[common.BytesToHash(key)]; !ok {
|
||||
t.Errorf("state entry not reported %x", key)
|
||||
}
|
||||
}
|
||||
it.Release()
|
||||
return false, common.Hash{}
|
||||
}
|
||||
|
||||
+15
-2
@@ -138,6 +138,11 @@ type (
|
||||
address *common.Address
|
||||
slot *common.Hash
|
||||
}
|
||||
|
||||
transientStorageChange struct {
|
||||
account *common.Address
|
||||
key, prevalue common.Hash
|
||||
}
|
||||
)
|
||||
|
||||
func (ch createObjectChange) revert(s *StateDB) {
|
||||
@@ -151,8 +156,8 @@ func (ch createObjectChange) dirtied() *common.Address {
|
||||
|
||||
func (ch resetObjectChange) revert(s *StateDB) {
|
||||
s.setStateObject(ch.prev)
|
||||
if !ch.prevdestruct && s.snap != nil {
|
||||
delete(s.snapDestructs, ch.prev.addrHash)
|
||||
if !ch.prevdestruct {
|
||||
delete(s.stateObjectsDestruct, ch.prev.address)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,6 +218,14 @@ func (ch storageChange) dirtied() *common.Address {
|
||||
return ch.account
|
||||
}
|
||||
|
||||
func (ch transientStorageChange) revert(s *StateDB) {
|
||||
s.setTransientState(*ch.account, ch.key, ch.prevalue)
|
||||
}
|
||||
|
||||
func (ch transientStorageChange) dirtied() *common.Address {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ch refundChange) revert(s *StateDB) {
|
||||
s.refund = ch.prev
|
||||
}
|
||||
|
||||
@@ -19,10 +19,12 @@ package state
|
||||
import "github.com/ethereum/go-ethereum/metrics"
|
||||
|
||||
var (
|
||||
accountUpdatedMeter = metrics.NewRegisteredMeter("state/update/account", nil)
|
||||
storageUpdatedMeter = metrics.NewRegisteredMeter("state/update/storage", nil)
|
||||
accountDeletedMeter = metrics.NewRegisteredMeter("state/delete/account", nil)
|
||||
storageDeletedMeter = metrics.NewRegisteredMeter("state/delete/storage", nil)
|
||||
accountTrieCommittedMeter = metrics.NewRegisteredMeter("state/commit/accountnodes", nil)
|
||||
storageTriesCommittedMeter = metrics.NewRegisteredMeter("state/commit/storagenodes", nil)
|
||||
accountUpdatedMeter = metrics.NewRegisteredMeter("state/update/account", nil)
|
||||
storageUpdatedMeter = metrics.NewRegisteredMeter("state/update/storage", nil)
|
||||
accountDeletedMeter = metrics.NewRegisteredMeter("state/delete/account", nil)
|
||||
storageDeletedMeter = metrics.NewRegisteredMeter("state/delete/storage", nil)
|
||||
accountTrieUpdatedMeter = metrics.NewRegisteredMeter("state/update/accountnodes", nil)
|
||||
storageTriesUpdatedMeter = metrics.NewRegisteredMeter("state/update/storagenodes", nil)
|
||||
accountTrieDeletedMeter = metrics.NewRegisteredMeter("state/delete/accountnodes", nil)
|
||||
storageTriesDeletedMeter = metrics.NewRegisteredMeter("state/delete/storagenodes", nil)
|
||||
)
|
||||
|
||||
+52
-34
@@ -63,52 +63,63 @@ var (
|
||||
emptyCode = crypto.Keccak256(nil)
|
||||
)
|
||||
|
||||
// Config includes all the configurations for pruning.
|
||||
type Config struct {
|
||||
Datadir string // The directory of the state database
|
||||
Cachedir string // The directory of state clean cache
|
||||
BloomSize uint64 // The Megabytes of memory allocated to bloom-filter
|
||||
}
|
||||
|
||||
// Pruner is an offline tool to prune the stale state with the
|
||||
// help of the snapshot. The workflow of pruner is very simple:
|
||||
//
|
||||
// - iterate the snapshot, reconstruct the relevant state
|
||||
// - iterate the database, delete all other state entries which
|
||||
// don't belong to the target state and the genesis state
|
||||
// - iterate the snapshot, reconstruct the relevant state
|
||||
// - iterate the database, delete all other state entries which
|
||||
// don't belong to the target state and the genesis state
|
||||
//
|
||||
// It can take several hours(around 2 hours for mainnet) to finish
|
||||
// the whole pruning work. It's recommended to run this offline tool
|
||||
// periodically in order to release the disk usage and improve the
|
||||
// disk read performance to some extent.
|
||||
type Pruner struct {
|
||||
db ethdb.Database
|
||||
stateBloom *stateBloom
|
||||
datadir string
|
||||
trieCachePath string
|
||||
headHeader *types.Header
|
||||
snaptree *snapshot.Tree
|
||||
config Config
|
||||
chainHeader *types.Header
|
||||
db ethdb.Database
|
||||
stateBloom *stateBloom
|
||||
snaptree *snapshot.Tree
|
||||
}
|
||||
|
||||
// NewPruner creates the pruner instance.
|
||||
func NewPruner(db ethdb.Database, datadir, trieCachePath string, bloomSize uint64) (*Pruner, error) {
|
||||
func NewPruner(db ethdb.Database, config Config) (*Pruner, error) {
|
||||
headBlock := rawdb.ReadHeadBlock(db)
|
||||
if headBlock == nil {
|
||||
return nil, errors.New("Failed to load head block")
|
||||
return nil, errors.New("failed to load head block")
|
||||
}
|
||||
snaptree, err := snapshot.New(db, trie.NewDatabase(db), 256, headBlock.Root(), false, false, false)
|
||||
snapconfig := snapshot.Config{
|
||||
CacheSize: 256,
|
||||
Recovery: false,
|
||||
NoBuild: true,
|
||||
AsyncBuild: false,
|
||||
}
|
||||
snaptree, err := snapshot.New(snapconfig, db, trie.NewDatabase(db), headBlock.Root())
|
||||
if err != nil {
|
||||
return nil, err // The relevant snapshot(s) might not exist
|
||||
}
|
||||
// Sanitize the bloom filter size if it's too small.
|
||||
if bloomSize < 256 {
|
||||
log.Warn("Sanitizing bloomfilter size", "provided(MB)", bloomSize, "updated(MB)", 256)
|
||||
bloomSize = 256
|
||||
if config.BloomSize < 256 {
|
||||
log.Warn("Sanitizing bloomfilter size", "provided(MB)", config.BloomSize, "updated(MB)", 256)
|
||||
config.BloomSize = 256
|
||||
}
|
||||
stateBloom, err := newStateBloomWithSize(bloomSize)
|
||||
stateBloom, err := newStateBloomWithSize(config.BloomSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Pruner{
|
||||
db: db,
|
||||
stateBloom: stateBloom,
|
||||
datadir: datadir,
|
||||
trieCachePath: trieCachePath,
|
||||
headHeader: headBlock.Header(),
|
||||
snaptree: snaptree,
|
||||
config: config,
|
||||
chainHeader: headBlock.Header(),
|
||||
db: db,
|
||||
stateBloom: stateBloom,
|
||||
snaptree: snaptree,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -236,12 +247,12 @@ func (p *Pruner) Prune(root common.Hash) error {
|
||||
// reuse it for pruning instead of generating a new one. It's
|
||||
// mandatory because a part of state may already be deleted,
|
||||
// the recovery procedure is necessary.
|
||||
_, stateBloomRoot, err := findBloomFilter(p.datadir)
|
||||
_, stateBloomRoot, err := findBloomFilter(p.config.Datadir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if stateBloomRoot != (common.Hash{}) {
|
||||
return RecoverPruning(p.datadir, p.db, p.trieCachePath)
|
||||
return RecoverPruning(p.config.Datadir, p.db, p.config.Cachedir)
|
||||
}
|
||||
// If the target state root is not specified, use the HEAD-127 as the
|
||||
// target. The reason for picking it is:
|
||||
@@ -252,7 +263,7 @@ func (p *Pruner) Prune(root common.Hash) error {
|
||||
// Retrieve all snapshot layers from the current HEAD.
|
||||
// In theory there are 128 difflayers + 1 disk layer present,
|
||||
// so 128 diff layers are expected to be returned.
|
||||
layers = p.snaptree.Snapshots(p.headHeader.Root, 128, true)
|
||||
layers = p.snaptree.Snapshots(p.chainHeader.Root, 128, true)
|
||||
if len(layers) != 128 {
|
||||
// Reject if the accumulated diff layers are less than 128. It
|
||||
// means in most of normal cases, there is no associated state
|
||||
@@ -265,7 +276,7 @@ func (p *Pruner) Prune(root common.Hash) error {
|
||||
// Ensure the root is really present. The weak assumption
|
||||
// is the presence of root can indicate the presence of the
|
||||
// entire trie.
|
||||
if !rawdb.HasTrieNode(p.db, root) {
|
||||
if !rawdb.HasLegacyTrieNode(p.db, root) {
|
||||
// The special case is for clique based networks(rinkeby, goerli
|
||||
// and some other private networks), it's possible that two
|
||||
// consecutive blocks will have same root. In this case snapshot
|
||||
@@ -279,7 +290,7 @@ func (p *Pruner) Prune(root common.Hash) error {
|
||||
// as the pruning target.
|
||||
var found bool
|
||||
for i := len(layers) - 2; i >= 2; i-- {
|
||||
if rawdb.HasTrieNode(p.db, layers[i].Root()) {
|
||||
if rawdb.HasLegacyTrieNode(p.db, layers[i].Root()) {
|
||||
root = layers[i].Root()
|
||||
found = true
|
||||
log.Info("Selecting middle-layer as the pruning target", "root", root, "depth", i)
|
||||
@@ -294,7 +305,7 @@ func (p *Pruner) Prune(root common.Hash) error {
|
||||
}
|
||||
} else {
|
||||
if len(layers) > 0 {
|
||||
log.Info("Selecting bottom-most difflayer as the pruning target", "root", root, "height", p.headHeader.Number.Uint64()-127)
|
||||
log.Info("Selecting bottom-most difflayer as the pruning target", "root", root, "height", p.chainHeader.Number.Uint64()-127)
|
||||
} else {
|
||||
log.Info("Selecting user-specified state as the pruning target", "root", root)
|
||||
}
|
||||
@@ -303,7 +314,7 @@ func (p *Pruner) Prune(root common.Hash) error {
|
||||
// It's necessary otherwise in the next restart we will hit the
|
||||
// deleted state root in the "clean cache" so that the incomplete
|
||||
// state is picked for usage.
|
||||
deleteCleanTrieCache(p.trieCachePath)
|
||||
deleteCleanTrieCache(p.config.Cachedir)
|
||||
|
||||
// All the state roots of the middle layer should be forcibly pruned,
|
||||
// otherwise the dangling state will be left.
|
||||
@@ -325,7 +336,7 @@ func (p *Pruner) Prune(root common.Hash) error {
|
||||
if err := extractGenesis(p.db, p.stateBloom); err != nil {
|
||||
return err
|
||||
}
|
||||
filterName := bloomFilterName(p.datadir, root)
|
||||
filterName := bloomFilterName(p.config.Datadir, root)
|
||||
|
||||
log.Info("Writing state bloom to disk", "name", filterName)
|
||||
if err := p.stateBloom.Commit(filterName, filterName+stateBloomFileTempSuffix); err != nil {
|
||||
@@ -352,7 +363,7 @@ func RecoverPruning(datadir string, db ethdb.Database, trieCachePath string) err
|
||||
}
|
||||
headBlock := rawdb.ReadHeadBlock(db)
|
||||
if headBlock == nil {
|
||||
return errors.New("Failed to load head block")
|
||||
return errors.New("failed to load head block")
|
||||
}
|
||||
// Initialize the snapshot tree in recovery mode to handle this special case:
|
||||
// - Users run the `prune-state` command multiple times
|
||||
@@ -362,7 +373,13 @@ func RecoverPruning(datadir string, db ethdb.Database, trieCachePath string) err
|
||||
// - The state HEAD is rewound already because of multiple incomplete `prune-state`
|
||||
// In this case, even the state HEAD is not exactly matched with snapshot, it
|
||||
// still feasible to recover the pruning correctly.
|
||||
snaptree, err := snapshot.New(db, trie.NewDatabase(db), 256, headBlock.Root(), false, false, true)
|
||||
snapconfig := snapshot.Config{
|
||||
CacheSize: 256,
|
||||
Recovery: true,
|
||||
NoBuild: true,
|
||||
AsyncBuild: false,
|
||||
}
|
||||
snaptree, err := snapshot.New(snapconfig, db, trie.NewDatabase(db), headBlock.Root())
|
||||
if err != nil {
|
||||
return err // The relevant snapshot(s) might not exist
|
||||
}
|
||||
@@ -410,7 +427,7 @@ func extractGenesis(db ethdb.Database, stateBloom *stateBloom) error {
|
||||
if genesis == nil {
|
||||
return errors.New("missing genesis block")
|
||||
}
|
||||
t, err := trie.NewStateTrie(common.Hash{}, genesis.Root(), trie.NewDatabase(db))
|
||||
t, err := trie.NewStateTrie(trie.StateTrieID(genesis.Root()), trie.NewDatabase(db))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -430,7 +447,8 @@ func extractGenesis(db ethdb.Database, stateBloom *stateBloom) error {
|
||||
return err
|
||||
}
|
||||
if acc.Root != emptyRoot {
|
||||
storageTrie, err := trie.NewStateTrie(common.BytesToHash(accIter.LeafKey()), acc.Root, trie.NewDatabase(db))
|
||||
id := trie.StorageTrieID(genesis.Root(), common.BytesToHash(accIter.LeafKey()), acc.Root)
|
||||
storageTrie, err := trie.NewStateTrie(id, trie.NewDatabase(db))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ type trieKV struct {
|
||||
type (
|
||||
// trieGeneratorFn is the interface of trie generation which can
|
||||
// be implemented by different trie algorithm.
|
||||
trieGeneratorFn func(db ethdb.KeyValueWriter, owner common.Hash, in chan (trieKV), out chan (common.Hash))
|
||||
trieGeneratorFn func(db ethdb.KeyValueWriter, scheme string, owner common.Hash, in chan (trieKV), out chan (common.Hash))
|
||||
|
||||
// leafCallbackFn is the callback invoked at the leaves of the trie,
|
||||
// returns the subtrie root with the specified subtrie identifier.
|
||||
@@ -52,12 +52,12 @@ type (
|
||||
|
||||
// GenerateAccountTrieRoot takes an account iterator and reproduces the root hash.
|
||||
func GenerateAccountTrieRoot(it AccountIterator) (common.Hash, error) {
|
||||
return generateTrieRoot(nil, it, common.Hash{}, stackTrieGenerate, nil, newGenerateStats(), true)
|
||||
return generateTrieRoot(nil, "", it, common.Hash{}, stackTrieGenerate, nil, newGenerateStats(), true)
|
||||
}
|
||||
|
||||
// GenerateStorageTrieRoot takes a storage iterator and reproduces the root hash.
|
||||
func GenerateStorageTrieRoot(account common.Hash, it StorageIterator) (common.Hash, error) {
|
||||
return generateTrieRoot(nil, it, account, stackTrieGenerate, nil, newGenerateStats(), true)
|
||||
return generateTrieRoot(nil, "", it, account, stackTrieGenerate, nil, newGenerateStats(), true)
|
||||
}
|
||||
|
||||
// GenerateTrie takes the whole snapshot tree as the input, traverses all the
|
||||
@@ -71,7 +71,8 @@ func GenerateTrie(snaptree *Tree, root common.Hash, src ethdb.Database, dst ethd
|
||||
}
|
||||
defer acctIt.Release()
|
||||
|
||||
got, err := generateTrieRoot(dst, acctIt, common.Hash{}, stackTrieGenerate, func(dst ethdb.KeyValueWriter, accountHash, codeHash common.Hash, stat *generateStats) (common.Hash, error) {
|
||||
scheme := snaptree.triedb.Scheme()
|
||||
got, err := generateTrieRoot(dst, scheme, acctIt, common.Hash{}, stackTrieGenerate, func(dst ethdb.KeyValueWriter, accountHash, codeHash common.Hash, stat *generateStats) (common.Hash, error) {
|
||||
// Migrate the code first, commit the contract code into the tmp db.
|
||||
if codeHash != emptyCode {
|
||||
code := rawdb.ReadCode(src, codeHash)
|
||||
@@ -87,7 +88,7 @@ func GenerateTrie(snaptree *Tree, root common.Hash, src ethdb.Database, dst ethd
|
||||
}
|
||||
defer storageIt.Release()
|
||||
|
||||
hash, err := generateTrieRoot(dst, storageIt, accountHash, stackTrieGenerate, nil, stat, false)
|
||||
hash, err := generateTrieRoot(dst, scheme, storageIt, accountHash, stackTrieGenerate, nil, stat, false)
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
@@ -136,7 +137,7 @@ func (stat *generateStats) progressAccounts(account common.Hash, done uint64) {
|
||||
stat.head = account
|
||||
}
|
||||
|
||||
// finishAccounts updates the gemerator stats for the finished account range.
|
||||
// finishAccounts updates the generator stats for the finished account range.
|
||||
func (stat *generateStats) finishAccounts(done uint64) {
|
||||
stat.lock.Lock()
|
||||
defer stat.lock.Unlock()
|
||||
@@ -242,7 +243,7 @@ func runReport(stats *generateStats, stop chan bool) {
|
||||
// generateTrieRoot generates the trie hash based on the snapshot iterator.
|
||||
// It can be used for generating account trie, storage trie or even the
|
||||
// whole state which connects the accounts and the corresponding storages.
|
||||
func generateTrieRoot(db ethdb.KeyValueWriter, it Iterator, account common.Hash, generatorFn trieGeneratorFn, leafCallback leafCallbackFn, stats *generateStats, report bool) (common.Hash, error) {
|
||||
func generateTrieRoot(db ethdb.KeyValueWriter, scheme string, it Iterator, account common.Hash, generatorFn trieGeneratorFn, leafCallback leafCallbackFn, stats *generateStats, report bool) (common.Hash, error) {
|
||||
var (
|
||||
in = make(chan trieKV) // chan to pass leaves
|
||||
out = make(chan common.Hash, 1) // chan to collect result
|
||||
@@ -253,7 +254,7 @@ func generateTrieRoot(db ethdb.KeyValueWriter, it Iterator, account common.Hash,
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
generatorFn(db, account, in, out)
|
||||
generatorFn(db, scheme, account, in, out)
|
||||
}()
|
||||
// Spin up a go-routine for progress logging
|
||||
if report && stats != nil {
|
||||
@@ -360,8 +361,14 @@ func generateTrieRoot(db ethdb.KeyValueWriter, it Iterator, account common.Hash,
|
||||
return stop(nil)
|
||||
}
|
||||
|
||||
func stackTrieGenerate(db ethdb.KeyValueWriter, owner common.Hash, in chan trieKV, out chan common.Hash) {
|
||||
t := trie.NewStackTrieWithOwner(db, owner)
|
||||
func stackTrieGenerate(db ethdb.KeyValueWriter, scheme string, owner common.Hash, in chan trieKV, out chan common.Hash) {
|
||||
var nodeWriter trie.NodeWriteFunc
|
||||
if db != nil {
|
||||
nodeWriter = func(owner common.Hash, path []byte, hash common.Hash, blob []byte) {
|
||||
rawdb.WriteTrieNode(db, owner, path, hash, blob, scheme)
|
||||
}
|
||||
}
|
||||
t := trie.NewStackTrieWithOwner(nodeWriter, owner)
|
||||
for leaf := range in {
|
||||
t.TryUpdate(leaf.key[:], leaf.value)
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ var (
|
||||
bloomFuncs = math.Round((bloomSize / float64(aggregatorItemLimit)) * math.Log(2))
|
||||
|
||||
// the bloom offsets are runtime constants which determines which part of the
|
||||
// the account/storage hash the hasher functions looks at, to determine the
|
||||
// account/storage hash the hasher functions looks at, to determine the
|
||||
// bloom key for an account/slot. This is randomized at init(), so that the
|
||||
// global population of nodes do not all display the exact same behaviour with
|
||||
// regards to bloom content
|
||||
|
||||
@@ -18,6 +18,7 @@ package snapshot
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
crand "crypto/rand"
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
@@ -73,7 +74,7 @@ func TestMergeBasics(t *testing.T) {
|
||||
if rand.Intn(2) == 0 {
|
||||
accStorage := make(map[common.Hash][]byte)
|
||||
value := make([]byte, 32)
|
||||
rand.Read(value)
|
||||
crand.Read(value)
|
||||
accStorage[randomHash()] = value
|
||||
storage[h] = accStorage
|
||||
}
|
||||
@@ -294,7 +295,7 @@ func BenchmarkSearchSlot(b *testing.B) {
|
||||
accStorage := make(map[common.Hash][]byte)
|
||||
for i := 0; i < 5; i++ {
|
||||
value := make([]byte, 32)
|
||||
rand.Read(value)
|
||||
crand.Read(value)
|
||||
accStorage[randomHash()] = value
|
||||
storage[accountKey] = accStorage
|
||||
}
|
||||
@@ -330,7 +331,7 @@ func BenchmarkFlatten(b *testing.B) {
|
||||
accStorage := make(map[common.Hash][]byte)
|
||||
for i := 0; i < 20; i++ {
|
||||
value := make([]byte, 32)
|
||||
rand.Read(value)
|
||||
crand.Read(value)
|
||||
accStorage[randomHash()] = value
|
||||
}
|
||||
storage[accountKey] = accStorage
|
||||
@@ -379,7 +380,7 @@ func BenchmarkJournal(b *testing.B) {
|
||||
accStorage := make(map[common.Hash][]byte)
|
||||
for i := 0; i < 200; i++ {
|
||||
value := make([]byte, 32)
|
||||
rand.Read(value)
|
||||
crand.Read(value)
|
||||
accStorage[randomHash()] = value
|
||||
}
|
||||
storage[accountKey] = accStorage
|
||||
|
||||
@@ -29,7 +29,6 @@ import (
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/ethdb/memorydb"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
@@ -166,7 +165,7 @@ func (result *proofResult) forEach(callback func(key []byte, val []byte) error)
|
||||
//
|
||||
// The proof result will be returned if the range proving is finished, otherwise
|
||||
// the error will be returned to abort the entire procedure.
|
||||
func (dl *diskLayer) proveRange(ctx *generatorContext, owner common.Hash, root common.Hash, prefix []byte, kind string, origin []byte, max int, valueConvertFn func([]byte) ([]byte, error)) (*proofResult, error) {
|
||||
func (dl *diskLayer) proveRange(ctx *generatorContext, trieId *trie.ID, prefix []byte, kind string, origin []byte, max int, valueConvertFn func([]byte) ([]byte, error)) (*proofResult, error) {
|
||||
var (
|
||||
keys [][]byte
|
||||
vals [][]byte
|
||||
@@ -233,8 +232,9 @@ func (dl *diskLayer) proveRange(ctx *generatorContext, owner common.Hash, root c
|
||||
}(time.Now())
|
||||
|
||||
// The snap state is exhausted, pass the entire key/val set for verification
|
||||
root := trieId.Root
|
||||
if origin == nil && !diskMore {
|
||||
stackTr := trie.NewStackTrieWithOwner(nil, owner)
|
||||
stackTr := trie.NewStackTrie(nil)
|
||||
for i, key := range keys {
|
||||
stackTr.TryUpdate(key, vals[i])
|
||||
}
|
||||
@@ -248,7 +248,7 @@ func (dl *diskLayer) proveRange(ctx *generatorContext, owner common.Hash, root c
|
||||
return &proofResult{keys: keys, vals: vals}, nil
|
||||
}
|
||||
// Snap state is chunked, generate edge proofs for verification.
|
||||
tr, err := trie.New(owner, root, dl.triedb)
|
||||
tr, err := trie.New(trieId, dl.triedb)
|
||||
if err != nil {
|
||||
ctx.stats.Log("Trie missing, state snapshotting paused", dl.root, dl.genMarker)
|
||||
return nil, errMissingTrie
|
||||
@@ -313,9 +313,9 @@ type onStateCallback func(key []byte, val []byte, write bool, delete bool) error
|
||||
// generateRange generates the state segment with particular prefix. Generation can
|
||||
// either verify the correctness of existing state through range-proof and skip
|
||||
// generation, or iterate trie to regenerate state on demand.
|
||||
func (dl *diskLayer) generateRange(ctx *generatorContext, owner common.Hash, root common.Hash, prefix []byte, kind string, origin []byte, max int, onState onStateCallback, valueConvertFn func([]byte) ([]byte, error)) (bool, []byte, error) {
|
||||
func (dl *diskLayer) generateRange(ctx *generatorContext, trieId *trie.ID, prefix []byte, kind string, origin []byte, max int, onState onStateCallback, valueConvertFn func([]byte) ([]byte, error)) (bool, []byte, error) {
|
||||
// Use range prover to check the validity of the flat state in the range
|
||||
result, err := dl.proveRange(ctx, owner, root, prefix, kind, origin, max, valueConvertFn)
|
||||
result, err := dl.proveRange(ctx, trieId, prefix, kind, origin, max, valueConvertFn)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
@@ -359,25 +359,28 @@ func (dl *diskLayer) generateRange(ctx *generatorContext, owner common.Hash, roo
|
||||
}
|
||||
// We use the snap data to build up a cache which can be used by the
|
||||
// main account trie as a primary lookup when resolving hashes
|
||||
var snapNodeCache ethdb.KeyValueStore
|
||||
var resolver trie.NodeResolver
|
||||
if len(result.keys) > 0 {
|
||||
snapNodeCache = memorydb.New()
|
||||
snapTrieDb := trie.NewDatabase(snapNodeCache)
|
||||
snapTrie, _ := trie.New(owner, common.Hash{}, snapTrieDb)
|
||||
mdb := rawdb.NewMemoryDatabase()
|
||||
tdb := trie.NewDatabase(mdb)
|
||||
snapTrie := trie.NewEmpty(tdb)
|
||||
for i, key := range result.keys {
|
||||
snapTrie.Update(key, result.vals[i])
|
||||
}
|
||||
root, nodes, _ := snapTrie.Commit(false)
|
||||
root, nodes := snapTrie.Commit(false)
|
||||
if nodes != nil {
|
||||
snapTrieDb.Update(trie.NewWithNodeSet(nodes))
|
||||
tdb.Update(trie.NewWithNodeSet(nodes))
|
||||
tdb.Commit(root, false)
|
||||
}
|
||||
resolver = func(owner common.Hash, path []byte, hash common.Hash) []byte {
|
||||
return rawdb.ReadTrieNode(mdb, owner, path, hash, tdb.Scheme())
|
||||
}
|
||||
snapTrieDb.Commit(root, false, nil)
|
||||
}
|
||||
// Construct the trie for state iteration, reuse the trie
|
||||
// if it's already opened with some nodes resolved.
|
||||
tr := result.tr
|
||||
if tr == nil {
|
||||
tr, err = trie.New(owner, root, dl.triedb)
|
||||
tr, err = trie.New(trieId, dl.triedb)
|
||||
if err != nil {
|
||||
ctx.stats.Log("Trie missing, state snapshotting paused", dl.root, dl.genMarker)
|
||||
return false, nil, errMissingTrie
|
||||
@@ -400,7 +403,7 @@ func (dl *diskLayer) generateRange(ctx *generatorContext, owner common.Hash, roo
|
||||
start = time.Now()
|
||||
internal time.Duration
|
||||
)
|
||||
nodeIt.AddResolver(snapNodeCache)
|
||||
nodeIt.AddResolver(resolver)
|
||||
|
||||
for iter.Next() {
|
||||
if last != nil && bytes.Compare(iter.Key, last) > 0 {
|
||||
@@ -460,7 +463,7 @@ func (dl *diskLayer) generateRange(ctx *generatorContext, owner common.Hash, roo
|
||||
} else {
|
||||
snapAccountTrieReadCounter.Inc((time.Since(start) - internal).Nanoseconds())
|
||||
}
|
||||
logger.Debug("Regenerated state range", "root", root, "last", hexutil.Encode(last),
|
||||
logger.Debug("Regenerated state range", "root", trieId.Root, "last", hexutil.Encode(last),
|
||||
"count", count, "created", created, "updated", updated, "untouched", untouched, "deleted", deleted)
|
||||
|
||||
// If there are either more trie items, or there are more snap items
|
||||
@@ -511,7 +514,7 @@ func (dl *diskLayer) checkAndFlush(ctx *generatorContext, current []byte) error
|
||||
|
||||
// generateStorages generates the missing storage slots of the specific contract.
|
||||
// It's supposed to restart the generation from the given origin position.
|
||||
func generateStorages(ctx *generatorContext, dl *diskLayer, account common.Hash, storageRoot common.Hash, storeMarker []byte) error {
|
||||
func generateStorages(ctx *generatorContext, dl *diskLayer, stateRoot common.Hash, account common.Hash, storageRoot common.Hash, storeMarker []byte) error {
|
||||
onStorage := func(key []byte, val []byte, write bool, delete bool) error {
|
||||
defer func(start time.Time) {
|
||||
snapStorageWriteCounter.Inc(time.Since(start).Nanoseconds())
|
||||
@@ -540,7 +543,8 @@ func generateStorages(ctx *generatorContext, dl *diskLayer, account common.Hash,
|
||||
// Loop for re-generating the missing storage slots.
|
||||
var origin = common.CopyBytes(storeMarker)
|
||||
for {
|
||||
exhausted, last, err := dl.generateRange(ctx, account, storageRoot, append(rawdb.SnapshotStoragePrefix, account.Bytes()...), snapStorage, origin, storageCheckRange, onStorage, nil)
|
||||
id := trie.StorageTrieID(stateRoot, account, storageRoot)
|
||||
exhausted, last, err := dl.generateRange(ctx, id, append(rawdb.SnapshotStoragePrefix, account.Bytes()...), snapStorage, origin, storageCheckRange, onStorage, nil)
|
||||
if err != nil {
|
||||
return err // The procedure it aborted, either by external signal or internal error.
|
||||
}
|
||||
@@ -624,7 +628,7 @@ func generateAccounts(ctx *generatorContext, dl *diskLayer, accMarker []byte) er
|
||||
if accMarker != nil && bytes.Equal(account[:], accMarker) && len(dl.genMarker) > common.HashLength {
|
||||
storeMarker = dl.genMarker[common.HashLength:]
|
||||
}
|
||||
if err := generateStorages(ctx, dl, account, acc.Root, storeMarker); err != nil {
|
||||
if err := generateStorages(ctx, dl, dl.root, account, acc.Root, storeMarker); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -640,7 +644,8 @@ func generateAccounts(ctx *generatorContext, dl *diskLayer, accMarker []byte) er
|
||||
}
|
||||
origin := common.CopyBytes(accMarker)
|
||||
for {
|
||||
exhausted, last, err := dl.generateRange(ctx, common.Hash{}, dl.root, rawdb.SnapshotAccountPrefix, snapAccount, origin, accountRange, onAccount, FullAccountRLP)
|
||||
id := trie.StateTrieID(dl.root)
|
||||
exhausted, last, err := dl.generateRange(ctx, id, rawdb.SnapshotAccountPrefix, snapAccount, origin, accountRange, onAccount, FullAccountRLP)
|
||||
if err != nil {
|
||||
return err // The procedure it aborted, either by external signal or internal error.
|
||||
}
|
||||
|
||||
@@ -117,12 +117,12 @@ func checkSnapRoot(t *testing.T, snap *diskLayer, trieRoot common.Hash) {
|
||||
accIt := snap.AccountIterator(common.Hash{})
|
||||
defer accIt.Release()
|
||||
|
||||
snapRoot, err := generateTrieRoot(nil, accIt, common.Hash{}, stackTrieGenerate,
|
||||
snapRoot, err := generateTrieRoot(nil, "", accIt, common.Hash{}, stackTrieGenerate,
|
||||
func(db ethdb.KeyValueWriter, accountHash, codeHash common.Hash, stat *generateStats) (common.Hash, error) {
|
||||
storageIt, _ := snap.StorageIterator(accountHash, common.Hash{})
|
||||
defer storageIt.Release()
|
||||
|
||||
hash, err := generateTrieRoot(nil, storageIt, accountHash, stackTrieGenerate, nil, stat, false)
|
||||
hash, err := generateTrieRoot(nil, "", storageIt, accountHash, stackTrieGenerate, nil, stat, false)
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
@@ -149,7 +149,7 @@ type testHelper struct {
|
||||
func newHelper() *testHelper {
|
||||
diskdb := rawdb.NewMemoryDatabase()
|
||||
triedb := trie.NewDatabase(diskdb)
|
||||
accTrie, _ := trie.NewStateTrie(common.Hash{}, common.Hash{}, triedb)
|
||||
accTrie, _ := trie.NewStateTrie(trie.StateTrieID(common.Hash{}), triedb)
|
||||
return &testHelper{
|
||||
diskdb: diskdb,
|
||||
triedb: triedb,
|
||||
@@ -182,14 +182,15 @@ func (t *testHelper) addSnapStorage(accKey string, keys []string, vals []string)
|
||||
}
|
||||
|
||||
func (t *testHelper) makeStorageTrie(stateRoot, owner common.Hash, keys []string, vals []string, commit bool) []byte {
|
||||
stTrie, _ := trie.NewStateTrie(owner, common.Hash{}, t.triedb)
|
||||
id := trie.StorageTrieID(stateRoot, owner, common.Hash{})
|
||||
stTrie, _ := trie.NewStateTrie(id, t.triedb)
|
||||
for i, k := range keys {
|
||||
stTrie.Update([]byte(k), []byte(vals[i]))
|
||||
}
|
||||
if !commit {
|
||||
return stTrie.Hash().Bytes()
|
||||
}
|
||||
root, nodes, _ := stTrie.Commit(false)
|
||||
root, nodes := stTrie.Commit(false)
|
||||
if nodes != nil {
|
||||
t.nodes.Merge(nodes)
|
||||
}
|
||||
@@ -197,12 +198,12 @@ func (t *testHelper) makeStorageTrie(stateRoot, owner common.Hash, keys []string
|
||||
}
|
||||
|
||||
func (t *testHelper) Commit() common.Hash {
|
||||
root, nodes, _ := t.accTrie.Commit(true)
|
||||
root, nodes := t.accTrie.Commit(true)
|
||||
if nodes != nil {
|
||||
t.nodes.Merge(nodes)
|
||||
}
|
||||
t.triedb.Update(t.nodes)
|
||||
t.triedb.Commit(root, false, nil)
|
||||
t.triedb.Commit(root, false)
|
||||
return root
|
||||
}
|
||||
|
||||
@@ -220,10 +221,12 @@ func (t *testHelper) CommitAndGenerate() (common.Hash, *diskLayer) {
|
||||
// - miss in the beginning
|
||||
// - miss in the middle
|
||||
// - miss in the end
|
||||
//
|
||||
// - the contract(non-empty storage) has wrong storage slots
|
||||
// - wrong slots in the beginning
|
||||
// - wrong slots in the middle
|
||||
// - wrong slots in the end
|
||||
//
|
||||
// - the contract(non-empty storage) has extra storage slots
|
||||
// - extra slots in the beginning
|
||||
// - extra slots in the middle
|
||||
@@ -388,7 +391,7 @@ func TestGenerateCorruptAccountTrie(t *testing.T) {
|
||||
root := helper.Commit() // Root: 0xa04693ea110a31037fb5ee814308a6f1d76bdab0b11676bdf4541d2de55ba978
|
||||
|
||||
// Delete an account trie leaf and ensure the generator chokes
|
||||
helper.triedb.Commit(root, false, nil)
|
||||
helper.triedb.Commit(root, false)
|
||||
helper.diskdb.Delete(common.HexToHash("0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7").Bytes())
|
||||
|
||||
snap := generateSnapshot(helper.diskdb, helper.triedb, 16, root)
|
||||
@@ -491,12 +494,12 @@ func TestGenerateWithExtraAccounts(t *testing.T) {
|
||||
|
||||
// Identical in the snap
|
||||
key := hashData([]byte("acc-1"))
|
||||
rawdb.WriteAccountSnapshot(helper.triedb.DiskDB(), key, val)
|
||||
rawdb.WriteStorageSnapshot(helper.triedb.DiskDB(), key, hashData([]byte("key-1")), []byte("val-1"))
|
||||
rawdb.WriteStorageSnapshot(helper.triedb.DiskDB(), key, hashData([]byte("key-2")), []byte("val-2"))
|
||||
rawdb.WriteStorageSnapshot(helper.triedb.DiskDB(), key, hashData([]byte("key-3")), []byte("val-3"))
|
||||
rawdb.WriteStorageSnapshot(helper.triedb.DiskDB(), key, hashData([]byte("key-4")), []byte("val-4"))
|
||||
rawdb.WriteStorageSnapshot(helper.triedb.DiskDB(), key, hashData([]byte("key-5")), []byte("val-5"))
|
||||
rawdb.WriteAccountSnapshot(helper.diskdb, key, val)
|
||||
rawdb.WriteStorageSnapshot(helper.diskdb, key, hashData([]byte("key-1")), []byte("val-1"))
|
||||
rawdb.WriteStorageSnapshot(helper.diskdb, key, hashData([]byte("key-2")), []byte("val-2"))
|
||||
rawdb.WriteStorageSnapshot(helper.diskdb, key, hashData([]byte("key-3")), []byte("val-3"))
|
||||
rawdb.WriteStorageSnapshot(helper.diskdb, key, hashData([]byte("key-4")), []byte("val-4"))
|
||||
rawdb.WriteStorageSnapshot(helper.diskdb, key, hashData([]byte("key-5")), []byte("val-5"))
|
||||
}
|
||||
{
|
||||
// Account two exists only in the snapshot
|
||||
@@ -508,15 +511,15 @@ func TestGenerateWithExtraAccounts(t *testing.T) {
|
||||
acc := &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()}
|
||||
val, _ := rlp.EncodeToBytes(acc)
|
||||
key := hashData([]byte("acc-2"))
|
||||
rawdb.WriteAccountSnapshot(helper.triedb.DiskDB(), key, val)
|
||||
rawdb.WriteStorageSnapshot(helper.triedb.DiskDB(), key, hashData([]byte("b-key-1")), []byte("b-val-1"))
|
||||
rawdb.WriteStorageSnapshot(helper.triedb.DiskDB(), key, hashData([]byte("b-key-2")), []byte("b-val-2"))
|
||||
rawdb.WriteStorageSnapshot(helper.triedb.DiskDB(), key, hashData([]byte("b-key-3")), []byte("b-val-3"))
|
||||
rawdb.WriteAccountSnapshot(helper.diskdb, key, val)
|
||||
rawdb.WriteStorageSnapshot(helper.diskdb, key, hashData([]byte("b-key-1")), []byte("b-val-1"))
|
||||
rawdb.WriteStorageSnapshot(helper.diskdb, key, hashData([]byte("b-key-2")), []byte("b-val-2"))
|
||||
rawdb.WriteStorageSnapshot(helper.diskdb, key, hashData([]byte("b-key-3")), []byte("b-val-3"))
|
||||
}
|
||||
root := helper.Commit()
|
||||
|
||||
// To verify the test: If we now inspect the snap db, there should exist extraneous storage items
|
||||
if data := rawdb.ReadStorageSnapshot(helper.triedb.DiskDB(), hashData([]byte("acc-2")), hashData([]byte("b-key-1"))); data == nil {
|
||||
if data := rawdb.ReadStorageSnapshot(helper.diskdb, hashData([]byte("acc-2")), hashData([]byte("b-key-1"))); data == nil {
|
||||
t.Fatalf("expected snap storage to exist")
|
||||
}
|
||||
snap := generateSnapshot(helper.diskdb, helper.triedb, 16, root)
|
||||
@@ -534,7 +537,7 @@ func TestGenerateWithExtraAccounts(t *testing.T) {
|
||||
snap.genAbort <- stop
|
||||
<-stop
|
||||
// If we now inspect the snap db, there should exist no extraneous storage items
|
||||
if data := rawdb.ReadStorageSnapshot(helper.triedb.DiskDB(), hashData([]byte("acc-2")), hashData([]byte("b-key-1"))); data != nil {
|
||||
if data := rawdb.ReadStorageSnapshot(helper.diskdb, hashData([]byte("acc-2")), hashData([]byte("b-key-1"))); data != nil {
|
||||
t.Fatalf("expected slot to be removed, got %v", string(data))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,7 +276,7 @@ func (fi *fastIterator) next(idx int) bool {
|
||||
return false
|
||||
}
|
||||
// The elem we're placing it next to has the same value,
|
||||
// so whichever winds up on n+1 will need further iteraton
|
||||
// so whichever winds up on n+1 will need further iteration
|
||||
clash = n + 1
|
||||
|
||||
return cur.priority < fi.iterators[n+1].priority
|
||||
|
||||
@@ -18,6 +18,7 @@ package snapshot
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
crand "crypto/rand"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
@@ -47,7 +48,7 @@ func TestAccountIteratorBasics(t *testing.T) {
|
||||
if rand.Intn(2) == 0 {
|
||||
accStorage := make(map[common.Hash][]byte)
|
||||
value := make([]byte, 32)
|
||||
rand.Read(value)
|
||||
crand.Read(value)
|
||||
accStorage[randomHash()] = value
|
||||
storage[h] = accStorage
|
||||
}
|
||||
@@ -79,7 +80,7 @@ func TestStorageIteratorBasics(t *testing.T) {
|
||||
|
||||
var nilstorage int
|
||||
for i := 0; i < 100; i++ {
|
||||
rand.Read(value)
|
||||
crand.Read(value)
|
||||
if rand.Intn(2) == 0 {
|
||||
accStorage[randomHash()] = common.CopyBytes(value)
|
||||
} else {
|
||||
@@ -819,7 +820,7 @@ func TestStorageIteratorDeletions(t *testing.T) {
|
||||
// only spit out 200 values eventually.
|
||||
//
|
||||
// The value-fetching benchmark is easy on the binary iterator, since it never has to reach
|
||||
// down at any depth for retrieving the values -- all are on the toppmost layer
|
||||
// down at any depth for retrieving the values -- all are on the topmost layer
|
||||
//
|
||||
// BenchmarkAccountIteratorTraversal/binary_iterator_keys-6 2239 483674 ns/op
|
||||
// BenchmarkAccountIteratorTraversal/binary_iterator_values-6 2403 501810 ns/op
|
||||
|
||||
@@ -120,7 +120,7 @@ func loadAndParseJournal(db ethdb.KeyValueStore, base *diskLayer) (snapshot, jou
|
||||
}
|
||||
|
||||
// loadSnapshot loads a pre-existing state snapshot backed by a key-value store.
|
||||
func loadSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root common.Hash, recovery bool) (snapshot, bool, error) {
|
||||
func loadSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, root common.Hash, cache int, recovery bool, noBuild bool) (snapshot, bool, error) {
|
||||
// If snapshotting is disabled (initial sync in progress), don't do anything,
|
||||
// wait for the chain to permit us to do something meaningful
|
||||
if rawdb.ReadSnapshotDisabled(diskdb) {
|
||||
@@ -140,7 +140,7 @@ func loadSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int,
|
||||
}
|
||||
snapshot, generator, err := loadAndParseJournal(diskdb, base)
|
||||
if err != nil {
|
||||
log.Warn("Failed to load new-format journal", "error", err)
|
||||
log.Warn("Failed to load journal", "error", err)
|
||||
return nil, false, err
|
||||
}
|
||||
// Entire snapshot journal loaded, sanity check the head. If the loaded
|
||||
@@ -164,13 +164,16 @@ func loadSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int,
|
||||
// disk layer.
|
||||
log.Warn("Snapshot is not continuous with chain", "snaproot", head, "chainroot", root)
|
||||
}
|
||||
// Everything loaded correctly, resume any suspended operations
|
||||
// Load the disk layer status from the generator if it's not complete
|
||||
if !generator.Done {
|
||||
// Whether or not wiping was in progress, load any generator progress too
|
||||
base.genMarker = generator.Marker
|
||||
if base.genMarker == nil {
|
||||
base.genMarker = []byte{}
|
||||
}
|
||||
}
|
||||
// Everything loaded correctly, resume any suspended operations
|
||||
// if the background generation is allowed
|
||||
if !generator.Done && !noBuild {
|
||||
base.genPending = make(chan struct{})
|
||||
base.genAbort = make(chan chan *generatorStats)
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ var (
|
||||
snapAccountProveCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/account/prove", nil)
|
||||
// snapAccountTrieReadCounter measures time spent on the account trie iteration
|
||||
snapAccountTrieReadCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/account/trieread", nil)
|
||||
// snapAccountSnapReadCounter measues time spent on the snapshot account iteration
|
||||
// snapAccountSnapReadCounter measures time spent on the snapshot account iteration
|
||||
snapAccountSnapReadCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/account/snapread", nil)
|
||||
// snapAccountWriteCounter measures time spent on writing/updating/deleting accounts
|
||||
snapAccountWriteCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/account/write", nil)
|
||||
|
||||
@@ -148,6 +148,14 @@ type snapshot interface {
|
||||
StorageIterator(account common.Hash, seek common.Hash) (StorageIterator, bool)
|
||||
}
|
||||
|
||||
// Config includes the configurations for snapshots.
|
||||
type Config struct {
|
||||
CacheSize int // Megabytes permitted to use for read caches
|
||||
Recovery bool // Indicator that the snapshots is in the recovery mode
|
||||
NoBuild bool // Indicator that the snapshots generation is disallowed
|
||||
AsyncBuild bool // The snapshot generation is allowed to be constructed asynchronously
|
||||
}
|
||||
|
||||
// Tree is an Ethereum state snapshot tree. It consists of one persistent base
|
||||
// layer backed by a key-value store, on top of which arbitrarily many in-memory
|
||||
// diff layers are topped. The memory diffs can form a tree with branching, but
|
||||
@@ -158,9 +166,9 @@ type snapshot interface {
|
||||
// storage data to avoid expensive multi-level trie lookups; and to allow sorted,
|
||||
// cheap iteration of the account/storage tries for sync aid.
|
||||
type Tree struct {
|
||||
config Config // Snapshots configurations
|
||||
diskdb ethdb.KeyValueStore // Persistent database to store the snapshot
|
||||
triedb *trie.Database // In-memory cache to access the trie through
|
||||
cache int // Megabytes permitted to use for read caches
|
||||
layers map[common.Hash]snapshot // Collection of all known layers
|
||||
lock sync.RWMutex
|
||||
|
||||
@@ -179,30 +187,32 @@ type Tree struct {
|
||||
// If the memory layers in the journal do not match the disk layer (e.g. there is
|
||||
// a gap) or the journal is missing, there are two repair cases:
|
||||
//
|
||||
// - if the 'recovery' parameter is true, all memory diff-layers will be discarded.
|
||||
// This case happens when the snapshot is 'ahead' of the state trie.
|
||||
// - otherwise, the entire snapshot is considered invalid and will be recreated on
|
||||
// a background thread.
|
||||
func New(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root common.Hash, async bool, rebuild bool, recovery bool) (*Tree, error) {
|
||||
// - if the 'recovery' parameter is true, memory diff-layers and the disk-layer
|
||||
// will all be kept. This case happens when the snapshot is 'ahead' of the
|
||||
// state trie.
|
||||
// - otherwise, the entire snapshot is considered invalid and will be recreated on
|
||||
// a background thread.
|
||||
func New(config Config, diskdb ethdb.KeyValueStore, triedb *trie.Database, root common.Hash) (*Tree, error) {
|
||||
// Create a new, empty snapshot tree
|
||||
snap := &Tree{
|
||||
config: config,
|
||||
diskdb: diskdb,
|
||||
triedb: triedb,
|
||||
cache: cache,
|
||||
layers: make(map[common.Hash]snapshot),
|
||||
}
|
||||
if !async {
|
||||
defer snap.waitBuild()
|
||||
}
|
||||
// Attempt to load a previously persisted snapshot and rebuild one if failed
|
||||
head, disabled, err := loadSnapshot(diskdb, triedb, cache, root, recovery)
|
||||
head, disabled, err := loadSnapshot(diskdb, triedb, root, config.CacheSize, config.Recovery, config.NoBuild)
|
||||
if disabled {
|
||||
log.Warn("Snapshot maintenance disabled (syncing)")
|
||||
return snap, nil
|
||||
}
|
||||
// Create the building waiter iff the background generation is allowed
|
||||
if !config.NoBuild && !config.AsyncBuild {
|
||||
defer snap.waitBuild()
|
||||
}
|
||||
if err != nil {
|
||||
if rebuild {
|
||||
log.Warn("Failed to load snapshot, regenerating", "err", err)
|
||||
log.Warn("Failed to load snapshot", "err", err)
|
||||
if !config.NoBuild {
|
||||
snap.Rebuild(root)
|
||||
return snap, nil
|
||||
}
|
||||
@@ -727,7 +737,7 @@ func (t *Tree) Rebuild(root common.Hash) {
|
||||
// generator will run a wiper first if there's not one running right now.
|
||||
log.Info("Rebuilding state snapshot")
|
||||
t.layers = map[common.Hash]snapshot{
|
||||
root: generateSnapshot(t.diskdb, t.triedb, t.cache, root),
|
||||
root: generateSnapshot(t.diskdb, t.triedb, t.config.CacheSize, root),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -766,14 +776,14 @@ func (t *Tree) Verify(root common.Hash) error {
|
||||
}
|
||||
defer acctIt.Release()
|
||||
|
||||
got, err := generateTrieRoot(nil, acctIt, common.Hash{}, stackTrieGenerate, func(db ethdb.KeyValueWriter, accountHash, codeHash common.Hash, stat *generateStats) (common.Hash, error) {
|
||||
got, err := generateTrieRoot(nil, "", acctIt, common.Hash{}, stackTrieGenerate, func(db ethdb.KeyValueWriter, accountHash, codeHash common.Hash, stat *generateStats) (common.Hash, error) {
|
||||
storageIt, err := t.StorageIterator(root, accountHash, common.Hash{})
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
defer storageIt.Release()
|
||||
|
||||
hash, err := generateTrieRoot(nil, storageIt, accountHash, stackTrieGenerate, nil, stat, false)
|
||||
hash, err := generateTrieRoot(nil, "", storageIt, accountHash, stackTrieGenerate, nil, stat, false)
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
@@ -835,7 +845,7 @@ func (t *Tree) generating() (bool, error) {
|
||||
return layer.genMarker != nil, nil
|
||||
}
|
||||
|
||||
// diskRoot is a external helper function to return the disk layer root.
|
||||
// DiskRoot is a external helper function to return the disk layer root.
|
||||
func (t *Tree) DiskRoot() common.Hash {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package snapshot
|
||||
|
||||
import (
|
||||
crand "crypto/rand"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math/big"
|
||||
@@ -33,7 +34,7 @@ import (
|
||||
// randomHash generates a random blob of data and returns it as a hash.
|
||||
func randomHash() common.Hash {
|
||||
var hash common.Hash
|
||||
if n, err := rand.Read(hash[:]); n != common.HashLength || err != nil {
|
||||
if n, err := crand.Read(hash[:]); n != common.HashLength || err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return hash
|
||||
@@ -166,7 +167,7 @@ func TestDiskLayerExternalInvalidationPartialFlatten(t *testing.T) {
|
||||
if err := snaps.Cap(common.HexToHash("0x03"), 1); err != nil {
|
||||
t.Fatalf("failed to merge accumulator onto disk: %v", err)
|
||||
}
|
||||
// Since the base layer was modified, ensure that data retrievald on the external reference fail
|
||||
// Since the base layer was modified, ensure that data retrievals on the external reference fail
|
||||
if acc, err := ref.Account(common.HexToHash("0x01")); err != ErrSnapshotStale {
|
||||
t.Errorf("stale reference returned account: %#x (err: %v)", acc, err)
|
||||
}
|
||||
@@ -224,7 +225,7 @@ func TestDiffLayerExternalInvalidationPartialFlatten(t *testing.T) {
|
||||
if err := snaps.Cap(common.HexToHash("0x04"), 1); err != nil {
|
||||
t.Fatalf("failed to flatten diff layer into accumulator: %v", err)
|
||||
}
|
||||
// Since the accumulator diff layer was modified, ensure that data retrievald on the external reference fail
|
||||
// Since the accumulator diff layer was modified, ensure that data retrievals on the external reference fail
|
||||
if acc, err := ref.Account(common.HexToHash("0x01")); err != ErrSnapshotStale {
|
||||
t.Errorf("stale reference returned account: %#x (err: %v)", acc, err)
|
||||
}
|
||||
|
||||
+69
-74
@@ -63,7 +63,7 @@ func (s Storage) Copy() Storage {
|
||||
// The usage pattern is as follows:
|
||||
// First you need to obtain a state object.
|
||||
// Account values can be accessed and modified through the object.
|
||||
// Finally, call CommitTrie to write the modified storage trie into a database.
|
||||
// Finally, call commitTrie to write the modified storage trie into a database.
|
||||
type stateObject struct {
|
||||
address common.Address
|
||||
addrHash common.Hash // hash of ethereum address of the account
|
||||
@@ -84,7 +84,6 @@ type stateObject struct {
|
||||
originStorage Storage // Storage cache of original entries to dedup rewrites, reset for every transaction
|
||||
pendingStorage Storage // Storage entries that need to be flushed to disk, at the end of an entire block
|
||||
dirtyStorage Storage // Storage entries that have been modified in the current transaction execution
|
||||
fakeStorage Storage // Fake storage which constructed by caller for debugging purpose.
|
||||
|
||||
// Cache flags.
|
||||
// When an object is marked suicided it will be delete from the trie
|
||||
@@ -148,7 +147,10 @@ func (s *stateObject) touch() {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stateObject) getTrie(db Database) Trie {
|
||||
// getTrie returns the associated storage trie. The trie will be opened
|
||||
// if it's not loaded previously. An error will be returned if trie can't
|
||||
// be loaded.
|
||||
func (s *stateObject) getTrie(db Database) (Trie, error) {
|
||||
if s.trie == nil {
|
||||
// Try fetching from prefetcher first
|
||||
// We don't prefetch empty tries
|
||||
@@ -158,23 +160,18 @@ func (s *stateObject) getTrie(db Database) Trie {
|
||||
s.trie = s.db.prefetcher.trie(s.addrHash, s.data.Root)
|
||||
}
|
||||
if s.trie == nil {
|
||||
var err error
|
||||
s.trie, err = db.OpenStorageTrie(s.addrHash, s.data.Root)
|
||||
tr, err := db.OpenStorageTrie(s.db.originalRoot, s.addrHash, s.data.Root)
|
||||
if err != nil {
|
||||
s.trie, _ = db.OpenStorageTrie(s.addrHash, common.Hash{})
|
||||
s.setError(fmt.Errorf("can't create storage trie: %v", err))
|
||||
return nil, err
|
||||
}
|
||||
s.trie = tr
|
||||
}
|
||||
}
|
||||
return s.trie
|
||||
return s.trie, nil
|
||||
}
|
||||
|
||||
// GetState retrieves a value from the account storage trie.
|
||||
func (s *stateObject) GetState(db Database, key common.Hash) common.Hash {
|
||||
// If the fake storage is set, only lookup the state here(in the debugging mode)
|
||||
if s.fakeStorage != nil {
|
||||
return s.fakeStorage[key]
|
||||
}
|
||||
// If we have a dirty value for this state entry, return it
|
||||
value, dirty := s.dirtyStorage[key]
|
||||
if dirty {
|
||||
@@ -186,10 +183,6 @@ func (s *stateObject) GetState(db Database, key common.Hash) common.Hash {
|
||||
|
||||
// GetCommittedState retrieves a value from the committed account storage trie.
|
||||
func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Hash {
|
||||
// If the fake storage is set, only lookup the state here(in the debugging mode)
|
||||
if s.fakeStorage != nil {
|
||||
return s.fakeStorage[key]
|
||||
}
|
||||
// If we have a pending write or clean cached, return that
|
||||
if value, pending := s.pendingStorage[key]; pending {
|
||||
return value
|
||||
@@ -197,21 +190,21 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has
|
||||
if value, cached := s.originStorage[key]; cached {
|
||||
return value
|
||||
}
|
||||
// If the object was destructed in *this* block (and potentially resurrected),
|
||||
// the storage has been cleared out, and we should *not* consult the previous
|
||||
// database about any storage values. The only possible alternatives are:
|
||||
// 1) resurrect happened, and new slot values were set -- those should
|
||||
// have been handles via pendingStorage above.
|
||||
// 2) we don't have new values, and can deliver empty response back
|
||||
if _, destructed := s.db.stateObjectsDestruct[s.address]; destructed {
|
||||
return common.Hash{}
|
||||
}
|
||||
// If no live objects are available, attempt to use snapshots
|
||||
var (
|
||||
enc []byte
|
||||
err error
|
||||
)
|
||||
if s.db.snap != nil {
|
||||
// If the object was destructed in *this* block (and potentially resurrected),
|
||||
// the storage has been cleared out, and we should *not* consult the previous
|
||||
// snapshot about any storage values. The only possible alternatives are:
|
||||
// 1) resurrect happened, and new slot values were set -- those should
|
||||
// have been handles via pendingStorage above.
|
||||
// 2) we don't have new values, and can deliver empty response back
|
||||
if _, destructed := s.db.snapDestructs[s.addrHash]; destructed {
|
||||
return common.Hash{}
|
||||
}
|
||||
start := time.Now()
|
||||
enc, err = s.db.snap.Storage(s.addrHash, crypto.Keccak256Hash(key.Bytes()))
|
||||
if metrics.EnabledExpensive {
|
||||
@@ -221,7 +214,12 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has
|
||||
// If the snapshot is unavailable or reading from it fails, load from the database.
|
||||
if s.db.snap == nil || err != nil {
|
||||
start := time.Now()
|
||||
enc, err = s.getTrie(db).TryGet(key.Bytes())
|
||||
tr, err := s.getTrie(db)
|
||||
if err != nil {
|
||||
s.setError(err)
|
||||
return common.Hash{}
|
||||
}
|
||||
enc, err = tr.TryGet(key.Bytes())
|
||||
if metrics.EnabledExpensive {
|
||||
s.db.StorageReads += time.Since(start)
|
||||
}
|
||||
@@ -244,11 +242,6 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has
|
||||
|
||||
// SetState updates a value in account storage.
|
||||
func (s *stateObject) SetState(db Database, key, value common.Hash) {
|
||||
// If the fake storage is set, put the temporary state update here.
|
||||
if s.fakeStorage != nil {
|
||||
s.fakeStorage[key] = value
|
||||
return
|
||||
}
|
||||
// If the new value is the same as old, don't set
|
||||
prev := s.GetState(db, key)
|
||||
if prev == value {
|
||||
@@ -263,24 +256,6 @@ func (s *stateObject) SetState(db Database, key, value common.Hash) {
|
||||
s.setState(key, value)
|
||||
}
|
||||
|
||||
// SetStorage replaces the entire state storage with the given one.
|
||||
//
|
||||
// After this function is called, all original state will be ignored and state
|
||||
// lookup only happens in the fake state storage.
|
||||
//
|
||||
// Note this function should only be used for debugging purpose.
|
||||
func (s *stateObject) SetStorage(storage map[common.Hash]common.Hash) {
|
||||
// Allocate fake storage if it's nil.
|
||||
if s.fakeStorage == nil {
|
||||
s.fakeStorage = make(Storage)
|
||||
}
|
||||
for key, value := range storage {
|
||||
s.fakeStorage[key] = value
|
||||
}
|
||||
// Don't bother journal since this function should only be used for
|
||||
// debugging and the `fake` storage won't be committed to database.
|
||||
}
|
||||
|
||||
func (s *stateObject) setState(key, value common.Hash) {
|
||||
s.dirtyStorage[key] = value
|
||||
}
|
||||
@@ -304,23 +279,29 @@ func (s *stateObject) finalise(prefetch bool) {
|
||||
}
|
||||
|
||||
// updateTrie writes cached storage modifications into the object's storage trie.
|
||||
// It will return nil if the trie has not been loaded and no changes have been made
|
||||
func (s *stateObject) updateTrie(db Database) Trie {
|
||||
// It will return nil if the trie has not been loaded and no changes have been
|
||||
// made. An error will be returned if the trie can't be loaded/updated correctly.
|
||||
func (s *stateObject) updateTrie(db Database) (Trie, error) {
|
||||
// Make sure all dirty slots are finalized into the pending storage area
|
||||
s.finalise(false) // Don't prefetch anymore, pull directly if need be
|
||||
if len(s.pendingStorage) == 0 {
|
||||
return s.trie
|
||||
return s.trie, nil
|
||||
}
|
||||
// Track the amount of time wasted on updating the storage trie
|
||||
if metrics.EnabledExpensive {
|
||||
defer func(start time.Time) { s.db.StorageUpdates += time.Since(start) }(time.Now())
|
||||
}
|
||||
// The snapshot storage map for the object
|
||||
var storage map[common.Hash][]byte
|
||||
var (
|
||||
storage map[common.Hash][]byte
|
||||
hasher = s.db.hasher
|
||||
)
|
||||
tr, err := s.getTrie(db)
|
||||
if err != nil {
|
||||
s.setError(err)
|
||||
return nil, err
|
||||
}
|
||||
// Insert all the pending updates into the trie
|
||||
tr := s.getTrie(db)
|
||||
hasher := s.db.hasher
|
||||
|
||||
usedStorage := make([][]byte, 0, len(s.pendingStorage))
|
||||
for key, value := range s.pendingStorage {
|
||||
// Skip noop changes, persist actual changes
|
||||
@@ -331,12 +312,18 @@ func (s *stateObject) updateTrie(db Database) Trie {
|
||||
|
||||
var v []byte
|
||||
if (value == common.Hash{}) {
|
||||
s.setError(tr.TryDelete(key[:]))
|
||||
if err := tr.TryDelete(key[:]); err != nil {
|
||||
s.setError(err)
|
||||
return nil, err
|
||||
}
|
||||
s.db.StorageDeleted += 1
|
||||
} else {
|
||||
// Encoding []byte cannot fail, ok to ignore the error.
|
||||
v, _ = rlp.EncodeToBytes(common.TrimLeftZeroes(value[:]))
|
||||
s.setError(tr.TryUpdate(key[:], v))
|
||||
if err := tr.TryUpdate(key[:], v); err != nil {
|
||||
s.setError(err)
|
||||
return nil, err
|
||||
}
|
||||
s.db.StorageUpdated += 1
|
||||
}
|
||||
// If state snapshotting is active, cache the data til commit
|
||||
@@ -358,40 +345,48 @@ func (s *stateObject) updateTrie(db Database) Trie {
|
||||
if len(s.pendingStorage) > 0 {
|
||||
s.pendingStorage = make(Storage)
|
||||
}
|
||||
return tr
|
||||
return tr, nil
|
||||
}
|
||||
|
||||
// UpdateRoot sets the trie root to the current root hash of
|
||||
// UpdateRoot sets the trie root to the current root hash of. An error
|
||||
// will be returned if trie root hash is not computed correctly.
|
||||
func (s *stateObject) updateRoot(db Database) {
|
||||
tr, err := s.updateTrie(db)
|
||||
if err != nil {
|
||||
s.setError(fmt.Errorf("updateRoot (%x) error: %w", s.address, err))
|
||||
return
|
||||
}
|
||||
// If nothing changed, don't bother with hashing anything
|
||||
if s.updateTrie(db) == nil {
|
||||
if tr == nil {
|
||||
return
|
||||
}
|
||||
// Track the amount of time wasted on hashing the storage trie
|
||||
if metrics.EnabledExpensive {
|
||||
defer func(start time.Time) { s.db.StorageHashes += time.Since(start) }(time.Now())
|
||||
}
|
||||
s.data.Root = s.trie.Hash()
|
||||
s.data.Root = tr.Hash()
|
||||
}
|
||||
|
||||
// CommitTrie the storage trie of the object to db.
|
||||
// This updates the trie root.
|
||||
func (s *stateObject) CommitTrie(db Database) (*trie.NodeSet, error) {
|
||||
// If nothing changed, don't bother with hashing anything
|
||||
if s.updateTrie(db) == nil {
|
||||
return nil, nil
|
||||
// commitTrie submits the storage changes into the storage trie and re-computes
|
||||
// the root. Besides, all trie changes will be collected in a nodeset and returned.
|
||||
func (s *stateObject) commitTrie(db Database) (*trie.NodeSet, error) {
|
||||
tr, err := s.updateTrie(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if s.dbErr != nil {
|
||||
return nil, s.dbErr
|
||||
}
|
||||
// If nothing changed, don't bother with committing anything
|
||||
if tr == nil {
|
||||
return nil, nil
|
||||
}
|
||||
// Track the amount of time wasted on committing the storage trie
|
||||
if metrics.EnabledExpensive {
|
||||
defer func(start time.Time) { s.db.StorageCommits += time.Since(start) }(time.Now())
|
||||
}
|
||||
root, nodes, err := s.trie.Commit(false)
|
||||
if err == nil {
|
||||
s.data.Root = root
|
||||
}
|
||||
root, nodes := tr.Commit(false)
|
||||
s.data.Root = root
|
||||
return nodes, err
|
||||
}
|
||||
|
||||
@@ -449,7 +444,7 @@ func (s *stateObject) deepCopy(db *StateDB) *stateObject {
|
||||
// Attribute accessors
|
||||
//
|
||||
|
||||
// Returns the address of the contract/account
|
||||
// Address returns the address of the contract/account
|
||||
func (s *stateObject) Address() common.Address {
|
||||
return s.address
|
||||
}
|
||||
@@ -527,7 +522,7 @@ func (s *stateObject) Nonce() uint64 {
|
||||
return s.data.Nonce
|
||||
}
|
||||
|
||||
// Never called, but must be present to allow stateObject to be used
|
||||
// Value is never called, but must be present to allow stateObject to be used
|
||||
// as a vm.Account interface that also satisfies the vm.ContractRef
|
||||
// interface. Interfaces are awesome.
|
||||
func (s *stateObject) Value() *big.Int {
|
||||
|
||||
+232
-114
@@ -31,6 +31,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
)
|
||||
@@ -71,16 +72,16 @@ type StateDB struct {
|
||||
// It will be updated when the Commit is called.
|
||||
originalRoot common.Hash
|
||||
|
||||
snaps *snapshot.Tree
|
||||
snap snapshot.Snapshot
|
||||
snapDestructs map[common.Hash]struct{}
|
||||
snapAccounts map[common.Hash][]byte
|
||||
snapStorage map[common.Hash]map[common.Hash][]byte
|
||||
snaps *snapshot.Tree
|
||||
snap snapshot.Snapshot
|
||||
snapAccounts map[common.Hash][]byte
|
||||
snapStorage map[common.Hash]map[common.Hash][]byte
|
||||
|
||||
// This map holds 'live' objects, which will get modified while processing a state transition.
|
||||
stateObjects map[common.Address]*stateObject
|
||||
stateObjectsPending map[common.Address]struct{} // State objects finalized but not yet written to the trie
|
||||
stateObjectsDirty map[common.Address]struct{} // State objects modified in the current execution
|
||||
stateObjects map[common.Address]*stateObject
|
||||
stateObjectsPending map[common.Address]struct{} // State objects finalized but not yet written to the trie
|
||||
stateObjectsDirty map[common.Address]struct{} // State objects modified in the current execution
|
||||
stateObjectsDestruct map[common.Address]struct{} // State objects destructed in the block
|
||||
|
||||
// DB error.
|
||||
// State objects are used by the consensus core and VM which are
|
||||
@@ -102,6 +103,9 @@ type StateDB struct {
|
||||
// Per-transaction access list
|
||||
accessList *accessList
|
||||
|
||||
// Transient storage
|
||||
transientStorage transientStorage
|
||||
|
||||
// Journal of state modifications. This is the backbone of
|
||||
// Snapshot and RevertToSnapshot.
|
||||
journal *journal
|
||||
@@ -120,6 +124,7 @@ type StateDB struct {
|
||||
SnapshotAccountReads time.Duration
|
||||
SnapshotStorageReads time.Duration
|
||||
SnapshotCommits time.Duration
|
||||
TrieDBCommits time.Duration
|
||||
|
||||
AccountUpdated int
|
||||
StorageUpdated int
|
||||
@@ -134,22 +139,23 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error)
|
||||
return nil, err
|
||||
}
|
||||
sdb := &StateDB{
|
||||
db: db,
|
||||
trie: tr,
|
||||
originalRoot: root,
|
||||
snaps: snaps,
|
||||
stateObjects: make(map[common.Address]*stateObject),
|
||||
stateObjectsPending: make(map[common.Address]struct{}),
|
||||
stateObjectsDirty: make(map[common.Address]struct{}),
|
||||
logs: make(map[common.Hash][]*types.Log),
|
||||
preimages: make(map[common.Hash][]byte),
|
||||
journal: newJournal(),
|
||||
accessList: newAccessList(),
|
||||
hasher: crypto.NewKeccakState(),
|
||||
db: db,
|
||||
trie: tr,
|
||||
originalRoot: root,
|
||||
snaps: snaps,
|
||||
stateObjects: make(map[common.Address]*stateObject),
|
||||
stateObjectsPending: make(map[common.Address]struct{}),
|
||||
stateObjectsDirty: make(map[common.Address]struct{}),
|
||||
stateObjectsDestruct: make(map[common.Address]struct{}),
|
||||
logs: make(map[common.Hash][]*types.Log),
|
||||
preimages: make(map[common.Hash][]byte),
|
||||
journal: newJournal(),
|
||||
accessList: newAccessList(),
|
||||
transientStorage: newTransientStorage(),
|
||||
hasher: crypto.NewKeccakState(),
|
||||
}
|
||||
if sdb.snaps != nil {
|
||||
if sdb.snap = sdb.snaps.Snapshot(root); sdb.snap != nil {
|
||||
sdb.snapDestructs = make(map[common.Hash]struct{})
|
||||
sdb.snapAccounts = make(map[common.Hash][]byte)
|
||||
sdb.snapStorage = make(map[common.Hash]map[common.Hash][]byte)
|
||||
}
|
||||
@@ -209,9 +215,12 @@ func (s *StateDB) AddLog(log *types.Log) {
|
||||
s.logSize++
|
||||
}
|
||||
|
||||
func (s *StateDB) GetLogs(hash common.Hash, blockHash common.Hash) []*types.Log {
|
||||
// GetLogs returns the logs matching the specified transaction hash, and annotates
|
||||
// them with the given blockNumber and blockHash.
|
||||
func (s *StateDB) GetLogs(hash common.Hash, blockNumber uint64, blockHash common.Hash) []*types.Log {
|
||||
logs := s.logs[hash]
|
||||
for _, l := range logs {
|
||||
l.BlockNumber = blockNumber
|
||||
l.BlockHash = blockHash
|
||||
}
|
||||
return logs
|
||||
@@ -339,13 +348,19 @@ func (s *StateDB) GetProofByHash(addrHash common.Hash) ([][]byte, error) {
|
||||
|
||||
// GetStorageProof returns the Merkle proof for given storage slot.
|
||||
func (s *StateDB) GetStorageProof(a common.Address, key common.Hash) ([][]byte, error) {
|
||||
var proof proofList
|
||||
trie := s.StorageTrie(a)
|
||||
if trie == nil {
|
||||
return proof, errors.New("storage trie for requested address does not exist")
|
||||
trie, err := s.StorageTrie(a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err := trie.Prove(crypto.Keccak256(key.Bytes()), 0, &proof)
|
||||
return proof, err
|
||||
if trie == nil {
|
||||
return nil, errors.New("storage trie for requested address does not exist")
|
||||
}
|
||||
var proof proofList
|
||||
err = trie.Prove(crypto.Keccak256(key.Bytes()), 0, &proof)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return proof, nil
|
||||
}
|
||||
|
||||
// GetCommittedState retrieves a value from the given account's committed storage trie.
|
||||
@@ -362,15 +377,18 @@ func (s *StateDB) Database() Database {
|
||||
return s.db
|
||||
}
|
||||
|
||||
// StorageTrie returns the storage trie of an account.
|
||||
// The return value is a copy and is nil for non-existent accounts.
|
||||
func (s *StateDB) StorageTrie(addr common.Address) Trie {
|
||||
// StorageTrie returns the storage trie of an account. The return value is a copy
|
||||
// and is nil for non-existent accounts. An error will be returned if storage trie
|
||||
// is existent but can't be loaded correctly.
|
||||
func (s *StateDB) StorageTrie(addr common.Address) (Trie, error) {
|
||||
stateObject := s.getStateObject(addr)
|
||||
if stateObject == nil {
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
cpy := stateObject.deepCopy(s)
|
||||
cpy.updateTrie(s.db)
|
||||
if _, err := cpy.updateTrie(s.db); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cpy.getTrie(s.db)
|
||||
}
|
||||
|
||||
@@ -433,9 +451,15 @@ func (s *StateDB) SetState(addr common.Address, key, value common.Hash) {
|
||||
// SetStorage replaces the entire storage for the specified account with given
|
||||
// storage. This function should only be used for debugging.
|
||||
func (s *StateDB) SetStorage(addr common.Address, storage map[common.Hash]common.Hash) {
|
||||
// SetStorage needs to wipe existing storage. We achieve this by pretending
|
||||
// that the account self-destructed earlier in this block, by flagging
|
||||
// it in stateObjectsDestruct. The effect of doing so is that storage lookups
|
||||
// will not hit disk, since it is assumed that the disk-data is belonging
|
||||
// to a previous incarnation of the object.
|
||||
s.stateObjectsDestruct[addr] = struct{}{}
|
||||
stateObject := s.GetOrNewStateObject(addr)
|
||||
if stateObject != nil {
|
||||
stateObject.SetStorage(storage)
|
||||
for k, v := range storage {
|
||||
stateObject.SetState(s.db, k, v)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -460,6 +484,35 @@ func (s *StateDB) Suicide(addr common.Address) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// SetTransientState sets transient storage for a given account. It
|
||||
// adds the change to the journal so that it can be rolled back
|
||||
// to its previous value if there is a revert.
|
||||
func (s *StateDB) SetTransientState(addr common.Address, key, value common.Hash) {
|
||||
prev := s.GetTransientState(addr, key)
|
||||
if prev == value {
|
||||
return
|
||||
}
|
||||
|
||||
s.journal.append(transientStorageChange{
|
||||
account: &addr,
|
||||
key: key,
|
||||
prevalue: prev,
|
||||
})
|
||||
|
||||
s.setTransientState(addr, key, value)
|
||||
}
|
||||
|
||||
// setTransientState is a lower level setter for transient storage. It
|
||||
// is called during a revert to prevent modifications to the journal.
|
||||
func (s *StateDB) setTransientState(addr common.Address, key, value common.Hash) {
|
||||
s.transientStorage.Set(addr, key, value)
|
||||
}
|
||||
|
||||
// GetTransientState gets transient storage for a given account.
|
||||
func (s *StateDB) GetTransientState(addr common.Address, key common.Hash) common.Hash {
|
||||
return s.transientStorage.Get(addr, key)
|
||||
}
|
||||
|
||||
//
|
||||
// Setting, updating & deleting state object methods.
|
||||
//
|
||||
@@ -472,7 +525,7 @@ func (s *StateDB) updateStateObject(obj *stateObject) {
|
||||
}
|
||||
// Encode the account and update the account trie
|
||||
addr := obj.Address()
|
||||
if err := s.trie.TryUpdateAccount(addr[:], &obj.data); err != nil {
|
||||
if err := s.trie.TryUpdateAccount(addr, &obj.data); err != nil {
|
||||
s.setError(fmt.Errorf("updateStateObject (%x) error: %v", addr[:], err))
|
||||
}
|
||||
|
||||
@@ -493,7 +546,7 @@ func (s *StateDB) deleteStateObject(obj *stateObject) {
|
||||
}
|
||||
// Delete the account from the trie
|
||||
addr := obj.Address()
|
||||
if err := s.trie.TryDeleteAccount(addr[:]); err != nil {
|
||||
if err := s.trie.TryDeleteAccount(addr); err != nil {
|
||||
s.setError(fmt.Errorf("deleteStateObject (%x) error: %v", addr[:], err))
|
||||
}
|
||||
}
|
||||
@@ -547,7 +600,7 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject {
|
||||
if data == nil {
|
||||
start := time.Now()
|
||||
var err error
|
||||
data, err = s.trie.TryGetAccount(addr.Bytes())
|
||||
data, err = s.trie.TryGetAccount(addr)
|
||||
if metrics.EnabledExpensive {
|
||||
s.AccountReads += time.Since(start)
|
||||
}
|
||||
@@ -584,10 +637,10 @@ func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject)
|
||||
prev = s.getDeletedStateObject(addr) // Note, prev might have been deleted, we need that!
|
||||
|
||||
var prevdestruct bool
|
||||
if s.snap != nil && prev != nil {
|
||||
_, prevdestruct = s.snapDestructs[prev.addrHash]
|
||||
if prev != nil {
|
||||
_, prevdestruct = s.stateObjectsDestruct[prev.address]
|
||||
if !prevdestruct {
|
||||
s.snapDestructs[prev.addrHash] = struct{}{}
|
||||
s.stateObjectsDestruct[prev.address] = struct{}{}
|
||||
}
|
||||
}
|
||||
newobj = newObject(s, addr, types.StateAccount{})
|
||||
@@ -609,8 +662,8 @@ func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject)
|
||||
// CreateAccount is called during the EVM CREATE operation. The situation might arise that
|
||||
// a contract does the following:
|
||||
//
|
||||
// 1. sends funds to sha(account ++ (nonce + 1))
|
||||
// 2. tx_create(sha(account ++ nonce)) (note that this gets the address of 1)
|
||||
// 1. sends funds to sha(account ++ (nonce + 1))
|
||||
// 2. tx_create(sha(account ++ nonce)) (note that this gets the address of 1)
|
||||
//
|
||||
// Carrying over the balance ensures that Ether doesn't disappear.
|
||||
func (s *StateDB) CreateAccount(addr common.Address) {
|
||||
@@ -625,7 +678,11 @@ func (db *StateDB) ForEachStorage(addr common.Address, cb func(key, value common
|
||||
if so == nil {
|
||||
return nil
|
||||
}
|
||||
it := trie.NewIterator(so.getTrie(db.db).NodeIterator(nil))
|
||||
tr, err := so.getTrie(db.db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
it := trie.NewIterator(tr.NodeIterator(nil))
|
||||
|
||||
for it.Next() {
|
||||
key := common.BytesToHash(db.trie.GetKey(it.Key))
|
||||
@@ -654,18 +711,19 @@ func (db *StateDB) ForEachStorage(addr common.Address, cb func(key, value common
|
||||
func (s *StateDB) Copy() *StateDB {
|
||||
// Copy all the basic fields, initialize the memory ones
|
||||
state := &StateDB{
|
||||
db: s.db,
|
||||
trie: s.db.CopyTrie(s.trie),
|
||||
originalRoot: s.originalRoot,
|
||||
stateObjects: make(map[common.Address]*stateObject, len(s.journal.dirties)),
|
||||
stateObjectsPending: make(map[common.Address]struct{}, len(s.stateObjectsPending)),
|
||||
stateObjectsDirty: make(map[common.Address]struct{}, len(s.journal.dirties)),
|
||||
refund: s.refund,
|
||||
logs: make(map[common.Hash][]*types.Log, len(s.logs)),
|
||||
logSize: s.logSize,
|
||||
preimages: make(map[common.Hash][]byte, len(s.preimages)),
|
||||
journal: newJournal(),
|
||||
hasher: crypto.NewKeccakState(),
|
||||
db: s.db,
|
||||
trie: s.db.CopyTrie(s.trie),
|
||||
originalRoot: s.originalRoot,
|
||||
stateObjects: make(map[common.Address]*stateObject, len(s.journal.dirties)),
|
||||
stateObjectsPending: make(map[common.Address]struct{}, len(s.stateObjectsPending)),
|
||||
stateObjectsDirty: make(map[common.Address]struct{}, len(s.journal.dirties)),
|
||||
stateObjectsDestruct: make(map[common.Address]struct{}, len(s.stateObjectsDestruct)),
|
||||
refund: s.refund,
|
||||
logs: make(map[common.Hash][]*types.Log, len(s.logs)),
|
||||
logSize: s.logSize,
|
||||
preimages: make(map[common.Hash][]byte, len(s.preimages)),
|
||||
journal: newJournal(),
|
||||
hasher: crypto.NewKeccakState(),
|
||||
}
|
||||
// Copy the dirty states, logs, and preimages
|
||||
for addr := range s.journal.dirties {
|
||||
@@ -675,7 +733,7 @@ func (s *StateDB) Copy() *StateDB {
|
||||
// nil
|
||||
if object, exist := s.stateObjects[addr]; exist {
|
||||
// Even though the original object is dirty, we are not copying the journal,
|
||||
// so we need to make sure that anyside effect the journal would have caused
|
||||
// so we need to make sure that any side-effect the journal would have caused
|
||||
// during a commit (or similar op) is already applied to the copy.
|
||||
state.stateObjects[addr] = object.deepCopy(state)
|
||||
|
||||
@@ -683,9 +741,10 @@ func (s *StateDB) Copy() *StateDB {
|
||||
state.stateObjectsPending[addr] = struct{}{} // Mark the copy pending to force external (account) commits
|
||||
}
|
||||
}
|
||||
// Above, we don't copy the actual journal. This means that if the copy is copied, the
|
||||
// loop above will be a no-op, since the copy's journal is empty.
|
||||
// Thus, here we iterate over stateObjects, to enable copies of copies
|
||||
// Above, we don't copy the actual journal. This means that if the copy
|
||||
// is copied, the loop above will be a no-op, since the copy's journal
|
||||
// is empty. Thus, here we iterate over stateObjects, to enable copies
|
||||
// of copies.
|
||||
for addr := range s.stateObjectsPending {
|
||||
if _, exist := state.stateObjects[addr]; !exist {
|
||||
state.stateObjects[addr] = s.stateObjects[addr].deepCopy(state)
|
||||
@@ -698,6 +757,10 @@ func (s *StateDB) Copy() *StateDB {
|
||||
}
|
||||
state.stateObjectsDirty[addr] = struct{}{}
|
||||
}
|
||||
// Deep copy the destruction flag.
|
||||
for addr := range s.stateObjectsDestruct {
|
||||
state.stateObjectsDestruct[addr] = struct{}{}
|
||||
}
|
||||
for hash, logs := range s.logs {
|
||||
cpy := make([]*types.Log, len(logs))
|
||||
for i, l := range logs {
|
||||
@@ -709,12 +772,14 @@ func (s *StateDB) Copy() *StateDB {
|
||||
for hash, preimage := range s.preimages {
|
||||
state.preimages[hash] = preimage
|
||||
}
|
||||
// Do we need to copy the access list? In practice: No. At the start of a
|
||||
// transaction, the access list is empty. In practice, we only ever copy state
|
||||
// _between_ transactions/blocks, never in the middle of a transaction.
|
||||
// However, it doesn't cost us much to copy an empty list, so we do it anyway
|
||||
// to not blow up if we ever decide copy it in the middle of a transaction
|
||||
// Do we need to copy the access list and transient storage?
|
||||
// In practice: No. At the start of a transaction, these two lists are empty.
|
||||
// In practice, we only ever copy state _between_ transactions/blocks, never
|
||||
// in the middle of a transaction. However, it doesn't cost us much to copy
|
||||
// empty lists, so we do it anyway to not blow up if we ever decide copy them
|
||||
// in the middle of a transaction.
|
||||
state.accessList = s.accessList.Copy()
|
||||
state.transientStorage = s.transientStorage.Copy()
|
||||
|
||||
// If there's a prefetcher running, make an inactive copy of it that can
|
||||
// only access data but does not actively preload (since the user will not
|
||||
@@ -724,16 +789,13 @@ func (s *StateDB) Copy() *StateDB {
|
||||
}
|
||||
if s.snaps != nil {
|
||||
// In order for the miner to be able to use and make additions
|
||||
// to the snapshot tree, we need to copy that aswell.
|
||||
// to the snapshot tree, we need to copy that as well.
|
||||
// Otherwise, any block mined by ourselves will cause gaps in the tree,
|
||||
// and force the miner to operate trie-backed only
|
||||
state.snaps = s.snaps
|
||||
state.snap = s.snap
|
||||
|
||||
// deep copy needed
|
||||
state.snapDestructs = make(map[common.Hash]struct{})
|
||||
for k, v := range s.snapDestructs {
|
||||
state.snapDestructs[k] = v
|
||||
}
|
||||
state.snapAccounts = make(map[common.Hash][]byte)
|
||||
for k, v := range s.snapAccounts {
|
||||
state.snapAccounts[k] = v
|
||||
@@ -798,14 +860,17 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
|
||||
if obj.suicided || (deleteEmptyObjects && obj.empty()) {
|
||||
obj.deleted = true
|
||||
|
||||
// We need to maintain account deletions explicitly (will remain
|
||||
// set indefinitely).
|
||||
s.stateObjectsDestruct[obj.address] = struct{}{}
|
||||
|
||||
// If state snapshotting is active, also mark the destruction there.
|
||||
// Note, we can't do this only at the end of a block because multiple
|
||||
// transactions within the same block might self destruct and then
|
||||
// resurrect an account; but the snapshotter needs both events.
|
||||
if s.snap != nil {
|
||||
s.snapDestructs[obj.addrHash] = struct{}{} // We need to maintain account deletions explicitly (will remain set indefinitely)
|
||||
delete(s.snapAccounts, obj.addrHash) // Clear out any previously updated account data (may be recreated via a ressurrect)
|
||||
delete(s.snapStorage, obj.addrHash) // Clear out any previously updated storage data (may be recreated via a ressurrect)
|
||||
delete(s.snapAccounts, obj.addrHash) // Clear out any previously updated account data (may be recreated via a resurrect)
|
||||
delete(s.snapStorage, obj.addrHash) // Clear out any previously updated storage data (may be recreated via a resurrect)
|
||||
}
|
||||
} else {
|
||||
obj.finalise(true) // Prefetch slots in the background
|
||||
@@ -888,9 +953,10 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
||||
return s.trie.Hash()
|
||||
}
|
||||
|
||||
// Prepare sets the current transaction hash and index which are
|
||||
// used when the EVM emits new state logs.
|
||||
func (s *StateDB) Prepare(thash common.Hash, ti int) {
|
||||
// SetTxContext sets the current transaction hash and index which are
|
||||
// used when the EVM emits new state logs. It should be invoked before
|
||||
// transaction execution.
|
||||
func (s *StateDB) SetTxContext(thash common.Hash, ti int) {
|
||||
s.thash = thash
|
||||
s.txIndex = ti
|
||||
}
|
||||
@@ -913,9 +979,11 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
|
||||
|
||||
// Commit objects to the trie, measuring the elapsed time
|
||||
var (
|
||||
accountTrieNodes int
|
||||
storageTrieNodes int
|
||||
nodes = trie.NewMergedNodeSet()
|
||||
accountTrieNodesUpdated int
|
||||
accountTrieNodesDeleted int
|
||||
storageTrieNodesUpdated int
|
||||
storageTrieNodesDeleted int
|
||||
nodes = trie.NewMergedNodeSet()
|
||||
)
|
||||
// begin PluGeth injection
|
||||
codeUpdates := make(map[common.Hash][]byte)
|
||||
@@ -932,7 +1000,7 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
|
||||
obj.dirtyCode = false
|
||||
}
|
||||
// Write any storage changes in the state object to its storage trie
|
||||
set, err := obj.CommitTrie(s.db)
|
||||
set, err := obj.commitTrie(s.db)
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
@@ -941,9 +1009,17 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
|
||||
if err := nodes.Merge(set); err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
storageTrieNodes += set.Len()
|
||||
updates, deleted := set.Size()
|
||||
storageTrieNodesUpdated += updates
|
||||
storageTrieNodesDeleted += deleted
|
||||
}
|
||||
}
|
||||
// If the contract is destructed, the storage is still left in the
|
||||
// database as dangling data. Theoretically it's should be wiped from
|
||||
// database as well, but in hash-based-scheme it's extremely hard to
|
||||
// determine that if the trie nodes are also referenced by other storage,
|
||||
// and in path-based-scheme some technical challenges are still unsolved.
|
||||
// Although it won't affect the correctness but please fix it TODO(rjl493456442).
|
||||
}
|
||||
if len(s.stateObjectsDirty) > 0 {
|
||||
s.stateObjectsDirty = make(map[common.Address]struct{})
|
||||
@@ -958,16 +1034,13 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
|
||||
if metrics.EnabledExpensive {
|
||||
start = time.Now()
|
||||
}
|
||||
root, set, err := s.trie.Commit(true)
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
root, set := s.trie.Commit(true)
|
||||
// Merge the dirty nodes of account trie into global set
|
||||
if set != nil {
|
||||
if err := nodes.Merge(set); err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
accountTrieNodes = set.Len()
|
||||
accountTrieNodesUpdated, accountTrieNodesDeleted = set.Size()
|
||||
}
|
||||
if metrics.EnabledExpensive {
|
||||
s.AccountCommits += time.Since(start)
|
||||
@@ -976,16 +1049,16 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
|
||||
storageUpdatedMeter.Mark(int64(s.StorageUpdated))
|
||||
accountDeletedMeter.Mark(int64(s.AccountDeleted))
|
||||
storageDeletedMeter.Mark(int64(s.StorageDeleted))
|
||||
accountTrieCommittedMeter.Mark(int64(accountTrieNodes))
|
||||
storageTriesCommittedMeter.Mark(int64(storageTrieNodes))
|
||||
accountTrieUpdatedMeter.Mark(int64(accountTrieNodesUpdated))
|
||||
accountTrieDeletedMeter.Mark(int64(accountTrieNodesDeleted))
|
||||
storageTriesUpdatedMeter.Mark(int64(storageTrieNodesUpdated))
|
||||
storageTriesDeletedMeter.Mark(int64(storageTrieNodesDeleted))
|
||||
s.AccountUpdated, s.AccountDeleted = 0, 0
|
||||
s.StorageUpdated, s.StorageDeleted = 0, 0
|
||||
}
|
||||
// If snapshotting is enabled, update the snapshot tree with this new version
|
||||
if s.snap != nil {
|
||||
if metrics.EnabledExpensive {
|
||||
defer func(start time.Time) { s.SnapshotCommits += time.Since(start) }(time.Now())
|
||||
}
|
||||
start := time.Now()
|
||||
// Only update if there's a state transition (skip empty Clique blocks)
|
||||
if parent := s.snap.Root(); parent != root {
|
||||
//begin PluGeth code injection
|
||||
@@ -1004,42 +1077,73 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
s.snap, s.snapDestructs, s.snapAccounts, s.snapStorage = nil, nil, nil, nil
|
||||
if metrics.EnabledExpensive {
|
||||
s.SnapshotCommits += time.Since(start)
|
||||
}
|
||||
s.snap, s.snapAccounts, s.snapStorage = nil, nil, nil
|
||||
}
|
||||
if err := s.db.TrieDB().Update(nodes); err != nil {
|
||||
return common.Hash{}, err
|
||||
if len(s.stateObjectsDestruct) > 0 {
|
||||
s.stateObjectsDestruct = make(map[common.Address]struct{})
|
||||
}
|
||||
s.originalRoot = root
|
||||
return root, err
|
||||
if root == (common.Hash{}) {
|
||||
root = emptyRoot
|
||||
}
|
||||
origin := s.originalRoot
|
||||
if origin == (common.Hash{}) {
|
||||
origin = emptyRoot
|
||||
}
|
||||
if root != origin {
|
||||
start := time.Now()
|
||||
if err := s.db.TrieDB().Update(nodes); err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
s.originalRoot = root
|
||||
if metrics.EnabledExpensive {
|
||||
s.TrieDBCommits += time.Since(start)
|
||||
}
|
||||
}
|
||||
return root, nil
|
||||
}
|
||||
|
||||
// PrepareAccessList handles the preparatory steps for executing a state transition with
|
||||
// regards to both EIP-2929 and EIP-2930:
|
||||
// Prepare handles the preparatory steps for executing a state transition with.
|
||||
// This method must be invoked before state transition.
|
||||
//
|
||||
// Berlin fork:
|
||||
// - Add sender to access list (2929)
|
||||
// - Add destination to access list (2929)
|
||||
// - Add precompiles to access list (2929)
|
||||
// - Add the contents of the optional tx access list (2930)
|
||||
//
|
||||
// This method should only be called if Berlin/2929+2930 is applicable at the current number.
|
||||
func (s *StateDB) PrepareAccessList(sender common.Address, dst *common.Address, precompiles []common.Address, list types.AccessList) {
|
||||
// Clear out any leftover from previous executions
|
||||
s.accessList = newAccessList()
|
||||
// Potential EIPs:
|
||||
// - Reset access list (Berlin)
|
||||
// - Add coinbase to access list (EIP-3651)
|
||||
// - Reset transient storage (EIP-1153)
|
||||
func (s *StateDB) Prepare(rules params.Rules, sender, coinbase common.Address, dst *common.Address, precompiles []common.Address, list types.AccessList) {
|
||||
if rules.IsBerlin {
|
||||
// Clear out any leftover from previous executions
|
||||
al := newAccessList()
|
||||
s.accessList = al
|
||||
|
||||
s.AddAddressToAccessList(sender)
|
||||
if dst != nil {
|
||||
s.AddAddressToAccessList(*dst)
|
||||
// If it's a create-tx, the destination will be added inside evm.create
|
||||
}
|
||||
for _, addr := range precompiles {
|
||||
s.AddAddressToAccessList(addr)
|
||||
}
|
||||
for _, el := range list {
|
||||
s.AddAddressToAccessList(el.Address)
|
||||
for _, key := range el.StorageKeys {
|
||||
s.AddSlotToAccessList(el.Address, key)
|
||||
al.AddAddress(sender)
|
||||
if dst != nil {
|
||||
al.AddAddress(*dst)
|
||||
// If it's a create-tx, the destination will be added inside evm.create
|
||||
}
|
||||
for _, addr := range precompiles {
|
||||
al.AddAddress(addr)
|
||||
}
|
||||
for _, el := range list {
|
||||
al.AddAddress(el.Address)
|
||||
for _, key := range el.StorageKeys {
|
||||
al.AddSlot(el.Address, key)
|
||||
}
|
||||
}
|
||||
if rules.IsShanghai { // EIP-3651: warm coinbase
|
||||
al.AddAddress(coinbase)
|
||||
}
|
||||
}
|
||||
// Reset transient storage at the beginning of transaction execution
|
||||
s.transientStorage = newTransientStorage()
|
||||
}
|
||||
|
||||
// AddAddressToAccessList adds the given address to the access list
|
||||
@@ -1076,3 +1180,17 @@ func (s *StateDB) AddressInAccessList(addr common.Address) bool {
|
||||
func (s *StateDB) SlotInAccessList(addr common.Address, slot common.Hash) (addressPresent bool, slotPresent bool) {
|
||||
return s.accessList.Contains(addr, slot)
|
||||
}
|
||||
|
||||
// convertAccountSet converts a provided account set from address keyed to hash keyed.
|
||||
func (s *StateDB) convertAccountSet(set map[common.Address]struct{}) map[common.Hash]struct{} {
|
||||
ret := make(map[common.Hash]struct{})
|
||||
for addr := range set {
|
||||
obj, exist := s.stateObjects[addr]
|
||||
if !exist {
|
||||
ret[crypto.Keccak256Hash(addr[:])] = struct{}{}
|
||||
} else {
|
||||
ret[obj.addrHash] = struct{}{}
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ func TestUpdateLeaks(t *testing.T) {
|
||||
}
|
||||
|
||||
root := state.IntermediateRoot(false)
|
||||
if err := state.Database().TrieDB().Commit(root, false, nil); err != nil {
|
||||
if err := state.Database().TrieDB().Commit(root, false); err != nil {
|
||||
t.Errorf("can not commit trie %v to persistent database", root.Hex())
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ func TestIntermediateLeaks(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("failed to commit transition state: %v", err)
|
||||
}
|
||||
if err = transState.Database().TrieDB().Commit(transRoot, false, nil); err != nil {
|
||||
if err = transState.Database().TrieDB().Commit(transRoot, false); err != nil {
|
||||
t.Errorf("can not commit trie %v to persistent database", transRoot.Hex())
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ func TestIntermediateLeaks(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("failed to commit final state: %v", err)
|
||||
}
|
||||
if err = finalState.Database().TrieDB().Commit(finalRoot, false, nil); err != nil {
|
||||
if err = finalState.Database().TrieDB().Commit(finalRoot, false); err != nil {
|
||||
t.Errorf("can not commit trie %v to persistent database", finalRoot.Hex())
|
||||
}
|
||||
|
||||
@@ -342,6 +342,16 @@ func newTestAction(addr common.Address, r *rand.Rand) testAction {
|
||||
},
|
||||
args: make([]int64, 1),
|
||||
},
|
||||
{
|
||||
name: "SetTransientState",
|
||||
fn: func(a testAction, s *StateDB) {
|
||||
var key, val common.Hash
|
||||
binary.BigEndian.PutUint16(key[:], uint16(a.args[0]))
|
||||
binary.BigEndian.PutUint16(val[:], uint16(a.args[1]))
|
||||
s.SetTransientState(addr, key, val)
|
||||
},
|
||||
args: make([]int64, 2),
|
||||
},
|
||||
}
|
||||
action := actions[r.Intn(len(actions))]
|
||||
var nameargs []string
|
||||
@@ -463,9 +473,9 @@ func (test *snapshotTest) checkEqual(state, checkstate *StateDB) error {
|
||||
return fmt.Errorf("got GetRefund() == %d, want GetRefund() == %d",
|
||||
state.GetRefund(), checkstate.GetRefund())
|
||||
}
|
||||
if !reflect.DeepEqual(state.GetLogs(common.Hash{}, common.Hash{}), checkstate.GetLogs(common.Hash{}, common.Hash{})) {
|
||||
if !reflect.DeepEqual(state.GetLogs(common.Hash{}, 0, common.Hash{}), checkstate.GetLogs(common.Hash{}, 0, common.Hash{})) {
|
||||
return fmt.Errorf("got GetLogs(common.Hash{}) == %v, want GetLogs(common.Hash{}) == %v",
|
||||
state.GetLogs(common.Hash{}, common.Hash{}), checkstate.GetLogs(common.Hash{}, common.Hash{}))
|
||||
state.GetLogs(common.Hash{}, 0, common.Hash{}), checkstate.GetLogs(common.Hash{}, 0, common.Hash{}))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -938,7 +948,7 @@ func TestFlushOrderDataLoss(t *testing.T) {
|
||||
if err := statedb.TrieDB().Cap(1024); err != nil {
|
||||
t.Fatalf("failed to cap trie dirty cache: %v", err)
|
||||
}
|
||||
if err := statedb.TrieDB().Commit(root, false, nil); err != nil {
|
||||
if err := statedb.TrieDB().Commit(root, false); err != nil {
|
||||
t.Fatalf("failed to commit state trie: %v", err)
|
||||
}
|
||||
// Reopen the state trie from flushed disk and verify it
|
||||
@@ -954,3 +964,37 @@ func TestFlushOrderDataLoss(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStateDBTransientStorage(t *testing.T) {
|
||||
memDb := rawdb.NewMemoryDatabase()
|
||||
db := NewDatabase(memDb)
|
||||
state, _ := New(common.Hash{}, db, nil)
|
||||
|
||||
key := common.Hash{0x01}
|
||||
value := common.Hash{0x02}
|
||||
addr := common.Address{}
|
||||
|
||||
state.SetTransientState(addr, key, value)
|
||||
if exp, got := 1, state.journal.length(); exp != got {
|
||||
t.Fatalf("journal length mismatch: have %d, want %d", got, exp)
|
||||
}
|
||||
// the retrieved value should equal what was set
|
||||
if got := state.GetTransientState(addr, key); got != value {
|
||||
t.Fatalf("transient storage mismatch: have %x, want %x", got, value)
|
||||
}
|
||||
|
||||
// revert the transient state being set and then check that the
|
||||
// value is now the empty hash
|
||||
state.journal.revert(state, 0)
|
||||
if got, exp := state.GetTransientState(addr, key), (common.Hash{}); exp != got {
|
||||
t.Fatalf("transient storage mismatch: have %x, want %x", got, exp)
|
||||
}
|
||||
|
||||
// set transient state and then copy the statedb and ensure that
|
||||
// the transient state is copied
|
||||
state.SetTransientState(addr, key, value)
|
||||
cpy := state.Copy()
|
||||
if got := cpy.GetTransientState(addr, key); got != value {
|
||||
t.Fatalf("transient storage mismatch: have %x, want %x", got, value)
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -27,7 +27,7 @@ import (
|
||||
)
|
||||
|
||||
// NewStateSync create a new state trie download scheduler.
|
||||
func NewStateSync(root common.Hash, database ethdb.KeyValueReader, onLeaf func(keys [][]byte, leaf []byte) error) *trie.Sync {
|
||||
func NewStateSync(root common.Hash, database ethdb.KeyValueReader, onLeaf func(keys [][]byte, leaf []byte) error, scheme string) *trie.Sync {
|
||||
// Register the storage slot callback if the external callback is specified.
|
||||
var onSlot func(keys [][]byte, path []byte, leaf []byte, parent common.Hash, parentPath []byte) error
|
||||
if onLeaf != nil {
|
||||
@@ -52,6 +52,6 @@ func NewStateSync(root common.Hash, database ethdb.KeyValueReader, onLeaf func(k
|
||||
syncer.AddCodeEntry(common.BytesToHash(obj.CodeHash), path, parent, parentPath)
|
||||
return nil
|
||||
}
|
||||
syncer = trie.NewSync(root, database, onAccount)
|
||||
syncer = trie.NewSync(root, database, onAccount, scheme)
|
||||
return syncer
|
||||
}
|
||||
|
||||
+43
-32
@@ -39,10 +39,11 @@ type testAccount struct {
|
||||
}
|
||||
|
||||
// makeTestState create a sample test state to test node-wise reconstruction.
|
||||
func makeTestState() (Database, common.Hash, []*testAccount) {
|
||||
func makeTestState() (ethdb.Database, Database, common.Hash, []*testAccount) {
|
||||
// Create an empty state
|
||||
db := NewDatabase(rawdb.NewMemoryDatabase())
|
||||
state, _ := New(common.Hash{}, db, nil)
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
sdb := NewDatabase(db)
|
||||
state, _ := New(common.Hash{}, sdb, nil)
|
||||
|
||||
// Fill it with some arbitrary data
|
||||
var accounts []*testAccount
|
||||
@@ -63,7 +64,7 @@ func makeTestState() (Database, common.Hash, []*testAccount) {
|
||||
if i%5 == 0 {
|
||||
for j := byte(0); j < 5; j++ {
|
||||
hash := crypto.Keccak256Hash([]byte{i, i, i, i, i, j, j})
|
||||
obj.SetState(db, hash, hash)
|
||||
obj.SetState(sdb, hash, hash)
|
||||
}
|
||||
}
|
||||
state.updateStateObject(obj)
|
||||
@@ -72,7 +73,7 @@ func makeTestState() (Database, common.Hash, []*testAccount) {
|
||||
root, _ := state.Commit(false)
|
||||
|
||||
// Return the generated state
|
||||
return db, root, accounts
|
||||
return db, sdb, root, accounts
|
||||
}
|
||||
|
||||
// checkStateAccounts cross references a reconstructed state with an expected
|
||||
@@ -104,7 +105,7 @@ func checkTrieConsistency(db ethdb.Database, root common.Hash) error {
|
||||
if v, _ := db.Get(root[:]); v == nil {
|
||||
return nil // Consider a non existent state consistent.
|
||||
}
|
||||
trie, err := trie.New(common.Hash{}, root, trie.NewDatabase(db))
|
||||
trie, err := trie.New(trie.StateTrieID(root), trie.NewDatabase(db))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -132,8 +133,9 @@ func checkStateConsistency(db ethdb.Database, root common.Hash) error {
|
||||
|
||||
// Tests that an empty state is not scheduled for syncing.
|
||||
func TestEmptyStateSync(t *testing.T) {
|
||||
db := trie.NewDatabase(rawdb.NewMemoryDatabase())
|
||||
empty := common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
|
||||
sync := NewStateSync(empty, rawdb.NewMemoryDatabase(), nil)
|
||||
sync := NewStateSync(empty, rawdb.NewMemoryDatabase(), nil, db.Scheme())
|
||||
if paths, nodes, codes := sync.Missing(1); len(paths) != 0 || len(nodes) != 0 || len(codes) != 0 {
|
||||
t.Errorf("content requested for empty state: %v, %v, %v", nodes, paths, codes)
|
||||
}
|
||||
@@ -170,15 +172,15 @@ type stateElement struct {
|
||||
|
||||
func testIterativeStateSync(t *testing.T, count int, commit bool, bypath bool) {
|
||||
// Create a random state to copy
|
||||
srcDb, srcRoot, srcAccounts := makeTestState()
|
||||
_, srcDb, srcRoot, srcAccounts := makeTestState()
|
||||
if commit {
|
||||
srcDb.TrieDB().Commit(srcRoot, false, nil)
|
||||
srcDb.TrieDB().Commit(srcRoot, false)
|
||||
}
|
||||
srcTrie, _ := trie.New(common.Hash{}, srcRoot, srcDb.TrieDB())
|
||||
srcTrie, _ := trie.New(trie.StateTrieID(srcRoot), srcDb.TrieDB())
|
||||
|
||||
// Create a destination state and sync with the scheduler
|
||||
dstDb := rawdb.NewMemoryDatabase()
|
||||
sched := NewStateSync(srcRoot, dstDb, nil)
|
||||
sched := NewStateSync(srcRoot, dstDb, nil, srcDb.TrieDB().Scheme())
|
||||
|
||||
var (
|
||||
nodeElements []stateElement
|
||||
@@ -222,7 +224,8 @@ func testIterativeStateSync(t *testing.T, count int, commit bool, bypath bool) {
|
||||
if err := rlp.DecodeBytes(srcTrie.Get(node.syncPath[0]), &acc); err != nil {
|
||||
t.Fatalf("failed to decode account on path %x: %v", node.syncPath[0], err)
|
||||
}
|
||||
stTrie, err := trie.New(common.BytesToHash(node.syncPath[0]), acc.Root, srcDb.TrieDB())
|
||||
id := trie.StorageTrieID(srcRoot, common.BytesToHash(node.syncPath[0]), acc.Root)
|
||||
stTrie, err := trie.New(id, srcDb.TrieDB())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to retriev storage trie for path %x: %v", node.syncPath[1], err)
|
||||
}
|
||||
@@ -280,11 +283,11 @@ func testIterativeStateSync(t *testing.T, count int, commit bool, bypath bool) {
|
||||
// partial results are returned, and the others sent only later.
|
||||
func TestIterativeDelayedStateSync(t *testing.T) {
|
||||
// Create a random state to copy
|
||||
srcDb, srcRoot, srcAccounts := makeTestState()
|
||||
_, srcDb, srcRoot, srcAccounts := makeTestState()
|
||||
|
||||
// Create a destination state and sync with the scheduler
|
||||
dstDb := rawdb.NewMemoryDatabase()
|
||||
sched := NewStateSync(srcRoot, dstDb, nil)
|
||||
sched := NewStateSync(srcRoot, dstDb, nil, srcDb.TrieDB().Scheme())
|
||||
|
||||
var (
|
||||
nodeElements []stateElement
|
||||
@@ -373,11 +376,11 @@ func TestIterativeRandomStateSyncBatched(t *testing.T) { testIterativeRandomS
|
||||
|
||||
func testIterativeRandomStateSync(t *testing.T, count int) {
|
||||
// Create a random state to copy
|
||||
srcDb, srcRoot, srcAccounts := makeTestState()
|
||||
_, srcDb, srcRoot, srcAccounts := makeTestState()
|
||||
|
||||
// Create a destination state and sync with the scheduler
|
||||
dstDb := rawdb.NewMemoryDatabase()
|
||||
sched := NewStateSync(srcRoot, dstDb, nil)
|
||||
sched := NewStateSync(srcRoot, dstDb, nil, srcDb.TrieDB().Scheme())
|
||||
|
||||
nodeQueue := make(map[string]stateElement)
|
||||
codeQueue := make(map[common.Hash]struct{})
|
||||
@@ -453,11 +456,11 @@ func testIterativeRandomStateSync(t *testing.T, count int) {
|
||||
// partial results are returned (Even those randomly), others sent only later.
|
||||
func TestIterativeRandomDelayedStateSync(t *testing.T) {
|
||||
// Create a random state to copy
|
||||
srcDb, srcRoot, srcAccounts := makeTestState()
|
||||
_, srcDb, srcRoot, srcAccounts := makeTestState()
|
||||
|
||||
// Create a destination state and sync with the scheduler
|
||||
dstDb := rawdb.NewMemoryDatabase()
|
||||
sched := NewStateSync(srcRoot, dstDb, nil)
|
||||
sched := NewStateSync(srcRoot, dstDb, nil, srcDb.TrieDB().Scheme())
|
||||
|
||||
nodeQueue := make(map[string]stateElement)
|
||||
codeQueue := make(map[common.Hash]struct{})
|
||||
@@ -543,7 +546,7 @@ func TestIterativeRandomDelayedStateSync(t *testing.T) {
|
||||
// the database.
|
||||
func TestIncompleteStateSync(t *testing.T) {
|
||||
// Create a random state to copy
|
||||
srcDb, srcRoot, srcAccounts := makeTestState()
|
||||
db, srcDb, srcRoot, srcAccounts := makeTestState()
|
||||
|
||||
// isCodeLookup to save some hashing
|
||||
var isCode = make(map[common.Hash]struct{})
|
||||
@@ -553,15 +556,16 @@ func TestIncompleteStateSync(t *testing.T) {
|
||||
}
|
||||
}
|
||||
isCode[common.BytesToHash(emptyCodeHash)] = struct{}{}
|
||||
checkTrieConsistency(srcDb.TrieDB().DiskDB().(ethdb.Database), srcRoot)
|
||||
checkTrieConsistency(db, srcRoot)
|
||||
|
||||
// Create a destination state and sync with the scheduler
|
||||
dstDb := rawdb.NewMemoryDatabase()
|
||||
sched := NewStateSync(srcRoot, dstDb, nil)
|
||||
sched := NewStateSync(srcRoot, dstDb, nil, srcDb.TrieDB().Scheme())
|
||||
|
||||
var (
|
||||
addedCodes []common.Hash
|
||||
addedNodes []common.Hash
|
||||
addedCodes []common.Hash
|
||||
addedPaths []string
|
||||
addedHashes []common.Hash
|
||||
)
|
||||
nodeQueue := make(map[string]stateElement)
|
||||
codeQueue := make(map[common.Hash]struct{})
|
||||
@@ -598,15 +602,16 @@ func TestIncompleteStateSync(t *testing.T) {
|
||||
var nodehashes []common.Hash
|
||||
if len(nodeQueue) > 0 {
|
||||
results := make([]trie.NodeSyncResult, 0, len(nodeQueue))
|
||||
for key, element := range nodeQueue {
|
||||
for path, element := range nodeQueue {
|
||||
data, err := srcDb.TrieDB().Node(element.hash)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to retrieve node data for %x", element.hash)
|
||||
}
|
||||
results = append(results, trie.NodeSyncResult{Path: key, Data: data})
|
||||
results = append(results, trie.NodeSyncResult{Path: path, Data: data})
|
||||
|
||||
if element.hash != srcRoot {
|
||||
addedNodes = append(addedNodes, element.hash)
|
||||
addedPaths = append(addedPaths, element.path)
|
||||
addedHashes = append(addedHashes, element.hash)
|
||||
}
|
||||
nodehashes = append(nodehashes, element.hash)
|
||||
}
|
||||
@@ -654,12 +659,18 @@ func TestIncompleteStateSync(t *testing.T) {
|
||||
}
|
||||
rawdb.WriteCode(dstDb, node, val)
|
||||
}
|
||||
for _, node := range addedNodes {
|
||||
val := rawdb.ReadTrieNode(dstDb, node)
|
||||
rawdb.DeleteTrieNode(dstDb, node)
|
||||
if err := checkStateConsistency(dstDb, srcRoot); err == nil {
|
||||
t.Errorf("trie inconsistency not caught, missing: %v", node.Hex())
|
||||
scheme := srcDb.TrieDB().Scheme()
|
||||
for i, path := range addedPaths {
|
||||
owner, inner := trie.ResolvePath([]byte(path))
|
||||
hash := addedHashes[i]
|
||||
val := rawdb.ReadTrieNode(dstDb, owner, inner, hash, scheme)
|
||||
if val == nil {
|
||||
t.Error("missing trie node")
|
||||
}
|
||||
rawdb.WriteTrieNode(dstDb, node, val)
|
||||
rawdb.DeleteTrieNode(dstDb, owner, inner, hash, scheme)
|
||||
if err := checkStateConsistency(dstDb, srcRoot); err == nil {
|
||||
t.Errorf("trie inconsistency not caught, missing: %v", path)
|
||||
}
|
||||
rawdb.WriteTrieNode(dstDb, owner, inner, hash, val, scheme)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
// Copyright 2022 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 state
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
// transientStorage is a representation of EIP-1153 "Transient Storage".
|
||||
type transientStorage map[common.Address]Storage
|
||||
|
||||
// newTransientStorage creates a new instance of a transientStorage.
|
||||
func newTransientStorage() transientStorage {
|
||||
return make(transientStorage)
|
||||
}
|
||||
|
||||
// Set sets the transient-storage `value` for `key` at the given `addr`.
|
||||
func (t transientStorage) Set(addr common.Address, key, value common.Hash) {
|
||||
if _, ok := t[addr]; !ok {
|
||||
t[addr] = make(Storage)
|
||||
}
|
||||
t[addr][key] = value
|
||||
}
|
||||
|
||||
// Get gets the transient storage for `key` at the given `addr`.
|
||||
func (t transientStorage) Get(addr common.Address, key common.Hash) common.Hash {
|
||||
val, ok := t[addr]
|
||||
if !ok {
|
||||
return common.Hash{}
|
||||
}
|
||||
return val[key]
|
||||
}
|
||||
|
||||
// Copy does a deep copy of the transientStorage
|
||||
func (t transientStorage) Copy() transientStorage {
|
||||
storage := make(transientStorage)
|
||||
for key, value := range t {
|
||||
storage[key] = value.Copy()
|
||||
}
|
||||
return storage
|
||||
}
|
||||
@@ -126,6 +126,9 @@ func (p *triePrefetcher) copy() *triePrefetcher {
|
||||
// If the prefetcher is already a copy, duplicate the data
|
||||
if p.fetches != nil {
|
||||
for root, fetch := range p.fetches {
|
||||
if fetch == nil {
|
||||
continue
|
||||
}
|
||||
copy.fetches[root] = p.db.CopyTrie(fetch)
|
||||
}
|
||||
return copy
|
||||
@@ -147,7 +150,7 @@ func (p *triePrefetcher) prefetch(owner common.Hash, root common.Hash, keys [][]
|
||||
id := p.trieID(owner, root)
|
||||
fetcher := p.fetchers[id]
|
||||
if fetcher == nil {
|
||||
fetcher = newSubfetcher(p.db, owner, root)
|
||||
fetcher = newSubfetcher(p.db, p.root, owner, root)
|
||||
p.fetchers[id] = fetcher
|
||||
}
|
||||
fetcher.schedule(keys)
|
||||
@@ -203,6 +206,7 @@ func (p *triePrefetcher) trieID(owner common.Hash, root common.Hash) string {
|
||||
// the trie being worked on is retrieved from the prefetcher.
|
||||
type subfetcher struct {
|
||||
db Database // Database to load trie nodes through
|
||||
state common.Hash // Root hash of the state to prefetch
|
||||
owner common.Hash // Owner of the trie, usually account hash
|
||||
root common.Hash // Root hash of the trie to prefetch
|
||||
trie Trie // Trie being populated with nodes
|
||||
@@ -222,9 +226,10 @@ type subfetcher struct {
|
||||
|
||||
// newSubfetcher creates a goroutine to prefetch state items belonging to a
|
||||
// particular root hash.
|
||||
func newSubfetcher(db Database, owner common.Hash, root common.Hash) *subfetcher {
|
||||
func newSubfetcher(db Database, state common.Hash, owner common.Hash, root common.Hash) *subfetcher {
|
||||
sf := &subfetcher{
|
||||
db: db,
|
||||
state: state,
|
||||
owner: owner,
|
||||
root: root,
|
||||
wake: make(chan struct{}, 1),
|
||||
@@ -295,7 +300,7 @@ func (sf *subfetcher) loop() {
|
||||
}
|
||||
sf.trie = trie
|
||||
} else {
|
||||
trie, err := sf.db.OpenStorageTrie(sf.owner, sf.root)
|
||||
trie, err := sf.db.OpenStorageTrie(sf.state, sf.owner, sf.root)
|
||||
if err != nil {
|
||||
log.Warn("Trie prefetcher failed opening trie", "root", sf.root, "err", err)
|
||||
return
|
||||
@@ -331,11 +336,7 @@ func (sf *subfetcher) loop() {
|
||||
if _, ok := sf.seen[string(task)]; ok {
|
||||
sf.dups++
|
||||
} else {
|
||||
if len(task) == len(common.Address{}) {
|
||||
sf.trie.TryGetAccount(task)
|
||||
} else {
|
||||
sf.trie.TryGet(task)
|
||||
}
|
||||
sf.trie.TryGet(task)
|
||||
sf.seen[string(task)] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c
|
||||
if err != nil {
|
||||
return // Also invalid block, bail out
|
||||
}
|
||||
statedb.Prepare(tx.Hash(), i)
|
||||
statedb.SetTxContext(tx.Hash(), i)
|
||||
if err := precacheTransaction(msg, p.config, gaspool, statedb, header, evm); err != nil {
|
||||
return // Ugh, something went horribly wrong, bail out
|
||||
}
|
||||
|
||||
@@ -102,6 +102,11 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
|
||||
receipts = append(receipts, receipt)
|
||||
allLogs = append(allLogs, receipt.Logs...)
|
||||
}
|
||||
// Fail if Shanghai not enabled and len(withdrawals) is non-zero.
|
||||
withdrawals := block.Withdrawals()
|
||||
if len(withdrawals) > 0 && !p.config.IsShanghai(block.Time()) {
|
||||
return nil, nil, 0, fmt.Errorf("withdrawals before shanghai")
|
||||
}
|
||||
// Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
|
||||
p.engine.Finalize(p.bc, header, statedb, block.Transactions(), block.Uncles())
|
||||
pluginPostProcessBlock(block)
|
||||
@@ -110,7 +115,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
|
||||
return receipts, allLogs, *usedGas, nil
|
||||
}
|
||||
|
||||
func applyTransaction(msg types.Message, config *params.ChainConfig, author *common.Address, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (*types.Receipt, error) {
|
||||
func applyTransaction(msg types.Message, config *params.ChainConfig, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (*types.Receipt, error) {
|
||||
// Create a new context to be used in the EVM environment.
|
||||
txContext := NewEVMTxContext(msg)
|
||||
evm.Reset(txContext, statedb)
|
||||
@@ -147,7 +152,7 @@ func applyTransaction(msg types.Message, config *params.ChainConfig, author *com
|
||||
}
|
||||
|
||||
// Set the receipt logs and create the bloom filter.
|
||||
receipt.Logs = statedb.GetLogs(tx.Hash(), blockHash)
|
||||
receipt.Logs = statedb.GetLogs(tx.Hash(), blockNumber.Uint64(), blockHash)
|
||||
receipt.Bloom = types.CreateBloom(types.Receipts{receipt})
|
||||
receipt.BlockHash = blockHash
|
||||
receipt.BlockNumber = blockNumber
|
||||
@@ -167,5 +172,5 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo
|
||||
// Create a new context to be used in the EVM environment
|
||||
blockContext := NewEVMBlockContext(header, bc, author)
|
||||
vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, config, cfg)
|
||||
return applyTransaction(msg, config, author, gp, statedb, header.Number, header.Hash(), tx, usedGas, vmenv)
|
||||
return applyTransaction(msg, config, gp, statedb, header.Number, header.Hash(), tx, usedGas, vmenv)
|
||||
}
|
||||
|
||||
+106
-18
@@ -24,6 +24,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/consensus/beacon"
|
||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||
"github.com/ethereum/go-ethereum/consensus/misc"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
@@ -35,6 +36,8 @@ import (
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
func u64(val uint64) *uint64 { return &val }
|
||||
|
||||
// TestStateProcessorErrors tests the output from the 'core' errors
|
||||
// as defined in core/error.go. These errors are generated when the
|
||||
// blockchain imports bad blocks, meaning blocks which have valid headers but
|
||||
@@ -75,6 +78,17 @@ func TestStateProcessorErrors(t *testing.T) {
|
||||
}), signer, key1)
|
||||
return tx
|
||||
}
|
||||
var mkDynamicCreationTx = func(nonce uint64, gasLimit uint64, gasTipCap, gasFeeCap *big.Int, data []byte) *types.Transaction {
|
||||
tx, _ := types.SignTx(types.NewTx(&types.DynamicFeeTx{
|
||||
Nonce: nonce,
|
||||
GasTipCap: gasTipCap,
|
||||
GasFeeCap: gasFeeCap,
|
||||
Gas: gasLimit,
|
||||
Value: big.NewInt(0),
|
||||
Data: data,
|
||||
}), signer, key1)
|
||||
return tx
|
||||
}
|
||||
{ // Tests against a 'recent' chain definition
|
||||
var (
|
||||
db = rawdb.NewMemoryDatabase()
|
||||
@@ -91,8 +105,7 @@ func TestStateProcessorErrors(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
genesis = gspec.MustCommit(db)
|
||||
blockchain, _ = NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil)
|
||||
blockchain, _ = NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil)
|
||||
)
|
||||
defer blockchain.Stop()
|
||||
bigNumber := new(big.Int).SetBytes(common.FromHex("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))
|
||||
@@ -197,7 +210,7 @@ func TestStateProcessorErrors(t *testing.T) {
|
||||
want: "could not apply tx 0 [0xd82a0c2519acfeac9a948258c47e784acd20651d9d80f9a1c67b4137651c3a24]: insufficient funds for gas * price + value: address 0x71562b71999873DB5b286dF957af199Ec94617F7 have 1000000000000000000 want 2431633873983640103894990685182446064918669677978451844828609264166175722438635000",
|
||||
},
|
||||
} {
|
||||
block := GenerateBadBlock(genesis, ethash.NewFaker(), tt.txs, gspec.Config)
|
||||
block := GenerateBadBlock(gspec.ToBlock(), ethash.NewFaker(), tt.txs, gspec.Config)
|
||||
_, err := blockchain.InsertChain(types.Blocks{block})
|
||||
if err == nil {
|
||||
t.Fatal("block imported without errors")
|
||||
@@ -232,8 +245,7 @@ func TestStateProcessorErrors(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
genesis = gspec.MustCommit(db)
|
||||
blockchain, _ = NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil)
|
||||
blockchain, _ = NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil)
|
||||
)
|
||||
defer blockchain.Stop()
|
||||
for i, tt := range []struct {
|
||||
@@ -247,7 +259,7 @@ func TestStateProcessorErrors(t *testing.T) {
|
||||
want: "could not apply tx 0 [0x88626ac0d53cb65308f2416103c62bb1f18b805573d4f96a3640bbbfff13c14f]: transaction type not supported",
|
||||
},
|
||||
} {
|
||||
block := GenerateBadBlock(genesis, ethash.NewFaker(), tt.txs, gspec.Config)
|
||||
block := GenerateBadBlock(gspec.ToBlock(), ethash.NewFaker(), tt.txs, gspec.Config)
|
||||
_, err := blockchain.InsertChain(types.Blocks{block})
|
||||
if err == nil {
|
||||
t.Fatal("block imported without errors")
|
||||
@@ -272,8 +284,7 @@ func TestStateProcessorErrors(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
genesis = gspec.MustCommit(db)
|
||||
blockchain, _ = NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil)
|
||||
blockchain, _ = NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil)
|
||||
)
|
||||
defer blockchain.Stop()
|
||||
for i, tt := range []struct {
|
||||
@@ -287,7 +298,73 @@ func TestStateProcessorErrors(t *testing.T) {
|
||||
want: "could not apply tx 0 [0x88626ac0d53cb65308f2416103c62bb1f18b805573d4f96a3640bbbfff13c14f]: sender not an eoa: address 0x71562b71999873DB5b286dF957af199Ec94617F7, codehash: 0x9280914443471259d4570a8661015ae4a5b80186dbc619658fb494bebc3da3d1",
|
||||
},
|
||||
} {
|
||||
block := GenerateBadBlock(genesis, ethash.NewFaker(), tt.txs, gspec.Config)
|
||||
block := GenerateBadBlock(gspec.ToBlock(), ethash.NewFaker(), tt.txs, gspec.Config)
|
||||
_, err := blockchain.InsertChain(types.Blocks{block})
|
||||
if err == nil {
|
||||
t.Fatal("block imported without errors")
|
||||
}
|
||||
if have, want := err.Error(), tt.want; have != want {
|
||||
t.Errorf("test %d:\nhave \"%v\"\nwant \"%v\"\n", i, have, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ErrMaxInitCodeSizeExceeded, for this we need extra Shanghai (EIP-3860) enabled.
|
||||
{
|
||||
var (
|
||||
db = rawdb.NewMemoryDatabase()
|
||||
gspec = &Genesis{
|
||||
Config: ¶ms.ChainConfig{
|
||||
ChainID: big.NewInt(1),
|
||||
HomesteadBlock: big.NewInt(0),
|
||||
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),
|
||||
MergeNetsplitBlock: big.NewInt(0),
|
||||
TerminalTotalDifficulty: big.NewInt(0),
|
||||
TerminalTotalDifficultyPassed: true,
|
||||
ShanghaiTime: u64(0),
|
||||
},
|
||||
Alloc: GenesisAlloc{
|
||||
common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7"): GenesisAccount{
|
||||
Balance: big.NewInt(1000000000000000000), // 1 ether
|
||||
Nonce: 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
genesis = gspec.MustCommit(db)
|
||||
blockchain, _ = NewBlockChain(db, nil, gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{}, nil, nil)
|
||||
tooBigInitCode = [params.MaxInitCodeSize + 1]byte{}
|
||||
smallInitCode = [320]byte{}
|
||||
)
|
||||
defer blockchain.Stop()
|
||||
for i, tt := range []struct {
|
||||
txs []*types.Transaction
|
||||
want string
|
||||
}{
|
||||
{ // ErrMaxInitCodeSizeExceeded
|
||||
txs: []*types.Transaction{
|
||||
mkDynamicCreationTx(0, 500000, common.Big0, misc.CalcBaseFee(config, genesis.Header()), tooBigInitCode[:]),
|
||||
},
|
||||
want: "could not apply tx 0 [0x832b54a6c3359474a9f504b1003b2cc1b6fcaa18e4ef369eb45b5d40dad6378f]: max initcode size exceeded: code size 49153 limit 49152",
|
||||
},
|
||||
{ // ErrIntrinsicGas: Not enough gas to cover init code
|
||||
txs: []*types.Transaction{
|
||||
mkDynamicCreationTx(0, 54299, common.Big0, misc.CalcBaseFee(config, genesis.Header()), smallInitCode[:]),
|
||||
},
|
||||
want: "could not apply tx 0 [0x39b7436cb432d3662a25626474282c5c4c1a213326fd87e4e18a91477bae98b2]: intrinsic gas too low: have 54299, want 54300",
|
||||
},
|
||||
} {
|
||||
block := GenerateBadBlock(genesis, beacon.New(ethash.NewFaker()), tt.txs, gspec.Config)
|
||||
_, err := blockchain.InsertChain(types.Blocks{block})
|
||||
if err == nil {
|
||||
t.Fatal("block imported without errors")
|
||||
@@ -304,23 +381,31 @@ func TestStateProcessorErrors(t *testing.T) {
|
||||
// valid to be considered for import:
|
||||
// - valid pow (fake), ancestry, difficulty, gaslimit etc
|
||||
func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Transactions, config *params.ChainConfig) *types.Block {
|
||||
header := &types.Header{
|
||||
ParentHash: parent.Hash(),
|
||||
Coinbase: parent.Coinbase(),
|
||||
Difficulty: engine.CalcDifficulty(&fakeChainReader{config}, parent.Time()+10, &types.Header{
|
||||
difficulty := big.NewInt(0)
|
||||
if !config.TerminalTotalDifficultyPassed {
|
||||
difficulty = engine.CalcDifficulty(&fakeChainReader{config}, parent.Time()+10, &types.Header{
|
||||
Number: parent.Number(),
|
||||
Time: parent.Time(),
|
||||
Difficulty: parent.Difficulty(),
|
||||
UncleHash: parent.UncleHash(),
|
||||
}),
|
||||
GasLimit: parent.GasLimit(),
|
||||
Number: new(big.Int).Add(parent.Number(), common.Big1),
|
||||
Time: parent.Time() + 10,
|
||||
UncleHash: types.EmptyUncleHash,
|
||||
})
|
||||
}
|
||||
|
||||
header := &types.Header{
|
||||
ParentHash: parent.Hash(),
|
||||
Coinbase: parent.Coinbase(),
|
||||
Difficulty: difficulty,
|
||||
GasLimit: parent.GasLimit(),
|
||||
Number: new(big.Int).Add(parent.Number(), common.Big1),
|
||||
Time: parent.Time() + 10,
|
||||
UncleHash: types.EmptyUncleHash,
|
||||
}
|
||||
if config.IsLondon(header.Number) {
|
||||
header.BaseFee = misc.CalcBaseFee(config, parent.Header())
|
||||
}
|
||||
if config.IsShanghai(header.Time) {
|
||||
header.WithdrawalsHash = &types.EmptyRootHash
|
||||
}
|
||||
var receipts []*types.Receipt
|
||||
// The post-state result doesn't need to be correct (this is a bad block), but we do need something there
|
||||
// Preferably something unique. So let's use a combo of blocknum + txhash
|
||||
@@ -338,5 +423,8 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr
|
||||
}
|
||||
header.Root = common.BytesToHash(hasher.Sum(nil))
|
||||
// Assemble and return the final block for sealing
|
||||
if config.IsShanghai(header.Time) {
|
||||
return types.NewBlockWithWithdrawals(header, txs, nil, receipts, []*types.Withdrawal{}, trie.NewStackTrie(nil))
|
||||
}
|
||||
return types.NewBlock(header, txs, nil, receipts, trie.NewStackTrie(nil))
|
||||
}
|
||||
|
||||
+58
-32
@@ -31,23 +31,28 @@ import (
|
||||
|
||||
var emptyCodeHash = crypto.Keccak256Hash(nil)
|
||||
|
||||
/*
|
||||
The State Transitioning Model
|
||||
|
||||
A state transition is a change made when a transaction is applied to the current world state
|
||||
The state transitioning model does all the necessary work to work out a valid new state root.
|
||||
|
||||
1) Nonce handling
|
||||
2) Pre pay gas
|
||||
3) Create a new state object if the recipient is \0*32
|
||||
4) Value transfer
|
||||
== If contract creation ==
|
||||
4a) Attempt to run transaction data
|
||||
4b) If valid, use result as code for the new state object
|
||||
== end ==
|
||||
5) Run Script section
|
||||
6) Derive new state root
|
||||
*/
|
||||
// StateTransition represents a state transition.
|
||||
//
|
||||
// == The State Transitioning Model
|
||||
//
|
||||
// A state transition is a change made when a transaction is applied to the current world
|
||||
// state. The state transitioning model does all the necessary work to work out a valid new
|
||||
// state root.
|
||||
//
|
||||
// 1. Nonce handling
|
||||
// 2. Pre pay gas
|
||||
// 3. Create a new state object if the recipient is nil
|
||||
// 4. Value transfer
|
||||
//
|
||||
// == If contract creation ==
|
||||
//
|
||||
// 4a. Attempt to run transaction data
|
||||
// 4b. If valid, use result as code for the new state object
|
||||
//
|
||||
// == end ==
|
||||
//
|
||||
// 5. Run Script section
|
||||
// 6. Derive new state root
|
||||
type StateTransition struct {
|
||||
gp *GasPool
|
||||
msg Message
|
||||
@@ -115,7 +120,7 @@ func (result *ExecutionResult) Revert() []byte {
|
||||
}
|
||||
|
||||
// IntrinsicGas computes the 'intrinsic gas' for a message with the given data.
|
||||
func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation bool, isHomestead, isEIP2028 bool) (uint64, error) {
|
||||
func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation bool, isHomestead, isEIP2028 bool, isEIP3860 bool) (uint64, error) {
|
||||
// Set the starting gas for the raw transaction
|
||||
var gas uint64
|
||||
if isContractCreation && isHomestead {
|
||||
@@ -123,8 +128,9 @@ func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation b
|
||||
} else {
|
||||
gas = params.TxGas
|
||||
}
|
||||
dataLen := uint64(len(data))
|
||||
// Bump the required gas by the amount of transactional data
|
||||
if len(data) > 0 {
|
||||
if dataLen > 0 {
|
||||
// Zero and non-zero bytes are priced differently
|
||||
var nz uint64
|
||||
for _, byt := range data {
|
||||
@@ -142,11 +148,19 @@ func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation b
|
||||
}
|
||||
gas += nz * nonZeroGas
|
||||
|
||||
z := uint64(len(data)) - nz
|
||||
z := dataLen - nz
|
||||
if (math.MaxUint64-gas)/params.TxDataZeroGas < z {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
gas += z * params.TxDataZeroGas
|
||||
|
||||
if isContractCreation && isEIP3860 {
|
||||
lenWords := toWordSize(dataLen)
|
||||
if (math.MaxUint64-gas)/params.InitCodeWordGas < lenWords {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
gas += lenWords * params.InitCodeWordGas
|
||||
}
|
||||
}
|
||||
if accessList != nil {
|
||||
gas += uint64(len(accessList)) * params.TxAccessListAddressGas
|
||||
@@ -155,6 +169,15 @@ func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation b
|
||||
return gas, nil
|
||||
}
|
||||
|
||||
// toWordSize returns the ceiled word size required for init code payment calculation.
|
||||
func toWordSize(size uint64) uint64 {
|
||||
if size > math.MaxUint64-31 {
|
||||
return math.MaxUint64/32 + 1
|
||||
}
|
||||
|
||||
return (size + 31) / 32
|
||||
}
|
||||
|
||||
// NewStateTransition initialises and returns a new state transition object.
|
||||
func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition {
|
||||
return &StateTransition{
|
||||
@@ -262,13 +285,10 @@ func (st *StateTransition) preCheck() error {
|
||||
// TransitionDb will transition the state by applying the current message and
|
||||
// returning the evm execution result with following fields.
|
||||
//
|
||||
// - used gas:
|
||||
// total gas used (including gas being refunded)
|
||||
// - returndata:
|
||||
// the returned data from evm
|
||||
// - concrete execution error:
|
||||
// various **EVM** error which aborts the execution,
|
||||
// e.g. ErrOutOfGas, ErrExecutionReverted
|
||||
// - used gas: total gas used (including gas being refunded)
|
||||
// - returndata: the returned data from evm
|
||||
// - concrete execution error: various EVM errors which abort the execution, e.g.
|
||||
// ErrOutOfGas, ErrExecutionReverted
|
||||
//
|
||||
// However if any consensus issue encountered, return the error directly with
|
||||
// nil evm execution result.
|
||||
@@ -301,12 +321,12 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
|
||||
var (
|
||||
msg = st.msg
|
||||
sender = vm.AccountRef(msg.From())
|
||||
rules = st.evm.ChainConfig().Rules(st.evm.Context.BlockNumber, st.evm.Context.Random != nil)
|
||||
rules = st.evm.ChainConfig().Rules(st.evm.Context.BlockNumber, st.evm.Context.Random != nil, st.evm.Context.Time)
|
||||
contractCreation = msg.To() == nil
|
||||
)
|
||||
|
||||
// Check clauses 4-5, subtract intrinsic gas if everything is correct
|
||||
gas, err := IntrinsicGas(st.data, st.msg.AccessList(), contractCreation, rules.IsHomestead, rules.IsIstanbul)
|
||||
gas, err := IntrinsicGas(st.data, st.msg.AccessList(), contractCreation, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -320,10 +340,16 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
|
||||
return nil, fmt.Errorf("%w: address %v", ErrInsufficientFundsForTransfer, msg.From().Hex())
|
||||
}
|
||||
|
||||
// Set up the initial access list.
|
||||
if rules.IsBerlin {
|
||||
st.state.PrepareAccessList(msg.From(), msg.To(), vm.ActivePrecompiles(rules), msg.AccessList())
|
||||
// Check whether the init code size has been exceeded.
|
||||
if rules.IsShanghai && contractCreation && len(st.data) > params.MaxInitCodeSize {
|
||||
return nil, fmt.Errorf("%w: code size %v limit %v", ErrMaxInitCodeSizeExceeded, len(st.data), params.MaxInitCodeSize)
|
||||
}
|
||||
|
||||
// Execute the preparatory steps for state transition which includes:
|
||||
// - prepare accessList(post-berlin)
|
||||
// - reset transient storage(eip 1153)
|
||||
st.state.Prepare(rules, msg.From(), st.evm.Context.Coinbase, msg.To(), vm.ActivePrecompiles(rules), msg.AccessList())
|
||||
|
||||
var (
|
||||
ret []byte
|
||||
vmerr error // vm errors do not effect consensus and are therefore not assigned to err
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
// 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 core
|
||||
package txpool
|
||||
|
||||
import (
|
||||
"errors"
|
||||
@@ -41,23 +41,23 @@ type devNull struct{}
|
||||
func (*devNull) Write(p []byte) (n int, err error) { return len(p), nil }
|
||||
func (*devNull) Close() error { return nil }
|
||||
|
||||
// txJournal is a rotating log of transactions with the aim of storing locally
|
||||
// journal is a rotating log of transactions with the aim of storing locally
|
||||
// created transactions to allow non-executed ones to survive node restarts.
|
||||
type txJournal struct {
|
||||
type journal struct {
|
||||
path string // Filesystem path to store the transactions at
|
||||
writer io.WriteCloser // Output stream to write new transactions into
|
||||
}
|
||||
|
||||
// newTxJournal creates a new transaction journal to
|
||||
func newTxJournal(path string) *txJournal {
|
||||
return &txJournal{
|
||||
func newTxJournal(path string) *journal {
|
||||
return &journal{
|
||||
path: path,
|
||||
}
|
||||
}
|
||||
|
||||
// load parses a transaction journal dump from disk, loading its contents into
|
||||
// the specified pool.
|
||||
func (journal *txJournal) load(add func([]*types.Transaction) []error) error {
|
||||
func (journal *journal) load(add func([]*types.Transaction) []error) error {
|
||||
// Open the journal for loading any past transactions
|
||||
input, err := os.Open(journal.path)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
@@ -118,7 +118,7 @@ func (journal *txJournal) load(add func([]*types.Transaction) []error) error {
|
||||
}
|
||||
|
||||
// insert adds the specified transaction to the local disk journal.
|
||||
func (journal *txJournal) insert(tx *types.Transaction) error {
|
||||
func (journal *journal) insert(tx *types.Transaction) error {
|
||||
if journal.writer == nil {
|
||||
return errNoActiveJournal
|
||||
}
|
||||
@@ -130,7 +130,7 @@ func (journal *txJournal) insert(tx *types.Transaction) error {
|
||||
|
||||
// rotate regenerates the transaction journal based on the current contents of
|
||||
// the transaction pool.
|
||||
func (journal *txJournal) rotate(all map[common.Address]types.Transactions) error {
|
||||
func (journal *journal) rotate(all map[common.Address]types.Transactions) error {
|
||||
// Close the current journal (if any is open)
|
||||
if journal.writer != nil {
|
||||
if err := journal.writer.Close(); err != nil {
|
||||
@@ -170,7 +170,7 @@ func (journal *txJournal) rotate(all map[common.Address]types.Transactions) erro
|
||||
}
|
||||
|
||||
// close flushes the transaction journal contents to disk and closes the file.
|
||||
func (journal *txJournal) close() error {
|
||||
func (journal *journal) close() error {
|
||||
var err error
|
||||
|
||||
if journal.writer != nil {
|
||||
@@ -14,7 +14,7 @@
|
||||
// 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 core
|
||||
package txpool
|
||||
|
||||
import (
|
||||
"container/heap"
|
||||
@@ -45,34 +45,35 @@ func (h *nonceHeap) Pop() interface{} {
|
||||
old := *h
|
||||
n := len(old)
|
||||
x := old[n-1]
|
||||
old[n-1] = 0
|
||||
*h = old[0 : n-1]
|
||||
return x
|
||||
}
|
||||
|
||||
// txSortedMap is a nonce->transaction hash map with a heap based index to allow
|
||||
// sortedMap is a nonce->transaction hash map with a heap based index to allow
|
||||
// iterating over the contents in a nonce-incrementing way.
|
||||
type txSortedMap struct {
|
||||
type sortedMap struct {
|
||||
items map[uint64]*types.Transaction // Hash map storing the transaction data
|
||||
index *nonceHeap // Heap of nonces of all the stored transactions (non-strict mode)
|
||||
cache types.Transactions // Cache of the transactions already sorted
|
||||
}
|
||||
|
||||
// newTxSortedMap creates a new nonce-sorted transaction map.
|
||||
func newTxSortedMap() *txSortedMap {
|
||||
return &txSortedMap{
|
||||
// newSortedMap creates a new nonce-sorted transaction map.
|
||||
func newSortedMap() *sortedMap {
|
||||
return &sortedMap{
|
||||
items: make(map[uint64]*types.Transaction),
|
||||
index: new(nonceHeap),
|
||||
}
|
||||
}
|
||||
|
||||
// Get retrieves the current transactions associated with the given nonce.
|
||||
func (m *txSortedMap) Get(nonce uint64) *types.Transaction {
|
||||
func (m *sortedMap) Get(nonce uint64) *types.Transaction {
|
||||
return m.items[nonce]
|
||||
}
|
||||
|
||||
// Put inserts a new transaction into the map, also updating the map's nonce
|
||||
// index. If a transaction already exists with the same nonce, it's overwritten.
|
||||
func (m *txSortedMap) Put(tx *types.Transaction) {
|
||||
func (m *sortedMap) Put(tx *types.Transaction) {
|
||||
nonce := tx.Nonce()
|
||||
if m.items[nonce] == nil {
|
||||
heap.Push(m.index, nonce)
|
||||
@@ -83,7 +84,7 @@ func (m *txSortedMap) Put(tx *types.Transaction) {
|
||||
// Forward removes all transactions from the map with a nonce lower than the
|
||||
// provided threshold. Every removed transaction is returned for any post-removal
|
||||
// maintenance.
|
||||
func (m *txSortedMap) Forward(threshold uint64) types.Transactions {
|
||||
func (m *sortedMap) Forward(threshold uint64) types.Transactions {
|
||||
var removed types.Transactions
|
||||
|
||||
// Pop off heap items until the threshold is reached
|
||||
@@ -104,7 +105,7 @@ func (m *txSortedMap) Forward(threshold uint64) types.Transactions {
|
||||
// Filter, as opposed to 'filter', re-initialises the heap after the operation is done.
|
||||
// If you want to do several consecutive filterings, it's therefore better to first
|
||||
// do a .filter(func1) followed by .Filter(func2) or reheap()
|
||||
func (m *txSortedMap) Filter(filter func(*types.Transaction) bool) types.Transactions {
|
||||
func (m *sortedMap) Filter(filter func(*types.Transaction) bool) types.Transactions {
|
||||
removed := m.filter(filter)
|
||||
// If transactions were removed, the heap and cache are ruined
|
||||
if len(removed) > 0 {
|
||||
@@ -113,7 +114,7 @@ func (m *txSortedMap) Filter(filter func(*types.Transaction) bool) types.Transac
|
||||
return removed
|
||||
}
|
||||
|
||||
func (m *txSortedMap) reheap() {
|
||||
func (m *sortedMap) reheap() {
|
||||
*m.index = make([]uint64, 0, len(m.items))
|
||||
for nonce := range m.items {
|
||||
*m.index = append(*m.index, nonce)
|
||||
@@ -124,7 +125,7 @@ func (m *txSortedMap) reheap() {
|
||||
|
||||
// filter is identical to Filter, but **does not** regenerate the heap. This method
|
||||
// should only be used if followed immediately by a call to Filter or reheap()
|
||||
func (m *txSortedMap) filter(filter func(*types.Transaction) bool) types.Transactions {
|
||||
func (m *sortedMap) filter(filter func(*types.Transaction) bool) types.Transactions {
|
||||
var removed types.Transactions
|
||||
|
||||
// Collect all the transactions to filter out
|
||||
@@ -142,7 +143,7 @@ func (m *txSortedMap) filter(filter func(*types.Transaction) bool) types.Transac
|
||||
|
||||
// Cap places a hard limit on the number of items, returning all transactions
|
||||
// exceeding that limit.
|
||||
func (m *txSortedMap) Cap(threshold int) types.Transactions {
|
||||
func (m *sortedMap) Cap(threshold int) types.Transactions {
|
||||
// Short circuit if the number of items is under the limit
|
||||
if len(m.items) <= threshold {
|
||||
return nil
|
||||
@@ -167,7 +168,7 @@ func (m *txSortedMap) Cap(threshold int) types.Transactions {
|
||||
|
||||
// Remove deletes a transaction from the maintained map, returning whether the
|
||||
// transaction was found.
|
||||
func (m *txSortedMap) Remove(nonce uint64) bool {
|
||||
func (m *sortedMap) Remove(nonce uint64) bool {
|
||||
// Short circuit if no transaction is present
|
||||
_, ok := m.items[nonce]
|
||||
if !ok {
|
||||
@@ -193,7 +194,7 @@ func (m *txSortedMap) Remove(nonce uint64) bool {
|
||||
// Note, all transactions with nonces lower than start will also be returned to
|
||||
// prevent getting into and invalid state. This is not something that should ever
|
||||
// happen but better to be self correcting than failing!
|
||||
func (m *txSortedMap) Ready(start uint64) types.Transactions {
|
||||
func (m *sortedMap) Ready(start uint64) types.Transactions {
|
||||
// Short circuit if no transactions are available
|
||||
if m.index.Len() == 0 || (*m.index)[0] > start {
|
||||
return nil
|
||||
@@ -211,11 +212,11 @@ func (m *txSortedMap) Ready(start uint64) types.Transactions {
|
||||
}
|
||||
|
||||
// Len returns the length of the transaction map.
|
||||
func (m *txSortedMap) Len() int {
|
||||
func (m *sortedMap) Len() int {
|
||||
return len(m.items)
|
||||
}
|
||||
|
||||
func (m *txSortedMap) flatten() types.Transactions {
|
||||
func (m *sortedMap) flatten() types.Transactions {
|
||||
// If the sorting was not cached yet, create and cache it
|
||||
if m.cache == nil {
|
||||
m.cache = make(types.Transactions, 0, len(m.items))
|
||||
@@ -230,7 +231,7 @@ func (m *txSortedMap) flatten() types.Transactions {
|
||||
// Flatten creates a nonce-sorted slice of transactions based on the loosely
|
||||
// sorted internal representation. The result of the sorting is cached in case
|
||||
// it's requested again before any modifications are made to the contents.
|
||||
func (m *txSortedMap) Flatten() types.Transactions {
|
||||
func (m *sortedMap) Flatten() types.Transactions {
|
||||
// Copy the cache to prevent accidental modifications
|
||||
cache := m.flatten()
|
||||
txs := make(types.Transactions, len(cache))
|
||||
@@ -240,36 +241,36 @@ func (m *txSortedMap) Flatten() types.Transactions {
|
||||
|
||||
// LastElement returns the last element of a flattened list, thus, the
|
||||
// transaction with the highest nonce
|
||||
func (m *txSortedMap) LastElement() *types.Transaction {
|
||||
func (m *sortedMap) LastElement() *types.Transaction {
|
||||
cache := m.flatten()
|
||||
return cache[len(cache)-1]
|
||||
}
|
||||
|
||||
// txList is a "list" of transactions belonging to an account, sorted by account
|
||||
// list is a "list" of transactions belonging to an account, sorted by account
|
||||
// nonce. The same type can be used both for storing contiguous transactions for
|
||||
// the executable/pending queue; and for storing gapped transactions for the non-
|
||||
// executable/future queue, with minor behavioral changes.
|
||||
type txList struct {
|
||||
strict bool // Whether nonces are strictly continuous or not
|
||||
txs *txSortedMap // Heap indexed sorted hash map of the transactions
|
||||
type list struct {
|
||||
strict bool // Whether nonces are strictly continuous or not
|
||||
txs *sortedMap // Heap indexed sorted hash map of the transactions
|
||||
|
||||
costcap *big.Int // Price of the highest costing transaction (reset only if exceeds balance)
|
||||
gascap uint64 // Gas limit of the highest spending transaction (reset only if exceeds block limit)
|
||||
}
|
||||
|
||||
// newTxList create a new transaction list for maintaining nonce-indexable fast,
|
||||
// newList create a new transaction list for maintaining nonce-indexable fast,
|
||||
// gapped, sortable transaction lists.
|
||||
func newTxList(strict bool) *txList {
|
||||
return &txList{
|
||||
func newList(strict bool) *list {
|
||||
return &list{
|
||||
strict: strict,
|
||||
txs: newTxSortedMap(),
|
||||
txs: newSortedMap(),
|
||||
costcap: new(big.Int),
|
||||
}
|
||||
}
|
||||
|
||||
// Overlaps returns whether the transaction specified has the same nonce as one
|
||||
// already contained within the list.
|
||||
func (l *txList) Overlaps(tx *types.Transaction) bool {
|
||||
func (l *list) Overlaps(tx *types.Transaction) bool {
|
||||
return l.txs.Get(tx.Nonce()) != nil
|
||||
}
|
||||
|
||||
@@ -278,7 +279,7 @@ func (l *txList) Overlaps(tx *types.Transaction) bool {
|
||||
//
|
||||
// If the new transaction is accepted into the list, the lists' cost and gas
|
||||
// thresholds are also potentially updated.
|
||||
func (l *txList) Add(tx *types.Transaction, priceBump uint64) (bool, *types.Transaction) {
|
||||
func (l *list) Add(tx *types.Transaction, priceBump uint64) (bool, *types.Transaction) {
|
||||
// If there's an older better transaction, abort
|
||||
old := l.txs.Get(tx.Nonce())
|
||||
if old != nil {
|
||||
@@ -316,7 +317,7 @@ func (l *txList) Add(tx *types.Transaction, priceBump uint64) (bool, *types.Tran
|
||||
// Forward removes all transactions from the list with a nonce lower than the
|
||||
// provided threshold. Every removed transaction is returned for any post-removal
|
||||
// maintenance.
|
||||
func (l *txList) Forward(threshold uint64) types.Transactions {
|
||||
func (l *list) Forward(threshold uint64) types.Transactions {
|
||||
return l.txs.Forward(threshold)
|
||||
}
|
||||
|
||||
@@ -329,7 +330,7 @@ func (l *txList) Forward(threshold uint64) types.Transactions {
|
||||
// a point in calculating all the costs or if the balance covers all. If the threshold
|
||||
// is lower than the costgas cap, the caps will be reset to a new high after removing
|
||||
// the newly invalidated transactions.
|
||||
func (l *txList) Filter(costLimit *big.Int, gasLimit uint64) (types.Transactions, types.Transactions) {
|
||||
func (l *list) Filter(costLimit *big.Int, gasLimit uint64) (types.Transactions, types.Transactions) {
|
||||
// If all transactions are below the threshold, short circuit
|
||||
if l.costcap.Cmp(costLimit) <= 0 && l.gascap <= gasLimit {
|
||||
return nil, nil
|
||||
@@ -362,14 +363,14 @@ func (l *txList) Filter(costLimit *big.Int, gasLimit uint64) (types.Transactions
|
||||
|
||||
// Cap places a hard limit on the number of items, returning all transactions
|
||||
// exceeding that limit.
|
||||
func (l *txList) Cap(threshold int) types.Transactions {
|
||||
func (l *list) Cap(threshold int) types.Transactions {
|
||||
return l.txs.Cap(threshold)
|
||||
}
|
||||
|
||||
// Remove deletes a transaction from the maintained list, returning whether the
|
||||
// transaction was found, and also returning any transaction invalidated due to
|
||||
// the deletion (strict mode only).
|
||||
func (l *txList) Remove(tx *types.Transaction) (bool, types.Transactions) {
|
||||
func (l *list) Remove(tx *types.Transaction) (bool, types.Transactions) {
|
||||
// Remove the transaction from the set
|
||||
nonce := tx.Nonce()
|
||||
if removed := l.txs.Remove(nonce); !removed {
|
||||
@@ -389,30 +390,30 @@ func (l *txList) Remove(tx *types.Transaction) (bool, types.Transactions) {
|
||||
// Note, all transactions with nonces lower than start will also be returned to
|
||||
// prevent getting into and invalid state. This is not something that should ever
|
||||
// happen but better to be self correcting than failing!
|
||||
func (l *txList) Ready(start uint64) types.Transactions {
|
||||
func (l *list) Ready(start uint64) types.Transactions {
|
||||
return l.txs.Ready(start)
|
||||
}
|
||||
|
||||
// Len returns the length of the transaction list.
|
||||
func (l *txList) Len() int {
|
||||
func (l *list) Len() int {
|
||||
return l.txs.Len()
|
||||
}
|
||||
|
||||
// Empty returns whether the list of transactions is empty or not.
|
||||
func (l *txList) Empty() bool {
|
||||
func (l *list) Empty() bool {
|
||||
return l.Len() == 0
|
||||
}
|
||||
|
||||
// Flatten creates a nonce-sorted slice of transactions based on the loosely
|
||||
// sorted internal representation. The result of the sorting is cached in case
|
||||
// it's requested again before any modifications are made to the contents.
|
||||
func (l *txList) Flatten() types.Transactions {
|
||||
func (l *list) Flatten() types.Transactions {
|
||||
return l.txs.Flatten()
|
||||
}
|
||||
|
||||
// LastElement returns the last element of a flattened list, thus, the
|
||||
// transaction with the highest nonce
|
||||
func (l *txList) LastElement() *types.Transaction {
|
||||
func (l *list) LastElement() *types.Transaction {
|
||||
return l.txs.LastElement()
|
||||
}
|
||||
|
||||
@@ -468,8 +469,8 @@ func (h *priceHeap) Pop() interface{} {
|
||||
return x
|
||||
}
|
||||
|
||||
// txPricedList is a price-sorted heap to allow operating on transactions pool
|
||||
// contents in a price-incrementing way. It's built opon the all transactions
|
||||
// pricedList is a price-sorted heap to allow operating on transactions pool
|
||||
// contents in a price-incrementing way. It's built upon the all transactions
|
||||
// in txpool but only interested in the remote part. It means only remote transactions
|
||||
// will be considered for tracking, sorting, eviction, etc.
|
||||
//
|
||||
@@ -479,14 +480,14 @@ func (h *priceHeap) Pop() interface{} {
|
||||
// In some cases (during a congestion, when blocks are full) the urgent heap can provide
|
||||
// better candidates for inclusion while in other cases (at the top of the baseFee peak)
|
||||
// the floating heap is better. When baseFee is decreasing they behave similarly.
|
||||
type txPricedList struct {
|
||||
type pricedList struct {
|
||||
// Number of stale price points to (re-heap trigger).
|
||||
// This field is accessed atomically, and must be the first field
|
||||
// to ensure it has correct alignment for atomic.AddInt64.
|
||||
// See https://golang.org/pkg/sync/atomic/#pkg-note-BUG.
|
||||
stales int64
|
||||
|
||||
all *txLookup // Pointer to the map of all transactions
|
||||
all *lookup // Pointer to the map of all transactions
|
||||
urgent, floating priceHeap // Heaps of prices of all the stored **remote** transactions
|
||||
reheapMu sync.Mutex // Mutex asserts that only one routine is reheaping the list
|
||||
}
|
||||
@@ -497,15 +498,15 @@ const (
|
||||
floatingRatio = 1
|
||||
)
|
||||
|
||||
// newTxPricedList creates a new price-sorted transaction heap.
|
||||
func newTxPricedList(all *txLookup) *txPricedList {
|
||||
return &txPricedList{
|
||||
// newPricedList creates a new price-sorted transaction heap.
|
||||
func newPricedList(all *lookup) *pricedList {
|
||||
return &pricedList{
|
||||
all: all,
|
||||
}
|
||||
}
|
||||
|
||||
// Put inserts a new transaction into the heap.
|
||||
func (l *txPricedList) Put(tx *types.Transaction, local bool) {
|
||||
func (l *pricedList) Put(tx *types.Transaction, local bool) {
|
||||
if local {
|
||||
return
|
||||
}
|
||||
@@ -516,7 +517,7 @@ func (l *txPricedList) Put(tx *types.Transaction, local bool) {
|
||||
// Removed notifies the prices transaction list that an old transaction dropped
|
||||
// from the pool. The list will just keep a counter of stale objects and update
|
||||
// the heap if a large enough ratio of transactions go stale.
|
||||
func (l *txPricedList) Removed(count int) {
|
||||
func (l *pricedList) Removed(count int) {
|
||||
// Bump the stale counter, but exit if still too low (< 25%)
|
||||
stales := atomic.AddInt64(&l.stales, int64(count))
|
||||
if int(stales) <= (len(l.urgent.list)+len(l.floating.list))/4 {
|
||||
@@ -528,7 +529,7 @@ func (l *txPricedList) Removed(count int) {
|
||||
|
||||
// Underpriced checks whether a transaction is cheaper than (or as cheap as) the
|
||||
// lowest priced (remote) transaction currently being tracked.
|
||||
func (l *txPricedList) Underpriced(tx *types.Transaction) bool {
|
||||
func (l *pricedList) Underpriced(tx *types.Transaction) bool {
|
||||
// Note: with two queues, being underpriced is defined as being worse than the worst item
|
||||
// in all non-empty queues if there is any. If both queues are empty then nothing is underpriced.
|
||||
return (l.underpricedFor(&l.urgent, tx) || len(l.urgent.list) == 0) &&
|
||||
@@ -538,7 +539,7 @@ func (l *txPricedList) Underpriced(tx *types.Transaction) bool {
|
||||
|
||||
// underpricedFor checks whether a transaction is cheaper than (or as cheap as) the
|
||||
// lowest priced (remote) transaction in the given heap.
|
||||
func (l *txPricedList) underpricedFor(h *priceHeap, tx *types.Transaction) bool {
|
||||
func (l *pricedList) underpricedFor(h *priceHeap, tx *types.Transaction) bool {
|
||||
// Discard stale price points if found at the heap start
|
||||
for len(h.list) > 0 {
|
||||
head := h.list[0]
|
||||
@@ -562,7 +563,7 @@ func (l *txPricedList) underpricedFor(h *priceHeap, tx *types.Transaction) bool
|
||||
// priced list and returns them for further removal from the entire pool.
|
||||
//
|
||||
// Note local transaction won't be considered for eviction.
|
||||
func (l *txPricedList) Discard(slots int, force bool) (types.Transactions, bool) {
|
||||
func (l *pricedList) Discard(slots int, force bool) (types.Transactions, bool) {
|
||||
drop := make(types.Transactions, 0, slots) // Remote underpriced transactions to drop
|
||||
for slots > 0 {
|
||||
if len(l.urgent.list)*floatingRatio > len(l.floating.list)*urgentRatio || floatingRatio == 0 {
|
||||
@@ -601,7 +602,7 @@ func (l *txPricedList) Discard(slots int, force bool) (types.Transactions, bool)
|
||||
}
|
||||
|
||||
// Reheap forcibly rebuilds the heap based on the current remote transaction set.
|
||||
func (l *txPricedList) Reheap() {
|
||||
func (l *pricedList) Reheap() {
|
||||
l.reheapMu.Lock()
|
||||
defer l.reheapMu.Unlock()
|
||||
start := time.Now()
|
||||
@@ -629,7 +630,7 @@ func (l *txPricedList) Reheap() {
|
||||
|
||||
// SetBaseFee updates the base fee and triggers a re-heap. Note that Removed is not
|
||||
// necessary to call right before SetBaseFee when processing a new block.
|
||||
func (l *txPricedList) SetBaseFee(baseFee *big.Int) {
|
||||
func (l *pricedList) SetBaseFee(baseFee *big.Int) {
|
||||
l.urgent.baseFee = baseFee
|
||||
l.Reheap()
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
// 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 core
|
||||
package txpool
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
@@ -27,7 +27,7 @@ import (
|
||||
|
||||
// Tests that transactions can be added to strict lists and list contents and
|
||||
// nonce boundaries are correctly maintained.
|
||||
func TestStrictTxListAdd(t *testing.T) {
|
||||
func TestStrictListAdd(t *testing.T) {
|
||||
// Generate a list of transactions to insert
|
||||
key, _ := crypto.GenerateKey()
|
||||
|
||||
@@ -36,9 +36,9 @@ func TestStrictTxListAdd(t *testing.T) {
|
||||
txs[i] = transaction(uint64(i), 0, key)
|
||||
}
|
||||
// Insert the transactions in a random order
|
||||
list := newTxList(true)
|
||||
list := newList(true)
|
||||
for _, v := range rand.Perm(len(txs)) {
|
||||
list.Add(txs[v], DefaultTxPoolConfig.PriceBump)
|
||||
list.Add(txs[v], DefaultConfig.PriceBump)
|
||||
}
|
||||
// Verify internal state
|
||||
if len(list.txs.items) != len(txs) {
|
||||
@@ -51,7 +51,7 @@ func TestStrictTxListAdd(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTxListAdd(b *testing.B) {
|
||||
func BenchmarkListAdd(b *testing.B) {
|
||||
// Generate a list of transactions to insert
|
||||
key, _ := crypto.GenerateKey()
|
||||
|
||||
@@ -60,13 +60,13 @@ func BenchmarkTxListAdd(b *testing.B) {
|
||||
txs[i] = transaction(uint64(i), 0, key)
|
||||
}
|
||||
// Insert the transactions in a random order
|
||||
priceLimit := big.NewInt(int64(DefaultTxPoolConfig.PriceLimit))
|
||||
priceLimit := big.NewInt(int64(DefaultConfig.PriceLimit))
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
list := newTxList(true)
|
||||
list := newList(true)
|
||||
for _, v := range rand.Perm(len(txs)) {
|
||||
list.Add(txs[v], DefaultTxPoolConfig.PriceBump)
|
||||
list.Filter(priceLimit, DefaultTxPoolConfig.PriceBump)
|
||||
list.Add(txs[v], DefaultConfig.PriceBump)
|
||||
list.Filter(priceLimit, DefaultConfig.PriceBump)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
// 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 core
|
||||
package txpool
|
||||
|
||||
import (
|
||||
"sync"
|
||||
@@ -23,18 +23,18 @@ import (
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
)
|
||||
|
||||
// txNoncer is a tiny virtual state database to manage the executable nonces of
|
||||
// noncer is a tiny virtual state database to manage the executable nonces of
|
||||
// accounts in the pool, falling back to reading from a real state database if
|
||||
// an account is unknown.
|
||||
type txNoncer struct {
|
||||
type noncer struct {
|
||||
fallback *state.StateDB
|
||||
nonces map[common.Address]uint64
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
// newTxNoncer creates a new virtual state database to track the pool nonces.
|
||||
func newTxNoncer(statedb *state.StateDB) *txNoncer {
|
||||
return &txNoncer{
|
||||
// newNoncer creates a new virtual state database to track the pool nonces.
|
||||
func newNoncer(statedb *state.StateDB) *noncer {
|
||||
return &noncer{
|
||||
fallback: statedb.Copy(),
|
||||
nonces: make(map[common.Address]uint64),
|
||||
}
|
||||
@@ -42,21 +42,23 @@ func newTxNoncer(statedb *state.StateDB) *txNoncer {
|
||||
|
||||
// get returns the current nonce of an account, falling back to a real state
|
||||
// database if the account is unknown.
|
||||
func (txn *txNoncer) get(addr common.Address) uint64 {
|
||||
func (txn *noncer) get(addr common.Address) uint64 {
|
||||
// We use mutex for get operation is the underlying
|
||||
// state will mutate db even for read access.
|
||||
txn.lock.Lock()
|
||||
defer txn.lock.Unlock()
|
||||
|
||||
if _, ok := txn.nonces[addr]; !ok {
|
||||
txn.nonces[addr] = txn.fallback.GetNonce(addr)
|
||||
if nonce := txn.fallback.GetNonce(addr); nonce != 0 {
|
||||
txn.nonces[addr] = nonce
|
||||
}
|
||||
}
|
||||
return txn.nonces[addr]
|
||||
}
|
||||
|
||||
// set inserts a new virtual nonce into the virtual state database to be returned
|
||||
// whenever the pool requests it instead of reaching into the real state database.
|
||||
func (txn *txNoncer) set(addr common.Address, nonce uint64) {
|
||||
func (txn *noncer) set(addr common.Address, nonce uint64) {
|
||||
txn.lock.Lock()
|
||||
defer txn.lock.Unlock()
|
||||
|
||||
@@ -64,13 +66,15 @@ func (txn *txNoncer) set(addr common.Address, nonce uint64) {
|
||||
}
|
||||
|
||||
// setIfLower updates a new virtual nonce into the virtual state database if the
|
||||
// the new one is lower.
|
||||
func (txn *txNoncer) setIfLower(addr common.Address, nonce uint64) {
|
||||
// new one is lower.
|
||||
func (txn *noncer) setIfLower(addr common.Address, nonce uint64) {
|
||||
txn.lock.Lock()
|
||||
defer txn.lock.Unlock()
|
||||
|
||||
if _, ok := txn.nonces[addr]; !ok {
|
||||
txn.nonces[addr] = txn.fallback.GetNonce(addr)
|
||||
if nonce := txn.fallback.GetNonce(addr); nonce != 0 {
|
||||
txn.nonces[addr] = nonce
|
||||
}
|
||||
}
|
||||
if txn.nonces[addr] <= nonce {
|
||||
return
|
||||
@@ -79,7 +83,7 @@ func (txn *txNoncer) setIfLower(addr common.Address, nonce uint64) {
|
||||
}
|
||||
|
||||
// setAll sets the nonces for all accounts to the given map.
|
||||
func (txn *txNoncer) setAll(all map[common.Address]uint64) {
|
||||
func (txn *noncer) setAll(all map[common.Address]uint64) {
|
||||
txn.lock.Lock()
|
||||
defer txn.lock.Unlock()
|
||||
|
||||
@@ -14,10 +14,11 @@
|
||||
// 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 core
|
||||
package txpool
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"sort"
|
||||
@@ -28,6 +29,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/prque"
|
||||
"github.com/ethereum/go-ethereum/consensus/misc"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
@@ -65,7 +67,7 @@ var (
|
||||
// configured for the transaction pool.
|
||||
ErrUnderpriced = errors.New("transaction underpriced")
|
||||
|
||||
// ErrTxPoolOverflow is returned if the transaction pool is full and can't accpet
|
||||
// ErrTxPoolOverflow is returned if the transaction pool is full and can't accept
|
||||
// another remote transaction.
|
||||
ErrTxPoolOverflow = errors.New("txpool is full")
|
||||
|
||||
@@ -112,6 +114,7 @@ var (
|
||||
invalidTxMeter = metrics.NewRegisteredMeter("txpool/invalid", nil)
|
||||
underpricedTxMeter = metrics.NewRegisteredMeter("txpool/underpriced", nil)
|
||||
overflowedTxMeter = metrics.NewRegisteredMeter("txpool/overflowed", nil)
|
||||
|
||||
// throttleTxMeter counts how many transactions are rejected due to too-many-changes between
|
||||
// txpool reorgs.
|
||||
throttleTxMeter = metrics.NewRegisteredMeter("txpool/throttle", nil)
|
||||
@@ -146,11 +149,11 @@ type blockChain interface {
|
||||
GetBlock(hash common.Hash, number uint64) *types.Block
|
||||
StateAt(root common.Hash) (*state.StateDB, error)
|
||||
|
||||
SubscribeChainHeadEvent(ch chan<- ChainHeadEvent) event.Subscription
|
||||
SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
|
||||
}
|
||||
|
||||
// TxPoolConfig are the configuration parameters of the transaction pool.
|
||||
type TxPoolConfig struct {
|
||||
// Config are the configuration parameters of the transaction pool.
|
||||
type Config struct {
|
||||
Locals []common.Address // Addresses that should be treated by default as local
|
||||
NoLocals bool // Whether local transaction handling should be disabled
|
||||
Journal string // Journal of local transactions to survive node restarts
|
||||
@@ -167,9 +170,9 @@ type TxPoolConfig struct {
|
||||
Lifetime time.Duration // Maximum amount of time non-executable transaction are queued
|
||||
}
|
||||
|
||||
// DefaultTxPoolConfig contains the default configurations for the transaction
|
||||
// DefaultConfig contains the default configurations for the transaction
|
||||
// pool.
|
||||
var DefaultTxPoolConfig = TxPoolConfig{
|
||||
var DefaultConfig = Config{
|
||||
Journal: "transactions.rlp",
|
||||
Rejournal: time.Hour,
|
||||
|
||||
@@ -186,39 +189,39 @@ var DefaultTxPoolConfig = TxPoolConfig{
|
||||
|
||||
// sanitize checks the provided user configurations and changes anything that's
|
||||
// unreasonable or unworkable.
|
||||
func (config *TxPoolConfig) sanitize() TxPoolConfig {
|
||||
func (config *Config) sanitize() Config {
|
||||
conf := *config
|
||||
if conf.Rejournal < time.Second {
|
||||
log.Warn("Sanitizing invalid txpool journal time", "provided", conf.Rejournal, "updated", time.Second)
|
||||
conf.Rejournal = time.Second
|
||||
}
|
||||
if conf.PriceLimit < 1 {
|
||||
log.Warn("Sanitizing invalid txpool price limit", "provided", conf.PriceLimit, "updated", DefaultTxPoolConfig.PriceLimit)
|
||||
conf.PriceLimit = DefaultTxPoolConfig.PriceLimit
|
||||
log.Warn("Sanitizing invalid txpool price limit", "provided", conf.PriceLimit, "updated", DefaultConfig.PriceLimit)
|
||||
conf.PriceLimit = DefaultConfig.PriceLimit
|
||||
}
|
||||
if conf.PriceBump < 1 {
|
||||
log.Warn("Sanitizing invalid txpool price bump", "provided", conf.PriceBump, "updated", DefaultTxPoolConfig.PriceBump)
|
||||
conf.PriceBump = DefaultTxPoolConfig.PriceBump
|
||||
log.Warn("Sanitizing invalid txpool price bump", "provided", conf.PriceBump, "updated", DefaultConfig.PriceBump)
|
||||
conf.PriceBump = DefaultConfig.PriceBump
|
||||
}
|
||||
if conf.AccountSlots < 1 {
|
||||
log.Warn("Sanitizing invalid txpool account slots", "provided", conf.AccountSlots, "updated", DefaultTxPoolConfig.AccountSlots)
|
||||
conf.AccountSlots = DefaultTxPoolConfig.AccountSlots
|
||||
log.Warn("Sanitizing invalid txpool account slots", "provided", conf.AccountSlots, "updated", DefaultConfig.AccountSlots)
|
||||
conf.AccountSlots = DefaultConfig.AccountSlots
|
||||
}
|
||||
if conf.GlobalSlots < 1 {
|
||||
log.Warn("Sanitizing invalid txpool global slots", "provided", conf.GlobalSlots, "updated", DefaultTxPoolConfig.GlobalSlots)
|
||||
conf.GlobalSlots = DefaultTxPoolConfig.GlobalSlots
|
||||
log.Warn("Sanitizing invalid txpool global slots", "provided", conf.GlobalSlots, "updated", DefaultConfig.GlobalSlots)
|
||||
conf.GlobalSlots = DefaultConfig.GlobalSlots
|
||||
}
|
||||
if conf.AccountQueue < 1 {
|
||||
log.Warn("Sanitizing invalid txpool account queue", "provided", conf.AccountQueue, "updated", DefaultTxPoolConfig.AccountQueue)
|
||||
conf.AccountQueue = DefaultTxPoolConfig.AccountQueue
|
||||
log.Warn("Sanitizing invalid txpool account queue", "provided", conf.AccountQueue, "updated", DefaultConfig.AccountQueue)
|
||||
conf.AccountQueue = DefaultConfig.AccountQueue
|
||||
}
|
||||
if conf.GlobalQueue < 1 {
|
||||
log.Warn("Sanitizing invalid txpool global queue", "provided", conf.GlobalQueue, "updated", DefaultTxPoolConfig.GlobalQueue)
|
||||
conf.GlobalQueue = DefaultTxPoolConfig.GlobalQueue
|
||||
log.Warn("Sanitizing invalid txpool global queue", "provided", conf.GlobalQueue, "updated", DefaultConfig.GlobalQueue)
|
||||
conf.GlobalQueue = DefaultConfig.GlobalQueue
|
||||
}
|
||||
if conf.Lifetime < 1 {
|
||||
log.Warn("Sanitizing invalid txpool lifetime", "provided", conf.Lifetime, "updated", DefaultTxPoolConfig.Lifetime)
|
||||
conf.Lifetime = DefaultTxPoolConfig.Lifetime
|
||||
log.Warn("Sanitizing invalid txpool lifetime", "provided", conf.Lifetime, "updated", DefaultConfig.Lifetime)
|
||||
conf.Lifetime = DefaultConfig.Lifetime
|
||||
}
|
||||
return conf
|
||||
}
|
||||
@@ -231,7 +234,7 @@ func (config *TxPoolConfig) sanitize() TxPoolConfig {
|
||||
// current state) and future transactions. Transactions move between those
|
||||
// two states over time as they are received and processed.
|
||||
type TxPool struct {
|
||||
config TxPoolConfig
|
||||
config Config
|
||||
chainconfig *params.ChainConfig
|
||||
chain blockChain
|
||||
gasPrice *big.Int
|
||||
@@ -243,21 +246,22 @@ type TxPool struct {
|
||||
istanbul bool // Fork indicator whether we are in the istanbul stage.
|
||||
eip2718 bool // Fork indicator whether we are using EIP-2718 type transactions.
|
||||
eip1559 bool // Fork indicator whether we are using EIP-1559 type transactions.
|
||||
shanghai bool // Fork indicator whether we are in the Shanghai stage.
|
||||
|
||||
currentState *state.StateDB // Current state in the blockchain head
|
||||
pendingNonces *txNoncer // Pending state tracking virtual nonces
|
||||
pendingNonces *noncer // Pending state tracking virtual nonces
|
||||
currentMaxGas uint64 // Current gas limit for transaction caps
|
||||
|
||||
locals *accountSet // Set of local transaction to exempt from eviction rules
|
||||
journal *txJournal // Journal of local transaction to back up to disk
|
||||
journal *journal // Journal of local transaction to back up to disk
|
||||
|
||||
pending map[common.Address]*txList // All currently processable transactions
|
||||
queue map[common.Address]*txList // Queued but non-processable transactions
|
||||
pending map[common.Address]*list // All currently processable transactions
|
||||
queue map[common.Address]*list // Queued but non-processable transactions
|
||||
beats map[common.Address]time.Time // Last heartbeat from each known account
|
||||
all *txLookup // All transactions to allow lookups
|
||||
priced *txPricedList // All transactions sorted by price
|
||||
all *lookup // All transactions to allow lookups
|
||||
priced *pricedList // All transactions sorted by price
|
||||
|
||||
chainHeadCh chan ChainHeadEvent
|
||||
chainHeadCh chan core.ChainHeadEvent
|
||||
chainHeadSub event.Subscription
|
||||
reqResetCh chan *txpoolResetRequest
|
||||
reqPromoteCh chan *accountSet
|
||||
@@ -276,7 +280,7 @@ type txpoolResetRequest struct {
|
||||
|
||||
// NewTxPool creates a new transaction pool to gather, sort and filter inbound
|
||||
// transactions from the network.
|
||||
func NewTxPool(config TxPoolConfig, chainconfig *params.ChainConfig, chain blockChain) *TxPool {
|
||||
func NewTxPool(config Config, chainconfig *params.ChainConfig, chain blockChain) *TxPool {
|
||||
// Sanitize the input to ensure no vulnerable gas prices are set
|
||||
config = (&config).sanitize()
|
||||
|
||||
@@ -286,11 +290,11 @@ func NewTxPool(config TxPoolConfig, chainconfig *params.ChainConfig, chain block
|
||||
chainconfig: chainconfig,
|
||||
chain: chain,
|
||||
signer: types.LatestSigner(chainconfig),
|
||||
pending: make(map[common.Address]*txList),
|
||||
queue: make(map[common.Address]*txList),
|
||||
pending: make(map[common.Address]*list),
|
||||
queue: make(map[common.Address]*list),
|
||||
beats: make(map[common.Address]time.Time),
|
||||
all: newTxLookup(),
|
||||
chainHeadCh: make(chan ChainHeadEvent, chainHeadChanSize),
|
||||
all: newLookup(),
|
||||
chainHeadCh: make(chan core.ChainHeadEvent, chainHeadChanSize),
|
||||
reqResetCh: make(chan *txpoolResetRequest),
|
||||
reqPromoteCh: make(chan *accountSet),
|
||||
queueTxEventCh: make(chan *types.Transaction),
|
||||
@@ -304,7 +308,7 @@ func NewTxPool(config TxPoolConfig, chainconfig *params.ChainConfig, chain block
|
||||
log.Info("Setting new local account", "address", addr)
|
||||
pool.locals.add(addr)
|
||||
}
|
||||
pool.priced = newTxPricedList(pool.all)
|
||||
pool.priced = newPricedList(pool.all)
|
||||
pool.reset(nil, chain.CurrentBlock().Header())
|
||||
|
||||
// Start the reorg loop early so it can handle requests generated during journal loading.
|
||||
@@ -427,7 +431,7 @@ func (pool *TxPool) Stop() {
|
||||
|
||||
// SubscribeNewTxsEvent registers a subscription of NewTxsEvent and
|
||||
// starts sending event to the given channel.
|
||||
func (pool *TxPool) SubscribeNewTxsEvent(ch chan<- NewTxsEvent) event.Subscription {
|
||||
func (pool *TxPool) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription {
|
||||
return pool.scope.Track(pool.txFeed.Subscribe(ch))
|
||||
}
|
||||
|
||||
@@ -498,11 +502,11 @@ func (pool *TxPool) Content() (map[common.Address]types.Transactions, map[common
|
||||
pool.mu.Lock()
|
||||
defer pool.mu.Unlock()
|
||||
|
||||
pending := make(map[common.Address]types.Transactions)
|
||||
pending := make(map[common.Address]types.Transactions, len(pool.pending))
|
||||
for addr, list := range pool.pending {
|
||||
pending[addr] = list.Flatten()
|
||||
}
|
||||
queued := make(map[common.Address]types.Transactions)
|
||||
queued := make(map[common.Address]types.Transactions, len(pool.queue))
|
||||
for addr, list := range pool.queue {
|
||||
queued[addr] = list.Flatten()
|
||||
}
|
||||
@@ -586,16 +590,20 @@ func (pool *TxPool) local() map[common.Address]types.Transactions {
|
||||
func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
|
||||
// Accept only legacy transactions until EIP-2718/2930 activates.
|
||||
if !pool.eip2718 && tx.Type() != types.LegacyTxType {
|
||||
return ErrTxTypeNotSupported
|
||||
return core.ErrTxTypeNotSupported
|
||||
}
|
||||
// Reject dynamic fee transactions until EIP-1559 activates.
|
||||
if !pool.eip1559 && tx.Type() == types.DynamicFeeTxType {
|
||||
return ErrTxTypeNotSupported
|
||||
return core.ErrTxTypeNotSupported
|
||||
}
|
||||
// Reject transactions over defined size to prevent DOS attacks
|
||||
if uint64(tx.Size()) > txMaxSize {
|
||||
if tx.Size() > txMaxSize {
|
||||
return ErrOversizedData
|
||||
}
|
||||
// Check whether the init code size has been exceeded.
|
||||
if pool.shanghai && tx.To() == nil && len(tx.Data()) > params.MaxInitCodeSize {
|
||||
return fmt.Errorf("%w: code size %v limit %v", core.ErrMaxInitCodeSizeExceeded, len(tx.Data()), params.MaxInitCodeSize)
|
||||
}
|
||||
// Transactions can't be negative. This may never happen using RLP decoded
|
||||
// transactions but may occur if you create a transaction using the RPC.
|
||||
if tx.Value().Sign() < 0 {
|
||||
@@ -607,14 +615,14 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
|
||||
}
|
||||
// Sanity check for extremely large numbers
|
||||
if tx.GasFeeCap().BitLen() > 256 {
|
||||
return ErrFeeCapVeryHigh
|
||||
return core.ErrFeeCapVeryHigh
|
||||
}
|
||||
if tx.GasTipCap().BitLen() > 256 {
|
||||
return ErrTipVeryHigh
|
||||
return core.ErrTipVeryHigh
|
||||
}
|
||||
// Ensure gasFeeCap is greater than or equal to gasTipCap.
|
||||
if tx.GasFeeCapIntCmp(tx.GasTipCap()) < 0 {
|
||||
return ErrTipAboveFeeCap
|
||||
return core.ErrTipAboveFeeCap
|
||||
}
|
||||
// Make sure the transaction is signed properly.
|
||||
from, err := types.Sender(pool.signer, tx)
|
||||
@@ -627,20 +635,20 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
|
||||
}
|
||||
// Ensure the transaction adheres to nonce ordering
|
||||
if pool.currentState.GetNonce(from) > tx.Nonce() {
|
||||
return ErrNonceTooLow
|
||||
return core.ErrNonceTooLow
|
||||
}
|
||||
// Transactor should have enough funds to cover the costs
|
||||
// cost == V + GP * GL
|
||||
if pool.currentState.GetBalance(from).Cmp(tx.Cost()) < 0 {
|
||||
return ErrInsufficientFunds
|
||||
return core.ErrInsufficientFunds
|
||||
}
|
||||
// Ensure the transaction has more gas than the basic tx fee.
|
||||
intrGas, err := IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, true, pool.istanbul)
|
||||
intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, true, pool.istanbul, pool.shanghai)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tx.Gas() < intrGas {
|
||||
return ErrIntrinsicGas
|
||||
return core.ErrIntrinsicGas
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -759,7 +767,7 @@ func (pool *TxPool) enqueueTx(hash common.Hash, tx *types.Transaction, local boo
|
||||
// Try to insert the transaction into the future queue
|
||||
from, _ := types.Sender(pool.signer, tx) // already validated
|
||||
if pool.queue[from] == nil {
|
||||
pool.queue[from] = newTxList(false)
|
||||
pool.queue[from] = newList(false)
|
||||
}
|
||||
inserted, old := pool.queue[from].Add(tx, pool.config.PriceBump)
|
||||
if !inserted {
|
||||
@@ -811,7 +819,7 @@ func (pool *TxPool) journalTx(from common.Address, tx *types.Transaction) {
|
||||
func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.Transaction) bool {
|
||||
// Try to insert the transaction into the pending queue
|
||||
if pool.pending[addr] == nil {
|
||||
pool.pending[addr] = newTxList(true)
|
||||
pool.pending[addr] = newList(true)
|
||||
}
|
||||
list := pool.pending[addr]
|
||||
|
||||
@@ -850,7 +858,7 @@ func (pool *TxPool) AddLocals(txs []*types.Transaction) []error {
|
||||
}
|
||||
|
||||
// AddLocal enqueues a single local transaction into the pool if it is valid. This is
|
||||
// a convenience wrapper aroundd AddLocals.
|
||||
// a convenience wrapper around AddLocals.
|
||||
func (pool *TxPool) AddLocal(tx *types.Transaction) error {
|
||||
errs := pool.AddLocals([]*types.Transaction{tx})
|
||||
return errs[0]
|
||||
@@ -865,7 +873,7 @@ func (pool *TxPool) AddRemotes(txs []*types.Transaction) []error {
|
||||
return pool.addTxs(txs, false, false)
|
||||
}
|
||||
|
||||
// This is like AddRemotes, but waits for pool reorganization. Tests use this method.
|
||||
// AddRemotesSync is like AddRemotes, but waits for pool reorganization. Tests use this method.
|
||||
func (pool *TxPool) AddRemotesSync(txs []*types.Transaction) []error {
|
||||
return pool.addTxs(txs, false, true)
|
||||
}
|
||||
@@ -1078,7 +1086,7 @@ func (pool *TxPool) scheduleReorgLoop() {
|
||||
launchNextRun bool
|
||||
reset *txpoolResetRequest
|
||||
dirtyAccounts *accountSet
|
||||
queuedEvents = make(map[common.Address]*txSortedMap)
|
||||
queuedEvents = make(map[common.Address]*sortedMap)
|
||||
)
|
||||
for {
|
||||
// Launch next background reorg if needed
|
||||
@@ -1091,7 +1099,7 @@ func (pool *TxPool) scheduleReorgLoop() {
|
||||
launchNextRun = false
|
||||
|
||||
reset, dirtyAccounts = nil, nil
|
||||
queuedEvents = make(map[common.Address]*txSortedMap)
|
||||
queuedEvents = make(map[common.Address]*sortedMap)
|
||||
}
|
||||
|
||||
select {
|
||||
@@ -1120,7 +1128,7 @@ func (pool *TxPool) scheduleReorgLoop() {
|
||||
// request one later if they want the events sent.
|
||||
addr, _ := types.Sender(pool.signer, tx)
|
||||
if _, ok := queuedEvents[addr]; !ok {
|
||||
queuedEvents[addr] = newTxSortedMap()
|
||||
queuedEvents[addr] = newSortedMap()
|
||||
}
|
||||
queuedEvents[addr].Put(tx)
|
||||
|
||||
@@ -1139,7 +1147,7 @@ func (pool *TxPool) scheduleReorgLoop() {
|
||||
}
|
||||
|
||||
// runReorg runs reset and promoteExecutables on behalf of scheduleReorgLoop.
|
||||
func (pool *TxPool) runReorg(done chan struct{}, reset *txpoolResetRequest, dirtyAccounts *accountSet, events map[common.Address]*txSortedMap) {
|
||||
func (pool *TxPool) runReorg(done chan struct{}, reset *txpoolResetRequest, dirtyAccounts *accountSet, events map[common.Address]*sortedMap) {
|
||||
defer func(t0 time.Time) {
|
||||
reorgDurationTimer.Update(time.Since(t0))
|
||||
}(time.Now())
|
||||
@@ -1202,7 +1210,7 @@ func (pool *TxPool) runReorg(done chan struct{}, reset *txpoolResetRequest, dirt
|
||||
for _, tx := range promoted {
|
||||
addr, _ := types.Sender(pool.signer, tx)
|
||||
if _, ok := events[addr]; !ok {
|
||||
events[addr] = newTxSortedMap()
|
||||
events[addr] = newSortedMap()
|
||||
}
|
||||
events[addr].Put(tx)
|
||||
}
|
||||
@@ -1211,7 +1219,7 @@ func (pool *TxPool) runReorg(done chan struct{}, reset *txpoolResetRequest, dirt
|
||||
for _, set := range events {
|
||||
txs = append(txs, set.Flatten()...)
|
||||
}
|
||||
pool.txFeed.Send(NewTxsEvent{txs})
|
||||
pool.txFeed.Send(core.NewTxsEvent{Txs: txs})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1238,7 +1246,7 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) {
|
||||
if rem == nil {
|
||||
// This can happen if a setHead is performed, where we simply discard the old
|
||||
// head from the chain.
|
||||
// If that is the case, we don't have the lost transactions any more, and
|
||||
// If that is the case, we don't have the lost transactions anymore, and
|
||||
// there's nothing to add
|
||||
if newNum >= oldNum {
|
||||
// If we reorged to a same or higher number, then it's not a case of setHead
|
||||
@@ -1291,12 +1299,12 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) {
|
||||
return
|
||||
}
|
||||
pool.currentState = statedb
|
||||
pool.pendingNonces = newTxNoncer(statedb)
|
||||
pool.pendingNonces = newNoncer(statedb)
|
||||
pool.currentMaxGas = newHead.GasLimit
|
||||
|
||||
// Inject any transactions discarded due to reorgs
|
||||
log.Debug("Reinjecting stale transactions", "count", len(reinject))
|
||||
senderCacher.recover(pool.signer, reinject)
|
||||
core.SenderCacher.Recover(pool.signer, reinject)
|
||||
pool.addTxsLocked(reinject, false)
|
||||
|
||||
// Update all fork indicator by next pending block number.
|
||||
@@ -1304,6 +1312,7 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) {
|
||||
pool.istanbul = pool.chainconfig.IsIstanbul(next)
|
||||
pool.eip2718 = pool.chainconfig.IsBerlin(next)
|
||||
pool.eip1559 = pool.chainconfig.IsLondon(next)
|
||||
pool.shanghai = pool.chainconfig.IsShanghai(uint64(time.Now().Unix()))
|
||||
}
|
||||
|
||||
// promoteExecutables moves transactions that have become processable from the
|
||||
@@ -1386,7 +1395,7 @@ func (pool *TxPool) truncatePending() {
|
||||
|
||||
pendingBeforeCap := pending
|
||||
// Assemble a spam order to penalize large transactors first
|
||||
spammers := prque.New(nil)
|
||||
spammers := prque.New[int64, common.Address](nil)
|
||||
for addr, list := range pool.pending {
|
||||
// Only evict transactions from high rollers
|
||||
if !pool.locals.contains(addr) && uint64(list.Len()) > pool.config.AccountSlots {
|
||||
@@ -1398,12 +1407,12 @@ func (pool *TxPool) truncatePending() {
|
||||
for pending > pool.config.GlobalSlots && !spammers.Empty() {
|
||||
// Retrieve the next offender if not local address
|
||||
offender, _ := spammers.Pop()
|
||||
offenders = append(offenders, offender.(common.Address))
|
||||
offenders = append(offenders, offender)
|
||||
|
||||
// Equalize balances until all the same or below threshold
|
||||
if len(offenders) > 1 {
|
||||
// Calculate the equalization threshold for all current offenders
|
||||
threshold := pool.pending[offender.(common.Address)].Len()
|
||||
threshold := pool.pending[offender].Len()
|
||||
|
||||
// Iteratively reduce all offenders until below limit or threshold reached
|
||||
for pending > pool.config.GlobalSlots && pool.pending[offenders[len(offenders)-2]].Len() > threshold {
|
||||
@@ -1554,8 +1563,6 @@ func (pool *TxPool) demoteUnexecutables() {
|
||||
pool.enqueueTx(hash, tx, false, false)
|
||||
}
|
||||
pendingGauge.Dec(int64(len(gapped)))
|
||||
// This might happen in a reorg, so log it to the metering
|
||||
blockReorgInvalidatedTx.Mark(int64(len(gapped)))
|
||||
}
|
||||
// Delete the entire pending entry if it became empty.
|
||||
if list.Empty() {
|
||||
@@ -1588,7 +1595,7 @@ type accountSet struct {
|
||||
// derivations.
|
||||
func newAccountSet(signer types.Signer, addrs ...common.Address) *accountSet {
|
||||
as := &accountSet{
|
||||
accounts: make(map[common.Address]struct{}),
|
||||
accounts: make(map[common.Address]struct{}, len(addrs)),
|
||||
signer: signer,
|
||||
}
|
||||
for _, addr := range addrs {
|
||||
@@ -1646,7 +1653,7 @@ func (as *accountSet) merge(other *accountSet) {
|
||||
as.cache = nil
|
||||
}
|
||||
|
||||
// txLookup is used internally by TxPool to track transactions while allowing
|
||||
// lookup is used internally by TxPool to track transactions while allowing
|
||||
// lookup without mutex contention.
|
||||
//
|
||||
// Note, although this type is properly protected against concurrent access, it
|
||||
@@ -1658,16 +1665,16 @@ func (as *accountSet) merge(other *accountSet) {
|
||||
//
|
||||
// This lookup set combines the notion of "local transactions", which is useful
|
||||
// to build upper-level structure.
|
||||
type txLookup struct {
|
||||
type lookup struct {
|
||||
slots int
|
||||
lock sync.RWMutex
|
||||
locals map[common.Hash]*types.Transaction
|
||||
remotes map[common.Hash]*types.Transaction
|
||||
}
|
||||
|
||||
// newTxLookup returns a new txLookup structure.
|
||||
func newTxLookup() *txLookup {
|
||||
return &txLookup{
|
||||
// newLookup returns a new lookup structure.
|
||||
func newLookup() *lookup {
|
||||
return &lookup{
|
||||
locals: make(map[common.Hash]*types.Transaction),
|
||||
remotes: make(map[common.Hash]*types.Transaction),
|
||||
}
|
||||
@@ -1676,7 +1683,7 @@ func newTxLookup() *txLookup {
|
||||
// Range calls f on each key and value present in the map. The callback passed
|
||||
// should return the indicator whether the iteration needs to be continued.
|
||||
// Callers need to specify which set (or both) to be iterated.
|
||||
func (t *txLookup) Range(f func(hash common.Hash, tx *types.Transaction, local bool) bool, local bool, remote bool) {
|
||||
func (t *lookup) Range(f func(hash common.Hash, tx *types.Transaction, local bool) bool, local bool, remote bool) {
|
||||
t.lock.RLock()
|
||||
defer t.lock.RUnlock()
|
||||
|
||||
@@ -1697,7 +1704,7 @@ func (t *txLookup) Range(f func(hash common.Hash, tx *types.Transaction, local b
|
||||
}
|
||||
|
||||
// Get returns a transaction if it exists in the lookup, or nil if not found.
|
||||
func (t *txLookup) Get(hash common.Hash) *types.Transaction {
|
||||
func (t *lookup) Get(hash common.Hash) *types.Transaction {
|
||||
t.lock.RLock()
|
||||
defer t.lock.RUnlock()
|
||||
|
||||
@@ -1708,7 +1715,7 @@ func (t *txLookup) Get(hash common.Hash) *types.Transaction {
|
||||
}
|
||||
|
||||
// GetLocal returns a transaction if it exists in the lookup, or nil if not found.
|
||||
func (t *txLookup) GetLocal(hash common.Hash) *types.Transaction {
|
||||
func (t *lookup) GetLocal(hash common.Hash) *types.Transaction {
|
||||
t.lock.RLock()
|
||||
defer t.lock.RUnlock()
|
||||
|
||||
@@ -1716,7 +1723,7 @@ func (t *txLookup) GetLocal(hash common.Hash) *types.Transaction {
|
||||
}
|
||||
|
||||
// GetRemote returns a transaction if it exists in the lookup, or nil if not found.
|
||||
func (t *txLookup) GetRemote(hash common.Hash) *types.Transaction {
|
||||
func (t *lookup) GetRemote(hash common.Hash) *types.Transaction {
|
||||
t.lock.RLock()
|
||||
defer t.lock.RUnlock()
|
||||
|
||||
@@ -1724,7 +1731,7 @@ func (t *txLookup) GetRemote(hash common.Hash) *types.Transaction {
|
||||
}
|
||||
|
||||
// Count returns the current number of transactions in the lookup.
|
||||
func (t *txLookup) Count() int {
|
||||
func (t *lookup) Count() int {
|
||||
t.lock.RLock()
|
||||
defer t.lock.RUnlock()
|
||||
|
||||
@@ -1732,7 +1739,7 @@ func (t *txLookup) Count() int {
|
||||
}
|
||||
|
||||
// LocalCount returns the current number of local transactions in the lookup.
|
||||
func (t *txLookup) LocalCount() int {
|
||||
func (t *lookup) LocalCount() int {
|
||||
t.lock.RLock()
|
||||
defer t.lock.RUnlock()
|
||||
|
||||
@@ -1740,7 +1747,7 @@ func (t *txLookup) LocalCount() int {
|
||||
}
|
||||
|
||||
// RemoteCount returns the current number of remote transactions in the lookup.
|
||||
func (t *txLookup) RemoteCount() int {
|
||||
func (t *lookup) RemoteCount() int {
|
||||
t.lock.RLock()
|
||||
defer t.lock.RUnlock()
|
||||
|
||||
@@ -1748,7 +1755,7 @@ func (t *txLookup) RemoteCount() int {
|
||||
}
|
||||
|
||||
// Slots returns the current number of slots used in the lookup.
|
||||
func (t *txLookup) Slots() int {
|
||||
func (t *lookup) Slots() int {
|
||||
t.lock.RLock()
|
||||
defer t.lock.RUnlock()
|
||||
|
||||
@@ -1756,7 +1763,7 @@ func (t *txLookup) Slots() int {
|
||||
}
|
||||
|
||||
// Add adds a transaction to the lookup.
|
||||
func (t *txLookup) Add(tx *types.Transaction, local bool) {
|
||||
func (t *lookup) Add(tx *types.Transaction, local bool) {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
|
||||
@@ -1771,7 +1778,7 @@ func (t *txLookup) Add(tx *types.Transaction, local bool) {
|
||||
}
|
||||
|
||||
// Remove removes a transaction from the lookup.
|
||||
func (t *txLookup) Remove(hash common.Hash) {
|
||||
func (t *lookup) Remove(hash common.Hash) {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
|
||||
@@ -1792,7 +1799,7 @@ func (t *txLookup) Remove(hash common.Hash) {
|
||||
|
||||
// RemoteToLocals migrates the transactions belongs to the given locals to locals
|
||||
// set. The assumption is held the locals set is thread-safe to be used.
|
||||
func (t *txLookup) RemoteToLocals(locals *accountSet) int {
|
||||
func (t *lookup) RemoteToLocals(locals *accountSet) int {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
|
||||
@@ -1808,7 +1815,7 @@ func (t *txLookup) RemoteToLocals(locals *accountSet) int {
|
||||
}
|
||||
|
||||
// RemotesBelowTip finds all remote transactions below the given tip threshold.
|
||||
func (t *txLookup) RemotesBelowTip(threshold *big.Int) types.Transactions {
|
||||
func (t *lookup) RemotesBelowTip(threshold *big.Int) types.Transactions {
|
||||
found := make(types.Transactions, 0, 128)
|
||||
t.Range(func(hash common.Hash, tx *types.Transaction, local bool) bool {
|
||||
if tx.GasTipCapIntCmp(threshold) < 0 {
|
||||
@@ -14,10 +14,11 @@
|
||||
// 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 core
|
||||
package txpool
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
crand "crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
@@ -28,6 +29,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
@@ -40,14 +42,14 @@ import (
|
||||
var (
|
||||
// testTxPoolConfig is a transaction pool configuration without stateful disk
|
||||
// sideeffects used during testing.
|
||||
testTxPoolConfig TxPoolConfig
|
||||
testTxPoolConfig Config
|
||||
|
||||
// eip1559Config is a chain config with EIP-1559 enabled at block 0.
|
||||
eip1559Config *params.ChainConfig
|
||||
)
|
||||
|
||||
func init() {
|
||||
testTxPoolConfig = DefaultTxPoolConfig
|
||||
testTxPoolConfig = DefaultConfig
|
||||
testTxPoolConfig.Journal = ""
|
||||
|
||||
cpy := *params.TestChainConfig
|
||||
@@ -76,7 +78,7 @@ func (bc *testBlockChain) StateAt(common.Hash) (*state.StateDB, error) {
|
||||
return bc.statedb, nil
|
||||
}
|
||||
|
||||
func (bc *testBlockChain) SubscribeChainHeadEvent(ch chan<- ChainHeadEvent) event.Subscription {
|
||||
func (bc *testBlockChain) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription {
|
||||
return bc.chainHeadFeed.Subscribe(ch)
|
||||
}
|
||||
|
||||
@@ -91,7 +93,7 @@ func pricedTransaction(nonce uint64, gaslimit uint64, gasprice *big.Int, key *ec
|
||||
|
||||
func pricedDataTransaction(nonce uint64, gaslimit uint64, gasprice *big.Int, key *ecdsa.PrivateKey, bytes uint64) *types.Transaction {
|
||||
data := make([]byte, bytes)
|
||||
rand.Read(data)
|
||||
crand.Read(data)
|
||||
|
||||
tx, _ := types.SignTx(types.NewTransaction(nonce, common.Address{}, big.NewInt(0), gaslimit, gasprice, data), types.HomesteadSigner{}, key)
|
||||
return tx
|
||||
@@ -112,11 +114,11 @@ func dynamicFeeTx(nonce uint64, gaslimit uint64, gasFee *big.Int, tip *big.Int,
|
||||
return tx
|
||||
}
|
||||
|
||||
func setupTxPool() (*TxPool, *ecdsa.PrivateKey) {
|
||||
return setupTxPoolWithConfig(params.TestChainConfig)
|
||||
func setupPool() (*TxPool, *ecdsa.PrivateKey) {
|
||||
return setupPoolWithConfig(params.TestChainConfig)
|
||||
}
|
||||
|
||||
func setupTxPoolWithConfig(config *params.ChainConfig) (*TxPool, *ecdsa.PrivateKey) {
|
||||
func setupPoolWithConfig(config *params.ChainConfig) (*TxPool, *ecdsa.PrivateKey) {
|
||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
|
||||
blockchain := &testBlockChain{10000000, statedb, new(event.Feed)}
|
||||
|
||||
@@ -128,8 +130,8 @@ func setupTxPoolWithConfig(config *params.ChainConfig) (*TxPool, *ecdsa.PrivateK
|
||||
return pool, key
|
||||
}
|
||||
|
||||
// validateTxPoolInternals checks various consistency invariants within the pool.
|
||||
func validateTxPoolInternals(pool *TxPool) error {
|
||||
// validatePoolInternals checks various consistency invariants within the pool.
|
||||
func validatePoolInternals(pool *TxPool) error {
|
||||
pool.mu.RLock()
|
||||
defer pool.mu.RUnlock()
|
||||
|
||||
@@ -161,7 +163,7 @@ func validateTxPoolInternals(pool *TxPool) error {
|
||||
|
||||
// validateEvents checks that the correct number of transaction addition events
|
||||
// were fired on the pool's event feed.
|
||||
func validateEvents(events chan NewTxsEvent, count int) error {
|
||||
func validateEvents(events chan core.NewTxsEvent, count int) error {
|
||||
var received []*types.Transaction
|
||||
|
||||
for len(received) < count {
|
||||
@@ -218,7 +220,7 @@ func (c *testChain) State() (*state.StateDB, error) {
|
||||
// This test simulates a scenario where a new block is imported during a
|
||||
// state reset and tests whether the pending state is in sync with the
|
||||
// block head event that initiated the resetState().
|
||||
func TestStateChangeDuringTransactionPoolReset(t *testing.T) {
|
||||
func TestStateChangeDuringReset(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
@@ -275,28 +277,28 @@ func testSetNonce(pool *TxPool, addr common.Address, nonce uint64) {
|
||||
func TestInvalidTransactions(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pool, key := setupTxPool()
|
||||
pool, key := setupPool()
|
||||
defer pool.Stop()
|
||||
|
||||
tx := transaction(0, 100, key)
|
||||
from, _ := deriveSender(tx)
|
||||
|
||||
testAddBalance(pool, from, big.NewInt(1))
|
||||
if err := pool.AddRemote(tx); !errors.Is(err, ErrInsufficientFunds) {
|
||||
t.Error("expected", ErrInsufficientFunds)
|
||||
if err := pool.AddRemote(tx); !errors.Is(err, core.ErrInsufficientFunds) {
|
||||
t.Error("expected", core.ErrInsufficientFunds)
|
||||
}
|
||||
|
||||
balance := new(big.Int).Add(tx.Value(), new(big.Int).Mul(new(big.Int).SetUint64(tx.Gas()), tx.GasPrice()))
|
||||
testAddBalance(pool, from, balance)
|
||||
if err := pool.AddRemote(tx); !errors.Is(err, ErrIntrinsicGas) {
|
||||
t.Error("expected", ErrIntrinsicGas, "got", err)
|
||||
if err := pool.AddRemote(tx); !errors.Is(err, core.ErrIntrinsicGas) {
|
||||
t.Error("expected", core.ErrIntrinsicGas, "got", err)
|
||||
}
|
||||
|
||||
testSetNonce(pool, from, 1)
|
||||
testAddBalance(pool, from, big.NewInt(0xffffffffffffff))
|
||||
tx = transaction(0, 100000, key)
|
||||
if err := pool.AddRemote(tx); !errors.Is(err, ErrNonceTooLow) {
|
||||
t.Error("expected", ErrNonceTooLow)
|
||||
if err := pool.AddRemote(tx); !errors.Is(err, core.ErrNonceTooLow) {
|
||||
t.Error("expected", core.ErrNonceTooLow)
|
||||
}
|
||||
|
||||
tx = transaction(1, 100000, key)
|
||||
@@ -309,10 +311,10 @@ func TestInvalidTransactions(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTransactionQueue(t *testing.T) {
|
||||
func TestQueue(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pool, key := setupTxPool()
|
||||
pool, key := setupPool()
|
||||
defer pool.Stop()
|
||||
|
||||
tx := transaction(0, 100, key)
|
||||
@@ -340,10 +342,10 @@ func TestTransactionQueue(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTransactionQueue2(t *testing.T) {
|
||||
func TestQueue2(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pool, key := setupTxPool()
|
||||
pool, key := setupPool()
|
||||
defer pool.Stop()
|
||||
|
||||
tx1 := transaction(0, 100, key)
|
||||
@@ -366,10 +368,10 @@ func TestTransactionQueue2(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTransactionNegativeValue(t *testing.T) {
|
||||
func TestNegativeValue(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pool, key := setupTxPool()
|
||||
pool, key := setupPool()
|
||||
defer pool.Stop()
|
||||
|
||||
tx, _ := types.SignTx(types.NewTransaction(0, common.Address{}, big.NewInt(-1), 100, big.NewInt(1), nil), types.HomesteadSigner{}, key)
|
||||
@@ -380,43 +382,43 @@ func TestTransactionNegativeValue(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTransactionTipAboveFeeCap(t *testing.T) {
|
||||
func TestTipAboveFeeCap(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pool, key := setupTxPoolWithConfig(eip1559Config)
|
||||
pool, key := setupPoolWithConfig(eip1559Config)
|
||||
defer pool.Stop()
|
||||
|
||||
tx := dynamicFeeTx(0, 100, big.NewInt(1), big.NewInt(2), key)
|
||||
|
||||
if err := pool.AddRemote(tx); err != ErrTipAboveFeeCap {
|
||||
t.Error("expected", ErrTipAboveFeeCap, "got", err)
|
||||
if err := pool.AddRemote(tx); err != core.ErrTipAboveFeeCap {
|
||||
t.Error("expected", core.ErrTipAboveFeeCap, "got", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTransactionVeryHighValues(t *testing.T) {
|
||||
func TestVeryHighValues(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pool, key := setupTxPoolWithConfig(eip1559Config)
|
||||
pool, key := setupPoolWithConfig(eip1559Config)
|
||||
defer pool.Stop()
|
||||
|
||||
veryBigNumber := big.NewInt(1)
|
||||
veryBigNumber.Lsh(veryBigNumber, 300)
|
||||
|
||||
tx := dynamicFeeTx(0, 100, big.NewInt(1), veryBigNumber, key)
|
||||
if err := pool.AddRemote(tx); err != ErrTipVeryHigh {
|
||||
t.Error("expected", ErrTipVeryHigh, "got", err)
|
||||
if err := pool.AddRemote(tx); err != core.ErrTipVeryHigh {
|
||||
t.Error("expected", core.ErrTipVeryHigh, "got", err)
|
||||
}
|
||||
|
||||
tx2 := dynamicFeeTx(0, 100, veryBigNumber, big.NewInt(1), key)
|
||||
if err := pool.AddRemote(tx2); err != ErrFeeCapVeryHigh {
|
||||
t.Error("expected", ErrFeeCapVeryHigh, "got", err)
|
||||
if err := pool.AddRemote(tx2); err != core.ErrFeeCapVeryHigh {
|
||||
t.Error("expected", core.ErrFeeCapVeryHigh, "got", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTransactionChainFork(t *testing.T) {
|
||||
func TestChainFork(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pool, key := setupTxPool()
|
||||
pool, key := setupPool()
|
||||
defer pool.Stop()
|
||||
|
||||
addr := crypto.PubkeyToAddress(key.PublicKey)
|
||||
@@ -442,10 +444,10 @@ func TestTransactionChainFork(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTransactionDoubleNonce(t *testing.T) {
|
||||
func TestDoubleNonce(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pool, key := setupTxPool()
|
||||
pool, key := setupPool()
|
||||
defer pool.Stop()
|
||||
|
||||
addr := crypto.PubkeyToAddress(key.PublicKey)
|
||||
@@ -493,10 +495,10 @@ func TestTransactionDoubleNonce(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTransactionMissingNonce(t *testing.T) {
|
||||
func TestMissingNonce(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pool, key := setupTxPool()
|
||||
pool, key := setupPool()
|
||||
defer pool.Stop()
|
||||
|
||||
addr := crypto.PubkeyToAddress(key.PublicKey)
|
||||
@@ -516,11 +518,11 @@ func TestTransactionMissingNonce(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTransactionNonceRecovery(t *testing.T) {
|
||||
func TestNonceRecovery(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const n = 10
|
||||
pool, key := setupTxPool()
|
||||
pool, key := setupPool()
|
||||
defer pool.Stop()
|
||||
|
||||
addr := crypto.PubkeyToAddress(key.PublicKey)
|
||||
@@ -542,11 +544,11 @@ func TestTransactionNonceRecovery(t *testing.T) {
|
||||
|
||||
// Tests that if an account runs out of funds, any pending and queued transactions
|
||||
// are dropped.
|
||||
func TestTransactionDropping(t *testing.T) {
|
||||
func TestDropping(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create a test account and fund it
|
||||
pool, key := setupTxPool()
|
||||
pool, key := setupPool()
|
||||
defer pool.Stop()
|
||||
|
||||
account := crypto.PubkeyToAddress(key.PublicKey)
|
||||
@@ -646,7 +648,7 @@ func TestTransactionDropping(t *testing.T) {
|
||||
// Tests that if a transaction is dropped from the current pending pool (e.g. out
|
||||
// of fund), all consecutive (still valid, but not executable) transactions are
|
||||
// postponed back into the future queue to prevent broadcasting them.
|
||||
func TestTransactionPostponing(t *testing.T) {
|
||||
func TestPostponing(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create the pool to test the postponing with
|
||||
@@ -759,18 +761,18 @@ func TestTransactionPostponing(t *testing.T) {
|
||||
// Tests that if the transaction pool has both executable and non-executable
|
||||
// transactions from an origin account, filling the nonce gap moves all queued
|
||||
// ones into the pending pool.
|
||||
func TestTransactionGapFilling(t *testing.T) {
|
||||
func TestGapFilling(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create a test account and fund it
|
||||
pool, key := setupTxPool()
|
||||
pool, key := setupPool()
|
||||
defer pool.Stop()
|
||||
|
||||
account := crypto.PubkeyToAddress(key.PublicKey)
|
||||
testAddBalance(pool, account, big.NewInt(1000000))
|
||||
|
||||
// Keep track of transaction events to ensure all executables get announced
|
||||
events := make(chan NewTxsEvent, testTxPoolConfig.AccountQueue+5)
|
||||
events := make(chan core.NewTxsEvent, testTxPoolConfig.AccountQueue+5)
|
||||
sub := pool.txFeed.Subscribe(events)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
@@ -789,7 +791,7 @@ func TestTransactionGapFilling(t *testing.T) {
|
||||
if err := validateEvents(events, 1); err != nil {
|
||||
t.Fatalf("original event firing failed: %v", err)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
if err := validatePoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
// Fill the nonce gap and ensure all transactions become pending
|
||||
@@ -806,18 +808,18 @@ func TestTransactionGapFilling(t *testing.T) {
|
||||
if err := validateEvents(events, 2); err != nil {
|
||||
t.Fatalf("gap-filling event firing failed: %v", err)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
if err := validatePoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that if the transaction count belonging to a single account goes above
|
||||
// some threshold, the higher transactions are dropped to prevent DOS attacks.
|
||||
func TestTransactionQueueAccountLimiting(t *testing.T) {
|
||||
func TestQueueAccountLimiting(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create a test account and fund it
|
||||
pool, key := setupTxPool()
|
||||
pool, key := setupPool()
|
||||
defer pool.Stop()
|
||||
|
||||
account := crypto.PubkeyToAddress(key.PublicKey)
|
||||
@@ -851,14 +853,14 @@ func TestTransactionQueueAccountLimiting(t *testing.T) {
|
||||
//
|
||||
// This logic should not hold for local transactions, unless the local tracking
|
||||
// mechanism is disabled.
|
||||
func TestTransactionQueueGlobalLimiting(t *testing.T) {
|
||||
testTransactionQueueGlobalLimiting(t, false)
|
||||
func TestQueueGlobalLimiting(t *testing.T) {
|
||||
testQueueGlobalLimiting(t, false)
|
||||
}
|
||||
func TestTransactionQueueGlobalLimitingNoLocals(t *testing.T) {
|
||||
testTransactionQueueGlobalLimiting(t, true)
|
||||
func TestQueueGlobalLimitingNoLocals(t *testing.T) {
|
||||
testQueueGlobalLimiting(t, true)
|
||||
}
|
||||
|
||||
func testTransactionQueueGlobalLimiting(t *testing.T, nolocals bool) {
|
||||
func testQueueGlobalLimiting(t *testing.T, nolocals bool) {
|
||||
t.Parallel()
|
||||
|
||||
// Create the pool to test the limit enforcement with
|
||||
@@ -941,14 +943,14 @@ func testTransactionQueueGlobalLimiting(t *testing.T, nolocals bool) {
|
||||
//
|
||||
// This logic should not hold for local transactions, unless the local tracking
|
||||
// mechanism is disabled.
|
||||
func TestTransactionQueueTimeLimiting(t *testing.T) {
|
||||
testTransactionQueueTimeLimiting(t, false)
|
||||
func TestQueueTimeLimiting(t *testing.T) {
|
||||
testQueueTimeLimiting(t, false)
|
||||
}
|
||||
func TestTransactionQueueTimeLimitingNoLocals(t *testing.T) {
|
||||
testTransactionQueueTimeLimiting(t, true)
|
||||
func TestQueueTimeLimitingNoLocals(t *testing.T) {
|
||||
testQueueTimeLimiting(t, true)
|
||||
}
|
||||
|
||||
func testTransactionQueueTimeLimiting(t *testing.T, nolocals bool) {
|
||||
func testQueueTimeLimiting(t *testing.T, nolocals bool) {
|
||||
// Reduce the eviction interval to a testable amount
|
||||
defer func(old time.Duration) { evictionInterval = old }(evictionInterval)
|
||||
evictionInterval = time.Millisecond * 100
|
||||
@@ -985,7 +987,7 @@ func testTransactionQueueTimeLimiting(t *testing.T, nolocals bool) {
|
||||
if queued != 2 {
|
||||
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
if err := validatePoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
|
||||
@@ -1000,7 +1002,7 @@ func testTransactionQueueTimeLimiting(t *testing.T, nolocals bool) {
|
||||
if queued != 2 {
|
||||
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
if err := validatePoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
|
||||
@@ -1020,7 +1022,7 @@ func testTransactionQueueTimeLimiting(t *testing.T, nolocals bool) {
|
||||
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1)
|
||||
}
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
if err := validatePoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
|
||||
@@ -1037,7 +1039,7 @@ func testTransactionQueueTimeLimiting(t *testing.T, nolocals bool) {
|
||||
if queued != 0 {
|
||||
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
if err := validatePoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
|
||||
@@ -1067,7 +1069,7 @@ func testTransactionQueueTimeLimiting(t *testing.T, nolocals bool) {
|
||||
if queued != 2 {
|
||||
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 3)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
if err := validatePoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
|
||||
@@ -1086,7 +1088,7 @@ func testTransactionQueueTimeLimiting(t *testing.T, nolocals bool) {
|
||||
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1)
|
||||
}
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
if err := validatePoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -1094,18 +1096,18 @@ func testTransactionQueueTimeLimiting(t *testing.T, nolocals bool) {
|
||||
// Tests that even if the transaction count belonging to a single account goes
|
||||
// above some threshold, as long as the transactions are executable, they are
|
||||
// accepted.
|
||||
func TestTransactionPendingLimiting(t *testing.T) {
|
||||
func TestPendingLimiting(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create a test account and fund it
|
||||
pool, key := setupTxPool()
|
||||
pool, key := setupPool()
|
||||
defer pool.Stop()
|
||||
|
||||
account := crypto.PubkeyToAddress(key.PublicKey)
|
||||
testAddBalance(pool, account, big.NewInt(1000000))
|
||||
|
||||
// Keep track of transaction events to ensure all executables get announced
|
||||
events := make(chan NewTxsEvent, testTxPoolConfig.AccountQueue+5)
|
||||
events := make(chan core.NewTxsEvent, testTxPoolConfig.AccountQueue+5)
|
||||
sub := pool.txFeed.Subscribe(events)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
@@ -1127,7 +1129,7 @@ func TestTransactionPendingLimiting(t *testing.T) {
|
||||
if err := validateEvents(events, int(testTxPoolConfig.AccountQueue+5)); err != nil {
|
||||
t.Fatalf("event firing failed: %v", err)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
if err := validatePoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -1135,7 +1137,7 @@ func TestTransactionPendingLimiting(t *testing.T) {
|
||||
// Tests that if the transaction count belonging to multiple accounts go above
|
||||
// some hard threshold, the higher transactions are dropped to prevent DOS
|
||||
// attacks.
|
||||
func TestTransactionPendingGlobalLimiting(t *testing.T) {
|
||||
func TestPendingGlobalLimiting(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create the pool to test the limit enforcement with
|
||||
@@ -1175,7 +1177,7 @@ func TestTransactionPendingGlobalLimiting(t *testing.T) {
|
||||
if pending > int(config.GlobalSlots) {
|
||||
t.Fatalf("total pending transactions overflow allowance: %d > %d", pending, config.GlobalSlots)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
if err := validatePoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -1183,11 +1185,11 @@ func TestTransactionPendingGlobalLimiting(t *testing.T) {
|
||||
// Test the limit on transaction size is enforced correctly.
|
||||
// This test verifies every transaction having allowed size
|
||||
// is added to the pool, and longer transactions are rejected.
|
||||
func TestTransactionAllowedTxSize(t *testing.T) {
|
||||
func TestAllowedTxSize(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create a test account and fund it
|
||||
pool, key := setupTxPool()
|
||||
pool, key := setupPool()
|
||||
defer pool.Stop()
|
||||
|
||||
account := crypto.PubkeyToAddress(key.PublicKey)
|
||||
@@ -1231,13 +1233,13 @@ func TestTransactionAllowedTxSize(t *testing.T) {
|
||||
if queued != 0 {
|
||||
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
if err := validatePoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that if transactions start being capped, transactions are also removed from 'all'
|
||||
func TestTransactionCapClearsFromAll(t *testing.T) {
|
||||
func TestCapClearsFromAll(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create the pool to test the limit enforcement with
|
||||
@@ -1263,7 +1265,7 @@ func TestTransactionCapClearsFromAll(t *testing.T) {
|
||||
}
|
||||
// Import the batch and verify that limits have been enforced
|
||||
pool.AddRemotes(txs)
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
if err := validatePoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -1271,7 +1273,7 @@ func TestTransactionCapClearsFromAll(t *testing.T) {
|
||||
// Tests that if the transaction count belonging to multiple accounts go above
|
||||
// some hard threshold, if they are under the minimum guaranteed slot count then
|
||||
// the transactions are still kept.
|
||||
func TestTransactionPendingMinimumAllowance(t *testing.T) {
|
||||
func TestPendingMinimumAllowance(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create the pool to test the limit enforcement with
|
||||
@@ -1309,7 +1311,7 @@ func TestTransactionPendingMinimumAllowance(t *testing.T) {
|
||||
t.Errorf("addr %x: total pending transactions mismatch: have %d, want %d", addr, list.Len(), config.AccountSlots)
|
||||
}
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
if err := validatePoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -1319,7 +1321,7 @@ func TestTransactionPendingMinimumAllowance(t *testing.T) {
|
||||
// from the pending pool to the queue.
|
||||
//
|
||||
// Note, local transactions are never allowed to be dropped.
|
||||
func TestTransactionPoolRepricing(t *testing.T) {
|
||||
func TestRepricing(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create the pool to test the pricing enforcement with
|
||||
@@ -1330,7 +1332,7 @@ func TestTransactionPoolRepricing(t *testing.T) {
|
||||
defer pool.Stop()
|
||||
|
||||
// Keep track of transaction events to ensure all executables get announced
|
||||
events := make(chan NewTxsEvent, 32)
|
||||
events := make(chan core.NewTxsEvent, 32)
|
||||
sub := pool.txFeed.Subscribe(events)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
@@ -1371,7 +1373,7 @@ func TestTransactionPoolRepricing(t *testing.T) {
|
||||
if err := validateEvents(events, 7); err != nil {
|
||||
t.Fatalf("original event firing failed: %v", err)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
if err := validatePoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
// Reprice the pool and check that underpriced transactions get dropped
|
||||
@@ -1387,7 +1389,7 @@ func TestTransactionPoolRepricing(t *testing.T) {
|
||||
if err := validateEvents(events, 0); err != nil {
|
||||
t.Fatalf("reprice event firing failed: %v", err)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
if err := validatePoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
// Check that we can't add the old transactions back
|
||||
@@ -1403,7 +1405,7 @@ func TestTransactionPoolRepricing(t *testing.T) {
|
||||
if err := validateEvents(events, 0); err != nil {
|
||||
t.Fatalf("post-reprice event firing failed: %v", err)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
if err := validatePoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
// However we can add local underpriced transactions
|
||||
@@ -1417,7 +1419,7 @@ func TestTransactionPoolRepricing(t *testing.T) {
|
||||
if err := validateEvents(events, 1); err != nil {
|
||||
t.Fatalf("post-reprice local event firing failed: %v", err)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
if err := validatePoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
// And we can fill gaps with properly priced transactions
|
||||
@@ -1433,7 +1435,7 @@ func TestTransactionPoolRepricing(t *testing.T) {
|
||||
if err := validateEvents(events, 5); err != nil {
|
||||
t.Fatalf("post-reprice event firing failed: %v", err)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
if err := validatePoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -1443,15 +1445,15 @@ func TestTransactionPoolRepricing(t *testing.T) {
|
||||
// gapped transactions back from the pending pool to the queue.
|
||||
//
|
||||
// Note, local transactions are never allowed to be dropped.
|
||||
func TestTransactionPoolRepricingDynamicFee(t *testing.T) {
|
||||
func TestRepricingDynamicFee(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create the pool to test the pricing enforcement with
|
||||
pool, _ := setupTxPoolWithConfig(eip1559Config)
|
||||
pool, _ := setupPoolWithConfig(eip1559Config)
|
||||
defer pool.Stop()
|
||||
|
||||
// Keep track of transaction events to ensure all executables get announced
|
||||
events := make(chan NewTxsEvent, 32)
|
||||
events := make(chan core.NewTxsEvent, 32)
|
||||
sub := pool.txFeed.Subscribe(events)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
@@ -1492,7 +1494,7 @@ func TestTransactionPoolRepricingDynamicFee(t *testing.T) {
|
||||
if err := validateEvents(events, 7); err != nil {
|
||||
t.Fatalf("original event firing failed: %v", err)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
if err := validatePoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
// Reprice the pool and check that underpriced transactions get dropped
|
||||
@@ -1508,7 +1510,7 @@ func TestTransactionPoolRepricingDynamicFee(t *testing.T) {
|
||||
if err := validateEvents(events, 0); err != nil {
|
||||
t.Fatalf("reprice event firing failed: %v", err)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
if err := validatePoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
// Check that we can't add the old transactions back
|
||||
@@ -1527,7 +1529,7 @@ func TestTransactionPoolRepricingDynamicFee(t *testing.T) {
|
||||
if err := validateEvents(events, 0); err != nil {
|
||||
t.Fatalf("post-reprice event firing failed: %v", err)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
if err := validatePoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
// However we can add local underpriced transactions
|
||||
@@ -1541,7 +1543,7 @@ func TestTransactionPoolRepricingDynamicFee(t *testing.T) {
|
||||
if err := validateEvents(events, 1); err != nil {
|
||||
t.Fatalf("post-reprice local event firing failed: %v", err)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
if err := validatePoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
// And we can fill gaps with properly priced transactions
|
||||
@@ -1560,14 +1562,14 @@ func TestTransactionPoolRepricingDynamicFee(t *testing.T) {
|
||||
if err := validateEvents(events, 5); err != nil {
|
||||
t.Fatalf("post-reprice event firing failed: %v", err)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
if err := validatePoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that setting the transaction pool gas price to a higher value does not
|
||||
// remove local transactions (legacy & dynamic fee).
|
||||
func TestTransactionPoolRepricingKeepsLocals(t *testing.T) {
|
||||
func TestRepricingKeepsLocals(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create the pool to test the pricing enforcement with
|
||||
@@ -1618,7 +1620,7 @@ func TestTransactionPoolRepricingKeepsLocals(t *testing.T) {
|
||||
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, expQueued)
|
||||
}
|
||||
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
if err := validatePoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -1640,7 +1642,7 @@ func TestTransactionPoolRepricingKeepsLocals(t *testing.T) {
|
||||
// pending transactions are moved into the queue.
|
||||
//
|
||||
// Note, local transactions are never allowed to be dropped.
|
||||
func TestTransactionPoolUnderpricing(t *testing.T) {
|
||||
func TestUnderpricing(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create the pool to test the pricing enforcement with
|
||||
@@ -1655,7 +1657,7 @@ func TestTransactionPoolUnderpricing(t *testing.T) {
|
||||
defer pool.Stop()
|
||||
|
||||
// Keep track of transaction events to ensure all executables get announced
|
||||
events := make(chan NewTxsEvent, 32)
|
||||
events := make(chan core.NewTxsEvent, 32)
|
||||
sub := pool.txFeed.Subscribe(events)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
@@ -1689,7 +1691,7 @@ func TestTransactionPoolUnderpricing(t *testing.T) {
|
||||
if err := validateEvents(events, 3); err != nil {
|
||||
t.Fatalf("original event firing failed: %v", err)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
if err := validatePoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
// Ensure that adding an underpriced transaction on block limit fails
|
||||
@@ -1716,7 +1718,7 @@ func TestTransactionPoolUnderpricing(t *testing.T) {
|
||||
if err := validateEvents(events, 1); err != nil {
|
||||
t.Fatalf("additional event firing failed: %v", err)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
if err := validatePoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
// Ensure that adding local transactions can push out even higher priced ones
|
||||
@@ -1738,7 +1740,7 @@ func TestTransactionPoolUnderpricing(t *testing.T) {
|
||||
if err := validateEvents(events, 2); err != nil {
|
||||
t.Fatalf("local event firing failed: %v", err)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
if err := validatePoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -1746,7 +1748,7 @@ func TestTransactionPoolUnderpricing(t *testing.T) {
|
||||
// Tests that more expensive transactions push out cheap ones from the pool, but
|
||||
// without producing instability by creating gaps that start jumping transactions
|
||||
// back and forth between queued/pending.
|
||||
func TestTransactionPoolStableUnderpricing(t *testing.T) {
|
||||
func TestStableUnderpricing(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create the pool to test the pricing enforcement with
|
||||
@@ -1761,7 +1763,7 @@ func TestTransactionPoolStableUnderpricing(t *testing.T) {
|
||||
defer pool.Stop()
|
||||
|
||||
// Keep track of transaction events to ensure all executables get announced
|
||||
events := make(chan NewTxsEvent, 32)
|
||||
events := make(chan core.NewTxsEvent, 32)
|
||||
sub := pool.txFeed.Subscribe(events)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
@@ -1788,7 +1790,7 @@ func TestTransactionPoolStableUnderpricing(t *testing.T) {
|
||||
if err := validateEvents(events, int(config.GlobalSlots)); err != nil {
|
||||
t.Fatalf("original event firing failed: %v", err)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
if err := validatePoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
// Ensure that adding high priced transactions drops a cheap, but doesn't produce a gap
|
||||
@@ -1805,7 +1807,7 @@ func TestTransactionPoolStableUnderpricing(t *testing.T) {
|
||||
if err := validateEvents(events, 1); err != nil {
|
||||
t.Fatalf("additional event firing failed: %v", err)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
if err := validatePoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -1815,17 +1817,17 @@ func TestTransactionPoolStableUnderpricing(t *testing.T) {
|
||||
// expensive ones and any gapped pending transactions are moved into the queue.
|
||||
//
|
||||
// Note, local transactions are never allowed to be dropped.
|
||||
func TestTransactionPoolUnderpricingDynamicFee(t *testing.T) {
|
||||
func TestUnderpricingDynamicFee(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pool, _ := setupTxPoolWithConfig(eip1559Config)
|
||||
pool, _ := setupPoolWithConfig(eip1559Config)
|
||||
defer pool.Stop()
|
||||
|
||||
pool.config.GlobalSlots = 2
|
||||
pool.config.GlobalQueue = 2
|
||||
|
||||
// Keep track of transaction events to ensure all executables get announced
|
||||
events := make(chan NewTxsEvent, 32)
|
||||
events := make(chan core.NewTxsEvent, 32)
|
||||
sub := pool.txFeed.Subscribe(events)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
@@ -1859,7 +1861,7 @@ func TestTransactionPoolUnderpricingDynamicFee(t *testing.T) {
|
||||
if err := validateEvents(events, 3); err != nil {
|
||||
t.Fatalf("original event firing failed: %v", err)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
if err := validatePoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
|
||||
@@ -1893,7 +1895,7 @@ func TestTransactionPoolUnderpricingDynamicFee(t *testing.T) {
|
||||
if err := validateEvents(events, 1); err != nil {
|
||||
t.Fatalf("additional event firing failed: %v", err)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
if err := validatePoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
// Ensure that adding local transactions can push out even higher priced ones
|
||||
@@ -1915,7 +1917,7 @@ func TestTransactionPoolUnderpricingDynamicFee(t *testing.T) {
|
||||
if err := validateEvents(events, 2); err != nil {
|
||||
t.Fatalf("local event firing failed: %v", err)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
if err := validatePoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -1925,7 +1927,7 @@ func TestTransactionPoolUnderpricingDynamicFee(t *testing.T) {
|
||||
func TestDualHeapEviction(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pool, _ := setupTxPoolWithConfig(eip1559Config)
|
||||
pool, _ := setupPoolWithConfig(eip1559Config)
|
||||
defer pool.Stop()
|
||||
|
||||
pool.config.GlobalSlots = 10
|
||||
@@ -1972,13 +1974,13 @@ func TestDualHeapEviction(t *testing.T) {
|
||||
check(highTip, "effective tip")
|
||||
}
|
||||
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
if err := validatePoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that the pool rejects duplicate transactions.
|
||||
func TestTransactionDeduplication(t *testing.T) {
|
||||
func TestDeduplication(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create the pool to test the pricing enforcement with
|
||||
@@ -2037,14 +2039,14 @@ func TestTransactionDeduplication(t *testing.T) {
|
||||
if queued != 0 {
|
||||
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
if err := validatePoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that the pool rejects replacement transactions that don't meet the minimum
|
||||
// price bump required.
|
||||
func TestTransactionReplacement(t *testing.T) {
|
||||
func TestReplacement(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create the pool to test the pricing enforcement with
|
||||
@@ -2055,7 +2057,7 @@ func TestTransactionReplacement(t *testing.T) {
|
||||
defer pool.Stop()
|
||||
|
||||
// Keep track of transaction events to ensure all executables get announced
|
||||
events := make(chan NewTxsEvent, 32)
|
||||
events := make(chan core.NewTxsEvent, 32)
|
||||
sub := pool.txFeed.Subscribe(events)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
@@ -2117,23 +2119,23 @@ func TestTransactionReplacement(t *testing.T) {
|
||||
if err := validateEvents(events, 0); err != nil {
|
||||
t.Fatalf("queued replacement event firing failed: %v", err)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
if err := validatePoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that the pool rejects replacement dynamic fee transactions that don't
|
||||
// meet the minimum price bump required.
|
||||
func TestTransactionReplacementDynamicFee(t *testing.T) {
|
||||
func TestReplacementDynamicFee(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create the pool to test the pricing enforcement with
|
||||
pool, key := setupTxPoolWithConfig(eip1559Config)
|
||||
pool, key := setupPoolWithConfig(eip1559Config)
|
||||
defer pool.Stop()
|
||||
testAddBalance(pool, crypto.PubkeyToAddress(key.PublicKey), big.NewInt(1000000000))
|
||||
|
||||
// Keep track of transaction events to ensure all executables get announced
|
||||
events := make(chan NewTxsEvent, 32)
|
||||
events := make(chan core.NewTxsEvent, 32)
|
||||
sub := pool.txFeed.Subscribe(events)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
@@ -2158,7 +2160,7 @@ func TestTransactionReplacementDynamicFee(t *testing.T) {
|
||||
stages := []string{"pending", "queued"}
|
||||
for _, stage := range stages {
|
||||
// Since state is empty, 0 nonce txs are "executable" and can go
|
||||
// into pending immediately. 2 nonce txs are "happed
|
||||
// into pending immediately. 2 nonce txs are "gapped"
|
||||
nonce := uint64(0)
|
||||
if stage == "queued" {
|
||||
nonce = 2
|
||||
@@ -2227,17 +2229,17 @@ func TestTransactionReplacementDynamicFee(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
if err := validatePoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that local transactions are journaled to disk, but remote transactions
|
||||
// get discarded between restarts.
|
||||
func TestTransactionJournaling(t *testing.T) { testTransactionJournaling(t, false) }
|
||||
func TestTransactionJournalingNoLocals(t *testing.T) { testTransactionJournaling(t, true) }
|
||||
func TestJournaling(t *testing.T) { testJournaling(t, false) }
|
||||
func TestJournalingNoLocals(t *testing.T) { testJournaling(t, true) }
|
||||
|
||||
func testTransactionJournaling(t *testing.T, nolocals bool) {
|
||||
func testJournaling(t *testing.T, nolocals bool) {
|
||||
t.Parallel()
|
||||
|
||||
// Create a temporary file for the journal
|
||||
@@ -2290,7 +2292,7 @@ func testTransactionJournaling(t *testing.T, nolocals bool) {
|
||||
if queued != 0 {
|
||||
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
if err := validatePoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
// Terminate the old pool, bump the local nonce, create a new pool and ensure relevant transaction survive
|
||||
@@ -2313,7 +2315,7 @@ func testTransactionJournaling(t *testing.T, nolocals bool) {
|
||||
t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2)
|
||||
}
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
if err := validatePoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
// Bump the nonce temporarily and ensure the newly invalidated transaction is removed
|
||||
@@ -2339,15 +2341,15 @@ func testTransactionJournaling(t *testing.T, nolocals bool) {
|
||||
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1)
|
||||
}
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
if err := validatePoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
pool.Stop()
|
||||
}
|
||||
|
||||
// TestTransactionStatusCheck tests that the pool can correctly retrieve the
|
||||
// TestStatusCheck tests that the pool can correctly retrieve the
|
||||
// pending status of individual transactions.
|
||||
func TestTransactionStatusCheck(t *testing.T) {
|
||||
func TestStatusCheck(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create the pool to test the status retrievals with
|
||||
@@ -2381,7 +2383,7 @@ func TestTransactionStatusCheck(t *testing.T) {
|
||||
if queued != 2 {
|
||||
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
if err := validatePoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
// Retrieve the status of each transaction and validate them
|
||||
@@ -2402,7 +2404,7 @@ func TestTransactionStatusCheck(t *testing.T) {
|
||||
}
|
||||
|
||||
// Test the transaction slots consumption is computed correctly
|
||||
func TestTransactionSlotCount(t *testing.T) {
|
||||
func TestSlotCount(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
key, _ := crypto.GenerateKey()
|
||||
@@ -2427,7 +2429,7 @@ func BenchmarkPendingDemotion10000(b *testing.B) { benchmarkPendingDemotion(b, 1
|
||||
|
||||
func benchmarkPendingDemotion(b *testing.B, size int) {
|
||||
// Add a batch of transactions to a pool one by one
|
||||
pool, key := setupTxPool()
|
||||
pool, key := setupPool()
|
||||
defer pool.Stop()
|
||||
|
||||
account := crypto.PubkeyToAddress(key.PublicKey)
|
||||
@@ -2452,7 +2454,7 @@ func BenchmarkFuturePromotion10000(b *testing.B) { benchmarkFuturePromotion(b, 1
|
||||
|
||||
func benchmarkFuturePromotion(b *testing.B, size int) {
|
||||
// Add a batch of transactions to a pool one by one
|
||||
pool, key := setupTxPool()
|
||||
pool, key := setupPool()
|
||||
defer pool.Stop()
|
||||
|
||||
account := crypto.PubkeyToAddress(key.PublicKey)
|
||||
@@ -2470,17 +2472,17 @@ func benchmarkFuturePromotion(b *testing.B, size int) {
|
||||
}
|
||||
|
||||
// Benchmarks the speed of batched transaction insertion.
|
||||
func BenchmarkPoolBatchInsert100(b *testing.B) { benchmarkPoolBatchInsert(b, 100, false) }
|
||||
func BenchmarkPoolBatchInsert1000(b *testing.B) { benchmarkPoolBatchInsert(b, 1000, false) }
|
||||
func BenchmarkPoolBatchInsert10000(b *testing.B) { benchmarkPoolBatchInsert(b, 10000, false) }
|
||||
func BenchmarkBatchInsert100(b *testing.B) { benchmarkBatchInsert(b, 100, false) }
|
||||
func BenchmarkBatchInsert1000(b *testing.B) { benchmarkBatchInsert(b, 1000, false) }
|
||||
func BenchmarkBatchInsert10000(b *testing.B) { benchmarkBatchInsert(b, 10000, false) }
|
||||
|
||||
func BenchmarkPoolBatchLocalInsert100(b *testing.B) { benchmarkPoolBatchInsert(b, 100, true) }
|
||||
func BenchmarkPoolBatchLocalInsert1000(b *testing.B) { benchmarkPoolBatchInsert(b, 1000, true) }
|
||||
func BenchmarkPoolBatchLocalInsert10000(b *testing.B) { benchmarkPoolBatchInsert(b, 10000, true) }
|
||||
func BenchmarkBatchLocalInsert100(b *testing.B) { benchmarkBatchInsert(b, 100, true) }
|
||||
func BenchmarkBatchLocalInsert1000(b *testing.B) { benchmarkBatchInsert(b, 1000, true) }
|
||||
func BenchmarkBatchLocalInsert10000(b *testing.B) { benchmarkBatchInsert(b, 10000, true) }
|
||||
|
||||
func benchmarkPoolBatchInsert(b *testing.B, size int, local bool) {
|
||||
func benchmarkBatchInsert(b *testing.B, size int, local bool) {
|
||||
// Generate a batch of transactions to enqueue into the pool
|
||||
pool, key := setupTxPool()
|
||||
pool, key := setupPool()
|
||||
defer pool.Stop()
|
||||
|
||||
account := crypto.PubkeyToAddress(key.PublicKey)
|
||||
@@ -2524,7 +2526,7 @@ func BenchmarkInsertRemoteWithAllLocals(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
b.StopTimer()
|
||||
pool, _ := setupTxPool()
|
||||
pool, _ := setupPool()
|
||||
testAddBalance(pool, account, big.NewInt(100000000))
|
||||
for _, local := range locals {
|
||||
pool.AddLocal(local)
|
||||
@@ -2540,9 +2542,9 @@ func BenchmarkInsertRemoteWithAllLocals(b *testing.B) {
|
||||
}
|
||||
|
||||
// Benchmarks the speed of batch transaction insertion in case of multiple accounts.
|
||||
func BenchmarkPoolMultiAccountBatchInsert(b *testing.B) {
|
||||
func BenchmarkMultiAccountBatchInsert(b *testing.B) {
|
||||
// Generate a batch of transactions to enqueue into the pool
|
||||
pool, _ := setupTxPool()
|
||||
pool, _ := setupPool()
|
||||
defer pool.Stop()
|
||||
b.ReportAllocs()
|
||||
batches := make(types.Transactions, b.N)
|
||||
+70
-17
@@ -87,6 +87,9 @@ type Header struct {
|
||||
// BaseFee was added by EIP-1559 and is ignored in legacy headers.
|
||||
BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"`
|
||||
|
||||
// WithdrawalsHash was added by EIP-4895 and is ignored in legacy headers.
|
||||
WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"`
|
||||
|
||||
/*
|
||||
TODO (MariusVanDerWijden) Add this field once needed
|
||||
// Random was added during the merge and contains the BeaconState randomness
|
||||
@@ -117,7 +120,11 @@ var headerSize = common.StorageSize(reflect.TypeOf(Header{}).Size())
|
||||
// Size returns the approximate memory used by all internal contents. It is used
|
||||
// to approximate and limit the memory consumption of various caches.
|
||||
func (h *Header) Size() common.StorageSize {
|
||||
return headerSize + common.StorageSize(len(h.Extra)+(h.Difficulty.BitLen()+h.Number.BitLen())/8)
|
||||
var baseFeeBits int
|
||||
if h.BaseFee != nil {
|
||||
baseFeeBits = h.BaseFee.BitLen()
|
||||
}
|
||||
return headerSize + common.StorageSize(len(h.Extra)+(h.Difficulty.BitLen()+h.Number.BitLen()+baseFeeBits)/8)
|
||||
}
|
||||
|
||||
// SanityCheck checks a few basic things -- these checks are way beyond what
|
||||
@@ -145,9 +152,12 @@ func (h *Header) SanityCheck() error {
|
||||
}
|
||||
|
||||
// EmptyBody returns true if there is no additional 'body' to complete the header
|
||||
// that is: no transactions and no uncles.
|
||||
// that is: no transactions, no uncles and no withdrawals.
|
||||
func (h *Header) EmptyBody() bool {
|
||||
return h.TxHash == EmptyRootHash && h.UncleHash == EmptyUncleHash
|
||||
if h.WithdrawalsHash == nil {
|
||||
return h.TxHash == EmptyRootHash && h.UncleHash == EmptyUncleHash
|
||||
}
|
||||
return h.TxHash == EmptyRootHash && h.UncleHash == EmptyUncleHash && *h.WithdrawalsHash == EmptyRootHash
|
||||
}
|
||||
|
||||
// EmptyReceipts returns true if there are no receipts for this header/block.
|
||||
@@ -160,6 +170,7 @@ func (h *Header) EmptyReceipts() bool {
|
||||
type Body struct {
|
||||
Transactions []*Transaction
|
||||
Uncles []*Header
|
||||
Withdrawals []*Withdrawal `rlp:"optional"`
|
||||
}
|
||||
|
||||
// Block represents an entire block in the Ethereum blockchain.
|
||||
@@ -167,6 +178,7 @@ type Block struct {
|
||||
header *Header
|
||||
uncles []*Header
|
||||
transactions Transactions
|
||||
withdrawals Withdrawals
|
||||
|
||||
// caches
|
||||
hash atomic.Value
|
||||
@@ -180,9 +192,10 @@ type Block struct {
|
||||
|
||||
// "external" block encoding. used for eth protocol, etc.
|
||||
type extblock struct {
|
||||
Header *Header
|
||||
Txs []*Transaction
|
||||
Uncles []*Header
|
||||
Header *Header
|
||||
Txs []*Transaction
|
||||
Uncles []*Header
|
||||
Withdrawals []*Withdrawal `rlp:"optional"`
|
||||
}
|
||||
|
||||
// NewBlock creates a new block. The input data is copied,
|
||||
@@ -224,6 +237,28 @@ func NewBlock(header *Header, txs []*Transaction, uncles []*Header, receipts []*
|
||||
return b
|
||||
}
|
||||
|
||||
// NewBlockWithWithdrawals creates a new block with withdrawals. The input data
|
||||
// is copied, changes to header and to the field values will not
|
||||
// affect the block.
|
||||
//
|
||||
// The values of TxHash, UncleHash, ReceiptHash and Bloom in header
|
||||
// are ignored and set to values derived from the given txs, uncles
|
||||
// and receipts.
|
||||
func NewBlockWithWithdrawals(header *Header, txs []*Transaction, uncles []*Header, receipts []*Receipt, withdrawals []*Withdrawal, hasher TrieHasher) *Block {
|
||||
b := NewBlock(header, txs, uncles, receipts, hasher)
|
||||
|
||||
if withdrawals == nil {
|
||||
b.header.WithdrawalsHash = nil
|
||||
} else if len(withdrawals) == 0 {
|
||||
b.header.WithdrawalsHash = &EmptyRootHash
|
||||
} else {
|
||||
h := DeriveSha(Withdrawals(withdrawals), hasher)
|
||||
b.header.WithdrawalsHash = &h
|
||||
}
|
||||
|
||||
return b.WithWithdrawals(withdrawals)
|
||||
}
|
||||
|
||||
// NewBlockWithHeader creates a block with the given header data. The
|
||||
// header data is copied, changes to header and to the field values
|
||||
// will not affect the block.
|
||||
@@ -248,6 +283,9 @@ func CopyHeader(h *Header) *Header {
|
||||
cpy.Extra = make([]byte, len(h.Extra))
|
||||
copy(cpy.Extra, h.Extra)
|
||||
}
|
||||
if h.WithdrawalsHash != nil {
|
||||
*cpy.WithdrawalsHash = *h.WithdrawalsHash
|
||||
}
|
||||
return &cpy
|
||||
}
|
||||
|
||||
@@ -258,17 +296,18 @@ func (b *Block) DecodeRLP(s *rlp.Stream) error {
|
||||
if err := s.Decode(&eb); err != nil {
|
||||
return err
|
||||
}
|
||||
b.header, b.uncles, b.transactions = eb.Header, eb.Uncles, eb.Txs
|
||||
b.size.Store(common.StorageSize(rlp.ListSize(size)))
|
||||
b.header, b.uncles, b.transactions, b.withdrawals = eb.Header, eb.Uncles, eb.Txs, eb.Withdrawals
|
||||
b.size.Store(rlp.ListSize(size))
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncodeRLP serializes b into the Ethereum RLP block format.
|
||||
func (b *Block) EncodeRLP(w io.Writer) error {
|
||||
return rlp.Encode(w, extblock{
|
||||
Header: b.header,
|
||||
Txs: b.transactions,
|
||||
Uncles: b.uncles,
|
||||
Header: b.header,
|
||||
Txs: b.transactions,
|
||||
Uncles: b.uncles,
|
||||
Withdrawals: b.withdrawals,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -311,21 +350,25 @@ func (b *Block) BaseFee() *big.Int {
|
||||
return new(big.Int).Set(b.header.BaseFee)
|
||||
}
|
||||
|
||||
func (b *Block) Withdrawals() Withdrawals {
|
||||
return b.withdrawals
|
||||
}
|
||||
|
||||
func (b *Block) Header() *Header { return CopyHeader(b.header) }
|
||||
|
||||
// Body returns the non-header content of the block.
|
||||
func (b *Block) Body() *Body { return &Body{b.transactions, b.uncles} }
|
||||
func (b *Block) Body() *Body { return &Body{b.transactions, b.uncles, b.withdrawals} }
|
||||
|
||||
// Size returns the true RLP encoded storage size of the block, either by encoding
|
||||
// and returning it, or returning a previously cached value.
|
||||
func (b *Block) Size() common.StorageSize {
|
||||
func (b *Block) Size() uint64 {
|
||||
if size := b.size.Load(); size != nil {
|
||||
return size.(common.StorageSize)
|
||||
return size.(uint64)
|
||||
}
|
||||
c := writeCounter(0)
|
||||
rlp.Encode(&c, b)
|
||||
b.size.Store(common.StorageSize(c))
|
||||
return common.StorageSize(c)
|
||||
b.size.Store(uint64(c))
|
||||
return uint64(c)
|
||||
}
|
||||
|
||||
// SanityCheck can be used to prevent that unbounded fields are
|
||||
@@ -334,7 +377,7 @@ func (b *Block) SanityCheck() error {
|
||||
return b.header.SanityCheck()
|
||||
}
|
||||
|
||||
type writeCounter common.StorageSize
|
||||
type writeCounter uint64
|
||||
|
||||
func (c *writeCounter) Write(b []byte) (int, error) {
|
||||
*c += writeCounter(len(b))
|
||||
@@ -357,6 +400,7 @@ func (b *Block) WithSeal(header *Header) *Block {
|
||||
header: &cpy,
|
||||
transactions: b.transactions,
|
||||
uncles: b.uncles,
|
||||
withdrawals: b.withdrawals,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -374,6 +418,15 @@ func (b *Block) WithBody(transactions []*Transaction, uncles []*Header) *Block {
|
||||
return block
|
||||
}
|
||||
|
||||
// WithWithdrawals sets the withdrawal contents of a block, does not return a new block.
|
||||
func (b *Block) WithWithdrawals(withdrawals []*Withdrawal) *Block {
|
||||
if withdrawals != nil {
|
||||
b.withdrawals = make([]*Withdrawal, len(withdrawals))
|
||||
copy(b.withdrawals, withdrawals)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Hash returns the keccak256 hash of b's header.
|
||||
// The hash is computed on the first call and cached thereafter.
|
||||
func (b *Block) Hash() common.Hash {
|
||||
|
||||
@@ -53,7 +53,7 @@ func TestBlockEncoding(t *testing.T) {
|
||||
check("Hash", block.Hash(), common.HexToHash("0a5843ac1cb04865017cb35a57b50b07084e5fcee39b5acadade33149f4fff9e"))
|
||||
check("Nonce", block.Nonce(), uint64(0xa13a5a8c8f2bb1c4))
|
||||
check("Time", block.Time(), uint64(1426516743))
|
||||
check("Size", block.Size(), common.StorageSize(len(blockEnc)))
|
||||
check("Size", block.Size(), uint64(len(blockEnc)))
|
||||
|
||||
tx1 := NewTransaction(0, common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"), big.NewInt(10), 50000, big.NewInt(10), nil)
|
||||
tx1, _ = tx1.WithSignature(HomesteadSigner{}, common.Hex2Bytes("9bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094f8a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b100"))
|
||||
@@ -90,7 +90,7 @@ func TestEIP1559BlockEncoding(t *testing.T) {
|
||||
check("Hash", block.Hash(), common.HexToHash("c7252048cd273fe0dac09650027d07f0e3da4ee0675ebbb26627cea92729c372"))
|
||||
check("Nonce", block.Nonce(), uint64(0xa13a5a8c8f2bb1c4))
|
||||
check("Time", block.Time(), uint64(1426516743))
|
||||
check("Size", block.Size(), common.StorageSize(len(blockEnc)))
|
||||
check("Size", block.Size(), uint64(len(blockEnc)))
|
||||
check("BaseFee", block.BaseFee(), new(big.Int).SetUint64(params.InitialBaseFee))
|
||||
|
||||
tx1 := NewTransaction(0, common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"), big.NewInt(10), 50000, big.NewInt(10), nil)
|
||||
@@ -153,7 +153,7 @@ func TestEIP2718BlockEncoding(t *testing.T) {
|
||||
check("Root", block.Root(), common.HexToHash("ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017"))
|
||||
check("Nonce", block.Nonce(), uint64(0xa13a5a8c8f2bb1c4))
|
||||
check("Time", block.Time(), uint64(1426516743))
|
||||
check("Size", block.Size(), common.StorageSize(len(blockEnc)))
|
||||
check("Size", block.Size(), uint64(len(blockEnc)))
|
||||
|
||||
// Create legacy tx.
|
||||
to := common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87")
|
||||
|
||||
@@ -16,23 +16,24 @@ var _ = (*headerMarshaling)(nil)
|
||||
// MarshalJSON marshals as JSON.
|
||||
func (h Header) MarshalJSON() ([]byte, error) {
|
||||
type Header struct {
|
||||
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
|
||||
UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"`
|
||||
Coinbase common.Address `json:"miner"`
|
||||
Root common.Hash `json:"stateRoot" gencodec:"required"`
|
||||
TxHash common.Hash `json:"transactionsRoot" gencodec:"required"`
|
||||
ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"`
|
||||
Bloom Bloom `json:"logsBloom" gencodec:"required"`
|
||||
Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"`
|
||||
Number *hexutil.Big `json:"number" gencodec:"required"`
|
||||
GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
|
||||
GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
|
||||
Time hexutil.Uint64 `json:"timestamp" gencodec:"required"`
|
||||
Extra hexutil.Bytes `json:"extraData" gencodec:"required"`
|
||||
MixDigest common.Hash `json:"mixHash"`
|
||||
Nonce BlockNonce `json:"nonce"`
|
||||
BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"`
|
||||
Hash common.Hash `json:"hash"`
|
||||
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
|
||||
UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"`
|
||||
Coinbase common.Address `json:"miner"`
|
||||
Root common.Hash `json:"stateRoot" gencodec:"required"`
|
||||
TxHash common.Hash `json:"transactionsRoot" gencodec:"required"`
|
||||
ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"`
|
||||
Bloom Bloom `json:"logsBloom" gencodec:"required"`
|
||||
Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"`
|
||||
Number *hexutil.Big `json:"number" gencodec:"required"`
|
||||
GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
|
||||
GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
|
||||
Time hexutil.Uint64 `json:"timestamp" gencodec:"required"`
|
||||
Extra hexutil.Bytes `json:"extraData" gencodec:"required"`
|
||||
MixDigest common.Hash `json:"mixHash"`
|
||||
Nonce BlockNonce `json:"nonce"`
|
||||
BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"`
|
||||
WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"`
|
||||
Hash common.Hash `json:"hash"`
|
||||
}
|
||||
var enc Header
|
||||
enc.ParentHash = h.ParentHash
|
||||
@@ -51,6 +52,7 @@ func (h Header) MarshalJSON() ([]byte, error) {
|
||||
enc.MixDigest = h.MixDigest
|
||||
enc.Nonce = h.Nonce
|
||||
enc.BaseFee = (*hexutil.Big)(h.BaseFee)
|
||||
enc.WithdrawalsHash = h.WithdrawalsHash
|
||||
enc.Hash = h.Hash()
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
@@ -58,22 +60,23 @@ func (h Header) MarshalJSON() ([]byte, error) {
|
||||
// UnmarshalJSON unmarshals from JSON.
|
||||
func (h *Header) UnmarshalJSON(input []byte) error {
|
||||
type Header struct {
|
||||
ParentHash *common.Hash `json:"parentHash" gencodec:"required"`
|
||||
UncleHash *common.Hash `json:"sha3Uncles" gencodec:"required"`
|
||||
Coinbase *common.Address `json:"miner"`
|
||||
Root *common.Hash `json:"stateRoot" gencodec:"required"`
|
||||
TxHash *common.Hash `json:"transactionsRoot" gencodec:"required"`
|
||||
ReceiptHash *common.Hash `json:"receiptsRoot" gencodec:"required"`
|
||||
Bloom *Bloom `json:"logsBloom" gencodec:"required"`
|
||||
Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"`
|
||||
Number *hexutil.Big `json:"number" gencodec:"required"`
|
||||
GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
|
||||
GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
|
||||
Time *hexutil.Uint64 `json:"timestamp" gencodec:"required"`
|
||||
Extra *hexutil.Bytes `json:"extraData" gencodec:"required"`
|
||||
MixDigest *common.Hash `json:"mixHash"`
|
||||
Nonce *BlockNonce `json:"nonce"`
|
||||
BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"`
|
||||
ParentHash *common.Hash `json:"parentHash" gencodec:"required"`
|
||||
UncleHash *common.Hash `json:"sha3Uncles" gencodec:"required"`
|
||||
Coinbase *common.Address `json:"miner"`
|
||||
Root *common.Hash `json:"stateRoot" gencodec:"required"`
|
||||
TxHash *common.Hash `json:"transactionsRoot" gencodec:"required"`
|
||||
ReceiptHash *common.Hash `json:"receiptsRoot" gencodec:"required"`
|
||||
Bloom *Bloom `json:"logsBloom" gencodec:"required"`
|
||||
Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"`
|
||||
Number *hexutil.Big `json:"number" gencodec:"required"`
|
||||
GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
|
||||
GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
|
||||
Time *hexutil.Uint64 `json:"timestamp" gencodec:"required"`
|
||||
Extra *hexutil.Bytes `json:"extraData" gencodec:"required"`
|
||||
MixDigest *common.Hash `json:"mixHash"`
|
||||
Nonce *BlockNonce `json:"nonce"`
|
||||
BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"`
|
||||
WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"`
|
||||
}
|
||||
var dec Header
|
||||
if err := json.Unmarshal(input, &dec); err != nil {
|
||||
@@ -139,5 +142,8 @@ func (h *Header) UnmarshalJSON(input []byte) error {
|
||||
if dec.BaseFee != nil {
|
||||
h.BaseFee = (*big.Int)(dec.BaseFee)
|
||||
}
|
||||
if dec.WithdrawalsHash != nil {
|
||||
h.WithdrawalsHash = dec.WithdrawalsHash
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -41,7 +41,8 @@ func (obj *Header) EncodeRLP(_w io.Writer) error {
|
||||
w.WriteBytes(obj.MixDigest[:])
|
||||
w.WriteBytes(obj.Nonce[:])
|
||||
_tmp1 := obj.BaseFee != nil
|
||||
if _tmp1 {
|
||||
_tmp2 := obj.WithdrawalsHash != nil
|
||||
if _tmp1 || _tmp2 {
|
||||
if obj.BaseFee == nil {
|
||||
w.Write(rlp.EmptyString)
|
||||
} else {
|
||||
@@ -51,6 +52,13 @@ func (obj *Header) EncodeRLP(_w io.Writer) error {
|
||||
w.WriteBigInt(obj.BaseFee)
|
||||
}
|
||||
}
|
||||
if _tmp2 {
|
||||
if obj.WithdrawalsHash == nil {
|
||||
w.Write([]byte{0x80})
|
||||
} else {
|
||||
w.WriteBytes(obj.WithdrawalsHash[:])
|
||||
}
|
||||
}
|
||||
w.ListEnd(_tmp0)
|
||||
return w.Flush()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
)
|
||||
|
||||
var _ = (*withdrawalMarshaling)(nil)
|
||||
|
||||
// MarshalJSON marshals as JSON.
|
||||
func (w Withdrawal) MarshalJSON() ([]byte, error) {
|
||||
type Withdrawal struct {
|
||||
Index hexutil.Uint64 `json:"index"`
|
||||
Validator hexutil.Uint64 `json:"validatorIndex"`
|
||||
Address common.Address `json:"address"`
|
||||
Amount hexutil.Uint64 `json:"amount"`
|
||||
}
|
||||
var enc Withdrawal
|
||||
enc.Index = hexutil.Uint64(w.Index)
|
||||
enc.Validator = hexutil.Uint64(w.Validator)
|
||||
enc.Address = w.Address
|
||||
enc.Amount = hexutil.Uint64(w.Amount)
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals from JSON.
|
||||
func (w *Withdrawal) UnmarshalJSON(input []byte) error {
|
||||
type Withdrawal struct {
|
||||
Index *hexutil.Uint64 `json:"index"`
|
||||
Validator *hexutil.Uint64 `json:"validatorIndex"`
|
||||
Address *common.Address `json:"address"`
|
||||
Amount *hexutil.Uint64 `json:"amount"`
|
||||
}
|
||||
var dec Withdrawal
|
||||
if err := json.Unmarshal(input, &dec); err != nil {
|
||||
return err
|
||||
}
|
||||
if dec.Index != nil {
|
||||
w.Index = uint64(*dec.Index)
|
||||
}
|
||||
if dec.Validator != nil {
|
||||
w.Validator = uint64(*dec.Validator)
|
||||
}
|
||||
if dec.Address != nil {
|
||||
w.Address = *dec.Address
|
||||
}
|
||||
if dec.Amount != nil {
|
||||
w.Amount = uint64(*dec.Amount)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Code generated by rlpgen. DO NOT EDIT.
|
||||
|
||||
//go:build !norlpgen
|
||||
// +build !norlpgen
|
||||
|
||||
package types
|
||||
|
||||
import "github.com/ethereum/go-ethereum/rlp"
|
||||
import "io"
|
||||
|
||||
func (obj *Withdrawal) EncodeRLP(_w io.Writer) error {
|
||||
w := rlp.NewEncoderBuffer(_w)
|
||||
_tmp0 := w.List()
|
||||
w.WriteUint64(obj.Index)
|
||||
w.WriteUint64(obj.Validator)
|
||||
w.WriteBytes(obj.Address[:])
|
||||
w.WriteUint64(obj.Amount)
|
||||
w.ListEnd(_tmp0)
|
||||
return w.Flush()
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
// Copyright 2022 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 types
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
// IsLegacyStoredReceipts tries to parse the RLP-encoded blob
|
||||
// first as an array of v3 stored receipt, then v4 stored receipt and
|
||||
// returns true if successful.
|
||||
func IsLegacyStoredReceipts(raw []byte) (bool, error) {
|
||||
var v3 []v3StoredReceiptRLP
|
||||
if err := rlp.DecodeBytes(raw, &v3); err == nil {
|
||||
return true, nil
|
||||
}
|
||||
var v4 []v4StoredReceiptRLP
|
||||
if err := rlp.DecodeBytes(raw, &v4); err == nil {
|
||||
return true, nil
|
||||
}
|
||||
var v5 []storedReceiptRLP
|
||||
// Check to see valid fresh stored receipt
|
||||
if err := rlp.DecodeBytes(raw, &v5); err == nil {
|
||||
return false, nil
|
||||
}
|
||||
return false, errors.New("value is not a valid receipt encoding")
|
||||
}
|
||||
|
||||
// ConvertLegacyStoredReceipts takes the RLP encoding of an array of legacy
|
||||
// stored receipts and returns a fresh RLP-encoded stored receipt.
|
||||
func ConvertLegacyStoredReceipts(raw []byte) ([]byte, error) {
|
||||
var receipts []ReceiptForStorage
|
||||
if err := rlp.DecodeBytes(raw, &receipts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rlp.EncodeToBytes(&receipts)
|
||||
}
|
||||
+1
-53
@@ -64,24 +64,13 @@ type logMarshaling struct {
|
||||
|
||||
//go:generate go run ../../rlp/rlpgen -type rlpLog -out gen_log_rlp.go
|
||||
|
||||
// rlpLog is used to RLP-encode both the consensus and storage formats.
|
||||
type rlpLog struct {
|
||||
Address common.Address
|
||||
Topics []common.Hash
|
||||
Data []byte
|
||||
}
|
||||
|
||||
// legacyRlpStorageLog is the previous storage encoding of a log including some redundant fields.
|
||||
type legacyRlpStorageLog struct {
|
||||
Address common.Address
|
||||
Topics []common.Hash
|
||||
Data []byte
|
||||
BlockNumber uint64
|
||||
TxHash common.Hash
|
||||
TxIndex uint
|
||||
BlockHash common.Hash
|
||||
Index uint
|
||||
}
|
||||
|
||||
// EncodeRLP implements rlp.Encoder.
|
||||
func (l *Log) EncodeRLP(w io.Writer) error {
|
||||
rl := rlpLog{Address: l.Address, Topics: l.Topics, Data: l.Data}
|
||||
@@ -97,44 +86,3 @@ func (l *Log) DecodeRLP(s *rlp.Stream) error {
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// LogForStorage is a wrapper around a Log that handles
|
||||
// backward compatibility with prior storage formats.
|
||||
type LogForStorage Log
|
||||
|
||||
// EncodeRLP implements rlp.Encoder.
|
||||
func (l *LogForStorage) EncodeRLP(w io.Writer) error {
|
||||
rl := rlpLog{Address: l.Address, Topics: l.Topics, Data: l.Data}
|
||||
return rlp.Encode(w, &rl)
|
||||
}
|
||||
|
||||
// DecodeRLP implements rlp.Decoder.
|
||||
//
|
||||
// Note some redundant fields(e.g. block number, tx hash etc) will be assembled later.
|
||||
func (l *LogForStorage) DecodeRLP(s *rlp.Stream) error {
|
||||
blob, err := s.Raw()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var dec rlpLog
|
||||
err = rlp.DecodeBytes(blob, &dec)
|
||||
if err == nil {
|
||||
*l = LogForStorage{
|
||||
Address: dec.Address,
|
||||
Topics: dec.Topics,
|
||||
Data: dec.Data,
|
||||
}
|
||||
} else {
|
||||
// Try to decode log with previous definition.
|
||||
var dec legacyRlpStorageLog
|
||||
err = rlp.DecodeBytes(blob, &dec)
|
||||
if err == nil {
|
||||
*l = LogForStorage{
|
||||
Address: dec.Address,
|
||||
Topics: dec.Topics,
|
||||
Data: dec.Data,
|
||||
}
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
+3
-86
@@ -93,28 +93,7 @@ type receiptRLP struct {
|
||||
type storedReceiptRLP struct {
|
||||
PostStateOrStatus []byte
|
||||
CumulativeGasUsed uint64
|
||||
Logs []*LogForStorage
|
||||
}
|
||||
|
||||
// v4StoredReceiptRLP is the storage encoding of a receipt used in database version 4.
|
||||
type v4StoredReceiptRLP struct {
|
||||
PostStateOrStatus []byte
|
||||
CumulativeGasUsed uint64
|
||||
TxHash common.Hash
|
||||
ContractAddress common.Address
|
||||
Logs []*LogForStorage
|
||||
GasUsed uint64
|
||||
}
|
||||
|
||||
// v3StoredReceiptRLP is the original storage encoding of a receipt including some unnecessary fields.
|
||||
type v3StoredReceiptRLP struct {
|
||||
PostStateOrStatus []byte
|
||||
CumulativeGasUsed uint64
|
||||
Bloom Bloom
|
||||
TxHash common.Hash
|
||||
ContractAddress common.Address
|
||||
Logs []*LogForStorage
|
||||
GasUsed uint64
|
||||
Logs []*Log
|
||||
}
|
||||
|
||||
// NewReceipt creates a barebone transaction receipt, copying the init fields.
|
||||
@@ -292,82 +271,20 @@ func (r *ReceiptForStorage) EncodeRLP(_w io.Writer) error {
|
||||
// DecodeRLP implements rlp.Decoder, and loads both consensus and implementation
|
||||
// fields of a receipt from an RLP stream.
|
||||
func (r *ReceiptForStorage) DecodeRLP(s *rlp.Stream) error {
|
||||
// Retrieve the entire receipt blob as we need to try multiple decoders
|
||||
blob, err := s.Raw()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Try decoding from the newest format for future proofness, then the older one
|
||||
// for old nodes that just upgraded. V4 was an intermediate unreleased format so
|
||||
// we do need to decode it, but it's not common (try last).
|
||||
if err := decodeStoredReceiptRLP(r, blob); err == nil {
|
||||
return nil
|
||||
}
|
||||
if err := decodeV3StoredReceiptRLP(r, blob); err == nil {
|
||||
return nil
|
||||
}
|
||||
return decodeV4StoredReceiptRLP(r, blob)
|
||||
}
|
||||
|
||||
func decodeStoredReceiptRLP(r *ReceiptForStorage, blob []byte) error {
|
||||
var stored storedReceiptRLP
|
||||
if err := rlp.DecodeBytes(blob, &stored); err != nil {
|
||||
if err := s.Decode(&stored); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := (*Receipt)(r).setStatus(stored.PostStateOrStatus); err != nil {
|
||||
return err
|
||||
}
|
||||
r.CumulativeGasUsed = stored.CumulativeGasUsed
|
||||
r.Logs = make([]*Log, len(stored.Logs))
|
||||
for i, log := range stored.Logs {
|
||||
r.Logs[i] = (*Log)(log)
|
||||
}
|
||||
r.Logs = stored.Logs
|
||||
r.Bloom = CreateBloom(Receipts{(*Receipt)(r)})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeV4StoredReceiptRLP(r *ReceiptForStorage, blob []byte) error {
|
||||
var stored v4StoredReceiptRLP
|
||||
if err := rlp.DecodeBytes(blob, &stored); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := (*Receipt)(r).setStatus(stored.PostStateOrStatus); err != nil {
|
||||
return err
|
||||
}
|
||||
r.CumulativeGasUsed = stored.CumulativeGasUsed
|
||||
r.TxHash = stored.TxHash
|
||||
r.ContractAddress = stored.ContractAddress
|
||||
r.GasUsed = stored.GasUsed
|
||||
r.Logs = make([]*Log, len(stored.Logs))
|
||||
for i, log := range stored.Logs {
|
||||
r.Logs[i] = (*Log)(log)
|
||||
}
|
||||
r.Bloom = CreateBloom(Receipts{(*Receipt)(r)})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeV3StoredReceiptRLP(r *ReceiptForStorage, blob []byte) error {
|
||||
var stored v3StoredReceiptRLP
|
||||
if err := rlp.DecodeBytes(blob, &stored); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := (*Receipt)(r).setStatus(stored.PostStateOrStatus); err != nil {
|
||||
return err
|
||||
}
|
||||
r.CumulativeGasUsed = stored.CumulativeGasUsed
|
||||
r.Bloom = stored.Bloom
|
||||
r.TxHash = stored.TxHash
|
||||
r.ContractAddress = stored.ContractAddress
|
||||
r.GasUsed = stored.GasUsed
|
||||
r.Logs = make([]*Log, len(stored.Logs))
|
||||
for i, log := range stored.Logs {
|
||||
r.Logs[i] = (*Log)(log)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Receipts implements DerivableList for receipts.
|
||||
type Receipts []*Receipt
|
||||
|
||||
|
||||
@@ -91,128 +91,6 @@ func TestDecodeEmptyTypedReceipt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLegacyReceiptDecoding(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
encode func(*Receipt) ([]byte, error)
|
||||
}{
|
||||
{
|
||||
"StoredReceiptRLP",
|
||||
encodeAsStoredReceiptRLP,
|
||||
},
|
||||
{
|
||||
"V4StoredReceiptRLP",
|
||||
encodeAsV4StoredReceiptRLP,
|
||||
},
|
||||
{
|
||||
"V3StoredReceiptRLP",
|
||||
encodeAsV3StoredReceiptRLP,
|
||||
},
|
||||
}
|
||||
|
||||
tx := NewTransaction(1, common.HexToAddress("0x1"), big.NewInt(1), 1, big.NewInt(1), nil)
|
||||
receipt := &Receipt{
|
||||
Status: ReceiptStatusFailed,
|
||||
CumulativeGasUsed: 1,
|
||||
Logs: []*Log{
|
||||
{
|
||||
Address: common.BytesToAddress([]byte{0x11}),
|
||||
Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")},
|
||||
Data: []byte{0x01, 0x00, 0xff},
|
||||
},
|
||||
{
|
||||
Address: common.BytesToAddress([]byte{0x01, 0x11}),
|
||||
Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")},
|
||||
Data: []byte{0x01, 0x00, 0xff},
|
||||
},
|
||||
},
|
||||
TxHash: tx.Hash(),
|
||||
ContractAddress: common.BytesToAddress([]byte{0x01, 0x11, 0x11}),
|
||||
GasUsed: 111111,
|
||||
}
|
||||
receipt.Bloom = CreateBloom(Receipts{receipt})
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
enc, err := tc.encode(receipt)
|
||||
if err != nil {
|
||||
t.Fatalf("Error encoding receipt: %v", err)
|
||||
}
|
||||
var dec ReceiptForStorage
|
||||
if err := rlp.DecodeBytes(enc, &dec); err != nil {
|
||||
t.Fatalf("Error decoding RLP receipt: %v", err)
|
||||
}
|
||||
// Check whether all consensus fields are correct.
|
||||
if dec.Status != receipt.Status {
|
||||
t.Fatalf("Receipt status mismatch, want %v, have %v", receipt.Status, dec.Status)
|
||||
}
|
||||
if dec.CumulativeGasUsed != receipt.CumulativeGasUsed {
|
||||
t.Fatalf("Receipt CumulativeGasUsed mismatch, want %v, have %v", receipt.CumulativeGasUsed, dec.CumulativeGasUsed)
|
||||
}
|
||||
if dec.Bloom != receipt.Bloom {
|
||||
t.Fatalf("Bloom data mismatch, want %v, have %v", receipt.Bloom, dec.Bloom)
|
||||
}
|
||||
if len(dec.Logs) != len(receipt.Logs) {
|
||||
t.Fatalf("Receipt log number mismatch, want %v, have %v", len(receipt.Logs), len(dec.Logs))
|
||||
}
|
||||
for i := 0; i < len(dec.Logs); i++ {
|
||||
if dec.Logs[i].Address != receipt.Logs[i].Address {
|
||||
t.Fatalf("Receipt log %d address mismatch, want %v, have %v", i, receipt.Logs[i].Address, dec.Logs[i].Address)
|
||||
}
|
||||
if !reflect.DeepEqual(dec.Logs[i].Topics, receipt.Logs[i].Topics) {
|
||||
t.Fatalf("Receipt log %d topics mismatch, want %v, have %v", i, receipt.Logs[i].Topics, dec.Logs[i].Topics)
|
||||
}
|
||||
if !bytes.Equal(dec.Logs[i].Data, receipt.Logs[i].Data) {
|
||||
t.Fatalf("Receipt log %d data mismatch, want %v, have %v", i, receipt.Logs[i].Data, dec.Logs[i].Data)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func encodeAsStoredReceiptRLP(want *Receipt) ([]byte, error) {
|
||||
stored := &storedReceiptRLP{
|
||||
PostStateOrStatus: want.statusEncoding(),
|
||||
CumulativeGasUsed: want.CumulativeGasUsed,
|
||||
Logs: make([]*LogForStorage, len(want.Logs)),
|
||||
}
|
||||
for i, log := range want.Logs {
|
||||
stored.Logs[i] = (*LogForStorage)(log)
|
||||
}
|
||||
return rlp.EncodeToBytes(stored)
|
||||
}
|
||||
|
||||
func encodeAsV4StoredReceiptRLP(want *Receipt) ([]byte, error) {
|
||||
stored := &v4StoredReceiptRLP{
|
||||
PostStateOrStatus: want.statusEncoding(),
|
||||
CumulativeGasUsed: want.CumulativeGasUsed,
|
||||
TxHash: want.TxHash,
|
||||
ContractAddress: want.ContractAddress,
|
||||
Logs: make([]*LogForStorage, len(want.Logs)),
|
||||
GasUsed: want.GasUsed,
|
||||
}
|
||||
for i, log := range want.Logs {
|
||||
stored.Logs[i] = (*LogForStorage)(log)
|
||||
}
|
||||
return rlp.EncodeToBytes(stored)
|
||||
}
|
||||
|
||||
func encodeAsV3StoredReceiptRLP(want *Receipt) ([]byte, error) {
|
||||
stored := &v3StoredReceiptRLP{
|
||||
PostStateOrStatus: want.statusEncoding(),
|
||||
CumulativeGasUsed: want.CumulativeGasUsed,
|
||||
Bloom: want.Bloom,
|
||||
TxHash: want.TxHash,
|
||||
ContractAddress: want.ContractAddress,
|
||||
Logs: make([]*LogForStorage, len(want.Logs)),
|
||||
GasUsed: want.GasUsed,
|
||||
}
|
||||
for i, log := range want.Logs {
|
||||
stored.Logs[i] = (*LogForStorage)(log)
|
||||
}
|
||||
return rlp.EncodeToBytes(stored)
|
||||
}
|
||||
|
||||
// Tests that receipt data can be correctly derived from the contextual infos
|
||||
func TestDeriveFields(t *testing.T) {
|
||||
// Create a few transactions to have receipts for
|
||||
|
||||
+18
-12
@@ -131,7 +131,7 @@ func (tx *Transaction) DecodeRLP(s *rlp.Stream) error {
|
||||
var inner LegacyTx
|
||||
err := s.Decode(&inner)
|
||||
if err == nil {
|
||||
tx.setDecoded(&inner, int(rlp.ListSize(size)))
|
||||
tx.setDecoded(&inner, rlp.ListSize(size))
|
||||
}
|
||||
return err
|
||||
default:
|
||||
@@ -142,7 +142,7 @@ func (tx *Transaction) DecodeRLP(s *rlp.Stream) error {
|
||||
}
|
||||
inner, err := tx.decodeTyped(b)
|
||||
if err == nil {
|
||||
tx.setDecoded(inner, len(b))
|
||||
tx.setDecoded(inner, uint64(len(b)))
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -158,7 +158,7 @@ func (tx *Transaction) UnmarshalBinary(b []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tx.setDecoded(&data, len(b))
|
||||
tx.setDecoded(&data, uint64(len(b)))
|
||||
return nil
|
||||
}
|
||||
// It's an EIP2718 typed transaction envelope.
|
||||
@@ -166,7 +166,7 @@ func (tx *Transaction) UnmarshalBinary(b []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tx.setDecoded(inner, len(b))
|
||||
tx.setDecoded(inner, uint64(len(b)))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -190,11 +190,11 @@ func (tx *Transaction) decodeTyped(b []byte) (TxData, error) {
|
||||
}
|
||||
|
||||
// setDecoded sets the inner transaction and size after decoding.
|
||||
func (tx *Transaction) setDecoded(inner TxData, size int) {
|
||||
func (tx *Transaction) setDecoded(inner TxData, size uint64) {
|
||||
tx.inner = inner
|
||||
tx.time = time.Now()
|
||||
if size > 0 {
|
||||
tx.size.Store(common.StorageSize(size))
|
||||
tx.size.Store(size)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -372,16 +372,21 @@ func (tx *Transaction) Hash() common.Hash {
|
||||
return h
|
||||
}
|
||||
|
||||
// Size returns the true RLP encoded storage size of the transaction, either by
|
||||
// encoding and returning it, or returning a previously cached value.
|
||||
func (tx *Transaction) Size() common.StorageSize {
|
||||
// Size returns the true encoded storage size of the transaction, either by encoding
|
||||
// and returning it, or returning a previously cached value.
|
||||
func (tx *Transaction) Size() uint64 {
|
||||
if size := tx.size.Load(); size != nil {
|
||||
return size.(common.StorageSize)
|
||||
return size.(uint64)
|
||||
}
|
||||
c := writeCounter(0)
|
||||
rlp.Encode(&c, &tx.inner)
|
||||
tx.size.Store(common.StorageSize(c))
|
||||
return common.StorageSize(c)
|
||||
|
||||
size := uint64(c)
|
||||
if tx.Type() != LegacyTxType {
|
||||
size += 1 // type byte
|
||||
}
|
||||
tx.size.Store(size)
|
||||
return size
|
||||
}
|
||||
|
||||
// WithSignature returns a new transaction with the given signature.
|
||||
@@ -503,6 +508,7 @@ func (s *TxByPriceAndTime) Pop() interface{} {
|
||||
old := *s
|
||||
n := len(old)
|
||||
x := old[n-1]
|
||||
old[n-1] = nil
|
||||
*s = old[0 : n-1]
|
||||
return x
|
||||
}
|
||||
|
||||
@@ -51,55 +51,55 @@ type txJSON struct {
|
||||
}
|
||||
|
||||
// MarshalJSON marshals as JSON with a hash.
|
||||
func (t *Transaction) MarshalJSON() ([]byte, error) {
|
||||
func (tx *Transaction) MarshalJSON() ([]byte, error) {
|
||||
var enc txJSON
|
||||
// These are set for all tx types.
|
||||
enc.Hash = t.Hash()
|
||||
enc.Type = hexutil.Uint64(t.Type())
|
||||
enc.Hash = tx.Hash()
|
||||
enc.Type = hexutil.Uint64(tx.Type())
|
||||
|
||||
// Other fields are set conditionally depending on tx type.
|
||||
switch tx := t.inner.(type) {
|
||||
switch itx := tx.inner.(type) {
|
||||
case *LegacyTx:
|
||||
enc.Nonce = (*hexutil.Uint64)(&tx.Nonce)
|
||||
enc.Gas = (*hexutil.Uint64)(&tx.Gas)
|
||||
enc.GasPrice = (*hexutil.Big)(tx.GasPrice)
|
||||
enc.Value = (*hexutil.Big)(tx.Value)
|
||||
enc.Data = (*hexutil.Bytes)(&tx.Data)
|
||||
enc.To = t.To()
|
||||
enc.V = (*hexutil.Big)(tx.V)
|
||||
enc.R = (*hexutil.Big)(tx.R)
|
||||
enc.S = (*hexutil.Big)(tx.S)
|
||||
enc.Nonce = (*hexutil.Uint64)(&itx.Nonce)
|
||||
enc.Gas = (*hexutil.Uint64)(&itx.Gas)
|
||||
enc.GasPrice = (*hexutil.Big)(itx.GasPrice)
|
||||
enc.Value = (*hexutil.Big)(itx.Value)
|
||||
enc.Data = (*hexutil.Bytes)(&itx.Data)
|
||||
enc.To = tx.To()
|
||||
enc.V = (*hexutil.Big)(itx.V)
|
||||
enc.R = (*hexutil.Big)(itx.R)
|
||||
enc.S = (*hexutil.Big)(itx.S)
|
||||
case *AccessListTx:
|
||||
enc.ChainID = (*hexutil.Big)(tx.ChainID)
|
||||
enc.AccessList = &tx.AccessList
|
||||
enc.Nonce = (*hexutil.Uint64)(&tx.Nonce)
|
||||
enc.Gas = (*hexutil.Uint64)(&tx.Gas)
|
||||
enc.GasPrice = (*hexutil.Big)(tx.GasPrice)
|
||||
enc.Value = (*hexutil.Big)(tx.Value)
|
||||
enc.Data = (*hexutil.Bytes)(&tx.Data)
|
||||
enc.To = t.To()
|
||||
enc.V = (*hexutil.Big)(tx.V)
|
||||
enc.R = (*hexutil.Big)(tx.R)
|
||||
enc.S = (*hexutil.Big)(tx.S)
|
||||
enc.ChainID = (*hexutil.Big)(itx.ChainID)
|
||||
enc.AccessList = &itx.AccessList
|
||||
enc.Nonce = (*hexutil.Uint64)(&itx.Nonce)
|
||||
enc.Gas = (*hexutil.Uint64)(&itx.Gas)
|
||||
enc.GasPrice = (*hexutil.Big)(itx.GasPrice)
|
||||
enc.Value = (*hexutil.Big)(itx.Value)
|
||||
enc.Data = (*hexutil.Bytes)(&itx.Data)
|
||||
enc.To = tx.To()
|
||||
enc.V = (*hexutil.Big)(itx.V)
|
||||
enc.R = (*hexutil.Big)(itx.R)
|
||||
enc.S = (*hexutil.Big)(itx.S)
|
||||
case *DynamicFeeTx:
|
||||
enc.ChainID = (*hexutil.Big)(tx.ChainID)
|
||||
enc.AccessList = &tx.AccessList
|
||||
enc.Nonce = (*hexutil.Uint64)(&tx.Nonce)
|
||||
enc.Gas = (*hexutil.Uint64)(&tx.Gas)
|
||||
enc.MaxFeePerGas = (*hexutil.Big)(tx.GasFeeCap)
|
||||
enc.MaxPriorityFeePerGas = (*hexutil.Big)(tx.GasTipCap)
|
||||
enc.Value = (*hexutil.Big)(tx.Value)
|
||||
enc.Data = (*hexutil.Bytes)(&tx.Data)
|
||||
enc.To = t.To()
|
||||
enc.V = (*hexutil.Big)(tx.V)
|
||||
enc.R = (*hexutil.Big)(tx.R)
|
||||
enc.S = (*hexutil.Big)(tx.S)
|
||||
enc.ChainID = (*hexutil.Big)(itx.ChainID)
|
||||
enc.AccessList = &itx.AccessList
|
||||
enc.Nonce = (*hexutil.Uint64)(&itx.Nonce)
|
||||
enc.Gas = (*hexutil.Uint64)(&itx.Gas)
|
||||
enc.MaxFeePerGas = (*hexutil.Big)(itx.GasFeeCap)
|
||||
enc.MaxPriorityFeePerGas = (*hexutil.Big)(itx.GasTipCap)
|
||||
enc.Value = (*hexutil.Big)(itx.Value)
|
||||
enc.Data = (*hexutil.Bytes)(&itx.Data)
|
||||
enc.To = tx.To()
|
||||
enc.V = (*hexutil.Big)(itx.V)
|
||||
enc.R = (*hexutil.Big)(itx.R)
|
||||
enc.S = (*hexutil.Big)(itx.S)
|
||||
}
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals from JSON.
|
||||
func (t *Transaction) UnmarshalJSON(input []byte) error {
|
||||
func (tx *Transaction) UnmarshalJSON(input []byte) error {
|
||||
var dec txJSON
|
||||
if err := json.Unmarshal(input, &dec); err != nil {
|
||||
return err
|
||||
@@ -268,7 +268,7 @@ func (t *Transaction) UnmarshalJSON(input []byte) error {
|
||||
}
|
||||
|
||||
// Now set the inner transaction.
|
||||
t.setDecoded(inner, 0)
|
||||
tx.setDecoded(inner, 0)
|
||||
|
||||
// TODO: check hash here?
|
||||
return nil
|
||||
|
||||
@@ -190,7 +190,7 @@ func (s londonSigner) Sender(tx *Transaction) (common.Address, error) {
|
||||
// id, add 27 to become equivalent to unprotected Homestead signatures.
|
||||
V = new(big.Int).Add(V, big.NewInt(27))
|
||||
if tx.ChainId().Cmp(s.chainId) != 0 {
|
||||
return common.Address{}, ErrInvalidChainId
|
||||
return common.Address{}, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, tx.ChainId(), s.chainId)
|
||||
}
|
||||
return recoverPlain(s.Hash(tx), R, S, V, true)
|
||||
}
|
||||
@@ -208,7 +208,7 @@ func (s londonSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big
|
||||
// Check that chain ID of tx matches the signer. We also accept ID zero here,
|
||||
// because it indicates that the chain ID was not specified in the tx.
|
||||
if txdata.ChainID.Sign() != 0 && txdata.ChainID.Cmp(s.chainId) != 0 {
|
||||
return nil, nil, nil, ErrInvalidChainId
|
||||
return nil, nil, nil, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, txdata.ChainID, s.chainId)
|
||||
}
|
||||
R, S, _ = decodeSignature(sig)
|
||||
V = big.NewInt(int64(sig[64]))
|
||||
@@ -270,7 +270,7 @@ func (s eip2930Signer) Sender(tx *Transaction) (common.Address, error) {
|
||||
return common.Address{}, ErrTxTypeNotSupported
|
||||
}
|
||||
if tx.ChainId().Cmp(s.chainId) != 0 {
|
||||
return common.Address{}, ErrInvalidChainId
|
||||
return common.Address{}, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, tx.ChainId(), s.chainId)
|
||||
}
|
||||
return recoverPlain(s.Hash(tx), R, S, V, true)
|
||||
}
|
||||
@@ -283,7 +283,7 @@ func (s eip2930Signer) SignatureValues(tx *Transaction, sig []byte) (R, S, V *bi
|
||||
// Check that chain ID of tx matches the signer. We also accept ID zero here,
|
||||
// because it indicates that the chain ID was not specified in the tx.
|
||||
if txdata.ChainID.Sign() != 0 && txdata.ChainID.Cmp(s.chainId) != 0 {
|
||||
return nil, nil, nil, ErrInvalidChainId
|
||||
return nil, nil, nil, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, txdata.ChainID, s.chainId)
|
||||
}
|
||||
R, S, _ = decodeSignature(sig)
|
||||
V = big.NewInt(int64(sig[64]))
|
||||
@@ -364,7 +364,7 @@ func (s EIP155Signer) Sender(tx *Transaction) (common.Address, error) {
|
||||
return HomesteadSigner{}.Sender(tx)
|
||||
}
|
||||
if tx.ChainId().Cmp(s.chainId) != 0 {
|
||||
return common.Address{}, ErrInvalidChainId
|
||||
return common.Address{}, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, tx.ChainId(), s.chainId)
|
||||
}
|
||||
V, R, S := tx.RawSignatureValues()
|
||||
V = new(big.Int).Sub(V, s.chainIdMul)
|
||||
@@ -400,7 +400,7 @@ func (s EIP155Signer) Hash(tx *Transaction) common.Hash {
|
||||
})
|
||||
}
|
||||
|
||||
// HomesteadTransaction implements TransactionInterface using the
|
||||
// HomesteadSigner implements Signer interface using the
|
||||
// homestead rules.
|
||||
type HomesteadSigner struct{ FrontierSigner }
|
||||
|
||||
@@ -427,6 +427,8 @@ func (hs HomesteadSigner) Sender(tx *Transaction) (common.Address, error) {
|
||||
return recoverPlain(hs.Hash(tx), r, s, v, true)
|
||||
}
|
||||
|
||||
// FrontierSigner implements Signer interface using the
|
||||
// frontier rules.
|
||||
type FrontierSigner struct{}
|
||||
|
||||
func (s FrontierSigner) ChainID() *big.Int {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
@@ -126,8 +127,8 @@ func TestChainId(t *testing.T) {
|
||||
}
|
||||
|
||||
_, err = Sender(NewEIP155Signer(big.NewInt(2)), tx)
|
||||
if err != ErrInvalidChainId {
|
||||
t.Error("expected error:", ErrInvalidChainId)
|
||||
if !errors.Is(err, ErrInvalidChainId) {
|
||||
t.Error("expected error:", ErrInvalidChainId, err)
|
||||
}
|
||||
|
||||
_, err = Sender(NewEIP155Signer(big.NewInt(1)), tx)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user