From c7469426b134587838b86dd5cd695ac8c805c903 Mon Sep 17 00:00:00 2001
From: Andrew Ashikhmin <34320705+yperbasis@users.noreply.github.com>
Date: Thu, 15 Aug 2024 14:30:02 +0200
Subject: [PATCH] Revert "E3: Remove Proof-Of-Work Consensus code" (#11628)
This reverts PR #11556, which caused Hive tests (e.g. "Bad Hash on
NewPayload") to crash.
---
accounts/abi/bind/backends/simulated.go | 4 +-
cmd/evm/README.md | 4 +
cmd/evm/internal/t8ntool/execution.go | 21 +
cmd/evm/internal/t8ntool/transition.go | 17 +-
cmd/integration/commands/stages.go | 3 +-
cmd/rpcdaemon/cli/config.go | 9 +-
cmd/rpcdaemon/rpcdaemontest/test_util.go | 8 +-
cmd/state/commands/opcode_tracer.go | 4 +-
cmd/txpool/main.go | 2 +-
cmd/utils/flags.go | 41 +
consensus/aura/aura.go | 4 +-
consensus/clique/snapshot_test.go | 10 +-
consensus/ethash/algorithm.go | 1228 +++++++++++++++++++
consensus/ethash/algorithm_test.go | 795 ++++++++++++
consensus/ethash/api.go | 118 ++
consensus/ethash/consensus.go | 680 ++++++++++
consensus/ethash/consensus_test.go | 202 +++
consensus/ethash/difficulty.go | 197 +++
consensus/ethash/ethash.go | 588 +++++++++
consensus/ethash/ethash_test.go | 114 ++
consensus/ethash/ethashcfg/ethashcfg.go | 50 +
consensus/ethash/fake.go | 170 +++
consensus/ethash/meter.go | 209 ++++
consensus/ethash/sealer.go | 338 +++++
consensus/ethash/sealer_test.go | 328 +++++
consensus/mainnet/mainnet.go | 437 -------
core/block_validator_test.go | 18 +-
core/rlp_test.go | 4 +-
eth/backend.go | 11 +-
eth/ethconfig/config.go | 39 +
eth/ethconfig/gen_config.go | 8 +-
eth/ethconsensusconfig/config.go | 31 +-
eth/filters/filter_system_test.go | 2 +-
ethdb/privateapi/mining.go | 53 +-
go.mod | 2 +-
polygon/bor/fake.go | 8 +-
tests/difficulty_test.go | 62 +
tests/difficulty_test_util.go | 78 ++
tests/fuzzers/difficulty/debug/main.go | 22 +
tests/fuzzers/difficulty/difficulty-fuzz.go | 153 +++
tests/gen_difficultytest.go | 67 +
tests/init_test.go | 1 +
turbo/cli/default_flags.go | 1 +
turbo/jsonrpc/erigon_receipts_test.go | 6 +-
turbo/jsonrpc/eth_block.go | 8 +-
turbo/jsonrpc/eth_block_test.go | 16 +-
turbo/jsonrpc/eth_call_test.go | 4 +-
turbo/jsonrpc/eth_mining_test.go | 4 +-
turbo/jsonrpc/gen_traces_test.go | 16 +-
turbo/jsonrpc/trace_filtering.go | 4 +-
turbo/stages/blockchain_test.go | 14 +-
turbo/stages/bodydownload/body_test.go | 4 +-
turbo/stages/mock/mock_sentry.go | 6 +-
53 files changed, 5700 insertions(+), 523 deletions(-)
create mode 100644 consensus/ethash/algorithm.go
create mode 100644 consensus/ethash/algorithm_test.go
create mode 100644 consensus/ethash/api.go
create mode 100644 consensus/ethash/consensus.go
create mode 100644 consensus/ethash/consensus_test.go
create mode 100644 consensus/ethash/difficulty.go
create mode 100644 consensus/ethash/ethash.go
create mode 100644 consensus/ethash/ethash_test.go
create mode 100644 consensus/ethash/ethashcfg/ethashcfg.go
create mode 100644 consensus/ethash/fake.go
create mode 100644 consensus/ethash/meter.go
create mode 100644 consensus/ethash/sealer.go
create mode 100644 consensus/ethash/sealer_test.go
delete mode 100644 consensus/mainnet/mainnet.go
create mode 100644 tests/difficulty_test.go
create mode 100644 tests/difficulty_test_util.go
create mode 100644 tests/fuzzers/difficulty/debug/main.go
create mode 100644 tests/fuzzers/difficulty/difficulty-fuzz.go
create mode 100644 tests/gen_difficultytest.go
diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go
index 318e9eeeadb..58939db2e91 100644
--- a/accounts/abi/bind/backends/simulated.go
+++ b/accounts/abi/bind/backends/simulated.go
@@ -44,7 +44,7 @@ import (
"github.com/erigontech/erigon/common/math"
"github.com/erigontech/erigon/common/u256"
"github.com/erigontech/erigon/consensus"
- "github.com/erigontech/erigon/consensus/mainnet"
+ "github.com/erigontech/erigon/consensus/ethash"
"github.com/erigontech/erigon/consensus/misc"
"github.com/erigontech/erigon/core"
"github.com/erigontech/erigon/core/rawdb"
@@ -96,7 +96,7 @@ type SimulatedBackend struct {
// for testing purposes.
func NewSimulatedBackendWithConfig(t *testing.T, alloc types.GenesisAlloc, config *chain.Config, gasLimit uint64) *SimulatedBackend {
genesis := types.Genesis{Config: config, GasLimit: gasLimit, Alloc: alloc}
- engine := mainnet.NewMainnetConsensus()
+ engine := ethash.NewFaker()
checkStateRoot := true
m := mock.MockWithGenesisEngine(t, &genesis, engine, false, checkStateRoot)
diff --git a/cmd/evm/README.md b/cmd/evm/README.md
index 07eb527349b..aef35ad36e2 100644
--- a/cmd/evm/README.md
+++ b/cmd/evm/README.md
@@ -339,6 +339,7 @@ Mining rewards and ommer rewards might need to be added. This is how those are a
To make `t8n` apply these, the following inputs are required:
- `--state.reward`
+ - For ethash, it is `5000000000000000000` `wei`,
- If this is not defined, mining rewards are not applied,
- A value of `0` is valid, and causes accounts to be 'touched'.
- For each ommer, the tool needs to be given an `address\` and a `delta`. This
@@ -533,6 +534,9 @@ Command line params that need to be supported are:
`stdout` - into the stdout output
`stderr` - into the stderr output
--seal.clique value Seal block with Clique. `stdin` or file name of where to find the Clique sealing data.
+ --seal.ethash Seal block with ethash. (default: false)
+ --seal.ethash.dir value Path to ethash DAG. If none exists, a new DAG will be generated.
+ --seal.ethash.mode value Defines the type and amount of PoW verification an ethash engine makes. (default: "normal")
--verbosity value Sets the verbosity level. (default: 3)
```
diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go
index 43888f88dfe..9cf60061557 100644
--- a/cmd/evm/internal/t8ntool/execution.go
+++ b/cmd/evm/internal/t8ntool/execution.go
@@ -31,6 +31,7 @@ import (
state3 "github.com/erigontech/erigon-lib/state"
"github.com/erigontech/erigon/common"
"github.com/erigontech/erigon/common/math"
+ "github.com/erigontech/erigon/consensus/ethash"
"github.com/erigontech/erigon/core/state"
"github.com/erigontech/erigon/core/tracing"
"github.com/erigontech/erigon/core/types"
@@ -116,3 +117,23 @@ func MakePreState(chainRules *chain.Rules, tx kv.RwTx, sd *state3.SharedDomains,
}
return stateReader, stateWriter
}
+
+// calcDifficulty is based on ethash.CalcDifficulty. This method is used in case
+// the caller does not provide an explicit difficulty, but instead provides only
+// parent timestamp + difficulty.
+// Note: this method only works for ethash engine.
+func calcDifficulty(config *chain.Config, number, currentTime, parentTime uint64,
+ parentDifficulty *big.Int, parentUncleHash libcommon.Hash) *big.Int {
+ uncleHash := parentUncleHash
+ if uncleHash == (libcommon.Hash{}) {
+ uncleHash = types.EmptyUncleHash
+ }
+ parent := &types.Header{
+ ParentHash: libcommon.Hash{},
+ UncleHash: uncleHash,
+ Difficulty: parentDifficulty,
+ Number: new(big.Int).SetUint64(number - 1),
+ Time: parentTime,
+ }
+ return ethash.CalcDifficulty(config, currentTime, parent.Time, parent.Difficulty, number-1, parent.UncleHash)
+}
diff --git a/cmd/evm/internal/t8ntool/transition.go b/cmd/evm/internal/t8ntool/transition.go
index 39a382f85fb..2a259960ef9 100644
--- a/cmd/evm/internal/t8ntool/transition.go
+++ b/cmd/evm/internal/t8ntool/transition.go
@@ -45,7 +45,7 @@ import (
"github.com/erigontech/erigon-lib/kv"
libstate "github.com/erigontech/erigon-lib/state"
"github.com/erigontech/erigon/common/math"
- "github.com/erigontech/erigon/consensus/mainnet"
+ "github.com/erigontech/erigon/consensus/ethash"
"github.com/erigontech/erigon/consensus/merge"
"github.com/erigontech/erigon/core"
"github.com/erigontech/erigon/core/state"
@@ -262,7 +262,18 @@ func Main(ctx *cli.Context) error {
}
prestate.Env.Difficulty = nil
} else if env.Difficulty == nil {
- return NewError(ErrorVMConfig, errors.New("POW not supported, difficulty must be defined in env"))
+ // If difficulty was not provided by caller, we need to calculate it.
+ switch {
+ case env.ParentDifficulty == nil:
+ return NewError(ErrorVMConfig, errors.New("currentDifficulty was not provided, and cannot be calculated due to missing parentDifficulty"))
+ case env.Number == 0:
+ return NewError(ErrorVMConfig, errors.New("currentDifficulty needs to be provided for block number 0"))
+ case env.Timestamp <= env.ParentTimestamp:
+ return NewError(ErrorVMConfig, fmt.Errorf("currentDifficulty cannot be calculated -- currentTime (%d) needs to be after parent time (%d)",
+ env.Timestamp, env.ParentTimestamp))
+ }
+ prestate.Env.Difficulty = calcDifficulty(chainConfig, env.Number, env.Timestamp,
+ env.ParentTimestamp, env.ParentDifficulty, env.ParentUncleHash)
}
// manufacture block from above inputs
@@ -309,7 +320,7 @@ func Main(ctx *cli.Context) error {
reader, writer := MakePreState(chainConfig.Rules(0, 0), tx, sd, prestate.Pre)
// Merge engine can be used for pre-merge blocks as well, as it
// redirects to the ethash engine based on the block number
- engine := merge.New(&mainnet.MainnetConsensus{})
+ engine := merge.New(ðash.FakeEthash{})
t8logger := log.New("t8ntool")
chainReader := consensuschain.NewReader(chainConfig, tx, nil, t8logger)
diff --git a/cmd/integration/commands/stages.go b/cmd/integration/commands/stages.go
index 2dff27aabb2..049416d6c91 100644
--- a/cmd/integration/commands/stages.go
+++ b/cmd/integration/commands/stages.go
@@ -52,7 +52,6 @@ import (
"github.com/erigontech/erigon/cl/clparams"
"github.com/erigontech/erigon/cmd/hack/tool/fromdb"
"github.com/erigontech/erigon/consensus"
- "github.com/erigontech/erigon/consensus/mainnet"
"github.com/erigontech/erigon/core"
"github.com/erigontech/erigon/core/rawdb"
"github.com/erigontech/erigon/core/rawdb/blockio"
@@ -1518,7 +1517,7 @@ func initConsensusEngine(ctx context.Context, cc *chain2.Config, dir string, db
heimdallClient = heimdall.NewHeimdallClient(config.HeimdallURL, logger)
}
} else {
- consensusConfig = &mainnet.MainnetConfig{}
+ consensusConfig = &config.Ethash
}
return ethconsensusconfig.CreateConsensusEngine(ctx, &nodecfg.Config{Dirs: datadir.New(dir)}, cc, consensusConfig, config.Miner.Notify, config.Miner.Noverify,
heimdallClient, config.WithoutHeimdall, blockReader, db.ReadOnly(), logger, nil, nil), heimdallClient
diff --git a/cmd/rpcdaemon/cli/config.go b/cmd/rpcdaemon/cli/config.go
index f242dab502d..1d01c76d7fb 100644
--- a/cmd/rpcdaemon/cli/config.go
+++ b/cmd/rpcdaemon/cli/config.go
@@ -65,7 +65,7 @@ import (
"github.com/erigontech/erigon/common"
"github.com/erigontech/erigon/common/paths"
"github.com/erigontech/erigon/consensus"
- "github.com/erigontech/erigon/consensus/mainnet"
+ "github.com/erigontech/erigon/consensus/ethash"
"github.com/erigontech/erigon/core/rawdb"
"github.com/erigontech/erigon/core/state"
"github.com/erigontech/erigon/core/types"
@@ -81,7 +81,6 @@ import (
"github.com/erigontech/erigon/turbo/rpchelper"
"github.com/erigontech/erigon/turbo/services"
"github.com/erigontech/erigon/turbo/snapshotsync/freezeblocks"
-
// Force-load native and js packages, to trigger registration
_ "github.com/erigontech/erigon/eth/tracers/js"
_ "github.com/erigontech/erigon/eth/tracers/native"
@@ -511,11 +510,11 @@ func RemoteServices(ctx context.Context, cfg *httpcfg.HttpCfg, logger log.Logger
// Skip the compatibility check, until we have a schema in erigon-lib
engine = bor.NewRo(cc, borKv, blockReader, logger)
default:
- engine = mainnet.NewMainnetConsensus()
+ engine = ethash.NewFaker()
}
default:
- engine = mainnet.NewMainnetConsensus()
+ engine = ethash.NewFaker()
}
} else {
remoteCE = &remoteConsensusEngine{}
@@ -930,7 +929,7 @@ func (e *remoteConsensusEngine) init(db kv.RoDB, blockReader services.FullBlockR
e.engine = bor.NewRo(cc, borKv, blockReader, logger)
} else {
- e.engine = mainnet.NewMainnetConsensus()
+ e.engine = ethash.NewFaker()
}
return true
diff --git a/cmd/rpcdaemon/rpcdaemontest/test_util.go b/cmd/rpcdaemon/rpcdaemontest/test_util.go
index d59b516ff26..c48c5c32f1c 100644
--- a/cmd/rpcdaemon/rpcdaemontest/test_util.go
+++ b/cmd/rpcdaemon/rpcdaemontest/test_util.go
@@ -40,7 +40,7 @@ import (
"github.com/erigontech/erigon/accounts/abi/bind/backends"
"github.com/erigontech/erigon/common/u256"
"github.com/erigontech/erigon/consensus"
- "github.com/erigontech/erigon/consensus/merge"
+ "github.com/erigontech/erigon/consensus/ethash"
"github.com/erigontech/erigon/core"
"github.com/erigontech/erigon/core/types"
"github.com/erigontech/erigon/core/vm"
@@ -304,17 +304,17 @@ func CreateTestGrpcConn(t *testing.T, m *mock.MockSentry) (context.Context, *grp
ctx, cancel := context.WithCancel(context.Background())
apis := m.Engine.APIs(nil)
- _, isEngineAPI := m.Engine.(*merge.Merge)
- if len(apis) < 1 && isEngineAPI {
+ if len(apis) < 1 {
t.Fatal("couldn't instantiate Engine api")
}
+ ethashApi := apis[1].Service.(*ethash.API)
server := grpc.NewServer()
remote.RegisterETHBACKENDServer(server, privateapi.NewEthBackendServer(ctx, nil, m.DB, m.Notifications.Events,
m.BlockReader, log.New(), builder.NewLatestBlockBuiltStore()))
txpool.RegisterTxpoolServer(server, m.TxPoolGrpcServer)
- txpool.RegisterMiningServer(server, privateapi.NewMiningServer(ctx, &IsMiningMock{}, m.Log))
+ txpool.RegisterMiningServer(server, privateapi.NewMiningServer(ctx, &IsMiningMock{}, ethashApi, m.Log))
listener := bufconn.Listen(1024 * 1024)
dialer := func() func(context.Context, string) (net.Conn, error) {
diff --git a/cmd/state/commands/opcode_tracer.go b/cmd/state/commands/opcode_tracer.go
index 170da707ee0..8255306c7c3 100644
--- a/cmd/state/commands/opcode_tracer.go
+++ b/cmd/state/commands/opcode_tracer.go
@@ -42,7 +42,7 @@ import (
"github.com/erigontech/erigon/common/debug"
"github.com/erigontech/erigon/consensus"
- "github.com/erigontech/erigon/consensus/mainnet"
+ "github.com/erigontech/erigon/consensus/ethash"
"github.com/erigontech/erigon/core"
"github.com/erigontech/erigon/core/rawdb"
"github.com/erigontech/erigon/core/state"
@@ -603,7 +603,7 @@ func OpcodeTracer(genesis *types.Genesis, blockNum uint64, chaindata string, num
getHeader := func(hash libcommon.Hash, number uint64) *types.Header {
return rawdb.ReadHeader(historyTx, hash, number)
}
- receipts, err1 := runBlock(mainnet.NewFullFaker(), intraBlockState, noOpWriter, noOpWriter, chainConfig, getHeader, block, vmConfig, false, logger)
+ receipts, err1 := runBlock(ethash.NewFullFaker(), intraBlockState, noOpWriter, noOpWriter, chainConfig, getHeader, block, vmConfig, false, logger)
if err1 != nil {
return err1
}
diff --git a/cmd/txpool/main.go b/cmd/txpool/main.go
index 1204e2df356..f1b93709eae 100644
--- a/cmd/txpool/main.go
+++ b/cmd/txpool/main.go
@@ -195,7 +195,7 @@ func doTxpool(ctx context.Context, logger log.Logger) error {
fetch.ConnectCore()
fetch.ConnectSentries()
- miningGrpcServer := privateapi.NewMiningServer(ctx, &rpcdaemontest.IsMiningMock{}, logger)
+ miningGrpcServer := privateapi.NewMiningServer(ctx, &rpcdaemontest.IsMiningMock{}, nil, logger)
grpcServer, err := txpool.StartGrpc(txpoolGrpcServer, miningGrpcServer, txpoolApiAddr, nil, logger)
if err != nil {
diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go
index 502bc532f1a..4d7cff453de 100644
--- a/cmd/utils/flags.go
+++ b/cmd/utils/flags.go
@@ -52,6 +52,7 @@ import (
"github.com/erigontech/erigon/cmd/utils/flags"
common2 "github.com/erigontech/erigon/common"
"github.com/erigontech/erigon/common/paths"
+ "github.com/erigontech/erigon/consensus/ethash/ethashcfg"
"github.com/erigontech/erigon/core"
"github.com/erigontech/erigon/crypto"
"github.com/erigontech/erigon/eth/ethconfig"
@@ -120,6 +121,25 @@ var (
Name: "trusted-setup-file",
Usage: "Absolute path to trusted_setup.json file",
}
+ // Ethash settings
+ EthashCachesInMemoryFlag = cli.IntFlag{
+ Name: "ethash.cachesinmem",
+ Usage: "Number of recent ethash caches to keep in memory (16MB each)",
+ Value: ethconfig.Defaults.Ethash.CachesInMem,
+ }
+ EthashCachesLockMmapFlag = cli.BoolFlag{
+ Name: "ethash.cacheslockmmap",
+ Usage: "Lock memory maps of recent ethash caches",
+ }
+ EthashDatasetDirFlag = flags.DirectoryFlag{
+ Name: "ethash.dagdir",
+ Usage: "Directory to store the ethash mining DAGs",
+ Value: flags.DirectoryString(ethconfig.Defaults.Ethash.DatasetDir),
+ }
+ EthashDatasetsLockMmapFlag = cli.BoolFlag{
+ Name: "ethash.dagslockmmap",
+ Usage: "Lock memory maps for recent ethash mining DAGs",
+ }
ExternalConsensusFlag = cli.BoolFlag{
Name: "externalcl",
Usage: "Enables the external consensus layer",
@@ -1515,6 +1535,26 @@ func setTxPool(ctx *cli.Context, fullCfg *ethconfig.Config) {
cfg.CommitEvery = common2.RandomizeDuration(ctx.Duration(TxPoolCommitEveryFlag.Name))
}
+func setEthash(ctx *cli.Context, datadir string, cfg *ethconfig.Config) {
+ if ctx.IsSet(EthashDatasetDirFlag.Name) {
+ cfg.Ethash.DatasetDir = ctx.String(EthashDatasetDirFlag.Name)
+ } else {
+ cfg.Ethash.DatasetDir = filepath.Join(datadir, "ethash-dags")
+ }
+ if ctx.IsSet(EthashCachesInMemoryFlag.Name) {
+ cfg.Ethash.CachesInMem = ctx.Int(EthashCachesInMemoryFlag.Name)
+ }
+ if ctx.IsSet(EthashCachesLockMmapFlag.Name) {
+ cfg.Ethash.CachesLockMmap = ctx.Bool(EthashCachesLockMmapFlag.Name)
+ }
+ if ctx.IsSet(FakePoWFlag.Name) {
+ cfg.Ethash.PowMode = ethashcfg.ModeFake
+ }
+ if ctx.IsSet(EthashDatasetsLockMmapFlag.Name) {
+ cfg.Ethash.DatasetsLockMmap = ctx.Bool(EthashDatasetsLockMmapFlag.Name)
+ }
+}
+
func SetupMinerCobra(cmd *cobra.Command, cfg *params.MiningConfig) {
flags := cmd.Flags()
var err error
@@ -1794,6 +1834,7 @@ func SetEthConfig(ctx *cli.Context, nodeConfig *nodecfg.Config, cfg *ethconfig.C
cfg.TxPool = ethconfig.DefaultTxPool2Config(cfg)
cfg.TxPool.DBDir = nodeConfig.Dirs.TxPool
+ setEthash(ctx, nodeConfig.Dirs.DataDir, cfg)
setClique(ctx, &cfg.Clique, nodeConfig.Dirs.DataDir)
setMiner(ctx, &cfg.Miner)
setWhitelist(ctx, cfg)
diff --git a/consensus/aura/aura.go b/consensus/aura/aura.go
index dd74a02ff90..265313a103f 100644
--- a/consensus/aura/aura.go
+++ b/consensus/aura/aura.go
@@ -35,7 +35,7 @@ import (
"github.com/erigontech/erigon/consensus"
"github.com/erigontech/erigon/consensus/clique"
- "github.com/erigontech/erigon/consensus/mainnet"
+ "github.com/erigontech/erigon/consensus/ethash"
"github.com/erigontech/erigon/core/state"
"github.com/erigontech/erigon/core/tracing"
"github.com/erigontech/erigon/core/types"
@@ -371,7 +371,7 @@ func (c *AuRa) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Hea
log.Error("consensus.ErrUnknownAncestor", "parentNum", number-1, "hash", header.ParentHash.String())
return consensus.ErrUnknownAncestor
}
- return mainnet.VerifyHeaderBasics(chain, header, parent, true /*checkTimestamp*/, c.HasGasLimitContract() /*skipGasLimit*/)
+ return ethash.VerifyHeaderBasics(chain, header, parent, true /*checkTimestamp*/, c.HasGasLimitContract() /*skipGasLimit*/)
}
// nolint
diff --git a/consensus/clique/snapshot_test.go b/consensus/clique/snapshot_test.go
index 15fc5fdf192..49d6b5443d5 100644
--- a/consensus/clique/snapshot_test.go
+++ b/consensus/clique/snapshot_test.go
@@ -490,7 +490,7 @@ func TestClique(t *testing.T) {
}
}
if failed {
- _ = engine.Close()
+ engine.Close()
return
}
chainX := &core.ChainPack{Blocks: batches[len(batches)-1]}
@@ -507,7 +507,7 @@ func TestClique(t *testing.T) {
t.Errorf("test %d: unexpected failure: %v", i, err)
}
if tt.failure != nil {
- _ = engine.Close()
+ engine.Close()
return
}
// No failure was produced or requested, generate the final voting snapshot
@@ -529,7 +529,7 @@ func TestClique(t *testing.T) {
}); err != nil {
t.Errorf("test %d: failed to retrieve voting snapshot %d(%s): %v",
i, head.NumberU64(), head.Hash().Hex(), err)
- _ = engine.Close()
+ engine.Close()
return
}
@@ -548,7 +548,7 @@ func TestClique(t *testing.T) {
result := snap.GetSigners()
if len(result) != len(signers) {
t.Errorf("test %d: signers mismatch: have %x, want %x", i, result, signers)
- _ = engine.Close()
+ engine.Close()
return
}
for j := 0; j < len(result); j++ {
@@ -556,7 +556,7 @@ func TestClique(t *testing.T) {
t.Errorf("test %d, signer %d: signer mismatch: have %x, want %x", i, j, result[j], signers[j])
}
}
- _ = engine.Close()
+ engine.Close()
})
}
}
diff --git a/consensus/ethash/algorithm.go b/consensus/ethash/algorithm.go
new file mode 100644
index 00000000000..0cca67bb8de
--- /dev/null
+++ b/consensus/ethash/algorithm.go
@@ -0,0 +1,1228 @@
+// Copyright 2017 The go-ethereum Authors
+// (original work)
+// Copyright 2024 The Erigon Authors
+// (modifications)
+// This file is part of Erigon.
+//
+// Erigon 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.
+//
+// Erigon 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 Erigon. If not, see .
+
+package ethash
+
+import (
+ "encoding/binary"
+ "hash"
+ "math/big"
+ "reflect"
+ "runtime"
+ "sync"
+ "sync/atomic"
+ "time"
+ "unsafe"
+
+ common2 "github.com/erigontech/erigon-lib/common"
+
+ "golang.org/x/crypto/sha3"
+
+ "github.com/erigontech/erigon-lib/common/length"
+
+ "github.com/erigontech/erigon-lib/log/v3"
+
+ "github.com/erigontech/erigon/common"
+ "github.com/erigontech/erigon/common/bitutil"
+ "github.com/erigontech/erigon/common/debug"
+ "github.com/erigontech/erigon/crypto"
+)
+
+const (
+ datasetInitBytes = 1 << 30 // Bytes in dataset at genesis
+ datasetGrowthBytes = 1 << 23 // Dataset growth per epoch
+ cacheInitBytes = 1 << 24 // Bytes in cache at genesis
+ cacheGrowthBytes = 1 << 17 // Cache growth per epoch
+ epochLength = 30000 // Blocks per epoch
+ mixBytes = 128 // Width of mix
+ hashBytes = 64 // Hash length in bytes
+ hashWords = 16 // Number of 32 bit ints in a hash
+ datasetParents = 256 // Number of parents of each dataset element
+ cacheRounds = 3 // Number of rounds in cache production
+ loopAccesses = 64 // Number of accesses in hashimoto loop
+)
+
+// cacheSize returns the size of the ethash verification cache that belongs to a certain
+// block number.
+func cacheSize(block uint64) uint64 {
+ epoch := int(block / epochLength)
+ if epoch < maxEpoch {
+ return cacheSizes[epoch]
+ }
+ return calcCacheSize(epoch)
+}
+
+// calcCacheSize calculates the cache size for epoch. The cache size grows linearly,
+// however, we always take the highest prime below the linearly growing threshold in order
+// to reduce the risk of accidental regularities leading to cyclic behavior.
+func calcCacheSize(epoch int) uint64 {
+ size := cacheInitBytes + cacheGrowthBytes*uint64(epoch) - hashBytes
+ for !new(big.Int).SetUint64(size / hashBytes).ProbablyPrime(1) { // Always accurate for n < 2^64
+ size -= 2 * hashBytes
+ }
+ return size
+}
+
+// datasetSize returns the size of the ethash mining dataset that belongs to a certain
+// block number.
+func datasetSize(block uint64) uint64 {
+ epoch := int(block / epochLength)
+ if epoch < maxEpoch {
+ return datasetSizes[epoch]
+ }
+ return calcDatasetSize(epoch)
+}
+
+// calcDatasetSize calculates the dataset size for epoch. The dataset size grows linearly,
+// however, we always take the highest prime below the linearly growing threshold in order
+// to reduce the risk of accidental regularities leading to cyclic behavior.
+func calcDatasetSize(epoch int) uint64 {
+ size := datasetInitBytes + datasetGrowthBytes*uint64(epoch) - mixBytes
+ for !new(big.Int).SetUint64(size / mixBytes).ProbablyPrime(1) { // Always accurate for n < 2^64
+ size -= 2 * mixBytes
+ }
+ return size
+}
+
+// hasher is a repetitive hasher allowing the same hash data structures to be
+// reused between hash runs instead of requiring new ones to be created.
+type hasher func(dest []byte, data []byte)
+
+type readerHash interface {
+ hash.Hash
+ Read([]byte) (int, error)
+}
+
+// makeHasher creates a repetitive hasher, allowing the same hash data structures to
+// be reused between hash runs instead of requiring new ones to be created. The returned
+// function is not thread safe!
+func makeHasher(h hash.Hash) hasher {
+ // sha3.state supports Read to get the sum, use it to avoid the overhead of Sum.
+ // Read alters the state but we reset the hash before every operation.
+ rh, ok := h.(readerHash)
+ if !ok {
+ panic("can't find Read method on hash")
+ }
+ outputLen := rh.Size()
+ return func(dest []byte, data []byte) {
+ rh.Reset()
+ _, writeErr := rh.Write(data)
+ if writeErr != nil {
+ log.Warn("Failed to write data", "err", writeErr)
+ }
+ _, readErr := rh.Read(dest[:outputLen])
+ if readErr != nil {
+ log.Warn("Failed to read data", "err", readErr)
+ }
+ }
+}
+
+// seedHash is the seed to use for generating a verification cache and the mining
+// dataset.
+func seedHash(block uint64) []byte {
+ seed := make([]byte, 32)
+ if block < epochLength {
+ return seed
+ }
+
+ h := common2.NewHasher()
+
+ for i := 0; i < int(block/epochLength); i++ {
+ h.Sha.Reset()
+ //nolint:errcheck
+ _, writeErr := h.Sha.Write(seed)
+ if writeErr != nil {
+ log.Warn("Failed to write data", "err", writeErr)
+ }
+ //nolint:errcheck
+ _, readErr := h.Sha.Read(seed)
+ if readErr != nil {
+ log.Warn("Failed to read data", "err", readErr)
+ }
+ }
+
+ common2.ReturnHasherToPool(h)
+
+ return seed
+}
+
+func seedHashOld(block uint64) []byte {
+ seed := make([]byte, 32)
+ if block < epochLength {
+ return seed
+ }
+ keccak256 := makeHasher(sha3.NewLegacyKeccak256())
+ for i := 0; i < int(block/epochLength); i++ {
+ keccak256(seed, seed)
+ }
+ return seed
+}
+
+// generateCache creates a verification cache of a given size for an input seed.
+// The cache production process involves first sequentially filling up 32 MB of
+// memory, then performing two passes of Sergio Demian Lerner's RandMemoHash
+// algorithm from Strict Memory Hard Hashing Functions (2014). The output is a
+// set of 524288 64-byte values.
+// This method places the result into dest in machine byte order.
+func generateCache(dest []uint32, epoch uint64, seed []byte) {
+ // Print some debug logs to allow analysis on low end devices
+ logger := log.New("epoch", epoch)
+
+ start := time.Now()
+ defer func() {
+ elapsed := time.Since(start)
+
+ logFn := logger.Trace
+ if elapsed > 5*time.Second {
+ logFn = logger.Info
+ }
+ logFn("Generated ethash verification cache", "elapsed", common.PrettyDuration(elapsed))
+ }()
+ // Convert our destination slice to a byte buffer
+ var cache []byte
+ cacheHdr := (*reflect.SliceHeader)(unsafe.Pointer(&cache))
+ dstHdr := (*reflect.SliceHeader)(unsafe.Pointer(&dest))
+ cacheHdr.Data = dstHdr.Data
+ cacheHdr.Len = dstHdr.Len * 4
+ cacheHdr.Cap = dstHdr.Cap * 4
+
+ // Calculate the number of theoretical rows (we'll store in one buffer nonetheless)
+ size := uint64(len(cache))
+ rows := int(size) / hashBytes
+
+ // Start a monitoring goroutine to report progress on low end devices
+ var progress uint32
+
+ done := make(chan struct{})
+ defer close(done)
+
+ go func() {
+ for {
+ select {
+ case <-done:
+ return
+ case <-time.After(3 * time.Second):
+ logger.Info("Generating ethash verification cache", "percentage", atomic.LoadUint32(&progress)*100/uint32(rows)/4, "elapsed", common.PrettyDuration(time.Since(start)))
+ }
+ }
+ }()
+ // Create a hasher to reuse between invocations
+ keccak512 := makeHasher(sha3.NewLegacyKeccak512())
+
+ // Sequentially produce the initial dataset
+ keccak512(cache, seed)
+ for offset := uint64(hashBytes); offset < size; offset += hashBytes {
+ keccak512(cache[offset:], cache[offset-hashBytes:offset])
+ atomic.AddUint32(&progress, 1)
+ }
+ // Use a low-round version of randmemohash
+ tempRef := bytes64Pool.Get().(*[]byte)
+ temp := *tempRef
+ defer bytes64Pool.Put(tempRef)
+
+ for i := 0; i < cacheRounds; i++ {
+ for j := 0; j < rows; j++ {
+ var (
+ srcOff = ((j - 1 + rows) % rows) * hashBytes
+ dstOff = j * hashBytes
+ xorOff = (binary.LittleEndian.Uint32(cache[dstOff:]) % uint32(rows)) * hashBytes
+ )
+ bitutil.XORBytes(temp, cache[srcOff:srcOff+hashBytes], cache[xorOff:xorOff+hashBytes])
+ keccak512(cache[dstOff:], temp)
+
+ atomic.AddUint32(&progress, 1)
+ }
+ }
+ // Swap the byte order on big endian systems and return
+ if !isLittleEndian() {
+ swap(cache)
+ }
+}
+
+// swap changes the byte order of the buffer assuming a uint32 representation.
+func swap(buffer []byte) {
+ for i := 0; i < len(buffer); i += 4 {
+ binary.BigEndian.PutUint32(buffer[i:], binary.LittleEndian.Uint32(buffer[i:]))
+ }
+}
+
+const primeFNV = 0x01000193
+
+// fnv is an algorithm inspired by the FNV hash, which in some cases is used as
+// a non-associative substitute for XOR. Note that we multiply the prime with
+// the full 32-bit input, in contrast with the FNV-1 spec which multiplies the
+// prime with one byte (octet) in turn.
+func fnv(a, b uint32) uint32 {
+ return a*primeFNV ^ b
+}
+
+// fnvHash mixes in data into mix using the ethash fnv method.
+func fnvHash16(mix []uint32, data []uint32) {
+ for i := 0; i < 16; i++ {
+ mix[i] = mix[i]*primeFNV ^ data[i]
+ }
+}
+
+func fnvHash32(mix []uint32, data []uint32) {
+ for i := 0; i < 32; i++ {
+ mix[i] = mix[i]*primeFNV ^ data[i]
+ }
+}
+
+var bytes64Pool = sync.Pool{
+ New: func() interface{} {
+ buf := make([]byte, hashBytes)
+ return &buf
+ },
+}
+var bytes40Pool = sync.Pool{
+ New: func() interface{} {
+ buf := make([]byte, 40)
+ return &buf
+ },
+}
+
+// generateDatasetItem combines data from 256 pseudorandomly selected cache nodes,
+// and hashes that to compute a single dataset node.
+func generateDatasetItem(cache []uint32, index uint32, keccak512 hasher, fn func(item []byte)) {
+ // Calculate the number of theoretical rows (we use one buffer nonetheless)
+ rows := uint32(len(cache) / hashWords)
+
+ // Initialize the mix
+ mixRef := bytes64Pool.Get().(*[]byte)
+ mix := *mixRef
+
+ binary.LittleEndian.PutUint32(mix, cache[(index%rows)*hashWords]^index)
+ for i := 1; i < hashWords; i++ {
+ binary.LittleEndian.PutUint32(mix[i*4:], cache[(index%rows)*hashWords+uint32(i)])
+ }
+ keccak512(mix, mix)
+
+ // Convert the mix to uint32s to avoid constant bit shifting
+ intMix := make([]uint32, hashWords)
+ for i := 0; i < len(intMix); i++ {
+ intMix[i] = binary.LittleEndian.Uint32(mix[i*4:])
+ }
+ // fnv it with a lot of random cache nodes based on index
+ for i := uint32(0); i < datasetParents; i++ {
+ parent := fnv(index^i, intMix[i%16]) % rows
+ fnvHash16(intMix, cache[parent*hashWords:])
+ }
+ // Flatten the uint32 mix into a binary one and return
+ for i, val := range intMix {
+ binary.LittleEndian.PutUint32(mix[i*4:], val)
+ }
+ keccak512(mix, mix)
+
+ fn(mix)
+ bytes64Pool.Put(mixRef)
+}
+
+// generateDataset generates the entire ethash dataset for mining.
+// This method places the result into dest in machine byte order.
+func generateDataset(dest []uint32, epoch uint64, cache []uint32) {
+ // Print some debug logs to allow analysis on low end devices
+ logger := log.New("epoch", epoch)
+
+ start := time.Now()
+ defer func() {
+ elapsed := time.Since(start)
+
+ logFn := logger.Debug
+ if elapsed > 3*time.Second {
+ logFn = logger.Info
+ }
+ logFn("Generated ethash verification cache", "elapsed", common.PrettyDuration(elapsed))
+ }()
+
+ // Figure out whether the bytes need to be swapped for the machine
+ swapped := !isLittleEndian()
+
+ // Convert our destination slice to a byte buffer
+ var dataset []byte
+ datasetHdr := (*reflect.SliceHeader)(unsafe.Pointer(&dataset))
+ destHdr := (*reflect.SliceHeader)(unsafe.Pointer(&dest))
+ datasetHdr.Data = destHdr.Data
+ datasetHdr.Len = destHdr.Len * 4
+ datasetHdr.Cap = destHdr.Cap * 4
+
+ // Generate the dataset on many goroutines since it takes a while
+ threads := runtime.GOMAXPROCS(-1)
+ size := uint64(len(dataset))
+
+ var pend sync.WaitGroup
+ pend.Add(threads)
+
+ var progress uint64
+ for i := 0; i < threads; i++ {
+ go func(id int) {
+ defer debug.LogPanic()
+ defer pend.Done()
+
+ // Create a hasher to reuse between invocations
+ keccak512 := makeHasher(sha3.NewLegacyKeccak512())
+
+ // Calculate the data segment this thread should generate
+ batch := (size + hashBytes*uint64(threads) - 1) / (hashBytes * uint64(threads))
+ first := uint64(id) * batch
+ limit := first + batch
+ if limit > size/hashBytes {
+ limit = size / hashBytes
+ }
+ // Calculate the dataset segment
+ percent := size / hashBytes / 100
+ for index := first; index < limit; index++ {
+ generateDatasetItem(cache, uint32(index), keccak512, func(item []byte) {
+ if swapped {
+ swap(item)
+ }
+ copy(dataset[index*hashBytes:], item)
+ })
+
+ if status := atomic.AddUint64(&progress, 1); status%percent == 0 {
+ logger.Info("Generating DAG in progress", "percentage", (status*100)/(size/hashBytes), "elapsed", common.PrettyDuration(time.Since(start)))
+ }
+ }
+ }(i)
+ }
+ // Wait for all the generators to finish and return
+ pend.Wait()
+}
+
+// hashimoto aggregates data from the full dataset in order to produce our final
+// value for a particular header hash and nonce.
+func hashimoto(hash []byte, nonce uint64, size uint64, lookup func(index uint32) []uint32) ([]byte, []byte) {
+ // Calculate the number of theoretical rows (we use one buffer nonetheless)
+ rows := uint32(size / mixBytes)
+
+ // Combine header+nonce into a 64 byte seed
+ seedRef := bytes40Pool.Get().(*[]byte)
+ seed := *seedRef
+ defer bytes40Pool.Put(seedRef)
+
+ copy(seed, hash)
+ binary.LittleEndian.PutUint64(seed[32:], nonce)
+
+ seed = crypto.Keccak512(seed)
+ seedHead := binary.LittleEndian.Uint32(seed)
+
+ // Start the mix with replicated seed
+ mix := make([]uint32, mixBytes/4)
+ for i := 0; i < len(mix); i++ {
+ mix[i] = binary.LittleEndian.Uint32(seed[i%16*4:])
+ }
+ // Mix in random dataset nodes
+ temp := make([]uint32, len(mix))
+
+ for i := 0; i < loopAccesses; i++ {
+ parent := fnv(uint32(i)^seedHead, mix[i%len(mix)]) % rows
+ for j := uint32(0); j < mixBytes/hashBytes; j++ {
+ copy(temp[j*hashWords:], lookup(2*parent+j))
+ }
+ fnvHash32(mix, temp)
+ }
+ // Compress mix
+ for i := 0; i < len(mix); i += 4 {
+ mix[i/4] = fnv(fnv(fnv(mix[i], mix[i+1]), mix[i+2]), mix[i+3])
+ }
+ mix = mix[:len(mix)/4]
+
+ digest := make([]byte, length.Hash)
+ for i, val := range mix {
+ binary.LittleEndian.PutUint32(digest[i*4:], val)
+ }
+ return digest, crypto.Keccak256(append(seed, digest...))
+}
+
+// hashimotoLight aggregates data from the full dataset (using only a small
+// in-memory cache) in order to produce our final value for a particular header
+// hash and nonce.
+func hashimotoLight(size uint64, cache []uint32, hash []byte, nonce uint64) ([]byte, []byte) {
+ keccak512 := makeHasher(sha3.NewLegacyKeccak512())
+
+ lookup := func(index uint32) []uint32 {
+ data := make([]uint32, 16) // 64/4
+ generateDatasetItem(cache, index, keccak512, func(item []byte) {
+ for i := 0; i < len(data); i++ {
+ data[i] = binary.LittleEndian.Uint32(item[i*4:])
+ }
+ })
+
+ return data
+ }
+ return hashimoto(hash, nonce, size, lookup)
+}
+
+// hashimotoFull aggregates data from the full dataset (using the full in-memory
+// dataset) in order to produce our final value for a particular header hash and
+// nonce.
+func hashimotoFull(dataset []uint32, hash []byte, nonce uint64) ([]byte, []byte) {
+ lookup := func(index uint32) []uint32 {
+ offset := index * hashWords
+ return dataset[offset : offset+hashWords]
+ }
+ return hashimoto(hash, nonce, uint64(len(dataset))*4, lookup)
+}
+
+const maxEpoch = 2048
+
+// datasetSizes is a lookup table for the ethash dataset size for the first 2048
+// epochs (i.e. 61440000 blocks).
+var datasetSizes = [maxEpoch]uint64{
+ 1073739904, 1082130304, 1090514816, 1098906752, 1107293056,
+ 1115684224, 1124070016, 1132461952, 1140849536, 1149232768,
+ 1157627776, 1166013824, 1174404736, 1182786944, 1191180416,
+ 1199568512, 1207958912, 1216345216, 1224732032, 1233124736,
+ 1241513344, 1249902464, 1258290304, 1266673792, 1275067264,
+ 1283453312, 1291844992, 1300234112, 1308619904, 1317010048,
+ 1325397376, 1333787776, 1342176128, 1350561664, 1358954368,
+ 1367339392, 1375731584, 1384118144, 1392507008, 1400897408,
+ 1409284736, 1417673344, 1426062464, 1434451072, 1442839168,
+ 1451229056, 1459615616, 1468006016, 1476394112, 1484782976,
+ 1493171584, 1501559168, 1509948032, 1518337664, 1526726528,
+ 1535114624, 1543503488, 1551892096, 1560278656, 1568669056,
+ 1577056384, 1585446272, 1593831296, 1602219392, 1610610304,
+ 1619000192, 1627386752, 1635773824, 1644164224, 1652555648,
+ 1660943488, 1669332608, 1677721216, 1686109312, 1694497664,
+ 1702886272, 1711274624, 1719661184, 1728047744, 1736434816,
+ 1744829056, 1753218944, 1761606272, 1769995904, 1778382464,
+ 1786772864, 1795157888, 1803550592, 1811937664, 1820327552,
+ 1828711552, 1837102976, 1845488768, 1853879936, 1862269312,
+ 1870656896, 1879048064, 1887431552, 1895825024, 1904212096,
+ 1912601216, 1920988544, 1929379456, 1937765504, 1946156672,
+ 1954543232, 1962932096, 1971321728, 1979707264, 1988093056,
+ 1996487552, 2004874624, 2013262208, 2021653888, 2030039936,
+ 2038430848, 2046819968, 2055208576, 2063596672, 2071981952,
+ 2080373632, 2088762752, 2097149056, 2105539712, 2113928576,
+ 2122315136, 2130700672, 2139092608, 2147483264, 2155872128,
+ 2164257664, 2172642176, 2181035392, 2189426048, 2197814912,
+ 2206203008, 2214587264, 2222979712, 2231367808, 2239758208,
+ 2248145024, 2256527744, 2264922752, 2273312128, 2281701248,
+ 2290086272, 2298476672, 2306867072, 2315251072, 2323639168,
+ 2332032128, 2340420224, 2348808064, 2357196416, 2365580416,
+ 2373966976, 2382363008, 2390748544, 2399139968, 2407530368,
+ 2415918976, 2424307328, 2432695424, 2441084288, 2449472384,
+ 2457861248, 2466247808, 2474637184, 2483026816, 2491414144,
+ 2499803776, 2508191872, 2516582272, 2524970368, 2533359232,
+ 2541743488, 2550134144, 2558525056, 2566913408, 2575301504,
+ 2583686528, 2592073856, 2600467328, 2608856192, 2617240448,
+ 2625631616, 2634022016, 2642407552, 2650796416, 2659188352,
+ 2667574912, 2675965312, 2684352896, 2692738688, 2701130624,
+ 2709518464, 2717907328, 2726293376, 2734685056, 2743073152,
+ 2751462016, 2759851648, 2768232832, 2776625536, 2785017728,
+ 2793401984, 2801794432, 2810182016, 2818571648, 2826959488,
+ 2835349376, 2843734144, 2852121472, 2860514432, 2868900992,
+ 2877286784, 2885676928, 2894069632, 2902451584, 2910843008,
+ 2919234688, 2927622784, 2936011648, 2944400768, 2952789376,
+ 2961177728, 2969565568, 2977951616, 2986338944, 2994731392,
+ 3003120256, 3011508352, 3019895936, 3028287104, 3036675968,
+ 3045063808, 3053452928, 3061837696, 3070228352, 3078615424,
+ 3087003776, 3095394944, 3103782272, 3112173184, 3120562048,
+ 3128944768, 3137339264, 3145725056, 3154109312, 3162505088,
+ 3170893184, 3179280256, 3187669376, 3196056704, 3204445568,
+ 3212836736, 3221224064, 3229612928, 3238002304, 3246391168,
+ 3254778496, 3263165824, 3271556224, 3279944576, 3288332416,
+ 3296719232, 3305110912, 3313500032, 3321887104, 3330273152,
+ 3338658944, 3347053184, 3355440512, 3363827072, 3372220288,
+ 3380608384, 3388997504, 3397384576, 3405774208, 3414163072,
+ 3422551936, 3430937984, 3439328384, 3447714176, 3456104576,
+ 3464493952, 3472883584, 3481268864, 3489655168, 3498048896,
+ 3506434432, 3514826368, 3523213952, 3531603584, 3539987072,
+ 3548380288, 3556763264, 3565157248, 3573545344, 3581934464,
+ 3590324096, 3598712704, 3607098752, 3615488384, 3623877248,
+ 3632265856, 3640646528, 3649043584, 3657430144, 3665821568,
+ 3674207872, 3682597504, 3690984832, 3699367808, 3707764352,
+ 3716152448, 3724541056, 3732925568, 3741318016, 3749706368,
+ 3758091136, 3766481536, 3774872704, 3783260032, 3791650432,
+ 3800036224, 3808427648, 3816815488, 3825204608, 3833592704,
+ 3841981568, 3850370432, 3858755968, 3867147904, 3875536256,
+ 3883920512, 3892313728, 3900702592, 3909087872, 3917478784,
+ 3925868416, 3934256512, 3942645376, 3951032192, 3959422336,
+ 3967809152, 3976200064, 3984588416, 3992974976, 4001363584,
+ 4009751168, 4018141312, 4026530432, 4034911616, 4043308928,
+ 4051695488, 4060084352, 4068472448, 4076862848, 4085249408,
+ 4093640576, 4102028416, 4110413696, 4118805632, 4127194496,
+ 4135583104, 4143971968, 4152360832, 4160746112, 4169135744,
+ 4177525888, 4185912704, 4194303616, 4202691968, 4211076736,
+ 4219463552, 4227855488, 4236246656, 4244633728, 4253022848,
+ 4261412224, 4269799808, 4278184832, 4286578048, 4294962304,
+ 4303349632, 4311743104, 4320130432, 4328521088, 4336909184,
+ 4345295488, 4353687424, 4362073472, 4370458496, 4378852736,
+ 4387238528, 4395630208, 4404019072, 4412407424, 4420790656,
+ 4429182848, 4437571456, 4445962112, 4454344064, 4462738048,
+ 4471119232, 4479516544, 4487904128, 4496289664, 4504682368,
+ 4513068416, 4521459584, 4529846144, 4538232704, 4546619776,
+ 4555010176, 4563402112, 4571790208, 4580174464, 4588567936,
+ 4596957056, 4605344896, 4613734016, 4622119808, 4630511488,
+ 4638898816, 4647287936, 4655675264, 4664065664, 4672451968,
+ 4680842624, 4689231488, 4697620352, 4706007424, 4714397056,
+ 4722786176, 4731173248, 4739562368, 4747951744, 4756340608,
+ 4764727936, 4773114496, 4781504384, 4789894784, 4798283648,
+ 4806667648, 4815059584, 4823449472, 4831835776, 4840226176,
+ 4848612224, 4857003392, 4865391488, 4873780096, 4882169728,
+ 4890557312, 4898946944, 4907333248, 4915722368, 4924110976,
+ 4932499328, 4940889728, 4949276032, 4957666432, 4966054784,
+ 4974438016, 4982831488, 4991221376, 4999607168, 5007998848,
+ 5016386432, 5024763776, 5033164672, 5041544576, 5049941888,
+ 5058329728, 5066717056, 5075107456, 5083494272, 5091883904,
+ 5100273536, 5108662144, 5117048192, 5125436032, 5133827456,
+ 5142215296, 5150605184, 5158993024, 5167382144, 5175769472,
+ 5184157568, 5192543872, 5200936064, 5209324928, 5217711232,
+ 5226102656, 5234490496, 5242877312, 5251263872, 5259654016,
+ 5268040832, 5276434304, 5284819328, 5293209728, 5301598592,
+ 5309986688, 5318374784, 5326764416, 5335151488, 5343542144,
+ 5351929472, 5360319872, 5368706944, 5377096576, 5385484928,
+ 5393871232, 5402263424, 5410650496, 5419040384, 5427426944,
+ 5435816576, 5444205952, 5452594816, 5460981376, 5469367936,
+ 5477760896, 5486148736, 5494536832, 5502925952, 5511315328,
+ 5519703424, 5528089984, 5536481152, 5544869504, 5553256064,
+ 5561645696, 5570032768, 5578423936, 5586811264, 5595193216,
+ 5603585408, 5611972736, 5620366208, 5628750464, 5637143936,
+ 5645528192, 5653921408, 5662310272, 5670694784, 5679082624,
+ 5687474048, 5695864448, 5704251008, 5712641408, 5721030272,
+ 5729416832, 5737806208, 5746194304, 5754583936, 5762969984,
+ 5771358592, 5779748224, 5788137856, 5796527488, 5804911232,
+ 5813300608, 5821692544, 5830082176, 5838468992, 5846855552,
+ 5855247488, 5863636096, 5872024448, 5880411008, 5888799872,
+ 5897186432, 5905576832, 5913966976, 5922352768, 5930744704,
+ 5939132288, 5947522432, 5955911296, 5964299392, 5972688256,
+ 5981074304, 5989465472, 5997851008, 6006241408, 6014627968,
+ 6023015552, 6031408256, 6039796096, 6048185216, 6056574848,
+ 6064963456, 6073351808, 6081736064, 6090128768, 6098517632,
+ 6106906496, 6115289216, 6123680896, 6132070016, 6140459648,
+ 6148849024, 6157237376, 6165624704, 6174009728, 6182403712,
+ 6190792064, 6199176064, 6207569792, 6215952256, 6224345216,
+ 6232732544, 6241124224, 6249510272, 6257899136, 6266287744,
+ 6274676864, 6283065728, 6291454336, 6299843456, 6308232064,
+ 6316620928, 6325006208, 6333395584, 6341784704, 6350174848,
+ 6358562176, 6366951296, 6375337856, 6383729536, 6392119168,
+ 6400504192, 6408895616, 6417283456, 6425673344, 6434059136,
+ 6442444672, 6450837376, 6459223424, 6467613056, 6476004224,
+ 6484393088, 6492781952, 6501170048, 6509555072, 6517947008,
+ 6526336384, 6534725504, 6543112832, 6551500672, 6559888768,
+ 6568278656, 6576662912, 6585055616, 6593443456, 6601834112,
+ 6610219648, 6618610304, 6626999168, 6635385472, 6643777408,
+ 6652164224, 6660552832, 6668941952, 6677330048, 6685719424,
+ 6694107776, 6702493568, 6710882176, 6719274112, 6727662976,
+ 6736052096, 6744437632, 6752825984, 6761213824, 6769604224,
+ 6777993856, 6786383488, 6794770816, 6803158144, 6811549312,
+ 6819937664, 6828326528, 6836706176, 6845101696, 6853491328,
+ 6861880448, 6870269312, 6878655104, 6887046272, 6895433344,
+ 6903822208, 6912212864, 6920596864, 6928988288, 6937377152,
+ 6945764992, 6954149248, 6962544256, 6970928768, 6979317376,
+ 6987709312, 6996093824, 7004487296, 7012875392, 7021258624,
+ 7029652352, 7038038912, 7046427776, 7054818944, 7063207808,
+ 7071595136, 7079980928, 7088372608, 7096759424, 7105149824,
+ 7113536896, 7121928064, 7130315392, 7138699648, 7147092352,
+ 7155479168, 7163865728, 7172249984, 7180648064, 7189036672,
+ 7197424768, 7205810816, 7214196608, 7222589824, 7230975104,
+ 7239367552, 7247755904, 7256145536, 7264533376, 7272921472,
+ 7281308032, 7289694848, 7298088832, 7306471808, 7314864512,
+ 7323253888, 7331643008, 7340029568, 7348419712, 7356808832,
+ 7365196672, 7373585792, 7381973888, 7390362752, 7398750592,
+ 7407138944, 7415528576, 7423915648, 7432302208, 7440690304,
+ 7449080192, 7457472128, 7465860992, 7474249088, 7482635648,
+ 7491023744, 7499412608, 7507803008, 7516192384, 7524579968,
+ 7532967296, 7541358464, 7549745792, 7558134656, 7566524032,
+ 7574912896, 7583300992, 7591690112, 7600075136, 7608466816,
+ 7616854912, 7625244544, 7633629824, 7642020992, 7650410368,
+ 7658794112, 7667187328, 7675574912, 7683961984, 7692349568,
+ 7700739712, 7709130368, 7717519232, 7725905536, 7734295424,
+ 7742683264, 7751069056, 7759457408, 7767849088, 7776238208,
+ 7784626816, 7793014912, 7801405312, 7809792128, 7818179968,
+ 7826571136, 7834957184, 7843347328, 7851732352, 7860124544,
+ 7868512384, 7876902016, 7885287808, 7893679744, 7902067072,
+ 7910455936, 7918844288, 7927230848, 7935622784, 7944009344,
+ 7952400256, 7960786048, 7969176704, 7977565312, 7985953408,
+ 7994339968, 8002730368, 8011119488, 8019508096, 8027896192,
+ 8036285056, 8044674688, 8053062272, 8061448832, 8069838464,
+ 8078227328, 8086616704, 8095006592, 8103393664, 8111783552,
+ 8120171392, 8128560256, 8136949376, 8145336704, 8153726848,
+ 8162114944, 8170503296, 8178891904, 8187280768, 8195669632,
+ 8204058496, 8212444544, 8220834176, 8229222272, 8237612672,
+ 8246000768, 8254389376, 8262775168, 8271167104, 8279553664,
+ 8287944064, 8296333184, 8304715136, 8313108352, 8321497984,
+ 8329885568, 8338274432, 8346663296, 8355052928, 8363441536,
+ 8371828352, 8380217984, 8388606592, 8396996224, 8405384576,
+ 8413772672, 8422161536, 8430549376, 8438939008, 8447326592,
+ 8455715456, 8464104832, 8472492928, 8480882048, 8489270656,
+ 8497659776, 8506045312, 8514434944, 8522823808, 8531208832,
+ 8539602304, 8547990656, 8556378752, 8564768384, 8573154176,
+ 8581542784, 8589933952, 8598322816, 8606705024, 8615099264,
+ 8623487872, 8631876992, 8640264064, 8648653952, 8657040256,
+ 8665430656, 8673820544, 8682209152, 8690592128, 8698977152,
+ 8707374464, 8715763328, 8724151424, 8732540032, 8740928384,
+ 8749315712, 8757704576, 8766089344, 8774480768, 8782871936,
+ 8791260032, 8799645824, 8808034432, 8816426368, 8824812928,
+ 8833199488, 8841591424, 8849976448, 8858366336, 8866757248,
+ 8875147136, 8883532928, 8891923328, 8900306816, 8908700288,
+ 8917088384, 8925478784, 8933867392, 8942250368, 8950644608,
+ 8959032704, 8967420544, 8975809664, 8984197504, 8992584064,
+ 9000976256, 9009362048, 9017752448, 9026141312, 9034530688,
+ 9042917504, 9051307904, 9059694208, 9068084864, 9076471424,
+ 9084861824, 9093250688, 9101638528, 9110027648, 9118416512,
+ 9126803584, 9135188096, 9143581312, 9151969664, 9160356224,
+ 9168747136, 9177134464, 9185525632, 9193910144, 9202302848,
+ 9210690688, 9219079552, 9227465344, 9235854464, 9244244864,
+ 9252633472, 9261021824, 9269411456, 9277799296, 9286188928,
+ 9294574208, 9302965888, 9311351936, 9319740032, 9328131968,
+ 9336516736, 9344907392, 9353296768, 9361685888, 9370074752,
+ 9378463616, 9386849408, 9395239808, 9403629184, 9412016512,
+ 9420405376, 9428795008, 9437181568, 9445570688, 9453960832,
+ 9462346624, 9470738048, 9479121536, 9487515008, 9495903616,
+ 9504289664, 9512678528, 9521067904, 9529456256, 9537843584,
+ 9546233728, 9554621312, 9563011456, 9571398784, 9579788672,
+ 9588178304, 9596567168, 9604954496, 9613343104, 9621732992,
+ 9630121856, 9638508416, 9646898816, 9655283584, 9663675776,
+ 9672061312, 9680449664, 9688840064, 9697230464, 9705617536,
+ 9714003584, 9722393984, 9730772608, 9739172224, 9747561088,
+ 9755945344, 9764338816, 9772726144, 9781116544, 9789503872,
+ 9797892992, 9806282624, 9814670464, 9823056512, 9831439232,
+ 9839833984, 9848224384, 9856613504, 9865000576, 9873391232,
+ 9881772416, 9890162816, 9898556288, 9906940544, 9915333248,
+ 9923721088, 9932108672, 9940496512, 9948888448, 9957276544,
+ 9965666176, 9974048384, 9982441088, 9990830464, 9999219584,
+ 10007602816, 10015996544, 10024385152, 10032774016, 10041163648,
+ 10049548928, 10057940096, 10066329472, 10074717824, 10083105152,
+ 10091495296, 10099878784, 10108272256, 10116660608, 10125049216,
+ 10133437312, 10141825664, 10150213504, 10158601088, 10166991232,
+ 10175378816, 10183766144, 10192157312, 10200545408, 10208935552,
+ 10217322112, 10225712768, 10234099328, 10242489472, 10250876032,
+ 10259264896, 10267656064, 10276042624, 10284429184, 10292820352,
+ 10301209472, 10309598848, 10317987712, 10326375296, 10334763392,
+ 10343153536, 10351541632, 10359930752, 10368318592, 10376707456,
+ 10385096576, 10393484672, 10401867136, 10410262144, 10418647424,
+ 10427039104, 10435425664, 10443810176, 10452203648, 10460589952,
+ 10468982144, 10477369472, 10485759104, 10494147712, 10502533504,
+ 10510923392, 10519313536, 10527702656, 10536091264, 10544478592,
+ 10552867712, 10561255808, 10569642368, 10578032768, 10586423168,
+ 10594805632, 10603200128, 10611588992, 10619976064, 10628361344,
+ 10636754048, 10645143424, 10653531776, 10661920384, 10670307968,
+ 10678696832, 10687086464, 10695475072, 10703863168, 10712246144,
+ 10720639616, 10729026688, 10737414784, 10745806208, 10754190976,
+ 10762581376, 10770971264, 10779356288, 10787747456, 10796135552,
+ 10804525184, 10812915584, 10821301888, 10829692288, 10838078336,
+ 10846469248, 10854858368, 10863247232, 10871631488, 10880023424,
+ 10888412032, 10896799616, 10905188992, 10913574016, 10921964672,
+ 10930352768, 10938742912, 10947132544, 10955518592, 10963909504,
+ 10972298368, 10980687488, 10989074816, 10997462912, 11005851776,
+ 11014241152, 11022627712, 11031017344, 11039403904, 11047793024,
+ 11056184704, 11064570752, 11072960896, 11081343872, 11089737856,
+ 11098128256, 11106514816, 11114904448, 11123293568, 11131680128,
+ 11140065152, 11148458368, 11156845696, 11165236864, 11173624192,
+ 11182013824, 11190402688, 11198790784, 11207179136, 11215568768,
+ 11223957376, 11232345728, 11240734592, 11249122688, 11257511296,
+ 11265899648, 11274285952, 11282675584, 11291065472, 11299452544,
+ 11307842432, 11316231296, 11324616832, 11333009024, 11341395584,
+ 11349782656, 11358172288, 11366560384, 11374950016, 11383339648,
+ 11391721856, 11400117376, 11408504192, 11416893568, 11425283456,
+ 11433671552, 11442061184, 11450444672, 11458837888, 11467226752,
+ 11475611776, 11484003968, 11492392064, 11500780672, 11509169024,
+ 11517550976, 11525944448, 11534335616, 11542724224, 11551111808,
+ 11559500672, 11567890304, 11576277376, 11584667008, 11593056128,
+ 11601443456, 11609830016, 11618221952, 11626607488, 11634995072,
+ 11643387776, 11651775104, 11660161664, 11668552576, 11676940928,
+ 11685330304, 11693718656, 11702106496, 11710496128, 11718882688,
+ 11727273088, 11735660416, 11744050048, 11752437376, 11760824704,
+ 11769216128, 11777604736, 11785991296, 11794381952, 11802770048,
+ 11811157888, 11819548544, 11827932544, 11836324736, 11844713344,
+ 11853100928, 11861486464, 11869879936, 11878268032, 11886656896,
+ 11895044992, 11903433088, 11911822976, 11920210816, 11928600448,
+ 11936987264, 11945375872, 11953761152, 11962151296, 11970543488,
+ 11978928512, 11987320448, 11995708288, 12004095104, 12012486272,
+ 12020875136, 12029255552, 12037652096, 12046039168, 12054429568,
+ 12062813824, 12071206528, 12079594624, 12087983744, 12096371072,
+ 12104759936, 12113147264, 12121534592, 12129924992, 12138314624,
+ 12146703232, 12155091584, 12163481216, 12171864704, 12180255872,
+ 12188643968, 12197034112, 12205424512, 12213811328, 12222199424,
+ 12230590336, 12238977664, 12247365248, 12255755392, 12264143488,
+ 12272531584, 12280920448, 12289309568, 12297694592, 12306086528,
+ 12314475392, 12322865024, 12331253632, 12339640448, 12348029312,
+ 12356418944, 12364805248, 12373196672, 12381580928, 12389969024,
+ 12398357632, 12406750592, 12415138432, 12423527552, 12431916416,
+ 12440304512, 12448692352, 12457081216, 12465467776, 12473859968,
+ 12482245504, 12490636672, 12499025536, 12507411584, 12515801728,
+ 12524190592, 12532577152, 12540966272, 12549354368, 12557743232,
+ 12566129536, 12574523264, 12582911872, 12591299456, 12599688064,
+ 12608074624, 12616463488, 12624845696, 12633239936, 12641631616,
+ 12650019968, 12658407296, 12666795136, 12675183232, 12683574656,
+ 12691960192, 12700350592, 12708740224, 12717128576, 12725515904,
+ 12733906816, 12742295168, 12750680192, 12759071872, 12767460736,
+ 12775848832, 12784236928, 12792626816, 12801014656, 12809404288,
+ 12817789312, 12826181504, 12834568832, 12842954624, 12851345792,
+ 12859732352, 12868122496, 12876512128, 12884901248, 12893289088,
+ 12901672832, 12910067584, 12918455168, 12926842496, 12935232896,
+ 12943620736, 12952009856, 12960396928, 12968786816, 12977176192,
+ 12985563776, 12993951104, 13002341504, 13010730368, 13019115392,
+ 13027506304, 13035895168, 13044272512, 13052673152, 13061062528,
+ 13069446272, 13077838976, 13086227072, 13094613632, 13103000192,
+ 13111393664, 13119782528, 13128157568, 13136559232, 13144945024,
+ 13153329536, 13161724288, 13170111872, 13178502784, 13186884736,
+ 13195279744, 13203667072, 13212057472, 13220445824, 13228832128,
+ 13237221248, 13245610624, 13254000512, 13262388352, 13270777472,
+ 13279166336, 13287553408, 13295943296, 13304331904, 13312719488,
+ 13321108096, 13329494656, 13337885824, 13346274944, 13354663808,
+ 13363051136, 13371439232, 13379825024, 13388210816, 13396605056,
+ 13404995456, 13413380224, 13421771392, 13430159744, 13438546048,
+ 13446937216, 13455326848, 13463708288, 13472103808, 13480492672,
+ 13488875648, 13497269888, 13505657728, 13514045312, 13522435712,
+ 13530824576, 13539210112, 13547599232, 13555989376, 13564379008,
+ 13572766336, 13581154432, 13589544832, 13597932928, 13606320512,
+ 13614710656, 13623097472, 13631477632, 13639874944, 13648264064,
+ 13656652928, 13665041792, 13673430656, 13681818496, 13690207616,
+ 13698595712, 13706982272, 13715373184, 13723762048, 13732150144,
+ 13740536704, 13748926592, 13757316224, 13765700992, 13774090112,
+ 13782477952, 13790869376, 13799259008, 13807647872, 13816036736,
+ 13824425344, 13832814208, 13841202304, 13849591424, 13857978752,
+ 13866368896, 13874754688, 13883145344, 13891533184, 13899919232,
+ 13908311168, 13916692096, 13925085056, 13933473152, 13941866368,
+ 13950253696, 13958643584, 13967032192, 13975417216, 13983807616,
+ 13992197504, 14000582272, 14008973696, 14017363072, 14025752192,
+ 14034137984, 14042528384, 14050918016, 14059301504, 14067691648,
+ 14076083584, 14084470144, 14092852352, 14101249664, 14109635968,
+ 14118024832, 14126407552, 14134804352, 14143188608, 14151577984,
+ 14159968384, 14168357248, 14176741504, 14185127296, 14193521024,
+ 14201911424, 14210301824, 14218685056, 14227067264, 14235467392,
+ 14243855488, 14252243072, 14260630144, 14269021568, 14277409408,
+ 14285799296, 14294187904, 14302571392, 14310961792, 14319353728,
+ 14327738752, 14336130944, 14344518784, 14352906368, 14361296512,
+ 14369685376, 14378071424, 14386462592, 14394848128, 14403230848,
+ 14411627392, 14420013952, 14428402304, 14436793472, 14445181568,
+ 14453569664, 14461959808, 14470347904, 14478737024, 14487122816,
+ 14495511424, 14503901824, 14512291712, 14520677504, 14529064832,
+ 14537456768, 14545845632, 14554234496, 14562618496, 14571011456,
+ 14579398784, 14587789184, 14596172672, 14604564608, 14612953984,
+ 14621341312, 14629724288, 14638120832, 14646503296, 14654897536,
+ 14663284864, 14671675264, 14680061056, 14688447616, 14696835968,
+ 14705228416, 14713616768, 14722003328, 14730392192, 14738784128,
+ 14747172736, 14755561088, 14763947648, 14772336512, 14780725376,
+ 14789110144, 14797499776, 14805892736, 14814276992, 14822670208,
+ 14831056256, 14839444352, 14847836032, 14856222848, 14864612992,
+ 14872997504, 14881388672, 14889775744, 14898165376, 14906553472,
+ 14914944896, 14923329664, 14931721856, 14940109696, 14948497024,
+ 14956887424, 14965276544, 14973663616, 14982053248, 14990439808,
+ 14998830976, 15007216768, 15015605888, 15023995264, 15032385152,
+ 15040768384, 15049154944, 15057549184, 15065939072, 15074328448,
+ 15082715008, 15091104128, 15099493504, 15107879296, 15116269184,
+ 15124659584, 15133042304, 15141431936, 15149824384, 15158214272,
+ 15166602368, 15174991232, 15183378304, 15191760512, 15200154496,
+ 15208542592, 15216931712, 15225323392, 15233708416, 15242098048,
+ 15250489216, 15258875264, 15267265408, 15275654528, 15284043136,
+ 15292431488, 15300819584, 15309208192, 15317596544, 15325986176,
+ 15334374784, 15342763648, 15351151744, 15359540608, 15367929728,
+ 15376318336, 15384706432, 15393092992, 15401481856, 15409869952,
+ 15418258816, 15426649984, 15435037568, 15443425664, 15451815296,
+ 15460203392, 15468589184, 15476979328, 15485369216, 15493755776,
+ 15502146944, 15510534272, 15518924416, 15527311232, 15535699072,
+ 15544089472, 15552478336, 15560866688, 15569254528, 15577642624,
+ 15586031488, 15594419072, 15602809472, 15611199104, 15619586432,
+ 15627975296, 15636364928, 15644753792, 15653141888, 15661529216,
+ 15669918848, 15678305152, 15686696576, 15695083136, 15703474048,
+ 15711861632, 15720251264, 15728636288, 15737027456, 15745417088,
+ 15753804928, 15762194048, 15770582656, 15778971008, 15787358336,
+ 15795747712, 15804132224, 15812523392, 15820909696, 15829300096,
+ 15837691264, 15846071936, 15854466944, 15862855808, 15871244672,
+ 15879634816, 15888020608, 15896409728, 15904799104, 15913185152,
+ 15921577088, 15929966464, 15938354816, 15946743424, 15955129472,
+ 15963519872, 15971907968, 15980296064, 15988684928, 15997073024,
+ 16005460864, 16013851264, 16022241152, 16030629248, 16039012736,
+ 16047406976, 16055794816, 16064181376, 16072571264, 16080957824,
+ 16089346688, 16097737856, 16106125184, 16114514816, 16122904192,
+ 16131292544, 16139678848, 16148066944, 16156453504, 16164839552,
+ 16173236096, 16181623424, 16190012032, 16198401152, 16206790528,
+ 16215177344, 16223567744, 16231956352, 16240344704, 16248731008,
+ 16257117824, 16265504384, 16273898624, 16282281856, 16290668672,
+ 16299064192, 16307449216, 16315842176, 16324230016, 16332613504,
+ 16341006464, 16349394304, 16357783168, 16366172288, 16374561664,
+ 16382951296, 16391337856, 16399726208, 16408116352, 16416505472,
+ 16424892032, 16433282176, 16441668224, 16450058624, 16458448768,
+ 16466836864, 16475224448, 16483613056, 16492001408, 16500391808,
+ 16508779648, 16517166976, 16525555328, 16533944192, 16542330752,
+ 16550719616, 16559110528, 16567497088, 16575888512, 16584274816,
+ 16592665472, 16601051008, 16609442944, 16617832064, 16626218624,
+ 16634607488, 16642996096, 16651385728, 16659773824, 16668163712,
+ 16676552576, 16684938112, 16693328768, 16701718144, 16710095488,
+ 16718492288, 16726883968, 16735272832, 16743661184, 16752049792,
+ 16760436608, 16768827008, 16777214336, 16785599104, 16793992832,
+ 16802381696, 16810768768, 16819151744, 16827542656, 16835934848,
+ 16844323712, 16852711552, 16861101952, 16869489536, 16877876864,
+ 16886265728, 16894653056, 16903044736, 16911431296, 16919821696,
+ 16928207488, 16936592768, 16944987776, 16953375616, 16961763968,
+ 16970152832, 16978540928, 16986929536, 16995319168, 17003704448,
+ 17012096896, 17020481152, 17028870784, 17037262208, 17045649536,
+ 17054039936, 17062426496, 17070814336, 17079205504, 17087592064,
+ 17095978112, 17104369024, 17112759424, 17121147776, 17129536384,
+ 17137926016, 17146314368, 17154700928, 17163089792, 17171480192,
+ 17179864192, 17188256896, 17196644992, 17205033856, 17213423488,
+ 17221811072, 17230198912, 17238588032, 17246976896, 17255360384,
+ 17263754624, 17272143232, 17280530048, 17288918912, 17297309312,
+ 17305696384, 17314085504, 17322475136, 17330863744, 17339252096,
+ 17347640192, 17356026496, 17364413824, 17372796544, 17381190016,
+ 17389583488, 17397972608, 17406360704, 17414748544, 17423135872,
+ 17431527296, 17439915904, 17448303232, 17456691584, 17465081728,
+ 17473468288, 17481857408, 17490247552, 17498635904, 17507022464,
+ 17515409024, 17523801728, 17532189824, 17540577664, 17548966016,
+ 17557353344, 17565741184, 17574131584, 17582519168, 17590907008,
+ 17599296128, 17607687808, 17616076672, 17624455808, 17632852352,
+ 17641238656, 17649630848, 17658018944, 17666403968, 17674794112,
+ 17683178368, 17691573376, 17699962496, 17708350592, 17716739968,
+ 17725126528, 17733517184, 17741898112, 17750293888, 17758673024,
+ 17767070336, 17775458432, 17783848832, 17792236928, 17800625536,
+ 17809012352, 17817402752, 17825785984, 17834178944, 17842563968,
+ 17850955648, 17859344512, 17867732864, 17876119424, 17884511872,
+ 17892900224, 17901287296, 17909677696, 17918058112, 17926451072,
+ 17934843776, 17943230848, 17951609216, 17960008576, 17968397696,
+ 17976784256, 17985175424, 17993564032, 18001952128, 18010339712,
+ 18018728576, 18027116672, 18035503232, 18043894144, 18052283264,
+ 18060672128, 18069056384, 18077449856, 18085837184, 18094225792,
+ 18102613376, 18111004544, 18119388544, 18127781248, 18136170368,
+ 18144558976, 18152947328, 18161336192, 18169724288, 18178108544,
+ 18186498944, 18194886784, 18203275648, 18211666048, 18220048768,
+ 18228444544, 18236833408, 18245220736}
+
+// cacheSizes is a lookup table for the ethash verification cache size for the
+// first 2048 epochs (i.e. 61440000 blocks).
+var cacheSizes = [maxEpoch]uint64{
+ 16776896, 16907456, 17039296, 17170112, 17301056, 17432512, 17563072,
+ 17693888, 17824192, 17955904, 18087488, 18218176, 18349504, 18481088,
+ 18611392, 18742336, 18874304, 19004224, 19135936, 19267264, 19398208,
+ 19529408, 19660096, 19791424, 19922752, 20053952, 20184896, 20315968,
+ 20446912, 20576576, 20709184, 20840384, 20971072, 21102272, 21233216,
+ 21364544, 21494848, 21626816, 21757376, 21887552, 22019392, 22151104,
+ 22281536, 22412224, 22543936, 22675264, 22806464, 22935872, 23068096,
+ 23198272, 23330752, 23459008, 23592512, 23723968, 23854912, 23986112,
+ 24116672, 24247616, 24378688, 24509504, 24640832, 24772544, 24903488,
+ 25034432, 25165376, 25296704, 25427392, 25558592, 25690048, 25820096,
+ 25951936, 26081728, 26214208, 26345024, 26476096, 26606656, 26737472,
+ 26869184, 26998208, 27131584, 27262528, 27393728, 27523904, 27655744,
+ 27786688, 27917888, 28049344, 28179904, 28311488, 28441792, 28573504,
+ 28700864, 28835648, 28966208, 29096768, 29228608, 29359808, 29490752,
+ 29621824, 29752256, 29882816, 30014912, 30144448, 30273728, 30406976,
+ 30538432, 30670784, 30799936, 30932672, 31063744, 31195072, 31325248,
+ 31456192, 31588288, 31719232, 31850432, 31981504, 32110784, 32243392,
+ 32372672, 32505664, 32636608, 32767808, 32897344, 33029824, 33160768,
+ 33289664, 33423296, 33554368, 33683648, 33816512, 33947456, 34076992,
+ 34208704, 34340032, 34471744, 34600256, 34734016, 34864576, 34993984,
+ 35127104, 35258176, 35386688, 35518528, 35650624, 35782336, 35910976,
+ 36044608, 36175808, 36305728, 36436672, 36568384, 36699968, 36830656,
+ 36961984, 37093312, 37223488, 37355072, 37486528, 37617472, 37747904,
+ 37879232, 38009792, 38141888, 38272448, 38403392, 38535104, 38660672,
+ 38795584, 38925632, 39059264, 39190336, 39320768, 39452096, 39581632,
+ 39713984, 39844928, 39974848, 40107968, 40238144, 40367168, 40500032,
+ 40631744, 40762816, 40894144, 41023552, 41155904, 41286208, 41418304,
+ 41547712, 41680448, 41811904, 41942848, 42073792, 42204992, 42334912,
+ 42467008, 42597824, 42729152, 42860096, 42991552, 43122368, 43253696,
+ 43382848, 43515712, 43646912, 43777088, 43907648, 44039104, 44170432,
+ 44302144, 44433344, 44564288, 44694976, 44825152, 44956864, 45088448,
+ 45219008, 45350464, 45481024, 45612608, 45744064, 45874496, 46006208,
+ 46136768, 46267712, 46399424, 46529344, 46660672, 46791488, 46923328,
+ 47053504, 47185856, 47316928, 47447872, 47579072, 47710144, 47839936,
+ 47971648, 48103232, 48234176, 48365248, 48496192, 48627136, 48757312,
+ 48889664, 49020736, 49149248, 49283008, 49413824, 49545152, 49675712,
+ 49807168, 49938368, 50069056, 50200256, 50331584, 50462656, 50593472,
+ 50724032, 50853952, 50986048, 51117632, 51248576, 51379904, 51510848,
+ 51641792, 51773248, 51903296, 52035136, 52164032, 52297664, 52427968,
+ 52557376, 52690112, 52821952, 52952896, 53081536, 53213504, 53344576,
+ 53475776, 53608384, 53738816, 53870528, 54000832, 54131776, 54263744,
+ 54394688, 54525248, 54655936, 54787904, 54918592, 55049152, 55181248,
+ 55312064, 55442752, 55574336, 55705024, 55836224, 55967168, 56097856,
+ 56228672, 56358592, 56490176, 56621888, 56753728, 56884928, 57015488,
+ 57146816, 57278272, 57409216, 57540416, 57671104, 57802432, 57933632,
+ 58064576, 58195264, 58326976, 58457408, 58588864, 58720192, 58849984,
+ 58981696, 59113024, 59243456, 59375552, 59506624, 59637568, 59768512,
+ 59897792, 60030016, 60161984, 60293056, 60423872, 60554432, 60683968,
+ 60817216, 60948032, 61079488, 61209664, 61341376, 61471936, 61602752,
+ 61733696, 61865792, 61996736, 62127808, 62259136, 62389568, 62520512,
+ 62651584, 62781632, 62910784, 63045056, 63176128, 63307072, 63438656,
+ 63569216, 63700928, 63831616, 63960896, 64093888, 64225088, 64355392,
+ 64486976, 64617664, 64748608, 64879424, 65009216, 65142464, 65273792,
+ 65402816, 65535424, 65666752, 65797696, 65927744, 66060224, 66191296,
+ 66321344, 66453056, 66584384, 66715328, 66846656, 66977728, 67108672,
+ 67239104, 67370432, 67501888, 67631296, 67763776, 67895104, 68026304,
+ 68157248, 68287936, 68419264, 68548288, 68681408, 68811968, 68942912,
+ 69074624, 69205568, 69337024, 69467584, 69599168, 69729472, 69861184,
+ 69989824, 70122944, 70253888, 70385344, 70515904, 70647232, 70778816,
+ 70907968, 71040832, 71171648, 71303104, 71432512, 71564992, 71695168,
+ 71826368, 71958464, 72089536, 72219712, 72350144, 72482624, 72613568,
+ 72744512, 72875584, 73006144, 73138112, 73268672, 73400128, 73530944,
+ 73662272, 73793344, 73924544, 74055104, 74185792, 74316992, 74448832,
+ 74579392, 74710976, 74841664, 74972864, 75102784, 75233344, 75364544,
+ 75497024, 75627584, 75759296, 75890624, 76021696, 76152256, 76283072,
+ 76414144, 76545856, 76676672, 76806976, 76937792, 77070016, 77200832,
+ 77331392, 77462464, 77593664, 77725376, 77856448, 77987776, 78118336,
+ 78249664, 78380992, 78511424, 78642496, 78773056, 78905152, 79033664,
+ 79166656, 79297472, 79429568, 79560512, 79690816, 79822784, 79953472,
+ 80084672, 80214208, 80346944, 80477632, 80608576, 80740288, 80870848,
+ 81002048, 81133504, 81264448, 81395648, 81525952, 81657536, 81786304,
+ 81919808, 82050112, 82181312, 82311616, 82443968, 82573376, 82705984,
+ 82835776, 82967744, 83096768, 83230528, 83359552, 83491264, 83622464,
+ 83753536, 83886016, 84015296, 84147776, 84277184, 84409792, 84540608,
+ 84672064, 84803008, 84934336, 85065152, 85193792, 85326784, 85458496,
+ 85589312, 85721024, 85851968, 85982656, 86112448, 86244416, 86370112,
+ 86506688, 86637632, 86769344, 86900672, 87031744, 87162304, 87293632,
+ 87424576, 87555392, 87687104, 87816896, 87947968, 88079168, 88211264,
+ 88341824, 88473152, 88603712, 88735424, 88862912, 88996672, 89128384,
+ 89259712, 89390272, 89521984, 89652544, 89783872, 89914816, 90045376,
+ 90177088, 90307904, 90438848, 90569152, 90700096, 90832832, 90963776,
+ 91093696, 91223744, 91356992, 91486784, 91618496, 91749824, 91880384,
+ 92012224, 92143552, 92273344, 92405696, 92536768, 92666432, 92798912,
+ 92926016, 93060544, 93192128, 93322816, 93453632, 93583936, 93715136,
+ 93845056, 93977792, 94109504, 94240448, 94371776, 94501184, 94632896,
+ 94764224, 94895552, 95023424, 95158208, 95287744, 95420224, 95550016,
+ 95681216, 95811904, 95943872, 96075328, 96203584, 96337856, 96468544,
+ 96599744, 96731072, 96860992, 96992576, 97124288, 97254848, 97385536,
+ 97517248, 97647808, 97779392, 97910464, 98041408, 98172608, 98303168,
+ 98434496, 98565568, 98696768, 98827328, 98958784, 99089728, 99220928,
+ 99352384, 99482816, 99614272, 99745472, 99876416, 100007104,
+ 100138048, 100267072, 100401088, 100529984, 100662592, 100791872,
+ 100925248, 101056064, 101187392, 101317952, 101449408, 101580608,
+ 101711296, 101841728, 101973824, 102104896, 102235712, 102366016,
+ 102498112, 102628672, 102760384, 102890432, 103021888, 103153472,
+ 103284032, 103415744, 103545152, 103677248, 103808576, 103939648,
+ 104070976, 104201792, 104332736, 104462528, 104594752, 104725952,
+ 104854592, 104988608, 105118912, 105247808, 105381184, 105511232,
+ 105643072, 105774784, 105903296, 106037056, 106167872, 106298944,
+ 106429504, 106561472, 106691392, 106822592, 106954304, 107085376,
+ 107216576, 107346368, 107478464, 107609792, 107739712, 107872192,
+ 108003136, 108131392, 108265408, 108396224, 108527168, 108657344,
+ 108789568, 108920384, 109049792, 109182272, 109312576, 109444928,
+ 109572928, 109706944, 109837888, 109969088, 110099648, 110230976,
+ 110362432, 110492992, 110624704, 110755264, 110886208, 111017408,
+ 111148864, 111279296, 111410752, 111541952, 111673024, 111803456,
+ 111933632, 112066496, 112196416, 112328512, 112457792, 112590784,
+ 112715968, 112852672, 112983616, 113114944, 113244224, 113376448,
+ 113505472, 113639104, 113770304, 113901376, 114031552, 114163264,
+ 114294592, 114425536, 114556864, 114687424, 114818624, 114948544,
+ 115080512, 115212224, 115343296, 115473472, 115605184, 115736128,
+ 115867072, 115997248, 116128576, 116260288, 116391488, 116522944,
+ 116652992, 116784704, 116915648, 117046208, 117178304, 117308608,
+ 117440192, 117569728, 117701824, 117833024, 117964096, 118094656,
+ 118225984, 118357312, 118489024, 118617536, 118749632, 118882112,
+ 119012416, 119144384, 119275328, 119406016, 119537344, 119668672,
+ 119798464, 119928896, 120061376, 120192832, 120321728, 120454336,
+ 120584512, 120716608, 120848192, 120979136, 121109056, 121241408,
+ 121372352, 121502912, 121634752, 121764416, 121895744, 122027072,
+ 122157632, 122289088, 122421184, 122550592, 122682944, 122813888,
+ 122945344, 123075776, 123207488, 123338048, 123468736, 123600704,
+ 123731264, 123861952, 123993664, 124124608, 124256192, 124386368,
+ 124518208, 124649024, 124778048, 124911296, 125041088, 125173696,
+ 125303744, 125432896, 125566912, 125696576, 125829056, 125958592,
+ 126090304, 126221248, 126352832, 126483776, 126615232, 126746432,
+ 126876608, 127008704, 127139392, 127270336, 127401152, 127532224,
+ 127663552, 127794752, 127925696, 128055232, 128188096, 128319424,
+ 128449856, 128581312, 128712256, 128843584, 128973632, 129103808,
+ 129236288, 129365696, 129498944, 129629888, 129760832, 129892288,
+ 130023104, 130154048, 130283968, 130416448, 130547008, 130678336,
+ 130807616, 130939456, 131071552, 131202112, 131331776, 131464384,
+ 131594048, 131727296, 131858368, 131987392, 132120256, 132250816,
+ 132382528, 132513728, 132644672, 132774976, 132905792, 133038016,
+ 133168832, 133299392, 133429312, 133562048, 133692992, 133823296,
+ 133954624, 134086336, 134217152, 134348608, 134479808, 134607296,
+ 134741056, 134872384, 135002944, 135134144, 135265472, 135396544,
+ 135527872, 135659072, 135787712, 135921472, 136052416, 136182848,
+ 136313792, 136444864, 136576448, 136707904, 136837952, 136970048,
+ 137099584, 137232064, 137363392, 137494208, 137625536, 137755712,
+ 137887424, 138018368, 138149824, 138280256, 138411584, 138539584,
+ 138672832, 138804928, 138936128, 139066688, 139196864, 139328704,
+ 139460032, 139590208, 139721024, 139852864, 139984576, 140115776,
+ 140245696, 140376512, 140508352, 140640064, 140769856, 140902336,
+ 141032768, 141162688, 141294016, 141426496, 141556544, 141687488,
+ 141819584, 141949888, 142080448, 142212544, 142342336, 142474432,
+ 142606144, 142736192, 142868288, 142997824, 143129408, 143258944,
+ 143392448, 143523136, 143653696, 143785024, 143916992, 144045632,
+ 144177856, 144309184, 144440768, 144570688, 144701888, 144832448,
+ 144965056, 145096384, 145227584, 145358656, 145489856, 145620928,
+ 145751488, 145883072, 146011456, 146144704, 146275264, 146407232,
+ 146538176, 146668736, 146800448, 146931392, 147062336, 147193664,
+ 147324224, 147455936, 147586624, 147717056, 147848768, 147979456,
+ 148110784, 148242368, 148373312, 148503232, 148635584, 148766144,
+ 148897088, 149028416, 149159488, 149290688, 149420224, 149551552,
+ 149683136, 149814976, 149943616, 150076352, 150208064, 150338624,
+ 150470464, 150600256, 150732224, 150862784, 150993088, 151125952,
+ 151254976, 151388096, 151519168, 151649728, 151778752, 151911104,
+ 152042944, 152174144, 152304704, 152435648, 152567488, 152698816,
+ 152828992, 152960576, 153091648, 153222976, 153353792, 153484096,
+ 153616192, 153747008, 153878336, 154008256, 154139968, 154270912,
+ 154402624, 154533824, 154663616, 154795712, 154926272, 155057984,
+ 155188928, 155319872, 155450816, 155580608, 155712064, 155843392,
+ 155971136, 156106688, 156237376, 156367424, 156499264, 156630976,
+ 156761536, 156892352, 157024064, 157155008, 157284416, 157415872,
+ 157545536, 157677248, 157810496, 157938112, 158071744, 158203328,
+ 158334656, 158464832, 158596288, 158727616, 158858048, 158988992,
+ 159121216, 159252416, 159381568, 159513152, 159645632, 159776192,
+ 159906496, 160038464, 160169536, 160300352, 160430656, 160563008,
+ 160693952, 160822208, 160956352, 161086784, 161217344, 161349184,
+ 161480512, 161611456, 161742272, 161873216, 162002752, 162135872,
+ 162266432, 162397888, 162529216, 162660032, 162790976, 162922048,
+ 163052096, 163184576, 163314752, 163446592, 163577408, 163707968,
+ 163839296, 163969984, 164100928, 164233024, 164364224, 164494912,
+ 164625856, 164756672, 164887616, 165019072, 165150016, 165280064,
+ 165412672, 165543104, 165674944, 165805888, 165936832, 166067648,
+ 166198336, 166330048, 166461248, 166591552, 166722496, 166854208,
+ 166985408, 167116736, 167246656, 167378368, 167508416, 167641024,
+ 167771584, 167903168, 168034112, 168164032, 168295744, 168427456,
+ 168557632, 168688448, 168819136, 168951616, 169082176, 169213504,
+ 169344832, 169475648, 169605952, 169738048, 169866304, 169999552,
+ 170131264, 170262464, 170393536, 170524352, 170655424, 170782016,
+ 170917696, 171048896, 171179072, 171310784, 171439936, 171573184,
+ 171702976, 171835072, 171966272, 172097216, 172228288, 172359232,
+ 172489664, 172621376, 172747712, 172883264, 173014208, 173144512,
+ 173275072, 173407424, 173539136, 173669696, 173800768, 173931712,
+ 174063424, 174193472, 174325696, 174455744, 174586816, 174718912,
+ 174849728, 174977728, 175109696, 175242688, 175374272, 175504832,
+ 175636288, 175765696, 175898432, 176028992, 176159936, 176291264,
+ 176422592, 176552512, 176684864, 176815424, 176946496, 177076544,
+ 177209152, 177340096, 177470528, 177600704, 177731648, 177864256,
+ 177994816, 178126528, 178257472, 178387648, 178518464, 178650176,
+ 178781888, 178912064, 179044288, 179174848, 179305024, 179436736,
+ 179568448, 179698496, 179830208, 179960512, 180092608, 180223808,
+ 180354752, 180485696, 180617152, 180748096, 180877504, 181009984,
+ 181139264, 181272512, 181402688, 181532608, 181663168, 181795136,
+ 181926592, 182057536, 182190016, 182320192, 182451904, 182582336,
+ 182713792, 182843072, 182976064, 183107264, 183237056, 183368384,
+ 183494848, 183631424, 183762752, 183893824, 184024768, 184154816,
+ 184286656, 184417984, 184548928, 184680128, 184810816, 184941248,
+ 185072704, 185203904, 185335616, 185465408, 185596352, 185727296,
+ 185859904, 185989696, 186121664, 186252992, 186383552, 186514112,
+ 186645952, 186777152, 186907328, 187037504, 187170112, 187301824,
+ 187429184, 187562048, 187693504, 187825472, 187957184, 188087104,
+ 188218304, 188349376, 188481344, 188609728, 188743616, 188874304,
+ 189005248, 189136448, 189265088, 189396544, 189528128, 189660992,
+ 189791936, 189923264, 190054208, 190182848, 190315072, 190447424,
+ 190577984, 190709312, 190840768, 190971328, 191102656, 191233472,
+ 191364032, 191495872, 191626816, 191758016, 191888192, 192020288,
+ 192148928, 192282176, 192413504, 192542528, 192674752, 192805952,
+ 192937792, 193068608, 193198912, 193330496, 193462208, 193592384,
+ 193723456, 193854272, 193985984, 194116672, 194247232, 194379712,
+ 194508352, 194641856, 194772544, 194900672, 195035072, 195166016,
+ 195296704, 195428032, 195558592, 195690304, 195818176, 195952576,
+ 196083392, 196214336, 196345792, 196476736, 196607552, 196739008,
+ 196869952, 197000768, 197130688, 197262784, 197394368, 197523904,
+ 197656384, 197787584, 197916608, 198049472, 198180544, 198310208,
+ 198442432, 198573632, 198705088, 198834368, 198967232, 199097792,
+ 199228352, 199360192, 199491392, 199621696, 199751744, 199883968,
+ 200014016, 200146624, 200276672, 200408128, 200540096, 200671168,
+ 200801984, 200933312, 201062464, 201194944, 201326144, 201457472,
+ 201588544, 201719744, 201850816, 201981632, 202111552, 202244032,
+ 202374464, 202505152, 202636352, 202767808, 202898368, 203030336,
+ 203159872, 203292608, 203423296, 203553472, 203685824, 203816896,
+ 203947712, 204078272, 204208192, 204341056, 204472256, 204603328,
+ 204733888, 204864448, 204996544, 205125568, 205258304, 205388864,
+ 205517632, 205650112, 205782208, 205913536, 206044736, 206176192,
+ 206307008, 206434496, 206569024, 206700224, 206831168, 206961856,
+ 207093056, 207223616, 207355328, 207486784, 207616832, 207749056,
+ 207879104, 208010048, 208141888, 208273216, 208404032, 208534336,
+ 208666048, 208796864, 208927424, 209059264, 209189824, 209321792,
+ 209451584, 209582656, 209715136, 209845568, 209976896, 210106432,
+ 210239296, 210370112, 210501568, 210630976, 210763712, 210894272,
+ 211024832, 211156672, 211287616, 211418176, 211549376, 211679296,
+ 211812032, 211942592, 212074432, 212204864, 212334016, 212467648,
+ 212597824, 212727616, 212860352, 212991424, 213120832, 213253952,
+ 213385024, 213515584, 213645632, 213777728, 213909184, 214040128,
+ 214170688, 214302656, 214433728, 214564544, 214695232, 214826048,
+ 214956992, 215089088, 215219776, 215350592, 215482304, 215613248,
+ 215743552, 215874752, 216005312, 216137024, 216267328, 216399296,
+ 216530752, 216661696, 216790592, 216923968, 217054528, 217183168,
+ 217316672, 217448128, 217579072, 217709504, 217838912, 217972672,
+ 218102848, 218233024, 218364736, 218496832, 218627776, 218759104,
+ 218888896, 219021248, 219151936, 219281728, 219413056, 219545024,
+ 219675968, 219807296, 219938624, 220069312, 220200128, 220331456,
+ 220461632, 220592704, 220725184, 220855744, 220987072, 221117888,
+ 221249216, 221378368, 221510336, 221642048, 221772736, 221904832,
+ 222031808, 222166976, 222297536, 222428992, 222559936, 222690368,
+ 222820672, 222953152, 223083968, 223213376, 223345984, 223476928,
+ 223608512, 223738688, 223869376, 224001472, 224132672, 224262848,
+ 224394944, 224524864, 224657344, 224788288, 224919488, 225050432,
+ 225181504, 225312704, 225443776, 225574592, 225704768, 225834176,
+ 225966784, 226097216, 226229824, 226360384, 226491712, 226623424,
+ 226754368, 226885312, 227015104, 227147456, 227278528, 227409472,
+ 227539904, 227669696, 227802944, 227932352, 228065216, 228196288,
+ 228326464, 228457792, 228588736, 228720064, 228850112, 228981056,
+ 229113152, 229243328, 229375936, 229505344, 229636928, 229769152,
+ 229894976, 230030272, 230162368, 230292416, 230424512, 230553152,
+ 230684864, 230816704, 230948416, 231079616, 231210944, 231342016,
+ 231472448, 231603776, 231733952, 231866176, 231996736, 232127296,
+ 232259392, 232388672, 232521664, 232652608, 232782272, 232914496,
+ 233043904, 233175616, 233306816, 233438528, 233569984, 233699776,
+ 233830592, 233962688, 234092224, 234221888, 234353984, 234485312,
+ 234618304, 234749888, 234880832, 235011776, 235142464, 235274048,
+ 235403456, 235535936, 235667392, 235797568, 235928768, 236057152,
+ 236190272, 236322752, 236453312, 236583616, 236715712, 236846528,
+ 236976448, 237108544, 237239104, 237371072, 237501632, 237630784,
+ 237764416, 237895232, 238026688, 238157632, 238286912, 238419392,
+ 238548032, 238681024, 238812608, 238941632, 239075008, 239206336,
+ 239335232, 239466944, 239599168, 239730496, 239861312, 239992384,
+ 240122816, 240254656, 240385856, 240516928, 240647872, 240779072,
+ 240909632, 241040704, 241171904, 241302848, 241433408, 241565248,
+ 241696192, 241825984, 241958848, 242088256, 242220224, 242352064,
+ 242481856, 242611648, 242744896, 242876224, 243005632, 243138496,
+ 243268672, 243400384, 243531712, 243662656, 243793856, 243924544,
+ 244054592, 244187072, 244316608, 244448704, 244580032, 244710976,
+ 244841536, 244972864, 245104448, 245233984, 245365312, 245497792,
+ 245628736, 245759936, 245889856, 246021056, 246152512, 246284224,
+ 246415168, 246545344, 246675904, 246808384, 246939584, 247070144,
+ 247199552, 247331648, 247463872, 247593536, 247726016, 247857088,
+ 247987648, 248116928, 248249536, 248380736, 248512064, 248643008,
+ 248773312, 248901056, 249036608, 249167552, 249298624, 249429184,
+ 249560512, 249692096, 249822784, 249954112, 250085312, 250215488,
+ 250345792, 250478528, 250608704, 250739264, 250870976, 251002816,
+ 251133632, 251263552, 251395136, 251523904, 251657792, 251789248,
+ 251919424, 252051392, 252182464, 252313408, 252444224, 252575552,
+ 252706624, 252836032, 252968512, 253099712, 253227584, 253361728,
+ 253493056, 253623488, 253754432, 253885504, 254017216, 254148032,
+ 254279488, 254410432, 254541376, 254672576, 254803264, 254933824,
+ 255065792, 255196736, 255326528, 255458752, 255589952, 255721408,
+ 255851072, 255983296, 256114624, 256244416, 256374208, 256507712,
+ 256636096, 256768832, 256900544, 257031616, 257162176, 257294272,
+ 257424448, 257555776, 257686976, 257818432, 257949632, 258079552,
+ 258211136, 258342464, 258473408, 258603712, 258734656, 258867008,
+ 258996544, 259127744, 259260224, 259391296, 259522112, 259651904,
+ 259784384, 259915328, 260045888, 260175424, 260308544, 260438336,
+ 260570944, 260700992, 260832448, 260963776, 261092672, 261226304,
+ 261356864, 261487936, 261619648, 261750592, 261879872, 262011968,
+ 262143424, 262274752, 262404416, 262537024, 262667968, 262799296,
+ 262928704, 263061184, 263191744, 263322944, 263454656, 263585216,
+ 263716672, 263847872, 263978944, 264108608, 264241088, 264371648,
+ 264501184, 264632768, 264764096, 264895936, 265024576, 265158464,
+ 265287488, 265418432, 265550528, 265681216, 265813312, 265943488,
+ 266075968, 266206144, 266337728, 266468032, 266600384, 266731072,
+ 266862272, 266993344, 267124288, 267255616, 267386432, 267516992,
+ 267648704, 267777728, 267910592, 268040512, 268172096, 268302784,
+ 268435264, 268566208, 268696256, 268828096, 268959296, 269090368,
+ 269221312, 269352256, 269482688, 269614784, 269745856, 269876416,
+ 270007616, 270139328, 270270272, 270401216, 270531904, 270663616,
+ 270791744, 270924736, 271056832, 271186112, 271317184, 271449536,
+ 271580992, 271711936, 271843136, 271973056, 272105408, 272236352,
+ 272367296, 272498368, 272629568, 272759488, 272891456, 273022784,
+ 273153856, 273284672, 273415616, 273547072, 273677632, 273808448,
+ 273937088, 274071488, 274200896, 274332992, 274463296, 274595392,
+ 274726208, 274857536, 274988992, 275118656, 275250496, 275382208,
+ 275513024, 275643968, 275775296, 275906368, 276037184, 276167872,
+ 276297664, 276429376, 276560576, 276692672, 276822976, 276955072,
+ 277085632, 277216832, 277347008, 277478848, 277609664, 277740992,
+ 277868608, 278002624, 278134336, 278265536, 278395328, 278526784,
+ 278657728, 278789824, 278921152, 279052096, 279182912, 279313088,
+ 279443776, 279576256, 279706048, 279838528, 279969728, 280099648,
+ 280230976, 280361408, 280493632, 280622528, 280755392, 280887104,
+ 281018176, 281147968, 281278912, 281411392, 281542592, 281673152,
+ 281803712, 281935552, 282066496, 282197312, 282329024, 282458816,
+ 282590272, 282720832, 282853184, 282983744, 283115072, 283246144,
+ 283377344, 283508416, 283639744, 283770304, 283901504, 284032576,
+ 284163136, 284294848, 284426176, 284556992, 284687296, 284819264,
+ 284950208, 285081536}
diff --git a/consensus/ethash/algorithm_test.go b/consensus/ethash/algorithm_test.go
new file mode 100644
index 00000000000..3684dd0a7fa
--- /dev/null
+++ b/consensus/ethash/algorithm_test.go
@@ -0,0 +1,795 @@
+// Copyright 2017 The go-ethereum Authors
+// (original work)
+// Copyright 2024 The Erigon Authors
+// (modifications)
+// This file is part of Erigon.
+//
+// Erigon 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.
+//
+// Erigon 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 Erigon. If not, see .
+
+package ethash
+
+import (
+ "bytes"
+ "encoding/binary"
+ "io"
+ "reflect"
+ "testing"
+
+ "github.com/erigontech/erigon-lib/common/hexutil"
+
+ "github.com/erigontech/erigon-lib/common/length"
+)
+
+// prepare converts an ethash cache or dataset from a byte stream into the internal
+// int representation. All ethash methods work with ints to avoid constant byte to
+// int conversions as well as to handle both little and big endian systems.
+func prepare(dest []uint32, src []byte) {
+ for i := 0; i < len(dest); i++ {
+ dest[i] = binary.LittleEndian.Uint32(src[i*4:])
+ }
+}
+
+// Tests whether the dataset size calculator works correctly by cross checking the
+// hard coded lookup table with the value generated by it.
+func TestSizeCalculations(t *testing.T) {
+ // Verify all the cache and dataset sizes from the lookup table.
+ for epoch, want := range cacheSizes {
+ if size := calcCacheSize(epoch); size != want {
+ t.Errorf("cache %d: cache size mismatch: have %d, want %d", epoch, size, want)
+ }
+ }
+ for epoch, want := range datasetSizes {
+ if size := calcDatasetSize(epoch); size != want {
+ t.Errorf("dataset %d: dataset size mismatch: have %d, want %d", epoch, size, want)
+ }
+ }
+}
+
+// Tests that verification caches can be correctly generated.
+func TestCacheGeneration(t *testing.T) {
+ tests := []struct {
+ size uint64
+ epoch uint64
+ cache []byte
+ }{
+ {
+ size: 1024,
+ epoch: 0,
+ cache: hexutil.MustDecode("0x" +
+ "7ce2991c951f7bf4c4c1bb119887ee07871eb5339d7b97b8588e85c742de90e5bafd5bbe6ce93a134fb6be9ad3e30db99d9528a2ea7846833f52e9ca119b6b54" +
+ "8979480c46e19972bd0738779c932c1b43e665a2fd3122fc3ddb2691f353ceb0ed3e38b8f51fd55b6940290743563c9f8fa8822e611924657501a12aafab8a8d" +
+ "88fb5fbae3a99d14792406672e783a06940a42799b1c38bc28715db6d37cb11f9f6b24e386dc52dd8c286bd8c36fa813dffe4448a9f56ebcbeea866b42f68d22" +
+ "6c32aae4d695a23cab28fd74af53b0c2efcc180ceaaccc0b2e280103d097a03c1d1b0f0f26ce5f32a90238f9bc49f645db001ef9cd3d13d44743f841fad11a37" +
+ "fa290c62c16042f703578921f30b9951465aae2af4a5dad43a7341d7b4a62750954965a47a1c3af638dc3495c4d62a9bab843168c9fc0114e79cffd1b2827b01" +
+ "75d30ba054658f214e946cf24c43b40d3383fbb0493408e5c5392434ca21bbcf43200dfb876c713d201813934fa485f48767c5915745cf0986b1dc0f33e57748" +
+ "bf483ee2aff4248dfe461ec0504a13628401020fc22638584a8f2f5206a13b2f233898c78359b21c8226024d0a7a93df5eb6c282bdbf005a4aab497e096f2847" +
+ "76c71cee57932a8fb89f6d6b8743b60a4ea374899a94a2e0f218d5c55818cefb1790c8529a76dba31ebb0f4592d709b49587d2317970d39c086f18dd244291d9" +
+ "eedb16705e53e3350591bd4ff4566a3595ac0f0ce24b5e112a3d033bc51b6fea0a92296dea7f5e20bf6ee6bc347d868fda193c395b9bb147e55e5a9f67cfe741" +
+ "7eea7d699b155bd13804204df7ea91fa9249e4474dddf35188f77019c67d201e4c10d7079c5ad492a71afff9a23ca7e900ba7d1bdeaf3270514d8eb35eab8a0a" +
+ "718bb7273aeb37768fa589ed8ab01fbf4027f4ebdbbae128d21e485f061c20183a9bc2e31edbda0727442e9d58eb0fe198440fe199e02e77c0f7b99973f1f74c" +
+ "c9089a51ab96c94a84d66e6aa48b2d0a4543adb5a789039a2aa7b335ca85c91026c7d3c894da53ae364188c3fd92f78e01d080399884a47385aa792e38150cda" +
+ "a8620b2ebeca41fbc773bb837b5e724d6eb2de570d99858df0d7d97067fb8103b21757873b735097b35d3bea8fd1c359a9e8a63c1540c76c9784cf8d975e995c" +
+ "778401b94a2e66e6993ad67ad3ecdc2acb17779f1ea8606827ec92b11c728f8c3b6d3f04a3e6ed05ff81dd76d5dc5695a50377bc135aaf1671cf68b750315493" +
+ "6c64510164d53312bf3c41740c7a237b05faf4a191bd8a95dafa068dbcf370255c725900ce5c934f36feadcfe55b687c440574c1f06f39d207a8553d39156a24" +
+ "845f64fd8324bb85312979dead74f764c9677aab89801ad4f927f1c00f12e28f22422bb44200d1969d9ab377dd6b099dc6dbc3222e9321b2c1e84f8e2f07731c"),
+ },
+ {
+ size: 1024,
+ epoch: 1,
+ cache: hexutil.MustDecode("0x" +
+ "1f56855d59cc5a085720899b4377a0198f1abe948d85fe5820dc0e346b7c0931b9cde8e541d751de3b2b3275d0aabfae316209d5879297d8bd99f8a033c9d4df" +
+ "35add1029f4e6404a022d504fb8023e42989aba985a65933b0109c7218854356f9284983c9e7de97de591828ae348b63d1fc78d8db58157344d4e06530ffd422" +
+ "5c7f6080d451ff94961ec2dd9e28e6d81b49102451676dbdcb6ef1094c1e8b29e7e808d47b2ba5aeb52dabf00d5f0ee08c116289cbf56d8132e5ca557c3d6220" +
+ "5ba3a48539acabfd4ca3c89e3aaa668e24ffeaeb9eb0136a9fc5a8a676b6d5ad76175eeda0a1fa44b5ff5591079e4b7f581569b6c82416adcb82d7e92980df67" +
+ "2248c4024013e7be52cf91a82491627d9e6d80eda2770ab82badc5e120cd33a4c84495f718b57396a8f397e797087fad81fa50f0e2f5da71e40816a85de35a96" +
+ "3cd351364905c45b3116ff25851d43a2ca1d2aa5cdb408440dabef8c57778fc18608bf431d0c7ffd37649a21a7bb9d90def39c821669dbaf165c0262434dfb08" +
+ "5d057a12de4a7a59fd2dfc931c29c20371abf748b69b618a9bd485b3fb3166cad4d3d27edf0197aabeceb28b96670bdf020f26d1bb9b564aaf82d866bdffd6d4" +
+ "1aea89e20b15a5d1264ab01d1556bfc2a266081609d60928216bd9646038f07de9fedcc9f2b86ab1b07d7bd88ba1df08b3d89b2ac789001b48a723f217debcb7" +
+ "090303a3ef50c1d5d99a75c640ec2b401ab149e06511753d8c49cafdde2929ae61e09cc0f0319d262869d21ead9e0cf5ff2de3dbedfb994f32432d2e4aa44c82" +
+ "7c42781d1477fe03ea0772998e776d63363c6c3edd2d52c89b4d2c9d89cdd90fa33b2b41c8e3f78ef06fe90bcf5cc5756d33a032f16b744141aaa8852bb4cb3a" +
+ "40792b93489c6d6e56c235ec4aa36c263e9b766a4daaff34b2ea709f9f811aef498a65bfbc1deffd36fcc4d1a123345fac7bf57a1fb50394843cd28976a6c7ff" +
+ "fe70f7b8d8f384aa06e2c9964c92a8788cef397fffdd35181b42a35d5d98cd7244bbd09e802888d7efc0311ae58e0961e3656205df4bdc553f317df4b6ede4ca" +
+ "846294a32aec830ab1aa5aac4e78b821c35c70fd752fec353e373bf9be656e775a0111bcbeffdfebd3bd5251d27b9f6971aa561a2bd27a99d61b2ce3965c3726" +
+ "1e114353e6a31b09340f4078b8a8c6ce6ff4213067a8f21020f78aff4f8b472b701ef730aacb8ce7806ea31b14abe8f8efdd6357ca299d339abc4e43ba324ad1" +
+ "efe6eb1a5a6e137daa6ec9f6be30931ca368a944cfcf2a0a29f9a9664188f0466e6f078c347f9fe26a9a89d2029462b19245f24ace47aecace6ef85a4e96b31b" +
+ "5f470eb0165c6375eb8f245d50a25d521d1e569e3b2dccce626752bb26eae624a24511e831a81fab6898a791579f462574ca4851e6588116493dbccc3072e0c5"),
+ },
+ }
+ for i, tt := range tests {
+ cache := make([]uint32, tt.size/4)
+ generateCache(cache, tt.epoch, seedHash(tt.epoch*epochLength+1))
+
+ want := make([]uint32, tt.size/4)
+ prepare(want, tt.cache)
+
+ if !reflect.DeepEqual(cache, want) {
+ t.Errorf("cache %d: content mismatch: have %x, want %x", i, cache, want)
+ }
+ }
+}
+
+func TestDatasetGeneration(t *testing.T) {
+ tests := []struct {
+ epoch uint64
+ cacheSize uint64
+ datasetSize uint64
+ dataset []byte
+ }{
+ {
+ epoch: 0,
+ cacheSize: 1024,
+ datasetSize: 32 * 1024,
+ dataset: hexutil.MustDecode("0x" +
+ "4bc09fbd530a041dd2ec296110a29e8f130f179c59d223f51ecce3126e8b0c74326abc2f32ccd9d7f976bd0944e3ccf8479db39343cbbffa467046ca97e2da63" +
+ "da5f9d9688c7c33ab7b8aace570e422fa48b24659b72fc534669209d66389ca15b099c5604601e7581488e3bd6925cec0f12d465f8004d4fa84793f8e1e46a1b" +
+ "31b7298991c6142f4f0b6e6b296728ae5fa63ccb667b61fbb1b078003d18d97b906af157debed5e6c55d5a61cae90c85f9e97d565314a2f9fd9e0c08430547d0" +
+ "7cfcee3271f921b95c32a11596219abaa30abc62c2c72c6725078c436c677320594df6bcb92134c1b114fffec982a1f68f13a9f812f074b9fb9c78f2cd4c1c90" +
+ "7ebf1e447f7a422b06303921e3d54f430584d849eaa4b7d652e92a5d659bdfc462adcdd7991e8c66a19da4ddb5390463d073941491859397f135ebbbdbdf5801" +
+ "cafb873c383893390141ae385515504d74a33608273310c312ba468046d2e20c271a38cc0e3920b39705050e752f34f244fc23ddd17ff18677756a87671d4145" +
+ "3aebf97e4890da1d645f41eb20da92a8537c787ce419580073c46aa3bb154952993142ec5b4fb6e8f108fd15fc618cd5c27b45a37ee6dcd52a4ce656c0f58604" +
+ "717ec55f5e592355f1f20e8316f8fd77243734a8b0f50ad93c1d95b5b0482afb22cd0667d935bd6053d7198b54974e10d100df7ca3ec2e0bb5ccce5807b266e0" +
+ "8429d5fec2ae6ae1cc7c5efc27f19c89d4b4a6c5c0b9397886dac635ba37446ff528b582457a4fe7f803f1a47903574f8982d4a679b627396a4e97aaa12fa179" +
+ "0d31ba52e9010bc3c26ace81f702f86649fe9eeda9ec03b74a8a5cf540d82e22af33ab893564397dfc4edd8b1677350df5b82ab61d24db95f58fd2d78afb49c7" +
+ "2d2b1fefa8ff6606b8623829cc752ea37d663b945f3f1d48ad07b1416af252f81b55acd8f164da4faa9d9453721b3b795041ce7df7c77edc13865dbe04fee331" +
+ "47daebe18c183c4a6594a6df3a4d2dc5e3811d805102c9c49286e3d12b38927fa49a7b0cdcb1d799f57118953e31c560aae213a1799d59a78ae68f0590347061" +
+ "fc2668caf08f860452f6b7d3ebc1efecc2e1227d33296b1f1850360dee7236e85274eaede4d18a58b4261ce1f6a7d283dcf64e6d021813f82a566354445327e5" +
+ "6217279b2393fe5aa0f9eb149d4866e1105106bcc221810ceaf053f2ec733d8a22f409c1baf955e50184005c5d55de907de97f5f713b62ae10937e1a7af6267b" +
+ "d2a239e8589017197c343b81540bc26bc52bffd5336fb1da1202a511c7175014d2f500b9d9ce78e4b9f2b158d0fb27af352b6f78c129cad642fe909612c9d658" +
+ "17a8d7f9195ee97201675a918e3cf520fdc19f92b7e6a3db806d4f3799361334082cc58a22ddb4e4f5760bd1667c177b26be325166c6bbed669a158fc87acd43" +
+ "a2462e12578d72db6606f9e24ae659ff411ac9b31d696b8354fd08a591622967a14f8468eaaae3907b7818154ba2d6e4581589354d178bb6ae1c03651c44bbf0" +
+ "e7fa52cb0da09508b5a444aed05a54f416841247a4fe36bd5529029e3adf78b105e22468ed775f4d0954504dd55f2c9b9e6b3a086370b2c0b6fec7efd6914e07" +
+ "26627edb7a04869a874e31f448271077a7de3031cf81bdbc39848efee6075e0d65fa3a32640e9f0395cf7ec12139992aff0a54e0a7dfe5048b3cc03246b56f7d" +
+ "3093538a7b87538d8792a665bc589373621b2f3cf47d2c1f8f580fe34d79c6b2a66323ce89808ce0e5cf77700f5a4446c4be01a310e8f7c7ebefe756b0044886" +
+ "a0477c88ee8ea8c71503748a4cf9eb40ad5c1c8accf7c63c0f43a94ed2b8a5999df3ab9b11b80de73310e036ca88668e640015fcf9cd18eed05517d54896f43e" +
+ "25e7931b44872c4e4183500e0e8c5103292bca1c0d6b0b00c9acce25d31204bb3e4f255c03a0a0916664e9c831b28b364078109a74411a11afb1e610c7d1c9d4" +
+ "ba5e10d0ee0da409654d9e7308395e17caeb9caebccb0192679866e6f2ecb5f10044333bb70d61712adb6d74cdec6918ed9a71d9925da576a1e6f4e906a5cd5f" +
+ "0e94a25e48a4141e4e2770144b63e2449b0f84c82879f34d78440cc430196ba85a213fdac1bcf279a46d7592fa29a876bb7a2efb7081365522a3f06fdceaedd3" +
+ "cc0335cef9ea570733fe8799bb1b918aa7732b4d175929d80c7844a78e19f2dc6a6febf648f49b40320b0f7d784e7f84e45408d70b046bd01cbd8fdaf606fcd3" +
+ "02f4e5a48ab8d13e93a246adfcc94f3109e02a7a969986e75b6ced6bf2d11a55ab77488e131b65a06398fa8e384dc90d875584c9b17cdcf2da5dd72a461cd07c" +
+ "4a955c5fe48509b3284476c42247e086de7d63839b7358cf4ebd9edf9ac8b6fd0c096166405de19c51e8785009d30feb67cdb8ff9ba55459dfdffba8c022e26c" +
+ "0ebd399e4b76ccb4d5491a862c2c4d8cdf1461a96c9b98150e170efacec980edc00a2c7f6d7c6bea3075627e1eb386a7f1ede1059da81a4ac5cf35aa173c88c5" +
+ "1818dc0fbc688b68b82ddc225b6c87588e0c680e303e737c82a13e34be58df8b0cb336aeacc698c79e7682ebb69e6cd6bdc5d11790c96afcfa9290f39515142f" +
+ "5f90b938216a1d14bc049ce3f0ac135722208b989d2557d3520c2186479f179e50fe5b125b8d6638a65047729c6249b9b2c6381c9103c97d1b389cc9cdb31c21" +
+ "8a2eecbf4b9ad1dcfa57446cde88f96563a544c49d6f5303a84a1b7cf074fca78e67e72c9ffa0c542fb646418c6434b16b771088140725cf2dc723c1a975c4ca" +
+ "8a80e633721274907353f51e95952c2b403b45750b42ad10961f60473eb54616f61f7b038c5b7eca475d6a2b844994a9eeddce4f7bb49782e50ef78bc13b85d1" +
+ "9e956f47c60823f3d1981413cb78d309f63a844694861b11b5238961c71f61d82daef6795734f0961e92b9167c57f48e91693e9656fcc6e88f9ce2d373da26bf" +
+ "45b3dff50211fec72387005a7e04828e4ae7ddd10fc2332acf5f1b0f67adcd863752573c2d24488857bfc58c41af45be7641f5cfff611f184612fc0d695866f4" +
+ "2b396b1d9881f442c4a995f4b500f02d4ab4b53ad6e01776ab0e244583f01301203a1515f3dbb73906014e36c7143bf882b005f0228ca0562623893c8a24b7c6" +
+ "4c2c561912010c121b5c3a1e35e75c0b094731e9c0d6acf5a2b1e5b179355525a175640579705f898feb98bffa25633bc126613fa27d2ceb214812902ada23f4" +
+ "367a78655d0d2276095c9e83dfa79153730103963499c367c5621fecfd0888253df82b3d5716703ef92594cf269310b9e6c892c488edb3bba1d0b216e92f622a" +
+ "7f8f7f00d2926d81a4c7ca6cef40d240576a8d5541ccf561c8e0e699925d20347ba7493ed6e182cfe3b633e70b3ce3a0d90813574f6fe329c495d3cd46fd5d7e" +
+ "bdde58d7eafcb134a9a5d3e5d66136e8c9b5d9ecac195dcc44158941c9fe2d87db52a7ddcedc9f82ec160901cc36a9c877af80ceae0563dfa75cabde5d7a7c94" +
+ "9f24bc190f7c2045368356474ff6eee284e7125d1c5f9a036fbde24cecfd3a30481ce077f20cbcb31924368296abf66ce4834102cf7cb949d1b4c6faa6d006ef" +
+ "21379cead5d5a39324d41555c46e0b42a1e871143e47f8e6b3d794e75d7a43c282732766d856e04e666ea346657b157404b0fc8534a2dee8243d40a5e37609e6" +
+ "18bc1d52b91a7623aaf8214a97e4c8c5d860b31c3792b129354a121a7a7e42b50dfbe3ab6590769401eb280545547a43c3a1455355d5d5fdedccb472abfe75b8" +
+ "f5e7d62b0b31553d8d55de0c3c71e6f5a2abba6fe81e9a42ec1968f235bc4296c1ac5df7430917453384450ab56dafa7c7af764cefa3b0bc861c52ae27692365" +
+ "9d7d9ed7609958884147acca867909a75bb6a2c364debefaf98c7ff70c7f4acb5cdb81100fd79a48c5139f8bbdc6553b509f1eb0f5d5d31886a602cd669b3f9f" +
+ "59195a1fa2bcff1170003ba1b2e5e9ad7f2bfcd0573d0f2be9d8fc1773c3a63a2b9292cdbf9b4515c0b1d51772e5ee95303ff493d85314c989e269df4ec3a916" +
+ "40988a11c6a4ad96f7d0541a150edf444c2b1672aa6d37564453b835c2d39864c05c4366492fc9164bf73795410e7aae8206430403357fec6389142b4976b218" +
+ "d70622b4098e322f73020a0d045f07668d1e512c6eeed6e2befbfc3a6ac64054396df96fd41f7aeefa0ab1f66bb52ee1a1df066f365fc43ff0800b0398b621f9" +
+ "a415895268505a81517c44a56dc94e76580fd107dba034bab9f4f4b8a9f881ff34c60c406c47b6d4a998894401006aa88f328393f9cd55a2b4d24db5abbcb05e" +
+ "20d392f3feab3ca12dac475eb3690f2bf9c699d7d90900d9a840068c8cdda2ca7a27bebd685a26eb01a768259a65ab4d7efc1811c87a5a1f4e5038f6b3dc74a6" +
+ "b46d9ac58d31bfc22dac23645aeef819329c9419326f22e1c24c53457baf62ae9b92ab5f999d4ef0ccfb5a21b7598340eb2d399ec81588b6a674c5a1e45aa238" +
+ "c55cae8e1af0f5d64ea378b8afeab263a3a2e5c71cdda4cdb824ae55df2b0260aadf386275ef57781d46f6da3d0b300ea68c14a620c25b5df738c54aef04d63b" +
+ "7dee06cd225e9ed00e78abcddca5a133d8b5e0d9b287e6436014c5da729442239bddb7ecd3fe34e6f6e530134a03ef45de4ae4fe3bf507f16cdfb9bab1fc90e8" +
+ "dc565e4a7ead95352c5a894661e5d82c6d0fc47843d5cab12c4013db76c90734cbff34c73d0d873ac9b27b417665f4948469865f33179624860604a9aba2ceb1" +
+ "68e74b6af3d1ad0bfcac4180ea844339a034b6b2c3e2f61f0c7afbaa76c1ebe93727df1d3db27d59a5cf51b2baaf637b6eb8a20302ef9af0b25dbe3a5e74331c" +
+ "6b0c8a0cf2a2ad72d2e19797983e09468ea95270dc229f2fa084dd2aa96e722016504f6d82508572d9c30711c3ef41ae3ae2f36cc6f5dddbcb0b40d9499b24c5" +
+ "4cd36d2927a6b9d57e335e4fca7f0f16887711a8c1ffa0b48bda46c506ca444b7c23e2c8dd086c2a87283d5fc0d58e9a384106837318dc84ffe65b52d4cb9141" +
+ "2672adfe139c3327350fe3cf355a08c0ca43598a253833e114243c5253077d65643323f5d69b3c7902d91bab7a0928754e7d80afab8d48539fcbe0d9ab83b4db" +
+ "43a6594c4071df2ef35acd1f53006a570f09104f1776b26a303e2aec93a00d2fd8c952d1ca0e54504cd9b469be7c1e71557ec31467ecc773ee817b17c4418712" +
+ "163ae86646b20b80c85860e828c48e88f1309c9ff018e6a95f4c1178de6a4f9f5860039511845da7d8727b5d824ba2502d0a3d76ce74372db77c2050c728dd65" +
+ "b3a15da4f1e1e41c3c2acfebc5618e5e923d503c43a3421d2628ac037c5ce13c74c4ee14d47af02323872f6bf2e8bf09d017ea6e8ec4d3f9fc4fb203ac4e1663" +
+ "756b11629224c676713a42b1f43dfd6362876be1c4865928688765589e26c8dd8bc04ca18d76ced7f786cdb0fa5028ae53991d5b7b45f93bbd50aeb97300f04e" +
+ "69c6736f270907f6a7ad76dde0a365183a961bc8385511e0f22ce0cb8f3c42c5d3928621841e30285fb625294865409267dbb0cf91730ba2fb1088fb79789a54" +
+ "a856311bdca5b0ac0e95fbc79b11c561dc03ea82db182808031e86ec327097143ee761bb62dae8a9f4101fabcac1fc87b3c2080820582dc8a7a8287364550013" +
+ "08053c781b3eb279c89e817fe97103b6930fef2dbf7728def389403a4283f63ec04ae953784b749f0ea6f08749781cd17fadfd15bb197afd2f4e0a8aade2b1ad" +
+ "5100cbdce49ed59658993c00e06bf57c0026b97beadc30cd25f586ff03ab40fcd731535c9a1ccb2c99dc7f8815feab767e1237cb069981f28d8fe26bdec24218" +
+ "488e6086c0ab0efc5d4211fa0726b3a11387df9bb62b863a7b154ca390a268f5e49f50dec45d24bece2a06575cc07a24bfff017d7445024739efb050ace5f345" +
+ "98dacda843d4ef5bfb2c931dc16ee3dd8b61a6f01d9a7de8bbb6d89ca8695f8ef8bd1cc6e0455848fac7691e6789218790270aef40fba114557fd88ff74fe8fc" +
+ "476d9b9665d7e45582540710ce92c8dcad1ad8c05642a23a0d58c02db37ae1a0e70fbc5f71b1300fe398c74cbad37fd57379f58dd3e2d3de6860a17acf3c9321" +
+ "02eb4f9d596497bd849c5bfaf59a83113ef389b6896aa4d4665504a22486299993a9987b2bbdb47d59b3f6ce5d2c9f9ba33b5f0760388ca7f8d8af07c1cd28f5" +
+ "67a417a59ebde4bb9867d4e7b7b79dd8665602c029e9a16a7718efde3d034f13f7f0b9af1702c335893526cb87afc2100e874b25c37fd666bf34bf6a653c7cf5" +
+ "44e1fe0286a6723c7d33461dea380b392dad68f79a78fe1b785d7833ca0d1cd68cff472991a625e3099f3ad2cdc99bd37eae35353cecf424098389dbaf1885fd" +
+ "7db54909a92ee879609eb2e9ef4de1f4338f0df53dbde486ede944ae69869fac701d4f1f48c83757b470ea28c9de2ae5f1ef5d1c91118d16ca0d80b1baf3d314" +
+ "056949df27a09eff70c9ac50b54feff67a165ce5e22ba2222defedc7c39e02356c3553e97524c1506441527da4f5de121142ccd494f83114b3ca2dc37e15c752" +
+ "e2faed7d50254124d68f67e26f4f50c9f0edf6e58b916ca830c4e33801dc11039b18292b87b08f4f2edbaaacddcdab78ff3a0004f86034080f2ca4394b14aed4" +
+ "31e38e3605e6b257bd772954d2f4b846a17df7ed6e5dafa33964d9e56a07a19898fb4dfe8b2ddbd11fa0013e6ebc0e429a5166a43d1ec45557cd1fc1bddbec4b" +
+ "2e9ca26395394c96395ff8f557bd0f7f805c09f0c18534585b7c7fc1d07f145372983ad77fa804fbb7765934e72beae0929a87cc6bf7f6c242ff5db2d4d5541c" +
+ "8c366d22e24e1da5379836fc0eb484683285f99f178b98ca170464bdff60ee04584c12c65408102ac6dc7d10bf58a7d770bf1b3c636a48f934f6f4bbdbcc75d3" +
+ "fc551de3ebaf77006707f6120b3804f2bef9b4bd59f5996610c09ba3953994d1b78a9f3bc3bafeb52266f10755ea842e5b4370c937c09afd34a092ff9b98b4d3" +
+ "518bc2480d4b132455b7f03774ad76b83b254742117921c31cebeab5f39c145f7f373a5603d17dd95217ba1af37a0aa95b2992efcd02d0bb4ad08ebafb31440f" +
+ "1ccfce45882b547ee4bf6ec7ecae11ed79fc63b03636c8a14ec4e0f6877eb658d839be2eac0f10a8948e74203f46078ce66aad2764ff05590e2ac7a8dd8b3036" +
+ "901fcb7ff3369ee989a28e34b9b62e1e607d14da3049ded1a4ee50257195eaaef995bed79ec85111abb522aba1fb306869a1ab381e82943f35345bb5502bb90a" +
+ "e2a0af77526a84754ee4d600ba7f8ac98705ee687bab949a081849889d7b83a21a3dd34af84dc2b9458230ef0ff44c6398d3c6e48e5c09c399ac4d4c7b285549" +
+ "e0bcab7fd96de42f072f1cb633e3e250745321049d0d7ecdef4636e70e94c8414e76ecaedd6ee0792e97de11e7dc1e1e1801ad68f9147278e268d7ad76c5bbb7" +
+ "98386fdc13ca8c77569d96e0debba8ea3b751352136c8f1c8d611a69f1baa9aa4b9d0a476ebd5dd21339ef7f97f09aa86b69a7b114cebe17a6b0e58bf52803d6" +
+ "fd47d9eac3a988b51e9bca95c546d49367a3126bf8ee44fbd0e77611473a1d3d2de0ce4ea54f9bb7f9dd0d0c065f613a623fad43a445eba294fd00037492914f" +
+ "b74d10d0b97a0cf9bd3151c3cade89521f36b6fe1aca7f352e79a77d063da5337a7c88d90e9e566bcd97732baa4459305967c2f65adf1a4a4c7991cbc99df3b7" +
+ "14335a107a97a4ab104bc94fecd1d003fe6d2f22e717853c449881c4ccaa7e7a1e44961a14a47a0d0aa1b1493dd02760ff4d31fbddf5941f93c8e5925d1886e2" +
+ "8761baef8610fa6be016c8f4fe65bb0f335152d5e94893e274f2ab90118e4c07957d252963755b4b638ffc0a734fbe6e32c2e304b10a46a4eed330d101c4f0ae" +
+ "011e7f94b89bc0eb9d358a6548b3f0c47ccc3c2d986d381437c49041629c6cbf61bdf0825efe17e4abef128003681450ceeff0e28842895d8e338c247abf81cb" +
+ "7260fd45042c1f6c630a4b195579721392e577fbfdb9f5b003a8b9a6bc15ae754f6255131a0be600c7b07e2cee1ffe32aad4687f9a429998ed9059a99fd879ea" +
+ "c4dcb55f4551bbb70c187cf1b162e2ca4a929edd6ec9260877df652622ae073fc63c0d8522d3882ba888ac50a67a68fb6530193f93165093a1d8132e87d8887e" +
+ "ff2fdab0fbae6ab9506dae61fada4023133d166bcf1956aedc3237c77d1c81dcc84ae957d89367b0fc950c78e58f2cb9c4fd93e16b94421fdecd46c3ff55592e" +
+ "4374a7f7d8ede9923115770cb416071e8f102d4ad78b891464ffd14f589c238c8e13a4e2a81744d179e7d3ae36cffee75ceb99633face85d077d0c15b3970930" +
+ "075dc08b420e0a545200895207c5a746a18ce9ab64a50d3dcf44da857fb65e4efc29b2b4d532dc6a03b699dcfd77030a4945e6431273e25f06ad8f913c2a9eb7" +
+ "59d8d3049868d337e451726d95c4cf8baf381096fc9b62679175dc8f14e52f8b99f212cab6544414c62f17c8323256cce95356dcd351e34c7a1576b17c1406d7" +
+ "5b8bcca8099a1993df1541ded61b876ae83396b191b719c4b1cbe50d73fc13da352d827ba09aa7fdfef3e4e0273c31ef4fd38b93cf64199c3969a7c09dd5e0f3" +
+ "ff93a5a7db9c2c1ec25e3060bcb5481c6802e1eca78f31862842ea08e8f92b8e52856c4c9fedd0bf20e386cfdf926425f7756ff41fd3567c5bf334e96e3f492f" +
+ "74bd0519d8d98efa0b427ba681b8b1be8fab041ff084dc5f8c4d5d48f481115d7e407ad8a6034f481c2be86f8451980c3aa83a3fff245d90d13801a54527e97b" +
+ "e392b25867882d43e3819f4a8aa380db63954ec23d2f0c11a7aa5bc7a3aedc43ecd3b024280ed8843399e28deb954bfc11a3197fb14a9c9a895859e390e9586e" +
+ "2ad21da39bb9ba79a62222d228a0fc96a24e801f00afc3f98d2168a8a253f24deffe461f6313de9b433e1d2e307239c0e3fd5d9fe4c8352c2c6797b1737e93fc" +
+ "14d411bc69bbc9d78cf91734052b8aa1dab348e4c243b8e6d623865c135f807de8d5fd88f3921327affa37066dd538351bc4ec52eece88856de0a424a87d062a" +
+ "f68cf24db37dbaa8e8e96a812fbf32ccafdf1b9d27f11bea23df02143bd09061a881c010819a315a5b6ee44b3c60979b3f7b41f488b2429d49377d6542fb0e22" +
+ "d09a0ef5b81aa7c8134c0aecdc7a4f9228559d0bb826d30fd77fe0f834212647ce61e22fef0a1c10eb4177de81c31c12054a15f81b605619f3045646110673d0" +
+ "b2d79d80577fa43284266fd2ed54f9a3b9df3509f79559c5bc51a58521bfeb2f95d8851527b7ea47b92a694f6ea2b67dc2d4f506d11d2db32c2929cdf5c8816b" +
+ "7f0c310cceb7ede08d5965ed2c7be6c0a317251c7d31cc4a15f6d7976a8a1e6a2f386fe0071d43a50bd0ce5e864a8e449fe9600c6e4a84866879c490de9f9d46" +
+ "3f22708abf34d3e180dbb6005484a6afad373838cdac335f05c034e3090b2fbaaa53fa2db1f96bbe141d570f17363ff98672500e16994b79be74634755b09e66" +
+ "f1b37e338c946bf85e06c97e31dbddf257d58fd10468278648d86f38710c2ca0b6ea7cac4ea0e2c49b96bf1998bde1b3d38aa853736308e12b4a0d467fdb8a73" +
+ "0d810ce45518614bd5845f58a9835a5cfbe745f45ef59ce9a677d10d8c9f6294f1a0565301efb3c6610afda35167150bd326c77057e530c213da63af3e6a600a" +
+ "d87b16ec5cbf76a13764f71b3e7e0c867086ebd9fad02e1d747030064e071a13da4758cd0fa20872b3dc350f4cbfcde1b68a97aca41e32207b40beddec412c0e" +
+ "c75d87c6671ed94bda5170aa2866509161c28d550190675f60139a7b460469f3d4829b3c65f5d185936582629160522fcfdcc53fd0dcc8fc46de11d52bfcc5e6" +
+ "3407ecbbb682cc1693d6543756fa4e068e92ae1a94924a1ff6891361e5f262b7d3c3a3bc2866f54e6d03ebd5479afa3f424077d51668cc60e23b35fb0222ae22" +
+ "5223ba8a8c416b68c8853022d150c951f06f8f85c2078d3035b8ac3ae984ffcfb024431acaae8bfbeb981870f9ad6bbb88d7d5ff34ba21a44cbffd0aeaa435ba" +
+ "7d40d22602e807ac9a69db514ab13248133142cf03fac999a2b185f34d83fdb495ef042d4a5e92f2624193c88858d91c0812b18fd67046cf50635e6ab1ea9ade" +
+ "7b1fe783dc5147f14f9194cfa92c03a0456f4171f9e5c156fee1c607a1e9e06535f2dac49b92ddf5fdacbf88a062bd7ca5439bae645100121e598deee6043baa" +
+ "85cc0d727f08d37a766a55a9ca21ffb6594fb73f9aad15be4a64bafddb6c85d00f7bb5705d9e56b410dd80df8b087b6d67c7ca84eff2ad699f901415fab21343" +
+ "6351a9bdf83b440e29f3950c7e4c49963ab109686d78fce629e9207db2e17eb5f02f01db6441002d72c06c6c6bbcdc0a7443589ba29909a5a78864ad51e1dfda" +
+ "14782d869e4989ac3c5ef0aa1eabe540e9e7cd4e8eabe25b07f300a134a92718186f085d5c10a711ed0e574bf7550f6bccfc3c094d6e59619bde9fd892af8ef2" +
+ "50e1cc3afdcd9c84ccb97344542843028b00064b0c3d18ac0f0703fe6f9683d40813abbb883e164c5797bc1555338566cf8cdd358e9fcb0e93f08f7ae06a5121" +
+ "c67a231106ad8fd42f0798d7185c2de78b8b76c10e82272a405212ce3b904f90236eeea02054953b967cb614e8f8ac49b977152a52df981c86fa4a92f7f70eb6" +
+ "cd4eb65986564039b0d77f8bafedb4fcbf9c34b8fe9c5fa87b0785c118a8624498fb0184a0dbbfb16777579e1964330c12e494449f6aa5cf69ec4a32054be553" +
+ "6027e0d27c7044abd4c0b8e43db703209037efcfd08944647a90a1ab0c71011753354990cac5a472fae44dc370aac8131ebdf31456a8484e7fdefd268cbf5cb5" +
+ "85ac615039d3655b1348fc0b3b078ac41cbcaf6aaedcc1153bb8d55c307f45405ad6a959abb37bf8891c8dec79a9d7ccd9b791cb60361d4a28f33ec0dfd13fa8" +
+ "e0b9b29e14bf36f5047e51a39c2efcefcc156bd08e46c5c1000a3cdc2bb20713e19d6f492c40e51eb93628cf85d07041ae5353e7decc824cbb1db8ab3a7a7fca" +
+ "ff04c2af423bcfb1864ddc864624b827ddcff2a2f8fdb7a3d86d76e72b4f850ec1262d8fc89e7b12e4cc618afe6a2bdf205075c2008f93b7281d80180199409c" +
+ "de850d1f14ca0ff960f69772385cf0f0a0f47cafd5489ea4fd8b68ec7aa539b942379139756c95bb90818842cd43511edbb7577ae469f46728b13a61e6eede06" +
+ "3a4cdfab5ed590feb807d55d76e518d1d74bfa6704f7c8ccc672824b4d5ef5fa5b3ab8fdf2b6c1753404ba35b76aaa931a4e0e5ca7e440524166b23e9a8be9e8" +
+ "635381f6c9086802d428fece81395dada6b3b866e905ec00ccc4fb9b8415dd15e443f84b7220e3b28700ce3d88f9c6df2afea39e0ead537a50ee11f0c247ee86" +
+ "d4b3074e8761de4de611c409c6d4c369c2c11742a7763f6550edfaae49afeec33353a14d2ae60687dbeefd2fe29689da6ae79d7b06042dfd25a68bde9182fba4" +
+ "1ac53706a8b96535057fc2f99ac84a9cfc6549920c3e2cab44e48a08e77207b6a95b2f6179d6dfd6c2d9e3c91106a7a687e40bb2a1c5ccf566c0e31a0fdbd0a4" +
+ "f270f9812208f939efd9698a8b28ce9c5633f18ace7ab0a7550d9e7e26cf62eef49200331e19a64bed648b5d18ceb389bafbcb3f280ba78e4cf03b053f2a5f08" +
+ "3c852452837138004073cf6726143179386279f1a8f15d44876c19bf6c2e2992ce6056191bb1a386f0e1f6f249495cff126991c6560e3f613e56525c0c49b5cc" +
+ "2ea4e736d83480f2b45d7dc840b849887f54a2aa072e72e3fd0db34e5cddb02221fdf2a40fb6ec271ba3a09de8dc73c24328c5d9a33ae0adc9874902f25d5bef" +
+ "4d85914557e2983c93fba16cdd4bd929e878b5d51b142b6e9aa0ce84871b7b03ee6cc13251e17547c2d20a7d4e948760e207e29de58a7ccb71b87f99d79837db" +
+ "d0f293ad3d33ffe91435598e8a4584b7b7ef5b1a895a2827b4976f81d335e4aa6feda3539690899619a4cb34fdcbbecf1b8b38cec2ec7c07ce84ec3044f49656" +
+ "28fdba8971585afb509526640d36425777b6ddf5b2a49d795fdcf71e57fd35f29fff37890541b6e152f14fb6ea4c70a1b9f159d02ed895a68dcc276f5d5ae83e" +
+ "47c021392ee22a398c8c73b3446d61562b3ec596036959aa645a65e5d24f733e142ec0e184b72a2adcbe3913932b2c9503c856a7e989d24f306e01e99268188d" +
+ "f858694e297803effeb8e28bf8fb63ed6787acc2c61f509e19099607512d40928a08e649474a43728b63523175fad12ad088aade0c1e20815c7c12773bc959e8" +
+ "640ee23eef2b1653ae8918615b45158a01be5a5f39a75a7c6cd8f1f6b463516539771ad251d5c2d40c5049877765512c44e58bd3b9ac3a0ac281771097880fe2" +
+ "c9516dcd6f1373e1e8a52fc485d104004dcc839fe3d120f1432b213388dd37980ce8238c87a70d5abe95d78d696d2436eb23a8f620ce74335d5e47f6524b11c3" +
+ "e22288644b539e3ab664dd5fd6bafb02897aab35adaef204f82d9318b22f45b787f5bacd74b01d23537973060868a47f2e3a45c1d8805a1d657f2332af8170e2" +
+ "9435d7540e70e92a8c8794bf22d3e11d54ff2d48cbc7a1ac3cecfc48f80fe521f6852f97aafa0605f3e7084b15e61a74869512c9c2d84180686ea07b562cf35b" +
+ "5a0ca529481ddbdba9c60729f821dc7a5a8b5c7eaef1ea7927d455a702aab538e7441933c4fff2d27de5659d6fa41f0ee72bb13a829839267f3a7b51a81a85b0" +
+ "d737194d94e1bf8173248cb057cee19eb5e2cdda38c529298f3c4d3b95400198063c5b27e9262f9c66425c65568a09035bed9cd55c1f2ec4becb6b9c59445398" +
+ "ad5b7c85142e713b6dd32493dcb817c8bcdbd728e325c25c5a14d764b63f960d1e48a0bc7f4d2bf51060f83b1d1f2591c6a9b79182e686b887a2c1461442e2f9" +
+ "16e8582e298f87ca95a8052df33af20ebded7bb1c528920233d1aca3b3789494d97084890fa3db0ea7eb561b0087c4a90000db41ea072613f91ebba82790f33c" +
+ "fd52cdd92d2ef1246911ef1dd82ad083881b72a08a40ee55884380dd136a7c0724cded69c6abf1f156b14ecd7284abcbf66522264145ee78ab0ef0d2a74eb390" +
+ "10946d5efefb7175164e96621d3f158de8b57956b8b1155c35b32007e47d915cb61dabd556a370537737574741fcf9a8a23f7155bf1f0e3d3c0d2088d1191d9c" +
+ "9c974139303f3dda55a70ab4810fddca3561114969d370f4e6bad60a53815eab1c4613854d04ba8b049dd7ab1a935c728299d1502ff9aa3fbb356f87f2a52b6e" +
+ "947dc79b5fd211ed31dee722d3fd857f43aad973fbfacb7cbfe1b2553bdc76142ccae5b4021a4647b8d8087925dd3191a57198792b6f918de87a92705ce57905" +
+ "f2dcfb67a20f8c77e700933432d60a4536d0959415f15f3eb8a788f1b19c497d3b68194e27ee736231835469d8bf0ce1717ecf533ab77dd97b35881d8eda959f" +
+ "54a7935b1bc11d7f2e472757734afaf0463da3fad9804eb948e8d6444e8394b33f1c187618c7c02371ee6d378ebb7a20b6049a5504daa71999d15944ee82650a" +
+ "2388f374f3ec3afd4ca58ef3f2588997d194a2741252cf6562e00cd6b5c5fe4066454d2b3150317694052b4dafb40c2f04c850e4062cd8f0af2da75280046850" +
+ "77990788b27fa457ae9d0b622d18fc070f1d2661ecab529b5cb82f30a29610dc6a9e93ca9a2617ab0109957a45c1204e5eedb8860c6f4d57122060f39a4194fc" +
+ "a285f1e9e7a75cc3511b8cb4865719c2260a630845051876e7795bba59573b6ce5faf7e5708eda7be25dd49c8cace4c04c541074d703e6601e043f6c63a0a371" +
+ "1a381f0ff83d136f4aa29de266169ce5b3105cbfeffba370fa306a93830e3c0519a495b8b9f4b72078e2c45421b4b0667f903676a1339c70ddd1a90dbd21853b" +
+ "2826ac3fa5add5073c634d4c5e87db0efe18638ee93c460257e52aacb8600ff36739818056110b2e974a1959e3784903aa97b0fcd9264f7d8f6bb5d8b7d9f03c" +
+ "4b643955bf7966250936d4e7d651712db5e695a6a36b5e6f56c651ff737042b5bb73638e21ca6ce9a3e63fbb1906675d97001d7ee240d277d62df18acb169677" +
+ "963d231c5276bdf5767ec35fbedb062e61c23d759aefd287b2dd62a0d6f0518d90b3c1756fde50afd33cab395ddf3cd538b9ad8862a199141331c63110c9ddaf" +
+ "fa3d6c63a1fb1b45529eace826cc29a1df5df327bb782e573c41864c18e6d31401d19719326e5c35bb50de7fdc67177a6a6015b4264fecba2360ab72ae8b060a" +
+ "6c66c5a05782a15fe3c1833b47e3495d29f2cfa579fcb08f02fd064e9ef2ef5564ac6a43cfbcae7d79e9f87ebc2176611823c6624db11892f8c47f8c96a49539" +
+ "1c18f821ecdefb343eae3fd98dae1ef96fa3527788543c0d06d9793579cc62d91dc4d25312901c6368ba81c8536c6287230e8f97d25f6c77366609580cf26a27" +
+ "88502a9aada84a794d3674ae11cd1742cf245e9d9502dbb5b340c2a6c79e3607f6b47666e1ea991ccfbdf6cc41ede46d043bc4d3e5e6882414dc65d62f9f47b9" +
+ "fb7b828a89afd6361ae458c2cdc82f459c54977072702ee5a4c22955b8019d8b8d91f558897c4b661f8e5412ccdc10c40521303c0ffd353a0c04cebca5622a71" +
+ "192b144d0f9c5c0706a130df887526b7b6e0f358ad9f7d0fd4d87c5fdb29a7453388c0d009da0d4c47a5d6cf8363892ac42b6ce3388771f698802b4dbfd66aa3" +
+ "5fa6a6f8b42dd8446324501c807b6e72cdd35cfe08956a52f86bb4709fe2980f62152dba3571f18fcc4c1cf7a25384c4b5174e93e5afc9b9f12db2bd505ddade" +
+ "d670d0d71b9548f9a07ef98521961cd96e8f363cf3222336bc4baa284b5305aab47dace615c1b3f3fb1ee23ad9ca3f58b086d9169ee5b2d3c2831e1db4f905da" +
+ "11e1fe79e3d48c01bd9879ed68391e4d24d6db8d6774cb8747e7ea368aba3bbf355386408af4a59b23fce74a5e673a1044db66ed8529a65462269480736cdaa5" +
+ "0784fbd77e1c41197335b4c517af8a67eef5b7165c5fd6022cceed0396089c3985c36595497db0a0fcae478e4e4d68c57b93f466aae86dd4244633beaa8116a0" +
+ "de25d2a54353b7ee85fee58ad4780a2957d69816585a64f65e75f332614aa6786d1a1432f6acde385d3d6e870bc968c60c81401726a958f0caae336c83a9523a" +
+ "c174faed43ec67473dcd151506e334a6aaf1731dd3aaa831f934be83beaefafa11810e7eb140f4fe80cfba574e6106c1bfe9f0b20173a4ec2663ce0580df6daa" +
+ "7966a3a8906677ab680025782c61b95cec6a73b5deb16599e6521f9c6c4cae0d9286566388d5181d6ba11c51a25c62b510d9b1793f3ce9f73ff0c9226c8aae69" +
+ "5d014287df074a244014720ee38e3968557db00aa63dab71854b8573c42c65116e3d88bf040d53ef3165a5827c717179e2939e310be5eaf6fb75447ba98ce925" +
+ "98e83a32a90eea848500a30eaaaceb307d37b1201b83a744468a1a52632ce5525c1fce5f702421e42e7cc4c61caed539dc09001cd31a8a2b48a783c36c56a3a2" +
+ "d50de42c63981c86642cc92bcceeec8a66b4afad3c1be1df4bcb8beedd442c281080c94692bf453196ed1a66a074d56a8e7f60238ce18358373efc173e70c691" +
+ "f832e1139bc04e6258d77cf7529af7ce5eca28ca5cda818625c0bb5beca96d99fc9b6689a7771434aa96e23c55a41cff7b7b718df58260b3bc91762034debf49" +
+ "7d8ca8d5764c52bc9665bf86db5407ee1b786d90f8d7772597eceb98f0121e3996e771d951568a162f6b71042998db8208ece5b8b0c68107b8e2079765b0d8c3" +
+ "2747597072756208b0d84415a5334a88d916bda390e26ccf3046b860e7ccbe22c48cd3d3f51bb65a98ace74d52613f782db726babd02780b8d620655bf9d551c" +
+ "ae9ef3056e3d24f5e7c3105c4857492fedd244ac2b8c30a874c1446630b042d819bc6b6d2d96829de903db22af706e93c5ae876d72c633600222443d1765bc62" +
+ "a8a20c458ae55bce8cbbef753cccc5e7d929408d6a3709467373651f0163128aba4142ecc56ef11ff1fabf5eaf6e955b4252d1350e9002300a1236ab2fa0ed34" +
+ "c9cc7dc1d4f09bd31296cec1493e725b57cc496fdac4e8d26197376bda7f74c0965c4352bc9d5c731df04f9908899cce6ec3afe15210d115992b2d95308dd032" +
+ "13c557ac527424c7db02475a2fc78b88d022d212c3d02d5ee490e2436e6e572e8a1330465b9052f8a3de01aab76662d18fa3d076fb77103fe432d549bc861fcf" +
+ "f63f3401cda31673ee48826b68b387802fea4471deb1fc928586f1b1614c16311c9820b563ab0112c28af5c1af5121818540c4b7d7f549b33906c1b86c6674ad" +
+ "799acee7342e4a79d9295493b2430fd08f373338795764621bca444868f3f42b0e40abd4b8e148cad2861fb4980b83bb58d40eeecd8d8cb1ef1ece17b0ab72e5" +
+ "06c6e650a3a43081f545acbac51ed7e121df51edb75120cce30ef7dbf41fad331120e537fb35be45d93de4fac0cadc7e5f644e2b767a285facd5f12845559785" +
+ "57f4afc276e21d77f6162062430dc8918435f035f435ea419ae9f1ddb6afd46b243f8bd6a3a33e7970e7e76fab9ba6afa72a4806189462f9d0f231a23e3ee1cc" +
+ "51cd10cb9043a27deecaca866751f971254fbe3084c243ef5f516bb652988b770896ae5abfa12db2eb2abe404cf694e9f60d47e734e260ae668b750e11b26001" +
+ "0d2bee5ca555a44523742fb069e484f7a69c12d4bad026c03ed7af10ebc9cf2f54d143fbe4de83448df80668217a11f5a1187f35ff306e6c685cfc2417c14aa7" +
+ "aeba1fb7dab05c913fbcbb8e677dd0f89324048862220ab6f5340c38b70804f625f5a526d6675a49fdc22ea6ceed477097fc723a7b6eaffd65c48dbee13df566" +
+ "f8f3449d91abb367cf37a8460fc8072c4ac75f88be8b9c840ef438cbf12a2e7d55799f641316e3381f72265425f3e90fbeaa9919533d8f9262da27f1f933d4f9" +
+ "a83e07aeb968016fed89e7b16babf0b6af3800a27c9c3d330b6bf8be447d31bedcc526b1bb53ecb10c3ea098bfa7d014d93274bec70b6e82bd5c443e860835f0" +
+ "ae82b7be7c78cd996e0990e3cac8c1c431481c8159ae1dbc40c03f4ac543e5758f347e12715822d86c881030de83a76ba1c49e4d4836bab7b5287122ccf523d2" +
+ "33935d802d2bca303cf57b36a5ff17e7c611f1cf99699881ae464da2911d77580587a7228db8325f204adb14413a13fe318e995d60e35c88bb47b99ba9ee8daa" +
+ "3e40ce5818876a3911107a159125dcf768ba04074e5771334e0de430c439070422508577e474e9532f7dfbc489d0c87d37103920415b6c116a422ac15e0736a8" +
+ "1e1e317adc87005f868815950882fc7497794c5eaf76f9def434d198304ff495bd2f9f4026aea330450741fb969700b953ab265aabf1fe146d861ba2aedc53d4" +
+ "f929abec2dee710aed8fa605fbb9bba914eaff01fdc113836d34d855383e4a311b4ec6ef6e80dfe32bc8035d84ddc4e2c305c112b93560112c1f3dff800d6043" +
+ "7eab01991f924075b4dea4db01c377ee1ca374d383ff1fbb463bf7078f6cc7509a0ecf536871abe7c95bf89f29c71f72f1a2002854113cb0d6d2192c00123010" +
+ "8dc9477808a218f84afb81f0274718c024393d5be66edaac7406e520b0c8e2c02ab98ee7b290db261f2122ea68bd79f2cc6dc64936af5064cce2b4d1b7078703" +
+ "951b6b81b9b60b99da4c2d12bbb50351a5b7713541db0958740910ff69e748c71bc7470a3c05489febefd384e06d267371935f652736bbcaacb20c34bd50144c" +
+ "71923b5a521ac4b1ba694d024ba51b4bef3ffcff74d5dc63810b2c0f529073e13ec3232d8647ad124b21ff73402d371c0db39d46cf4d2d4cf7ad43fd8dd365f6" +
+ "9b6b7bcdf664df0e62ba58f3ca0c62ad6fdcc9b091fb4926cb47b5ff8de7d3b12bd8709a46e5c3d5f0d22934c7a0574ee70b87af97d0fa46f7d9673915fed1d5" +
+ "a6c57197524ec9978d1bdf65633721ea2ccc25626dcb5e7f5e090b00e413c10a6d20b45fb8e98c22928de6dda184e856c86792c7cd09d38e4333a76882d363f1" +
+ "7f4d773ba104b2d04fd81027da087258fb175bfa8005c035a4719bac5b9630ae57889fb3b52a0fd47ec4060137b0f95fa5d5684172d07ca91e91eaf20dbfdea8" +
+ "a3e23937f33d8774f30c7e8e5d4b2d5371e5ea5e8d290970904c4c1ff33baf675ed79599653808f652ec4fd0088877f7dd7973023ccc8377d1ada2b80c07d077" +
+ "d7208686354f511925a3514c9e93c13525353b3d9528ab678e3e783c290ead88c2c3d6230bd4cb3bf79fce6dc3e95bfebda41e5d994e61ab083d73408ff6b627" +
+ "6996a263d2920170fff6869c2311441837a2fc190bee104328591b402defa38b421b972b01d020bd20b1b6a6ae884b23eb829fdf032a81d4f199a87ef125d4cc" +
+ "8662e24deb93700980e6ebc6882bcbaaa0283492e81f81e76bbe2ce18df4fb665436310658918ee217b5da262f1a1adbd59eb3c555cfebb12280058c75b5b33f" +
+ "8aa8c2d7cebf12ce46c5f49ecec5a865a9f0b65476793884f0021f8731b1bd288f55dfa1665776b2aee1007bcaa6d92a76a2ba9925bcfa68db7cc727b2a07ebc" +
+ "e24c0314c96ee4d6164c699e585461388dd73476a1e0519d92f51b64eb2842a7b17bb55d512d52da802df63206ee926f6a6a8c32de7b30e7cd3f23e37e0fd82a" +
+ "556323736ecd9de77494a2f8702463f40fb837c2a99270b9050b0cbbc2c305a32380ff5fa94bf9c101c667f36293c12ff9aaf6e0a810b75230caf915135cbe6e" +
+ "63ffb2a0e8632d32f72a65aa965fc556e10ddf6d5e40be919066eebda09d581a32156e1675300f52c8b355e88696fc2a67dd8e350a6e902e082af28a9809ba11" +
+ "ae0a5fd9c6627fb808d757147e5d59cffd9c45874478ab226e72909ccba6592a54391d072c7eb0221f1ff7be9924b9d037e4f8c31e94fdc814a8c4cc7ad4c9f6" +
+ "eacd5af66dd76bb6222b2fd3ea50a828fc3a91ef8b084214bfdcca56348517be18ca472166dd7f18c8e444e3641486e7dada626ced8710fc73a2b09b6e9395b0" +
+ "31ee2c48c9183851357d230204c911b345457de602824273193b795fc21e90a0c1cdaaba36787424b23ce73e2116947f143f9641d39a4c07c2e40e02f3bd7c68" +
+ "6899fd57e3eb23c6f5615c9dbc279fca0d4218bc79d928e70018533a85b4646bdc78015149b4d41d77ec7b46900e7fd5250116ce978f825569bd887bf3fd0365" +
+ "e1259a7514116fdcdd6da3ffdf432bbe8e59b9bca9222c5dca1eaa61caf29b8461ddced6f312838fe490f742db696fadddd19bab8de6bedaade878be07aca4ac" +
+ "76d69b81a6890e66dccd702720c3bd5601c6abdab95fbe4ccde6e35385b75e1977d5085ace928adfa382ea2890889017b9c4c81d9ba4629771f84cced6280db7" +
+ "a6cd83ff9375ffb0a75a6bebba9a209f048788ba39127c1036e4bd0aad9be40754fd75295611e455909a818a3541af32eae98df7222353a4405da0e7be9f1cf1" +
+ "bcb823fdea7976a810e8a3c7bf93fd947f961a344a93aa1ba99bf2df48ec82769d8c08e7b14191050d5706a9467c9122f34e27f060dd4d6e936c414c4e551b9e" +
+ "5d6b5b58347ed0012a8a323f41b43bf5e960b2806de59da85b998affdb490fbc965d569114223db3ca65df69a617f6808bea23017327ddaf32990070aaf5f444" +
+ "a9db44a57b5c92bc27bc71c5f8a2b6929edfed8e182bf5942564ef045c75448450eb1a4e4e09a1875e8a4a74f229879ccb7a2f2cd0359abd91a782c2ec1f68bb" +
+ "40ce0a63bcc014b198adc222fc957eec0483f5b93f0db91b7ab3b3e3c59841dae057eec97abb55fc42b2de124946e66ed2a7fe8cd047cb79051b55f82594ab45" +
+ "711c92364f932a5fd274fe184c85583ac7cfaf258c57e296f9c18fd181308565315e27272cbad3b21cb4490ca0e5f675365caac42f299e22d8a74ca51a9d0883" +
+ "bb376804e234502db66067e7a434d38c3dc075346e888e4558b1745d00458df99db02f0e4c37702fb0989387f74d002a924790a6b7351ee0f41684bef079be26" +
+ "ee9d70b560c006cff4b08b9578afb5019c21ab9418ae4ecaa7a1cfed2d880a06a03c2c7711b601a2cb3d9193e1577b4f1d0e614c0be1f69205fa6524fee80bf1" +
+ "e1f1906b50e75fea2d19b8a83071a460145e1730581e5e9538888d2e797ee3cbd3b31399ecb4d6244ee44362493802b142ea397c2e7a3c1bc86f0ea0546a38ce" +
+ "574e1df0c27ad8a28dca70f659ae6a1369d8b3aee7d0dd24ea370cc2bc1b1a4dc9f63911b63e60fe4ed8552bbca10e01c82d11b0ddf748d234b4aa3b31683c09" +
+ "86358fad680dd2178902beadc4646b3eceff572631ff9e6b64d8a622ad9f0308cc46b7d422ce792fe5573e9b9480e1ae9fedf31edaaac3b08c5a2c6c27d6b033" +
+ "6b92a3da7b838bb0a2916ebb6ee72bf33a7fa70630491f49c67031ce4b9dec2315088d0a5cbf7473fd121e0ef5f4e92d43114014c9f8c6e671086a446eb1f66f" +
+ "70f0cb0c668998ed96ee0ad2687946681fe40dc46cbd170e0cabb6f6216be61221f171fb2f4273f58c10d5c4eccafd1df62fdc8ac2c5c8f6d5eb637b71fa89e3" +
+ "f8347343f89667a4450c5c6e3791034d2dc3a593185b55bb95d8f8f2984ef981e4b692c1383ace4cb2c4adb80d5d582857b5d0e3ccb12845a59587b47232ad20" +
+ "926efa78e05a57b136e284401c516296b6b194d541ec165d11ef94f166cb52f45145d745ff3deaf643b5c45573ed0e69a22f0e0c9c5367f6d1398105516729b6" +
+ "3f2edf1b01ad9633edf80efbba6555d4253fd99b45a36f16ba98ea0bb0d80533aed806544a084a398a692f698c78b9bcfc9b4d3328dd869dbf7085893b8dafe1" +
+ "59e0517c2f6a3ddfd4a8c670072b30c96b90f81fcc08523e4fd75919752bfa52a1db7c374debbd83ca8e311b98b0d8275bedad215847fa8984cb50e108f69550" +
+ "f6517d719dbb5dade1d3c283357e14b6d9e85d61e33813546517e1262a7cbac814d79cf6b7e21b0fbbee9b6314f02b2d4e6995d2231670884c78cfd86a2acbcf" +
+ "0a178ba64de2f13f022e22b9b968ceefaff374aff02b703811f3dc541a69a21d6e1c5d1aca48889b125ff1274e65413f61e42bb0194b60b65a3454c696033cc8" +
+ "e3cc3613a52850296a0154bde0e2a81b7a6489bfce505dbe1bc44e0e1052f678297bb19cbdf7970bfa5268af8a54eee004063f9894118ddce7fae8bbba53a428" +
+ "678cec8a2bf6cca2b1a5f4a2e95562437e4eae41167f39d2a150f7c46c1eb6da35587f7234d870b16ed91c7db548ddc99967381b4bb4f3a2b0a5ebcbc7ab1b06" +
+ "7d5418768eaf7d526ca116e239ceb3ab393c45f3b32b713c11fa8e5ae8d7611e6008fa08d1305d5655315a72c85a04dc853da3e8ea9d46674194e15226f126c1" +
+ "a233c26dd7d3cc04ae572320d0c351911b6fcdbc0b8450523e96022f4b964d4e479b6cb1c40a6d27699b57ed2952ef7fb3172c69ba7beb8c8633a01070ac4344" +
+ "d4c401acf8ca7fcafeaa59e1d4c2ff251bb67dbe10a862103df1b416fd2097fe412b3da9d4095b48ea094fc3bbf2ca41e4452af3a179580e3bc11a7d97ba050c" +
+ "ae1d6b8075da267b3ae2231a1fcfce0c976402f34963c007d4f85d9ca95646990d1bb09691ceea3b34211dc58409e052d0acf8c2296a7e8fb52d7c673506d89b" +
+ "847c369daec7909da8657e8976f59f2ef4c8a049b46fdf30d6d223ea4175e4d60e469bcea0eb3bdcaa4d6024f2b43cf6de9bb40efa9172381291079dd82ac5b2" +
+ "39f2051a7f1aabcb8d50333e8c160de19ce1c76ced8056a0724ac630dd45ec4e315437391158a633c179a3d1f364b475454fd29c1e539077b9d5f7227786a5d9" +
+ "d8ec78e5615c25e517e9fcaf07611b85dff2c131a1b11a901a431a601854e5cb627cf7b8b0c5e66ad6cf60b7ffd6c6441f9ecd58f414013279e9de533d8d797b" +
+ "936cfdbfcc78342b7ab586457541df5f3b7d1873612df200896e2929f44c6fe10d24f7e6dbe52b6c42c0a40c947c1cbda2a41437079eebcdc29716d80957c159" +
+ "627e7366cc16df92cdedfa9f52edc848335f1c7152652fe24661a469fd503393229063c7ab20d8d895139a2f580dceac9f6dd4c4ac652b1d60c2b8a1b0b2923a" +
+ "86c31742807549e6d523b3c88d31e8534b9e05a6c63f6c8fb8a1eb4dad733d92e7071e410f0087ca3074f4a2df511ae89cefe9ed09a8df603d61f23754e43cc2" +
+ "e42bcdcbe58b0587aba9a62f32c7507116fdc8a9db3d65d6c0097c8f473eb7f3bcd11ab81d5b636b0812b7982201a63d0b8d40f2c38f65ffd953668eaa5751b3" +
+ "dab7f038aa7adbcd1f1102267c9d55d43649f9b4f65f1851546c5a9ef2c7ef56e84b16f12641e9d5ddaa78ec778b5f113b2e06bad5821e1a5203b006a774e36f" +
+ "56c9336d92c8cd8bddcf014b6d58c394e2a93554af6361fc1bbd13c359fed98bb5adfa4dd1266e2744e126e1bc029ab28fd68b648a2ab26ac23252171b298641" +
+ "2621f2a8697a00ab3fdc1b3b04921390ee16d213601ab249a51830661051d34eb777f690fc2d8dfb8e0898567e388830bac8b0bc896f43003feadf34256a927e" +
+ "b4d9293e32ca135351a19d1246cda30551c87de1e148ff5ea576b67e19e1a0389b88a5548b3b1a8cbee19eecc7de5c2333264c711d50d688a1c57eebc28dd6f3" +
+ "3dc0e4cb857973c3d0f28683a6f3c09db9f54b8fabeca9e4f9b86d794ca55d6611858f0d48736adc10dd6763ed7199bad81369ab1f3de30f521d43382bcccb7b" +
+ "be0178f716d5c3cb87488cebd7d9e2bbe671dfcf2512f1b815075777ea92a867f35e09ff0110e61db24423d0598eb6fd078dde0dc2b5d7f5e0bb6fee207da109" +
+ "2e656b5c982866d5fe01e6db79809646559a6f2b9088e977789aac74435dc625b54296b25788bfbbda9bbb25247d428f5141b03172fa11f12339b91ca96c92e7" +
+ "ea5a128c8046087dc7a7eba63e3bdb200565d8a103e7b3c292b088eb06aa27b43688c8516bbffcf123499574f00908ff43d66b79106cebaf16725f1dee600a29" +
+ "7b3a3da878940867f9549e65c73ea798ca923b012fb8a7ef3e2ded1d2c4e85635219f627dc4feb90f884ae6436e7b44f9159f9889d8e194828e079cd2ee60a7a" +
+ "6fbb6b8fc1f7355d7322709fabddd76e4283ddda3018b7882ad79b32bac133da415453eecd5bb1f0deb4f3b987a71a2f2e60194cde63a42b91b39bfe51b4aa8c" +
+ "20952b601df11d170c65a7fe935915890849a367936e97bd242edf305eaf2f4f4fb9e5ee1464c51a899ba5cc69cf56731502c1b75d0d565b1dce15440b0de0f5" +
+ "58bd4f810bf058af99c158a2be0dd02a01bb5317f55675f4d42c6766fc61271954b6988c33a84518bcedbac8de305946d060d19c4691c026953ebd680a4c9012" +
+ "0e8bd54675d6c33cc86e65f5cd3c34cb1e6fd47784a64f39e95a1945b5c21df2b3288f963863b33366908b05c2bd499dd25c1b8e97329d7e435899afeaed174d" +
+ "2a2471b6e8d6ad7a0b1b6a8b19fbd976362283e5abffcbd2cd310245092749b23e0d114e727622953487f373c833281a74a1b97742ca99e49cac14d9102e3680" +
+ "404509889ace009c47d075ba9891e7f67b89aca3e213150f3c715cbab1869135601612d7dffda3cc104b6508f56eb8b7e7f379b21e1ce290ce5fb96f53e3a7eb" +
+ "c7f7bddcbdfc266f23b775602d8d12527d30446cb4144df7fe3c2756e232a8ffca625d7b6ea2c8c0a92e6425ba67ab75160623c39f01fd96856b582e257a6930" +
+ "224c6da90a6eac4249214c3b85aef52835d904a8a5e224d59eae0c80a33b3141ffb31a7d8e62833fa4c850fa6be135558fff5434777df45feed00316c475759f" +
+ "ac6e014e9d3cf23e7322281ed75623ed69a81d6f05ee7de193f6b44ede4a94ced27aef5ab9056144593a836da80f5297875e7bd84d8ca6df95de8650b00b3528" +
+ "123132f26aabf755d00450648e44f3beafa4dc746775958c6dd88bee825c29112a3af582bb2ebe628d70364fe9ad01b8a9961d5b71018690440151486114af1a" +
+ "d85679bcd3eca510c6d6887e70e0d04b04fc2db5ab1eb21fff925b66f08f4fcbf31be3d743154056ba137727b63576e72f1756029c86bbcf9452fc6cfd89f3b5" +
+ "9f243d84c410253ba7c9284191a0ed87b2513901a93606f1aeb736c90dfe40c0a343d45e9a992ea894b22ee5d49e0f7d55d9bddaa6c74bde8ca5839db67b77a9" +
+ "ef740f9a47241f05e5dc1b9c95c459cc9db560b1db090daa3f4c6de46f695a158baaf357a1fc63ebc0d9db8144137ec4bd69c5af89cdf9cfa66e06bff6339d62" +
+ "2c372fbe5a855d14fa7ff3726512f966e4da0556b29ca6d7517803f897d0e1911f9b46a291002a8320091aa7016cb7ac993e35c8b0f5aed3c94ff0b5dadd8b77" +
+ "056d06d1bed59aaf7bca8516c3bba6b33e12df2e5ca4aa40664b3bf48c4dc2c57cfd74c765fe9f794f55b5df6ac6dd2b3592bbc71354c8dd9ae41b0a05e1c7c0" +
+ "d3bd1a0ac6b671c48c01d4a0fec7a01ad11040f213461759f9e029c835ca1d22f9a661b69d72bc46e34b1be7ed85a21830fb87baa74d7ab145ac1647f5f2df68" +
+ "671100d4d9e41082d3c81f3b5a6e603bb33fd56c1dbcbdce5e213c651da45d9d1dd7532d9a955202338387af6315137dc458fed62920a0e721aa7ff1660981c0" +
+ "e4c3de0a4863f6f660a7c1b9745ea26036a25cfa37e1337ded405ebb0401d7041a7938800a97a032fcababcc06391a77a580b1a61de014db9d7e280ffa6b2381" +
+ "ab6969ae5cfcca00a47ac2fa05be02aae7beb806d2afcc11dc0642d2a12ecde2d2926efd9fe790e1bee19f9114d22ca42f438ef656a1311e4931ab7fac93ed17" +
+ "3f68ea0abed18cc2c8905bb2d599780690eabe4996e38872a3190fee361df9fecd5906f664106de4835f8fbb657366327871a2d38cbb671df04e0d14fe97e260" +
+ "c42eb07bd1d70514913c7a64a51e405cc92e06845e5a78981fef9822fc79e9937ce0513138f6bbf247f5c457da708cf84e30d083b4ba48d2d43d70e7c31e9482" +
+ "4472617910a3de4369217b4daf892c2c3250d1de0457e88b3bcb5c4568f9b26aa675c551a9a730fe9ea8145ce7f8e23ec825be9be3b9edd588c391295fe31ac5" +
+ "bfc97d2e438ca9bf6551728b3be6d6c6ac064baca763e0eaa24f754f4bbc84a4377de45fb6a8f37150865df18749df1af4ea911b62f616dd4dd4b25b27c7b6fd" +
+ "99d8c00ce8a53fde3ced091891e8daf43cade10086be046ee5607003de24101db49b1a4fb0ac270d05bab12583e263e903e94dab8bba7c785e40499ab01ff92b" +
+ "b82c2e5342dce84881adedf77cab593f541e4c963f4f9ffc80a16bd4eb7f20ef4bf3f57abc7cbd86332d8be80f0794fc82767d13c71d8ee20468ee35c13308b0" +
+ "dc29ebe8c6a81e02ee9a21807ff57e4d932edcaf59ae9e76f7cdad46b32f94a74982f0887d7083c90ff54058e873b10cec67fba1b717deba5356e170dec1a40d" +
+ "36c57674ad8d43c5c98022b553fe060251b994271585f702de3e71fb1c8e36293dd44a4b99a1baf33f6205e9fbc9acdfe8cfdf007224f93a7104e7803454fdc0" +
+ "9fc5a20be59f600ee734847257a5ad62c599a7fa836d1174a6291e61c1be4b310bd4d7b7cb9be976dbdfdd2b99340a9863c8c0e5009165d7097317e6c3a29cfc" +
+ "dc84b19bc68f38694998f626567b80ce6699124b12bae4bb9e661c2484f5109517318341287e142a849d61d0d7b11d4996547e7325f28842dcaed26367f7a888" +
+ "e58c24c857da2f48a9fb91c78cf351a23e82ae443223580a9fe15a6a778f6c13be66888219e3e15971170712b6c356520cc15e4e75167993b66e6f125799cd40" +
+ "86c72588a85f68361f1c2f09e87f9a4de95ef9a3b92c3313664a706cb72916b96a9cb50771f6917ddcf696ce8d7f2525745fb6edc30bf3fdaad66ca5b013300a" +
+ "7ec7cd274327b1b9cd931c068d8fa9fd6336d59f6ac79b84a24b34c47e408b3bcb8ead49428c123922e54bfcaec7e39c4d6ef79e5645a35f715d151e679ef5c6" +
+ "6f86cd013fdaab978ee4e52eea5e2753e693271344a1f215e1c690de06f29c856c469ccb484d445bacb16694f4def1537cdb32260705e8a50fd65e98a24967a2" +
+ "456af6cf90643638999389a35de6e192068fd2e2ec29aced58611560c792ea5c7fa37583ebd5452a8d94cbf1898937dd8aa6656047e6e03f84dfd0bded514a6c" +
+ "b47ca71c2cf1e76f606c04374663712fd96925eecf0ac1c38392390c8cb095f39e1814252ded78b55ebeb9915dc5e2ec14fe99e3a075bd389ac601681f154286" +
+ "885289e568a8646d94abc806b4637492e3a407cde582d42764eef0d56ab14b00e9aa1f64d8fdd533d4314145c8255c44d0c746af6da844d285eb044d57e8cafb" +
+ "ab6c3b962e0177f11a839f4a5c0d2c2e8d5f76375ac115e0a89f460ea1be238f974a68e0693d15790117106c1a65ab5f7aa08e738aa888d5b56be39d2078837d" +
+ "fb2357d86f5be85a9af41aed611b231495564493e46acc90c6a3e67d5b055320290aef508aa6a1896f19cb5633edc0fec023216726e50960a44d81e0614ce748" +
+ "6ccfdaf620eaac0517e8cdeb1095d55f3a60d61dd27d967eb26128b84c9ea8418779e074cee8961c5dac811ce5ee8134d3910a47de7a1344293f5c64ae8f1b38" +
+ "9d6c457dc74e7005c339394f5f24630f5e40cf270640d1e4c27cb6a74fb440f3203026acfcd31f39cd4844ede7e785290878fac8770f930e96c3edf61748dc6f" +
+ "b7476832cf77ddfbe8eb8e12fd002038630301439ef8a7659bb10593a92cb84018e1ec78856f403e1eb9d6343aa0bbd77a63d776f1d12838f27f3cf6296ab0b3" +
+ "b4436f0ec545a5a1e92a5351fb273b3ed56a40e5a5d25e0057f4077bfeba2e2d8cb17a553b157609b20bfb5cd2699af9936f50d823bb59a950a24b8fd15ed705" +
+ "b1628663f0eb5b5c2b18f000ab039bc425ebafb2010e1a2264c38fa2bbd0f77e38eac8acd670565490fd60cac7fd28d988c8dc0658505dd98425f22c94647d44" +
+ "5d0236b97ea58b3c71feee90be0055ce1fabda5ebfced9d9bf5efaeac8408c4b6bcdb39851cfe038d88ada5211de2f0f69e9e3c62453106c366cf0c40971c0e8" +
+ "e8f2a790aa66999a0cb4cdb57a8c2d812e9e4a66df2f001a57e291864339257ec26c9bc2dc6cb2eb5c3301c167e1ed0387f9ce9f76c6759ebe5c68e8be378c42" +
+ "e0350b344acbc8b40c95cee9e82bb43cef5e91a32a6be8a727d5fbe089321ede3abee4da6b9f41775d7e9abc36f6a5d26ab88ba32978b5ea0ad63f0ce8a772be" +
+ "5aa51143bcd00d78bdcbd69beb652139ad658dc7ad242b2057eacee092aab4940d6ff993a8c7d8fbe93c08c93c45d5f3a01058f3c75c94be9da1a19a97754734" +
+ "b713e1ad6b7cd472619ec1abd4cf42f50b0648661c2b8dbe8976037c094c7176090ba94618e1918db44f5d2c367a0c7f911132d9a8b2398b9417542c7ad99b53" +
+ "a7ca48253bab8382a1a24d35b9b9818bda513f4b52fc576a71fa63e72aa8042ee1fc806c6fd3fc16e07ed2caf9f82bd3bb6b393b2708c051c24c2e05aaf72531" +
+ "d865888db06f719314d6094b2c4f0718c151c88958d2d6c8a6f49464f81cc46709dde026f4e05325ea4ca2dddf9a79bf98bff3aa5eb412434f0b7457b4ed47ab" +
+ "85a212e0c7720c78c961d56141bff0f964622d4d3984c1017de6f5846c72fae0c771a819ba6c111bd739fcf16f4b85f8101e7c3f0daefe753ec130a6f34c7697" +
+ "4dc531f83715ecae28bf2e55111778ae42aef17fa95340584cfae3d4599af9dbd10211baf3aafa8ac8a07edf8243daffd6a6486b1e3be4b60711194261e2b646" +
+ "e2667554cc0bb2fc07054b653231cede43154c9002890ca20b0ac81c4788847c6ecf7c174e528f36f8cfc53f3366fa9ce07b1843939cf6d318ed11f7ba6eb791" +
+ "ce25e75cbe37d2ee3d45bea487d969de041011959c0fed4e6c86802a7485fad70059ece14a29b03d4df41677acf71419ee63f1101060ca5e4ca0ab2edd71fe77" +
+ "46c6bd9f36bdbbf0a9956eaaf974f7bef982cd34881abd686fe77b536c85d042d77dadd00c5cb0130737e5318a025e6ae6af96ca28cbd41094d86a85765ff891" +
+ "af825793910c406470cc61be5d9282911d2faf82abfb309598fce0101ca64abe3920701a958c20ac35927733466a23de809afa2bdf331f68c3ab0cfa08b0c549" +
+ "a20e9b50dbe85d22d215d0e5fef854ba271a4c0f95e6abca19018bdd4a042721887418136b4a60cf291bf06ec47a5a4d2f9b29f988733c6bf6f65da5a95f8939" +
+ "fe0f2bab0bdce98569a81f861014e532f6a995542db02b6bdf3169191d300fb0429c1cae1d2dd4d29e0b61751576e04b558d38d3afcce8326c2871e969c1492a" +
+ "8391c0becec29edcf7f038a8093471763db9f13b97114acf7a979f5ba3bf6f990317890ee0705850fb97bfacf306a0ad621b2c3b633af01fc5aa059c0e22ed17" +
+ "23584dde6cf140bd1d0087ca9090ca9f07d3b93c60938af8df976555455bafbd8cc986ba32fc3f15b5962dbb2d37b6ae55a7de0c0c6f2366be0278e26bf9a725" +
+ "f61f2bcb545d66f79261783f7f03395f2a5d27e56af62a01ffcf778c3c686e244bd9b7e5029d1d40dd2250705c6825bf78e83730212640cb5ba54191b61fce33" +
+ "ce6df7721b15662162b631d99e6431efd24ec35639c2b97f10374fa5b9e2ca4231f523195206fb9695ec7721c98d74f29533cf714866adae8edbe8ed2d0969c4" +
+ "9ed36200c4b8b75131e6d1efa913106bb0759aa8255bd6a9dc2b00407358f4523486575b111676730094f46d0a7b95427df74f053c6611b4c465efa5310f760c" +
+ "5ff081e841e5f90c2de35855d45a7f35ce73d7c7f9f61fbfa953398e042c3946aaa4b7a2094d95410b8a5ef76c8b57d49f77311192b3f4578f37bda1a426de7c" +
+ "7cc54b5400bd16bb30cd8d1b7b42ff31c5e3759e3c9a7668174c02bc5a08f1bcc7e3ba145fa5f5c41e48877b41b0ef8fffd0f75c6547047c2e7b7c7e1aef2cda" +
+ "c4a778adbf71257618b4eb3c6dbd8211f829c1d6373415b969cc48f33d586d2678e7c1b441364a9fe2bb426a33b2a132741fac547766d196df3505fdb17977de" +
+ "7853cdcd8d9932eb9452620aa4921b4416f65055d77573b132a40795bf142815b655e670bf2c4464adb5d826a1744c8049d7a6cfbc8a4634e66eb32f0cb6fa17" +
+ "ffa8925131c3a253101733406a2a3a0dc61ec3ca1448623b6295791d4e2d65d303f78038e15d0ef75d823759bcb4b277b51410c37d5efbbb2e3a9e0cd78a8475" +
+ "05d44bb1fed7f72b1bf1a96ad0148e816d34c66b1b5faf172b8141ba007bf2e5dbbfee4b09ef66656ea3cde54f086040d14116aa7f3584ab6773f6091a2fbcee" +
+ "f59d6ea115f88ef9fcb358c87c35caf7c1a6022e141a3c688beef17da5a619e733d854218b30d5edc39b933b19dedd6750acabc52234934b08f930b608a18008" +
+ "838cb0fb73d4c78af0c468d9fa4fc5852135ae91ae00a99a6c603371d09b031ee37f87586cdc83897d8fd8ee2e07b9d0478a812d3f7eddca08860386e3ad9521" +
+ "98d5fc04fd0aba4b3da6ab8bdd9eca8e0399a2012d6158ed75ced5f432a223449b4e3db3fd4b19c494a69e9f2670833f8a88f7b2873319e9495f03fc69b6d098" +
+ "6006e3ffd8cdb9c1b98f72345848deea1b98ff6ef766f4398e642e5f2b217c1a87a608c1dc701bbb79d75a4433ca1d600061836888a220ee262124d145d371f6" +
+ "576f04cf71701133787a97aaa615ca98138c2be1046604d885e2f274b0de8743af50ad5dfc4c3a09164448e102be577eecf77ffaec1724f91f00f908ff6af41e" +
+ "57056dfa8f5dbcade85a66c10e524bae55922b4084407fb36ca8d6b7322f76a8139be9455a34440c719d0db8a36385efa48841170c8d35046407b586f5bbd169" +
+ "7cbaf6819b663fb17d0f0ae89691a099a8ccf47fa61fb6dbb22b3298e5cf2465e4b93c49da70fa76924fdf29389194cc5c61cb4b3084d0851bc3018270d1a24c" +
+ "b4b04e8af927d9fec9ea1c9ce18d4dbe61f7aac0ffd4e7c2e9729b49ed9874b883ec644864c6d9ad0422c4d89f87df1dfb2c96314b6a3e19afd21783f365445f" +
+ "bef10562a26b48df42dc344ddd63fcc03220dbde98f1109cade221027c40f0f996f4beb29513c3979ba374c4c6a2c2dc6276ca6be66eecf1dcb245d6efe78aea" +
+ "e49ece37f87894bef3c0cb1b993d974685564e2476c12c8d8f63a1aaf142fe34a6840be340b64f96d441f4537dff434ddce630101ed9f78e807881f6b7590697" +
+ "bc97e60accd7a135d8915781f4fc22e437145154dad0a39e5e306c117b11deb10462ba74d58e81de7674ef0bcb20b38511991447f63ad906b11abd4ba88df3c9" +
+ "e6931f87fece49f48543fed0439c88ad78f82aabb32dea03d030bdd76efef6b737daac2de2db1cce10e2ec74565b0a609606cbb6aa259ba88715229b8176c874" +
+ "db3fc4f6db9f167e7b2d55b33a261f9eecb69a0d36ecf9ec4f8f9cee5b74bcdc5d77b02ada89f56259edeec0d9ea866ccf454b9abd29d5d21041179912a5c302" +
+ "1862d850c3ff483e09479957df5bde03a29504b4a43e1fd40af2b8a2653a37cae89c8d917aecdec3959fecd32b7fd313a61e134abc15ad008aa993aba9629a5f" +
+ "0af0ec713f742bee096e171729e70530b60f910ef83746a61580f0cc6d67723792c0e0e94775d5b1edf37864a50678d197bb34a97e84d7f764c0bfe05f4b2d0c" +
+ "dc431d1f4410500dbe2758eb05bb6b19b154707c255a97cedc6aec1841f1817f6bcba0b9a9c1d3aebf747bec4423c71309fb8b4ada90dd9f7adbcffebbc905de" +
+ "74ce531403df33457c4d0b970fea5df4f85732e3c33c5b8242b041141a8c51a62f0bb14dbe07b14d3f5ce646d76e87b258e9b62128f9c0c0a8014f2c5b3d3dbd" +
+ "a3a77be6222419cd3fbbd3b842c46c099f142bcd36442961e8205ec5d7fd159befdbff12693953307026f1e06fd57b6447dd3cb52df466f0352cc46f27d1fc56" +
+ "56e06f68ca2847d291421bc9e0af6bbcfb7b3ce07600827809506ba3f96f40ca22766f8cba32d4461488f6596082a52c11e9ac908922075a7b443c41e55b719d" +
+ "9cac9fb587cf02432e1accf3cb7a16de0d5bc3a1c0aeff5a1795680b4551316e3d7b5a9bc63a09c6f75b0f00eb69fb6ef5130c1ec40c7a7d5d6ccce364b74f63" +
+ "a836a4a711027e592d6a70e10e573cc6d730a0def4a7a2d4dfcf3b0aba37fa2060ae6935710191c023a0b8e123a67ee811ed43b5127a1c4cf82d52ad6c40fd66" +
+ "1160f77dc320bbed349c8b6d08b2a7a6234a8dc88e4744b51d2d7c56e02f1c3bea9e6c2c3d5522ca02ec7e0b8160555eaf28797ed30b5c931a73562791f5f0a1" +
+ "b7ce83824bae17de449cff41312bd441f34df62904f4a0265d6fb9b8a352895ac6f0025d6b2074570970b4e679c559d03ef40794708eae36567008d9c33f7fc3" +
+ "5f8df7e901c27f408cc7fcc52631f1178695ea660d07df541e5a32721d145a32e8d32f06301b5073149e8798371fdb1a2daa5e1d02c24da07682a2cbacf5af55" +
+ "64810e479e5966dc6bfe14b4472c42cb154e19f7b8659d42de5ac926224cc6b0d8f3fa797058fd6e21ea85146838c4612760d84e24341825b6931a6417327394" +
+ "0154125254d4e11ac80e475a178605e851e1be39695cdc0781da241f232cedb32a04b1cc7352882fb635162ec3f5fc5004cfa7d03780753c14173ae7b12a71cd" +
+ "b40d4835023a00a4803bdfb6916956ade9f687af567e6f29981120d306084ad061ca1585f0e9497fdb27f9d54cbac8fecff176145114ebfc17e3f346b246ab91" +
+ "094dac0e684a708b45dcea16378fc29683dd033310068339b13d995dce77a50f9ee9cb4cf564566b05ce352a21159ad21e720e05ce6069a5ef4e9fe8ffd28516" +
+ "8356a0b80c4d1da547776f486a117f6f7ff6557edae7d68834cd71973517cfe4af045ad0fecdead68edc8017000958b69410247a51bd9bd3152dd57389f25223" +
+ "d5e88c0d343ab3aeb89b763eeb7ee48b3966fee147a4614e436c9a1a398487c80a001700666251b3dd6a2e5dc96814d21e6498e75811ba4c51160cbecb7d5510" +
+ "62697171a25a6abbc41fd806c3dfc83daaa10d7ce47f5a29ef0d85dda5a61429c637520e6a4048624cbb25f53977254cf803848ad81f25eda07690fe7a0466e4" +
+ "d18a2fd145dda1c94a994bc4ba5ce1aa1b50c38151febee757afceeaa91c7b35e57b90ff7b62efa929dcb962d32dde5a0bc3159524728621a3d7487eb7c3edd8" +
+ "6df3f8a18e590039bfc84a22b23b11468c90dcdc8506187233d8a6b3dc9785ddf6f341709fefccde91a7a0925a8446b1896aabd6a6826ef88b756a9711cb3b78" +
+ "1ab1f4df4d0515070e41fd5b0c5483270307e60eaaaf0b3a48f6bb96eef6141075445285675bb12f2ce38b42c91c1e063400d7bba9b322a0783e7d2f5d3f8874" +
+ "52ceb65bdedaa032336d969d2e0e3007d2ac07bcf054b9d0330f2e26c486c054bfa709fdabe283ed9a4ae67cee24f40f2a5c4e70160e6ceead208ca400959922" +
+ "70bc35be104c9ad94cdbe288b1c599db1758331340c9e927bc9d688e4186d5badd463bd3ba116bdc22a39c604778cd95503ce4ce642041e89bcdeb86fb19ab25" +
+ "e1f94ed2a2f857b228ed4a582ad411d7273a0d5189bf7a2b87a135753e03383033b989ea532041ab9ed397ecb3ce61e01923b3729068f6828ffd12e2ab1d28db" +
+ "6ed7423d458decb00476657a0580b4af3aa5615bf07df55beaa2bec71447aeb39791477dd09349bf573e29e9c4fd454b4bbf1e19591bf38dc47c83bf39babdc8" +
+ "737d69ce4b586cd10ed406426b88e686c11072f04c680e8b6275166e2dbe91f701642b1b4ed92d23d6fe14f39ff7f5a09401f3a398eb4bb742f6cf10aa35e767" +
+ "7e6e92aec791e94f8122e8c9cc9d0bc979e3eac6562ab614ff20330b00d9cdbd08e9deaeaf5cd67b49164f550c5f5c2d7523fe5ad71a2bd03fe2a97329980cb3" +
+ "049ecf6d677d815e56f7cc27407cf73528549ea98265ef90277c14763d5acb3572f5a482432cd8288972af580fdd3418889bfa6a373c4813c4c45e933ea4ec72" +
+ "cdd068089c2a30897dd57031445834de9f23faf506ad930d843b1cabad2c0aa8965d1b5e57032c969f9f55fe2a3049f4e63d5b5c6f5f760da5ba44e3bb9307e9" +
+ "ea39973d05a74a49e15bb71eaecf62373153ca316fdd40b1c64ce2896c95a7b5df970c2bec85edbd5ed84fa7949c08d5ec4d987052fffe357d444e2408a22295" +
+ "6ac1fb272f5023740b381e00dae9f09751a33bc6ad673c4221ce3f932164deb99f1da3eb3581caf475e385bcc56d47a7a1615a9543403750f0121d5482c4ea5c" +
+ "94fa3428178f6a4deea08d754ba2abb3d1aa48c3e06f06ffcdf4571579d398cd991e60599e9633fae6a0c07e10e538aebb7d33aa127c830f14b083728f6ad7eb" +
+ "c9a60a0ba222f47780eaa82a21393a49defee97aa8c3aa2fa53a2af86059a7587074128c2fee7060f398ae70b156d53aba0bf1af4bff10a966ce7e6382cec4b6" +
+ "054a8f6b9ef0e8729ee182f86c862f9b7d5ea36ef7e15bed10ed41b25738c380e58cf42795e3202749074fe5cb6e8fcb49a116f54d84734a834609a3443b8b42" +
+ "97c05ced428f5756ba59bfc1535bc7e16d370d81b72b1f3f78ba75c820b22e485dc042e4f38e93cc2918a491750c92998f03aee571cbe9abce4d00fdf9801f9e" +
+ "8e0fa276822e1e5349945f1d337e656b431c48c1a2e9d4142ea14e9427881bf201ad8cd8effaa6fe6a7e07c8112299db1b327a0cc34c9fbd35596f4ee25caeee" +
+ "221afad93ce7df64aa6ac766cf4fe1660446dcbafdfb86b4e0fea78c29c3e84ce42da4a503178bd250a6dbc4fc65e397062229001da05d5be118dea7ca5ce67f" +
+ "b4ee07a8b01e408aebef2c913434921df686a242b7d015a559f9efdc54ad62d7f31ceb72463041843d7fb60f948fed03ff143fe24ab81bd4ef6bdabb856ef1b7" +
+ "174cc987436322271bf48423114e05758a08cdbf300931fc7e950830b7ee920f7033541f1db9b0d2b91cad80d06c049b05fd0a76d6dc211bef2a08d53b1d16d4" +
+ "2232fb263941daac4e004542517807341bde98e9990a97739ef86d66c7a51324f1f6911cce4c3db37ebacb6e58eb02d8f7d6ea31338b56a99649c4e730a01bca" +
+ "deb6fc87cabe00addf1bf76b83927de26bc2bb3f0cd5945d863b0c31cfe8fd4b60462000a911755cbecdb6a98139041d52df498aa99aa3876836ce5b5bb426e7" +
+ "c22b5977902e0b3425fdbdb8f44e8758b207b469c3e5363f552c89fbf778e95e8b7ff6566ab591fb68a8bde38d8169c708a321b669c08d9ecf1a06c5321bb1cc" +
+ "9c8a585b6381645edfbd1ea4a2ad7e7eb8be6c431958add393c0a257aeb283644c6fe97580aef613f1b9d83e5b009f7a4d059025c11e0a0a67801be511dc097e" +
+ "4e7c065684effcafab83e0e716e2d0862e83b295f82089ed3ba4f6897c8d8eb2b358231f95eef840e1fe22e9065de2b3dfb3633e2968135756cd9c109e8acbb3" +
+ "172bbb6680c2e4fd69e179916a7849315c9f4dc86991d75cc6358617454694b3fcf2471ec7fca6ea2d99f704b9aa37a25a3b3183c5e32e3711346ba2336d6001" +
+ "489afb9cbd8822dbe4f0323ebf7cfa9367b6548213d473c0f07b1bb6d16e1c66fd2bfa1ca623e03149fc81eb6f71c12e7b4b76ca588548bb4872469687f334f9" +
+ "7e114a16a0a58ec70ed74ef69dd96666a85aa52d1ca812235796d90b9af4296247f4c1ab632effeaaef6acbb637f1aa9379195b3b668ca541bc6eb595bbc430b" +
+ "28adc5d1a26fd4cc2239516ac9ca9c0c028110926a2f88881a5886554c31539f4c8260e16364f4ac27710d2becdadf573f4a2b7b55d76ef059432c91c6f5beb1" +
+ "56686a620bdb4aea50df564cc0c5ccd8a93c454e06b8969a0f59d63ae5a29105149c08a5de65e87b0dc445dc5d86db8788ba77b83e22125c69621140d7f17906" +
+ "4ec0157a877cc51ce3c0d565bdf6c884f69b0ca631d6863770f6db30eff847e33c8b30d5714668a38a09f454ee44ff2b7f97207f10efcba74325378f6f272ef9" +
+ "9f09c501c12bd0a4155f559a604204b36751ce8d4c0af35a8b445a9290c305d5d3ea21f944e31df9a711ee90bd16a37850e2a87c3bd3fdecdc6e2f261a5d6d0d" +
+ "580990fcab9228cbb39f8c79608d821ce27c10b0ee0b5a96474759f67970cbe03cec9fe594765bc935abccf867b9717a4087465c8604eae89497c8ffae7e46f7" +
+ "ade2848916b54aa796809cb98a4364b7b44c17944dbc408909a92d4cbb24a514b72fe8de7d1cbba0a101973fa9b29d97dcf1f4ed8a05d5e0cb38849dc6e2d041" +
+ "16892ce649e0a553a727bfbb1d5794a059d6a411e43876e561d83bd22c054680cc8fa928f5f4be2d849f02ddf9c6d11ba35810b81553e1938ab013663f6ef35b" +
+ "08f06260932d7acf99f57967eec57a61f03d880c3225e53102a672f5842da21aaaee02444d372ab8ed7096235a4926e3288912d9c736c2c4dc49918abdfdd6d6" +
+ "d0df5be0133abd61b02a6f008909c5350f9077598ab2e612603431bddd3826e314feb280585b37eb89e597f7f0bdb738a9a93d9af224659d50c8f7479b240487" +
+ "76c2a960eb18923fa2d3b31b3d20ef538759cf22f5b415d19bdae689f2bab651d79ff99f77a721bc1b2233da12c12be0c9881ad82fc97a6343b3af8207dda8b6" +
+ "5c600644d741b8a16750964e341e060260c4de26f991f3a1f6a606d1153565f1c9cfee58eef327edc0e9cfaa206ce930b191f521be2344949bc75d583a413a96" +
+ "ee4edac424cbf9bdad2883c96a1306b96ee059d8044e3b7af4e7138697f142774ed6409a86a3c6c456600d4e405e6117ec759f4b22d7e5a185b0f9c67ad987bc" +
+ "58d2e8c929d4a487e5b77201d7c1416878e8d63258b2f58727cb631494cf1d68b99c28493b99b0409ccc1f9c218a2b95c45ff36563f0045ae5c3098f641ea6a9" +
+ "b48a3e1489831b2d176a1e0cb2afe6bd8cc5e797de01391e47e798c1aa945d33d5e7dd607aa73c9efe93f0646adcd7e211303ac7deea4d02c80370e8e867e2ad" +
+ "9942bfd5a66143560a1f59e5be1f3aeecd7eab689a4a481aec78045ae0604f69d9eea550152f6e2bc692529357b509d60e5a497bd94e63dc698cdfa2a3a55976" +
+ "0b2d072a2fe9c1fd41f31518aae0edaab532591476a9c5a61cd76937575cef71ff5dd66e158e7820b4b6bff4067cc26ee9aa66f41b80f078645b920512b5efd8" +
+ "88b3644601a72e3f665b9c8f0ee246593667379b8fa043718f2d75c21d2a11640c328971c32d5743c11ada6c95cfabf1c6b66e0b09342afc899e1f272ec48a7f" +
+ "ba5a51943763bf969cbac879363e14dad1952517d8f4b463511adccf25e655bced7cd9666d01dd4f2a0a21729ac4f44970c9c478a995d1c3b358a244110f1db9" +
+ "fe6335685701e0c2660ae69d33a93a75e44f5374b979a5af140200db43ff612be2728548192ebfd0a3860a9e135b910fe3fb249926d334167622bf4123bdf0d5" +
+ "38e9ff2a3bb67a44f8407328e3c94b47d92e0401aa1db85459967699804df245a7808f972683afded9cad8fbce15c1be38fd10c62c7abc302eb0537d5cc573ec" +
+ "245513a87c1a8d386f7ef0c4a91ec3c602b14a14ae395da13284df3391b929c7379e181c5d3d4597e3c955ef6e3dc2fc55890df04785bdd4e3fa35ac775f44ef" +
+ "9d7813cc036d6bcc316e869eeddf7b30e4b837e9285eb20397b4d7e0d12080c502c750268bcd6ffc323cb094afbe8304ae840d37be833878697f2cf931faf06d" +
+ "28dc6c7e1b5df69327127b47eddd0237f1bb5942ee5903d8cbfe1b11484199e90fe7c8e7f2f725deb2293630bd8c8a377d539736e2ccc2b90c08b97abd8c5ce4" +
+ "ea91a6219ab06c61c31eb48a35587b3c1719f387bd8c2063c5a79d041ca8a9ffac2e3c728f74efdb74ee0730f84cb3a8aefff7c8a1b570127cfc93eb6d3327f5" +
+ "ba7f886dee8be0548f710d6bfb18cbe5910bf61aed2c95028006f419241d968933aa00bb0760a41d2693465827a00837a84cadaf8a8e804d175adc5915c6cb6e" +
+ "fefb2cf70db063f2f3812da17586436c176aa0a815dfc7983eb88bfb1b6d1db7ab119cd3058c0db4d1910034f70f6eedfee8b742ea45af9780f415be2f851061" +
+ "313a218ad48e992b75afaa07c33ca47ee0155fe72e13d7e5736e512c5e5a45d351f7c7902d8b0fa31b34569a9aea31b018d63d572a9898c389d07caa427f114d" +
+ "251263d56cf5d6663159c2b32683b266fb909ba9d4caadaeda6700c03b25307cdea597a3287fd76082dbf33f073482872fdb494b892112c594d7f265d2799b5e" +
+ "5ec46a30fbf1557fa344a664a7af457a4e8ce2c014a270215d3f95d47a42d8f86a61d6d6b363d04a99a0d8f06c5b15cd803d951aea0ca185a807ca4c677db789" +
+ "fca64f0c5ba95b8c64f930eda658f9f773a9e1c8669589a7d98ade8dbc2c2c4cbbaf6ea2bbc6e762d4098f4db0d3f055958ae9da15ae57ee0b60fb9513dacf5a" +
+ "d65e34613570186acecf9e165bfa470aabcd35f22620497fbcabf220c53cff84eec12cf9965297b364f0e9122895c175d213fc2a9c9cbf27ebe1cf96fdacaf1c" +
+ "1c79ede66cfaa5057d33e09b31b43869812e5a0ce730663c18c4333141ae9565e437d99ade6b2cbe005214e8b3392c55bf4d7b38ef16e7f84b4ba3c85e1dfd1a" +
+ "ca8da1a5c75fd190e7752926533327880aed1461c7e9de2443ba0a2d094f4a14d5fffd3b102ea78acd34d162e82ab78fbb82bfbc8a9708ab532aa861643c39cd" +
+ "2bc89f2be53c583f9930fb2da14f1c5d5f218384b1740a76bd8b7ddd2c9888c8d7e7e78cc7a3304fa41995c7c1c3316894296caeeb9711f0e6bf16abc380bd41" +
+ "10448be3cb03cc3246ee7b9559c858307001033c84ecf89690526544c05c146f206d4a21e710597bb57759d232154a1f9d88eb3f3440374bad1e901da7a154b8" +
+ "39a6d1b1b6b2ab0be872ff036a9f9f769a169fbf91bada732d8f28c453b9be49011b211155fa5c588b43018775f99e3b92b322a4c41282326b79fd26541ccafc" +
+ "c0e2f09797e3217fb0e5785b72e654dbcde8ba14b2d56faa2218748c6789c158bb635d43c9a64690b004ab70f457e9fd959b2d90875966968c7ac44b103283e7" +
+ "50b60deeb1f89444aee25fbdb7fa3a96d70c3dce38246f111e466cdfa3b807c54ed584f5b1a64456e923dcf37f45b36cea3d602ba3a55a4fd883ebb6dc198650" +
+ "b522461614656897b9b7d408d48b12e594af06c91f715b32a4ed65a379f0ab461acb9b8b20d1f1b12e9f7fea422c0c7d545eff4152e06002cbd120fd74b483d3" +
+ "a0ee30cfd851c98e9aa8fb19b60528de4a75b412bed656933ae8ab600aeaef5befdcca4d35fa472ed38ffb91a9017c19c5d500426f262ba379034c45cf5d1627" +
+ "48da223207721b4bc4504b79309f3d622c53dfe3c83ff8866dd7614a2e90a85c077b2e18bf1cb6008f0d785d6a0ffd5f15a83a343036f3fdd25314bfe47b5a12" +
+ "58a7c89475f39a58a671d0a17f6fd100a8928181b94d8d53149316d5addf14bd398b538e2593273f02cf296fd73ff92d02230de939dae94e03d44ce93dd4dfa1" +
+ "b9219fd369c854ec409d7bf94b316e5e9c16e1ba525a783e24bd3fc0ecc949be245c402efae8ea77aaca74c78703506cfd5a5a614793e04c76652b4f344f79fd" +
+ "f2da1e34f650fc1094116ead723813d204ffe375d20707fd94d90f21c009194201c88d22afaee83a8a6be7518dc915331b863664e033d397c64e1516c0fd9324" +
+ "11614a1bdf2feb86e0d0ae21e784a55086c596c7eedd44d3afd7295455450f507f1c1a33c9ba94d50931ec054d8740510ea23990c266f30678a74fdd485b482b" +
+ "cbfb4070e3f10b66c65a4210794a3137adabe887ffb9bcf2a30c625138f840b2666610e76e5a0abc183088a94930c025836653eddbc440621bbf94761c74e108" +
+ "3672c6a914a753fd452e8e7a02c54b21d7bab4b705b4509b9b5b27e2e5144289eece950c3634b410b5e3cf8c5a5f74d98b55d17d45d7014390cf696a7e693777" +
+ "4c028517062a69276910cf5f139078e8ef6e77aa8b35aa55fd4f53e48ae6b4875d1732b286ffe8bf852b73af7b964fdf1aa4c4f16d9f14485a2b1a704c2615ac" +
+ "8ac74eaaacec7e8e4e506e1b418d377e4d5a271dfab47b3d3c11a809beda596fdf37935dfe06c147dfe7d5be696ffb2a0cff907d1eb2a88477c261d5a7aba06c" +
+ "d70dc52d00b9a9d851e849f86e1cba91b4c40d1ae3d4f21a2763369dde34d084adfc09d2a6cb5f09114cd8d6fa26d15f1ec428adc245064e5b8e80f21b0b3ff2" +
+ "6690398d3080f5355fc082bc4bf3a38576c7da00efbc80839dc9a06fab2b998a152553c36fab42e03e3e4b54456ed954e53bd63902d89e2617a263e70146d1eb" +
+ "71557baf43aeb0a681f600a784778c895afce26fe17e3ac33990c54cd96fcb2432de79d4f95ab2fb96effdd37f4e4247ae5b4c1fa461ca3269d45a90af090333" +
+ "fc3ab5152bd5aed4445eab93466701382ba76fc8745abd911bdb45a494e1c62647670380c04377bcdb5e631318dfa79850469a988094acd48a4110bbc7289617" +
+ "ce436294ff242302d154ad75437ae2f551df5b84f884c87497de0bb2ef7bd41a8c758e4b158084c78ef047389d88974faafa00ce7396e849509d39c403fdcca6" +
+ "8f47e1d0fc294e5510a07af24c165e1a4b4ba9498e7b333c4e8624c552801079775fc684b6e98b72ff133164a2052c2aadcef168d9cdeab8a935c98f08e23b95" +
+ "859277381a2ce23ea61fbe9ec1439a489523161ed370b0069aa6a5c7981e4a80c04e304ff2fd85f80b51e3de3484b53084f376cc72a390aaefc49baddf4d2545" +
+ "93dcb5a49326c9c15c3d1c0e0709c9879d68bee07b956d018a995bf1e7f8fa03ef2079d01e0bec601519704cced98854c94f1f0ae837653f14c0221e12f2cbdb" +
+ "1564066062bc1d4dcf7ed8b2c980b90e8101842d5844375cb370f402d858dffd9eb52572f8420d4d246462230ca0dbd567250e4f065730a6aecbd804b1acf949" +
+ "30e2890a39fdd4c1eb693f7e345504dbad5ac207f1a649968c3a7b416bd972b6a6bfde04375337a93b0ac08f6fae62c0fa7df8ae9deeee421f7ac62d8cf5ecf3" +
+ "b5ff39877ee4abaeb9db03d8a8f13f7925e54267a2651c55ecf580d5cbb24bf504fb01291e3e97ad1696ed995608fceda79f2441ca67bfe3c31f4f4bf0fffcd6" +
+ "55408744524412cd4d3cbdbdd216694aa7477e88b25f7efeb34abf491a50695ff686829a8fea9e999877bcb37291b8dbeeddfd44465a2c28a215aa532590c487" +
+ "d4747b6ece4e1aeaef725cb305d11b965a9647bef36a5c2fb45cc334d35ff4e308cd8813b6de3953b35a4ef6a3ae07794f8b54ef6365a573135320612bd1acfe" +
+ "6cac5524c0e98b6f2a33a790b94f5134f0cba075a6fa93c191f4176ca62ea2e365557d6b3363a17b9ee52f3c347c82cd19f8432d16a934ae9c5d4d4505e7d20e" +
+ "1ae31bb64ccb084f7a59744b27d58c2388d449ff4b63604878ae858240348ecfcb51761678265bd60d5dd7d51e25e91668fdf80f6b726b29ef6c3f0f229d8af4" +
+ "b2cdadc3ca7fbadab49b28819b9c9b92b49cbe9a281e5891f4eae7616013777605a0623dd7a650baf9a9dad66ca9aae3c76ef1e27db32bd9514a2776eb0c8d05" +
+ "65eec06fc4c8a69c417efa336842e248e5a51e3b5f3ba3227e3f78f1bd12d81595e03a01f4259c772fd481ab5f3d7a945e1c95fe0dc3c4742eeb7e15c9426ec3" +
+ "ed4c90ee07d56acc78fecfd7c5ce1e04e7db1a970091f15c90f0aae2865d135395d27787aaf68c6a179064d82691e0b6c795f61875f317dc6d2e8feea55a28f2" +
+ "461d74e14e350351720b6f536adfe3addd4111f08e3a84da2656fd4bc83989b329b383da9f01cf2392aa0b19577884d1281f2e6c106df451c078a472b36057d3" +
+ "065dfc4bbb47ce4e5dce4acf6da095bdd10322f3ae12bcdd1f805e73b303f1fc7a7e16cf3ffd822bd8b25fbc93be065019e394113182713f1ad299ae6537f6bf" +
+ "57116e8dc9ec775519b797ab4107c2ac5129ba85188852c3bc5f116044bbd8985b6dc8b8da4659589bf9d2351c4c3adaed87fe2ea20ef6bf62224c7af86fe8b4" +
+ "973e558f39465dff43bf23cf1f78957514e4e82a3009d40d97bf8d8442a11deabde806e2fa84c1ba75273da75ce8dad3b2a34786b2958ac4bfd248ebe604a173" +
+ "83c727b11dd922b1f72476af700b663fbd7033d0ac74b463d40a92d26c938b69f96fb4a9cb7a9ca2bd9496251270c0c5fcae6b3c2eda5377b897891648a97125" +
+ "8ac71fed8dce8e02c30961a299cb7f3145845dbe8f4dfaaaf4baf0ca3fb730abdd258e98215f072a943d5aee8d8bc4c86023524f7b69186d99ad88ccdfc0b4bd" +
+ "7db422bbad7eaa0824ce24b5c186e172c8c584f1cc5c126c901a69ebee8dbd230a653a3643b7875672d22fd86079daf8d834ba33664f5ad0b6eec767b4f58b45" +
+ "e67b776b90e0a5e130aa5365003eb7fd78b757b1cf9133f6a1d51064b293cb42c8c41b15b7e95e2a39fa5dae19c6e20031d2bfa4632c37779163fdecc6b45624" +
+ "4d6bfd01a8877f6fe7739591917a86e7dd795ad85cc3f256cff5961e8b62e92a0754a51f2c6d59819446eec8bdd08b87cf9f4fb5373e809d52240d2dd691cd50" +
+ "37fc79d35b61d63851917cfdba164868a3f79e2061bd4610c1f5216ed77df00baa75f949ad37142db4fd282a5c7d2e2636ca590f92fc4781d4f51efa69f53947" +
+ "d4fca1dc7dd2429837b6d7e5c9528effdecf6f731f676587785e5c4096bdb6f1f44e72f5f77d9025813e848881506f65bfb0f2b2d3ae6f9e00731929b5ac083d" +
+ "b1c9c324703e63fef6319e1d8150aa0ff7d9a2049961df9158f3e1f2e540a91feb742625d2a859a452186d2ccaa3ec2ba086ee0868a4dc24ae6818fc02f9c1df" +
+ "dc326cc31c46feefda97265238592f638968627ec24903b97513ab05ed58ce5b516decda0e2fbf01a70e6cb2e53c3e7b8855f80cd7e007b78da727ef0893e099" +
+ "592ba684d62ae2d1f06ad148fa7f34cfc724d804149cda21aee7eac064ad20d29132b260c2c2867fa6a2e747739fc30df2f002c2a99da6c7e64ee51e806af7d9" +
+ "768aec456b93a05002666cb61b2229c99f2cdef9afc9cea1c4ee3a85dd189823399781ee33cde2abedff09c47960c035e075a29156005d75845a11fa06abcc50" +
+ "5f7f849a0caaf683f334e9e7bbbef90fe6cf94cbf87767219440d31713daef6ad1e4a1cc720ce59fee4cf7731e46bbba9ec1648908ea345030aa8f10ade10ffa" +
+ "3d2acfd480b0b11eadc4fb2b740596b204e911456cb2f35ad9993ab7dd6a48b35ba0c207625384bb3c2ff24437810bd13c7ee96cd6f97f19ffd537ad182a3657" +
+ "b0e83d42fd6e2ebac6cbf5ea1bde97465b7cec6954ff5b5be049e59a49ea25ed6667dbace765401bde12031e5cfabf2df7afb728d2a0b2a38b24d79bf23a313c" +
+ "40fe5adef97487641c6088dd8712c0c352708e474b02c08fd2d71b6d44f16d82f291ccd61c43a339408379a8de54cfdbbae5e421e084112fbc17fb5561e084d1" +
+ "4149bf4bb06fd161878d8574f856867cff974d5898e161923d55bdac8699c9df6a220bcb6c800d3ae7f107b8c4acab206d780aafaf6c2e2379de8c900700d9c9" +
+ "c87d464772514c5aa3e5f5bfc00fb54f2b74702838b4731c5ac8a070b50783e81dd97fa8d55c739d026b607a2a78aba1bb79b1a7a3c22c78368672ac020061e6" +
+ "d9683d57d6989c6c6f08b8d5d74720f5cb25505fbe81d2bf53a68e972a54784705b20f83fd1ab5afff30764ef89dba4465b56f48b325ab3810bf8dcbf4faa61f" +
+ "676e2043ac8540df9e3af4c0f51d816e89c09bf67253be45fc5f75f64be97f6c7dc0c6392af6fa8e75aab58eda976b36773cd37d315771400a2cb846fdef3d8a" +
+ "a15bce5dfda0379e526f87cf67767a2ab93d41c85b4ed016ed0a89d2f94737433a3ebade813def29eaa18a1fb925fca7d08d1020f64caebc562cb4ad2fb241e2" +
+ "94923b2f2df5e6c4953c4b73be0f5568defe57ce49d16e2a205323e46cbb5a3e77fff1557671503bd7b5de5320f1fb951fbe26400cfa854af2d12fab0215310c" +
+ "f070add34dc4565d1757d7e10a03e3bb73a607ed7e10861b1274ddee76183cf7e56c1de7162c805c2dba0e0331d36f3a4e2019a2e0705ee2747ed1e52bc3a6fb" +
+ "3b061f784348204cdf8d643ff6c271fa72b56900edcc2f77201f3bd4fc296ad6534a7029ea66761bb9a3589a1f6ef566504c70297b98fbb603214fed2e4b7ca1" +
+ "06e3f0e993118897fa641fb9722d4667fa98d07a6837e5ab2144e5ec1548a7dbca28c559f2a9a99d54b8e55f56d0e59bcef1ac45e2046835b60579da0d2261e7" +
+ "30dab9009d138421c6458d146e870358b0b3fa20257e50b58f167c6b47edf7053513d58f33547d06ce52458baaa4dcf15f77b103565c66a81f183c827801b455" +
+ "b61b6287a46a37a96884075a7eada9ba7f0ddcc14654bf87a26d2e27a978b415257773796923a220e06b25af16fb5aaded9b2d081a4c64106df460ddce9c3b2a" +
+ "c8553e1521e501ad29a4b7f7681c9b60576a127087a5237c4c2bacf9b163dd590e63f2bc80e7f1e613773f87d034313064710404739d63363d204be7b14800c4" +
+ "d8c1b6a2a21da70223be51d281fee302ef806454f9d7d28244ba537c1d9f8f1bcc5d47038d986a8f95ca48437ffe94fd44a90bb03014a259112a97508adb3db4" +
+ "34f72a5268c1af6bc6d5801e579aab2228ca33600ebbf1a1959081c3a4ca99e444f97409f5e0ca4779241c9aacad1f4ee7fb4369bd6ae076378e4f63000b9a5c" +
+ "849ba6e72e47e2454a44659149338ac0767cd25d8693c0d143e354bd600f1c1d3a44eeb024923ea659060665d5cd9a4ca1ca86162819556535fd59b9fde90caa" +
+ "29920efe99479fe7e4b4e5371e13ccb43a1419cf023433239d840900d31bab37fabc3fd20e31bb7dbcb3ae8df66f67e2844944bcf544b658364f9e3d0b6d84b4" +
+ "63ad4eb621644fd7d774b501407a1178814b15149345d551b474347188067db2ab4d7f4d1abd3027133039e855e129f3f5649550da8c04fe2db57cb89bf1bf4f" +
+ "72eb35ccfe31afb92f6136d4c2a1c115b07b721b2da43151f11c356256230408979c5d95243714429e2c9500e7b043b20dac8a9763e5b487d1cbdb34ac379b9c" +
+ "6409454c79385b6e562459c4fdaad1b7f9297c1473e9b90fffe6d1c5390e241a187a4cefa2eb0cb0c11f4ca6c5b961c18ceb57892295290dbc991692556bffa3" +
+ "b8c405cf285e6bcb8a90246ad0ac15122f4cf73adc129d23aa2240733404beb6d74bf698e5589288a522573c774ce9f514b5d5c086382ea1dd4e89ff5facbf23" +
+ "d36bc3d203941e17747ede4b82820351f4df278ddb787ce8f6f1cc468ef953399efb072ce706e253f1bab76444bb70be6443cd0db633e958dc57bd223e00418e" +
+ "915a7c2e4d94c0623f9788276480cdfe798387d35e2ea2d304d066aec7627794cdd4200a44208d6c87f242c76e2d4a3f966b6fb96eaa63d892c1a177bef249b4" +
+ "fdd1a4c06c791f677dd9919f739ccf318bd77835330b0219786249c9c9736161dac771a838724f2dd70afba46a6782fd27601cf8a7126ae95a66e526131a68d7" +
+ "7a809e513533ed8021eecdbc5851dfcf95e10f1bbe47b5c7f079275a1837836245266b66d89fab25ac4bd6c1225560bea3259b67bf50a58ee056754d574da79e" +
+ "f9a1a0df3a5defed0f74fe74ce0bf65a04086f17e94a8451828c723c97932f26f9349f1a2c7866c617a528602721de4f3cc8916bcfc66cdc106bafa26ea87a13" +
+ "94dfa37e396365fb7f92df007b46a50ff04c7f85bfa679230ebedf18c2fb876fc7098dd1c4328adf85de71c31d94687a308053bfcdc158cfa7772170fbed63f5" +
+ "37dda41f65196dfacdd1186b5de0f3369d841ce6502192292d05a19ce7464f5bcab3015c721cac13ddca561b92dc1ee25d3068dc1945a1b4e2bd1e6604c42e4c" +
+ "3c04b490f6365828957990007394557854a903e19feb06906e41cbc8766bf37bd7dece90f4cdc987709b1129e84bfdc502543b5bfa887bf78553a5ec10ad68c5" +
+ "d10eff75f7aa495e7d934a55577fdc0aead31aee4522db0259d7d4ea8438a7996d80a787465a2980457193d1c4bf1a0a1e01741d72e5fc4dfe58475c1c01026b" +
+ "5a3bc973b902280753e9c3226db9cc778e2506c56ee86ae85b4d54dbf05394107329b2d1ee56522cb1ce562fb1aa4e592199d9c29f64cc3ab1d757531e209eec" +
+ "aa138d8388169b5e28c45f5aba267eeaa57f69869f0b6855d82b0eafcde63895251f41e8e676a0ab12ef3f569bb7de91b79fa46ad9637da01ca004f4d30259c1" +
+ "f5b00761f6ca9c17721a6718390624a10a11f7f52d7afb71ee5f8338828910e48f94a1347761abac87897b2dd0e23f1d325aec5031ef58f2972e8b402e05f8c1" +
+ "ae7053a90380a1ae0d4d06645548c23e13afa31aac8ff83b10f8341418af4114632f6406d6e33076391696c9161d63c8bcfd1c625fc737f68198046212d1638a" +
+ "d2d2d42ff7029c1fcc682a046edc4d4f24862d82c600180b1e8f57ff6a3865dfe9274f9886d00efa523a1b3b3757c4489200fec3dc5583854c955492336253dd" +
+ "767f2a60ce3d224afcff9cdc19e9b28830d33affda6af99942a8fe39562055f3e884fd6c1ebc1908ac159061f35e9b0da80434ce9673d9c6b87265170077c670" +
+ "743e37474d7605cd01c44af600f16d9ffaf24acf87fbe5ccf39bac41047a810d210051c87f06147a0bb8f1427a406700483679638f1af23f1dafb7aa0c468669" +
+ "71c3a82f535c26cf6cb335e8e915fda393799d3dbe0e04b907ed3612d12ac95783a6876cd986d2a13b82192532e02c250eaa42f891d2481655fa4494c723fe00" +
+ "87d224444245eb5b0eade5f741b025db1992a8ad0dce51b0c1af4a18a9e244f9f755891adf0f19179c7baa6c32bffc91e0b03c4ed3aaee1978b6a1f03b87ac6a" +
+ "fc3b9e7030bb212b17de198edfccde29d04224798c1204e47ea235f048724fac62d637d1ba0ee3922048fcf79c746b6c0c036d882e3491fd72bad6e009c6403e" +
+ "55876f4d31330caa02aedd0b0c121c3c41e736853a08071f0dd4ddc7412db0bbe274a9ac2932552bb37c40e72c2ef1d7cca8236942e480d709d3ea9d5ae0a1b7",
+ ),
+ },
+ }
+ for i, tt := range tests {
+ cache := make([]uint32, tt.cacheSize/4)
+ generateCache(cache, tt.epoch, seedHash(tt.epoch*epochLength+1))
+
+ dataset := make([]uint32, tt.datasetSize/4)
+ generateDataset(dataset, tt.epoch, cache)
+
+ want := make([]uint32, tt.datasetSize/4)
+ prepare(want, tt.dataset)
+
+ if !reflect.DeepEqual(dataset, want) {
+ t.Errorf("dataset %d: content mismatch: have %x, want %x", i, dataset, want)
+ }
+ }
+}
+
+// Tests whether the hashimoto lookup works for both light as well as the full
+// datasets.
+func TestHashimoto(t *testing.T) {
+ // Create the verification cache and mining dataset
+ cache := make([]uint32, 1024/4)
+ generateCache(cache, 0, make([]byte, 32))
+
+ dataset := make([]uint32, 32*1024/4)
+ generateDataset(dataset, 0, cache)
+
+ // Create a block to verify
+ hash := hexutil.MustDecode("0xc9149cc0386e689d789a1c2f3d5d169a61a6218ed30e74414dc736e442ef3d1f")
+ nonce := uint64(0)
+
+ wantDigest := hexutil.MustDecode("0xe4073cffaef931d37117cefd9afd27ea0f1cad6a981dd2605c4a1ac97c519800")
+ wantResult := hexutil.MustDecode("0xd3539235ee2e6f8db665c0a72169f55b7f6c605712330b778ec3944f0eb5a557")
+
+ digest, result := hashimotoLight(32*1024, cache, hash, nonce)
+ if !bytes.Equal(digest, wantDigest) {
+ t.Errorf("light hashimoto digest mismatch: have %x, want %x", digest, wantDigest)
+ }
+ if !bytes.Equal(result, wantResult) {
+ t.Errorf("light hashimoto result mismatch: have %x, want %x", result, wantResult)
+ }
+ digest, result = hashimotoFull(dataset, hash, nonce)
+ if !bytes.Equal(digest, wantDigest) {
+ t.Errorf("full hashimoto digest mismatch: have %x, want %x", digest, wantDigest)
+ }
+ if !bytes.Equal(result, wantResult) {
+ t.Errorf("full hashimoto result mismatch: have %x, want %x", result, wantResult)
+ }
+}
+
+// Benchmarks the cache generation performance.
+func BenchmarkCacheGeneration(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ cache := make([]uint32, cacheSize(1)/4)
+ generateCache(cache, 0, make([]byte, 32))
+ }
+}
+
+// Benchmarks the dataset (small) generation performance.
+func BenchmarkSmallDatasetGeneration(b *testing.B) {
+ cache := make([]uint32, 65536/4)
+ generateCache(cache, 0, make([]byte, 32))
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ dataset := make([]uint32, 32*65536/4)
+ generateDataset(dataset, 0, cache)
+ }
+}
+
+// Benchmarks the light verification performance.
+func BenchmarkHashimotoLight(b *testing.B) {
+ cache := make([]uint32, cacheSize(1)/4)
+ generateCache(cache, 0, make([]byte, 32))
+
+ hash := hexutil.MustDecode("0xc9149cc0386e689d789a1c2f3d5d169a61a6218ed30e74414dc736e442ef3d1f")
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ hashimotoLight(datasetSize(1), cache, hash, 0)
+ }
+}
+
+// Benchmarks the full (small) verification performance.
+func BenchmarkHashimotoFullSmall(b *testing.B) {
+ cache := make([]uint32, 65536/4)
+ generateCache(cache, 0, make([]byte, 32))
+
+ dataset := make([]uint32, 32*65536/4)
+ generateDataset(dataset, 0, cache)
+
+ hash := hexutil.MustDecode("0xc9149cc0386e689d789a1c2f3d5d169a61a6218ed30e74414dc736e442ef3d1f")
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ hashimotoFull(dataset, hash, 0)
+ }
+}
+
+func benchmarkHashimotoFullMmap(b *testing.B, name string, lock bool) {
+ b.Run(name, func(b *testing.B) {
+ tmpdir := b.TempDir()
+ d := &dataset{epoch: 0}
+ d.generate(tmpdir, 1, lock, false)
+ var hash [length.Hash]byte
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ binary.PutVarint(hash[:], int64(i))
+ hashimotoFull(d.dataset, hash[:], 0)
+ }
+ })
+}
+
+// Benchmarks the full verification performance for mmap
+func BenchmarkHashimotoFullMmap(b *testing.B) {
+ benchmarkHashimotoFullMmap(b, "WithLock", true)
+ benchmarkHashimotoFullMmap(b, "WithoutLock", false)
+}
+
+func BenchmarkSeedHash(b *testing.B) {
+ var res []byte
+ const repeats = 100
+ for n := 0; n < repeats; n++ {
+ for i := uint64(0); i < uint64(b.N); i++ {
+ res = seedHash(i*epochLength + 1)
+ }
+ }
+
+ _, err := io.Copy(io.Discard, bytes.NewBuffer(res))
+ if err != nil {
+ b.Error(err)
+ }
+}
+
+func BenchmarkSeedHashOld(b *testing.B) {
+ var res []byte
+ const repeats = 100
+ for n := 0; n < repeats; n++ {
+ for i := uint64(0); i < uint64(b.N); i++ {
+ res = seedHashOld(i*epochLength + 1)
+ }
+ }
+
+ _, err := io.Copy(io.Discard, bytes.NewBuffer(res))
+ if err != nil {
+ b.Error(err)
+ }
+}
diff --git a/consensus/ethash/api.go b/consensus/ethash/api.go
new file mode 100644
index 00000000000..403a5ed4057
--- /dev/null
+++ b/consensus/ethash/api.go
@@ -0,0 +1,118 @@
+// Copyright 2018 The go-ethereum Authors
+// (original work)
+// Copyright 2024 The Erigon Authors
+// (modifications)
+// This file is part of Erigon.
+//
+// Erigon 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.
+//
+// Erigon 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 Erigon. If not, see .
+
+package ethash
+
+import (
+ "errors"
+
+ "github.com/erigontech/erigon-lib/common/hexutil"
+
+ libcommon "github.com/erigontech/erigon-lib/common"
+
+ "github.com/erigontech/erigon/core/types"
+)
+
+var errEthashStopped = errors.New("ethash stopped")
+
+// API exposes ethash related methods for the RPC interface.
+type API struct {
+ ethash *Ethash
+}
+
+// GetWork returns a work package for external miner.
+//
+// The work package consists of 3 strings:
+//
+// result[0] - 32 bytes hex encoded current block header pow-hash
+// result[1] - 32 bytes hex encoded seed hash used for DAG
+// result[2] - 32 bytes hex encoded boundary condition ("target"), 2^256/difficulty
+// result[3] - hex encoded block number
+func (api *API) GetWork() ([4]string, error) {
+ if api.ethash.remote == nil {
+ return [4]string{}, errors.New("not supported")
+ }
+
+ var (
+ workCh = make(chan [4]string, 1)
+ errc = make(chan error, 1)
+ )
+ select {
+ case api.ethash.remote.fetchWorkCh <- &sealWork{errc: errc, res: workCh}:
+ case <-api.ethash.remote.exitCh:
+ return [4]string{}, errEthashStopped
+ }
+ select {
+ case work := <-workCh:
+ return work, nil
+ case err := <-errc:
+ return [4]string{}, err
+ }
+}
+
+// SubmitWork can be used by external miner to submit their POW solution.
+// It returns an indication if the work was accepted.
+// Note either an invalid solution, a stale work a non-existent work will return false.
+func (api *API) SubmitWork(nonce types.BlockNonce, hash, digest libcommon.Hash) bool {
+ if api.ethash.remote == nil {
+ return false
+ }
+
+ var errc = make(chan error, 1)
+ select {
+ case api.ethash.remote.submitWorkCh <- &mineResult{
+ nonce: nonce,
+ mixDigest: digest,
+ hash: hash,
+ errc: errc,
+ }:
+ case <-api.ethash.remote.exitCh:
+ return false
+ }
+ err := <-errc
+ return err == nil
+}
+
+// SubmitHashRate can be used for remote miners to submit their hash rate.
+// This enables the node to report the combined hash rate of all miners
+// which submit work through this node.
+//
+// It accepts the miner hash rate and an identifier which must be unique
+// between nodes.
+func (api *API) SubmitHashRate(rate hexutil.Uint64, id libcommon.Hash) bool {
+ if api.ethash.remote == nil {
+ return false
+ }
+
+ var done = make(chan struct{}, 1)
+ select {
+ case api.ethash.remote.submitRateCh <- &hashrate{done: done, rate: uint64(rate), id: id}:
+ case <-api.ethash.remote.exitCh:
+ return false
+ }
+
+ // Block until hash rate submitted successfully.
+ <-done
+ return true
+}
+
+// GetHashrate returns the current hashrate for local CPU miner and remote miner.
+func (api *API) GetHashrate() uint64 {
+ return uint64(api.ethash.Hashrate())
+}
diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go
new file mode 100644
index 00000000000..4c75240522c
--- /dev/null
+++ b/consensus/ethash/consensus.go
@@ -0,0 +1,680 @@
+// Copyright 2017 The go-ethereum Authors
+// (original work)
+// Copyright 2024 The Erigon Authors
+// (modifications)
+// This file is part of Erigon.
+//
+// Erigon 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.
+//
+// Erigon 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 Erigon. If not, see .
+
+package ethash
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "math/big"
+ "runtime"
+ "time"
+
+ mapset "github.com/deckarep/golang-set/v2"
+ "github.com/holiman/uint256"
+ "golang.org/x/crypto/sha3"
+
+ "github.com/erigontech/erigon-lib/chain"
+ libcommon "github.com/erigontech/erigon-lib/common"
+ "github.com/erigontech/erigon-lib/log/v3"
+
+ "github.com/erigontech/erigon/common/math"
+ "github.com/erigontech/erigon/common/u256"
+ "github.com/erigontech/erigon/consensus"
+ "github.com/erigontech/erigon/consensus/ethash/ethashcfg"
+ "github.com/erigontech/erigon/consensus/misc"
+ "github.com/erigontech/erigon/core/state"
+ "github.com/erigontech/erigon/core/tracing"
+ "github.com/erigontech/erigon/core/types"
+ "github.com/erigontech/erigon/params"
+ "github.com/erigontech/erigon/rlp"
+)
+
+// Ethash proof-of-work protocol constants.
+var (
+ FrontierBlockReward = uint256.NewInt(5e+18) // Block reward in wei for successfully mining a block
+ ByzantiumBlockReward = uint256.NewInt(3e+18) // Block reward in wei for successfully mining a block upward from Byzantium
+ ConstantinopleBlockReward = uint256.NewInt(2e+18) // Block reward in wei for successfully mining a block upward from Constantinople
+ maxUncles = 2 // Maximum number of uncles allowed in a single block
+ allowedFutureBlockTimeSeconds = int64(15) // Max seconds from current time allowed for blocks, before they're considered future blocks
+
+ // calcDifficultyEip5133 is the difficulty adjustment algorithm as specified by EIP 5133.
+ // It offsets the bomb a total of 11.4M blocks.
+ // Specification EIP-5133: https://eips.ethereum.org/EIPS/eip-5133
+ calcDifficultyEip5133 = makeDifficultyCalculator(11400000)
+
+ // calcDifficultyEip4345 is the difficulty adjustment algorithm as specified by EIP 4345.
+ // It offsets the bomb a total of 10.7M blocks.
+ // Specification EIP-4345: https://eips.ethereum.org/EIPS/eip-4345
+ calcDifficultyEip4345 = makeDifficultyCalculator(10700000)
+
+ // calcDifficultyEip3554 is the difficulty adjustment algorithm as specified by EIP 3554.
+ // It offsets the bomb a total of 9.7M blocks.
+ // Specification EIP-3554: https://eips.ethereum.org/EIPS/eip-3554
+ calcDifficultyEip3554 = makeDifficultyCalculator(9700000)
+
+ // calcDifficultyEip2384 is the difficulty adjustment algorithm as specified by EIP 2384.
+ // It offsets the bomb 4M blocks from Constantinople, so in total 9M blocks.
+ // Specification EIP-2384: https://eips.ethereum.org/EIPS/eip-2384
+ calcDifficultyEip2384 = makeDifficultyCalculator(9000000)
+
+ // calcDifficultyConstantinople is the difficulty adjustment algorithm for Constantinople.
+ // It returns the difficulty that a new block should have when created at time given the
+ // parent block's time and difficulty. The calculation uses the Byzantium rules, but with
+ // bomb offset 5M.
+ // Specification EIP-1234: https://eips.ethereum.org/EIPS/eip-1234
+ calcDifficultyConstantinople = makeDifficultyCalculator(5000000)
+
+ // calcDifficultyByzantium is the difficulty adjustment algorithm. It returns
+ // the difficulty that a new block should have when created at time given the
+ // parent block's time and difficulty. The calculation uses the Byzantium rules.
+ // Specification EIP-649: https://eips.ethereum.org/EIPS/eip-649
+ calcDifficultyByzantium = makeDifficultyCalculator(3000000)
+)
+
+// Various error messages to mark blocks invalid. These should be private to
+// prevent engine specific errors from being referenced in the remainder of the
+// codebase, inherently breaking if the engine is swapped out. Please put common
+// error types into the consensus package.
+var (
+ errOlderBlockTime = errors.New("timestamp older than parent")
+ errTooManyUncles = errors.New("too many uncles")
+ errDuplicateUncle = errors.New("duplicate uncle")
+ errUncleIsAncestor = errors.New("uncle is ancestor")
+ errDanglingUncle = errors.New("uncle's parent is not ancestor")
+ errInvalidDifficulty = errors.New("non-positive difficulty")
+ errInvalidMixDigest = errors.New("invalid mix digest")
+ errInvalidPoW = errors.New("invalid proof-of-work")
+)
+
+// Type returns underlying consensus engine
+func (ethash *Ethash) Type() chain.ConsensusName {
+ return chain.EtHashConsensus
+}
+
+// Author implements consensus.Engine, returning the header's coinbase as the
+// proof-of-work verified author of the block.
+// This is thread-safe (only access the header.Coinbase)
+func (ethash *Ethash) Author(header *types.Header) (libcommon.Address, error) {
+ return header.Coinbase, nil
+}
+
+// VerifyHeader checks whether a header conforms to the consensus rules of the
+// stock Ethereum ethash engine.
+func (ethash *Ethash) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Header, seal bool) error {
+ // Short circuit if the header is known, or its parent not
+ number := header.Number.Uint64()
+ if chain.GetHeader(header.Hash(), number) != nil {
+ return nil
+ }
+ if number == 0 {
+ return nil
+ }
+ parent := chain.GetHeader(header.ParentHash, number-1)
+ if parent == nil {
+ log.Error("consensus.ErrUnknownAncestor", "parentNum", number-1, "hash", header.ParentHash.String())
+ return consensus.ErrUnknownAncestor
+ }
+ // Sanity checks passed, do a proper verification
+ return ethash.verifyHeader(chain, header, parent, false, seal)
+}
+
+// VerifyUncles verifies that the given block's uncles conform to the consensus
+// rules of the stock Ethereum ethash engine.
+func (ethash *Ethash) VerifyUncles(chain consensus.ChainReader, header *types.Header, uncles []*types.Header) error {
+ // Verify that there are at most 2 uncles included in this block
+ if len(uncles) > maxUncles {
+ return errTooManyUncles
+ }
+ if len(uncles) == 0 {
+ return nil
+ }
+ uncleBlocks, ancestors := getUncles(chain, header)
+
+ // Verify each of the uncles that it's recent, but not an ancestor
+ for _, uncle := range uncles {
+ if err := ethash.VerifyUncle(chain, header, uncle, uncleBlocks, ancestors, true); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func getUncles(chain consensus.ChainReader, header *types.Header) (mapset.Set[libcommon.Hash], map[libcommon.Hash]*types.Header) {
+ // Gather the set of past uncles and ancestors
+ uncles, ancestors := mapset.NewSet[libcommon.Hash](), make(map[libcommon.Hash]*types.Header)
+
+ number, parent := header.Number.Uint64()-1, header.ParentHash
+ for i := 0; i < 7; i++ {
+ ancestorHeader := chain.GetHeader(parent, number)
+ if ancestorHeader == nil {
+ break
+ }
+ ancestors[parent] = ancestorHeader
+ // If the ancestor doesn't have any uncles, we don't have to iterate them
+ if ancestorHeader.UncleHash != types.EmptyUncleHash {
+ // Need to add those uncles to the blacklist too
+ ancestor := chain.GetBlock(parent, number)
+ if ancestor == nil {
+ break
+ }
+ for _, uncle := range ancestor.Uncles() {
+ uncles.Add(uncle.Hash())
+ }
+ }
+ parent, number = ancestorHeader.ParentHash, number-1
+ }
+ ancestors[header.Hash()] = header
+ uncles.Add(header.Hash())
+ return uncles, ancestors
+}
+
+func (ethash *Ethash) VerifyUncle(chain consensus.ChainHeaderReader, header *types.Header, uncle *types.Header, uncles mapset.Set[libcommon.Hash], ancestors map[libcommon.Hash]*types.Header, seal bool) error {
+ // Make sure every uncle is rewarded only once
+ hash := uncle.Hash()
+ if uncles.Contains(hash) {
+ return errDuplicateUncle
+ }
+ uncles.Add(hash)
+
+ // Make sure the uncle has a valid ancestry
+ if ancestors[hash] != nil {
+ return errUncleIsAncestor
+ }
+ if ancestors[uncle.ParentHash] == nil || uncle.ParentHash == header.ParentHash {
+ return errDanglingUncle
+ }
+
+ return ethash.verifyHeader(chain, uncle, ancestors[uncle.ParentHash], true, seal)
+}
+
+func VerifyHeaderBasics(chain consensus.ChainHeaderReader, header, parent *types.Header, checkTimestamp, skipGasLimit bool) error {
+ // Ensure that the header's extra-data section is of a reasonable size
+ if uint64(len(header.Extra)) > params.MaximumExtraDataSize {
+ return fmt.Errorf("extra-data too long: %d > %d", len(header.Extra), params.MaximumExtraDataSize)
+ }
+ // Verify the header's timestamp
+ if checkTimestamp {
+ unixNow := time.Now().Unix()
+ if header.Time > uint64(unixNow+allowedFutureBlockTimeSeconds) {
+ return consensus.ErrFutureBlock
+ }
+ }
+ if header.Time <= parent.Time {
+ return errOlderBlockTime
+ }
+ // Verify that the gas limit is <= 2^63-1
+ if header.GasLimit > params.MaxGasLimit {
+ return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, params.MaxGasLimit)
+ }
+ // Verify that the gasUsed is <= gasLimit
+ if header.GasUsed > header.GasLimit {
+ return fmt.Errorf("invalid gasUsed: have %d, gasLimit %d", header.GasUsed, header.GasLimit)
+ }
+ // Verify the block's gas usage and (if applicable) verify the base fee.
+ if !chain.Config().IsLondon(header.Number.Uint64()) {
+ // Verify BaseFee not present before EIP-1559 fork.
+ if header.BaseFee != nil {
+ return fmt.Errorf("invalid baseFee before fork: have %d, expected 'nil'", header.BaseFee)
+ }
+ if !skipGasLimit {
+ if err := misc.VerifyGaslimit(parent.GasLimit, header.GasLimit); err != nil {
+ return err
+ }
+ }
+ } else if err := misc.VerifyEip1559Header(chain.Config(), parent, header, skipGasLimit); err != nil {
+ // Verify the header's EIP-1559 attributes.
+ return err
+ }
+
+ if err := misc.VerifyAbsenceOfCancunHeaderFields(header); err != nil {
+ return err
+ }
+
+ // Verify that the block number is parent's +1
+ if diff := new(big.Int).Sub(header.Number, parent.Number); diff.Cmp(big.NewInt(1)) != 0 {
+ return consensus.ErrInvalidNumber
+ }
+
+ if header.WithdrawalsHash != nil {
+ return consensus.ErrUnexpectedWithdrawals
+ }
+
+ if header.RequestsRoot != nil {
+ return consensus.ErrUnexpectedRequests
+ }
+
+ // If all checks passed, validate any special fields for hard forks
+ if err := misc.VerifyDAOHeaderExtraData(chain.Config(), header); err != nil {
+ return err
+ }
+ return nil
+}
+
+// verifyHeader checks whether a header conforms to the consensus rules of the
+// stock Ethereum ethash engine.
+// See YP section 4.3.4. "Block Header Validity"
+func (ethash *Ethash) verifyHeader(chain consensus.ChainHeaderReader, header, parent *types.Header, uncle bool, seal bool) error {
+ if err := VerifyHeaderBasics(chain, header, parent, !uncle /*checkTimestamp*/, false /*skipGasLimit*/); err != nil {
+ return err
+ }
+ // Verify the block's difficulty based on its timestamp and parent's difficulty
+ expected := ethash.CalcDifficulty(chain, header.Time, parent.Time, parent.Difficulty, parent.Number.Uint64(), parent.Hash(), parent.UncleHash, parent.AuRaStep)
+ if expected.Cmp(header.Difficulty) != 0 {
+ return fmt.Errorf("invalid difficulty: have %v, want %v", header.Difficulty, expected)
+ }
+ // Verify the engine specific seal securing the block
+ if seal {
+ if err := ethash.VerifySeal(nil, header); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// CalcDifficulty is the difficulty adjustment algorithm. It returns
+// the difficulty that a new block should have when created at time
+// given the parent block's time and difficulty.
+func (ethash *Ethash) CalcDifficulty(chain consensus.ChainHeaderReader, time, parentTime uint64, parentDifficulty *big.Int, parentNumber uint64, _, parentUncleHash libcommon.Hash, _ uint64) *big.Int {
+ return CalcDifficulty(chain.Config(), time, parentTime, parentDifficulty, parentNumber, parentUncleHash)
+}
+
+// CalcDifficulty is the difficulty adjustment algorithm. It returns
+// the difficulty that a new block should have when created at time
+// given the parent block's time and difficulty.
+func CalcDifficulty(config *chain.Config, time, parentTime uint64, parentDifficulty *big.Int, parentNumber uint64, parentUncleHash libcommon.Hash) *big.Int {
+ next := parentNumber + 1
+ switch {
+ case config.IsGrayGlacier(next):
+ return calcDifficultyEip5133(time, parentTime, parentDifficulty, parentNumber, parentUncleHash)
+ case config.IsArrowGlacier(next):
+ return calcDifficultyEip4345(time, parentTime, parentDifficulty, parentNumber, parentUncleHash)
+ case config.IsLondon(next):
+ return calcDifficultyEip3554(time, parentTime, parentDifficulty, parentNumber, parentUncleHash)
+ case config.IsMuirGlacier(next):
+ return calcDifficultyEip2384(time, parentTime, parentDifficulty, parentNumber, parentUncleHash)
+ case config.IsConstantinople(next):
+ return calcDifficultyConstantinople(time, parentTime, parentDifficulty, parentNumber, parentUncleHash)
+ case config.IsByzantium(next):
+ return calcDifficultyByzantium(time, parentTime, parentDifficulty, parentNumber, parentUncleHash)
+ case config.IsHomestead(next):
+ return calcDifficultyHomestead(time, parentTime, parentDifficulty, parentNumber, parentUncleHash)
+ default:
+ return calcDifficultyFrontier(time, parentTime, parentDifficulty, parentNumber, parentUncleHash)
+ }
+}
+
+// Some weird constants to avoid constant memory allocs for them.
+var (
+ expDiffPeriod = big.NewInt(100000)
+ big1 = big.NewInt(1)
+ big2 = big.NewInt(2)
+ big9 = big.NewInt(9)
+ big10 = big.NewInt(10)
+ bigMinus99 = big.NewInt(-99)
+)
+
+// makeDifficultyCalculator creates a difficultyCalculator with the given bomb-delay.
+// the difficulty is calculated with Byzantium rules, which differs from Homestead in
+// how uncles affect the calculation
+func makeDifficultyCalculator(bombDelay uint64) func(time, parentTime uint64, parentDifficulty *big.Int, parentNumber uint64, parentUncleHash libcommon.Hash) *big.Int {
+ // Note, the calculations below looks at the parent number, which is 1 below
+ // the block number. Thus we remove one from the delay given
+ bombDelayFromParent := bombDelay - 1
+ return func(time, parentTime uint64, parentDifficulty *big.Int, parentNumber uint64, parentUncleHash libcommon.Hash) *big.Int {
+ // https://github.com/ethereum/EIPs/issues/100.
+ // algorithm:
+ // diff = (parent_diff +
+ // (parent_diff / 2048 * max((2 if len(parent.uncles) else 1) - ((timestamp - parent.timestamp) // 9), -99))
+ // ) + 2^(periodCount - 2)
+
+ bigTime := new(big.Int).SetUint64(time)
+ bigParentTime := new(big.Int).SetUint64(parentTime)
+
+ // holds intermediate values to make the algo easier to read & audit
+ x := new(big.Int)
+ y := new(big.Int)
+
+ // (2 if len(parent_uncles) else 1) - (block_timestamp - parent_timestamp) // 9
+ x.Sub(bigTime, bigParentTime)
+ x.Div(x, big9)
+ if parentUncleHash == types.EmptyUncleHash {
+ x.Sub(big1, x)
+ } else {
+ x.Sub(big2, x)
+ }
+ // max((2 if len(parent_uncles) else 1) - (block_timestamp - parent_timestamp) // 9, -99)
+ if x.Cmp(bigMinus99) < 0 {
+ x.Set(bigMinus99)
+ }
+ // parent_diff + (parent_diff / 2048 * max((2 if len(parent.uncles) else 1) - ((timestamp - parent.timestamp) // 9), -99))
+ y.Div(parentDifficulty, params.DifficultyBoundDivisor)
+ x.Mul(y, x)
+ x.Add(parentDifficulty, x)
+
+ // minimum difficulty can ever be (before exponential factor)
+ if x.Cmp(params.MinimumDifficulty) < 0 {
+ x.Set(params.MinimumDifficulty)
+ }
+ // calculate a fake block number for the ice-age delay
+ // Specification: https://eips.ethereum.org/EIPS/eip-1234
+ fakeBlockNumber := uint64(0)
+ if parentNumber >= bombDelayFromParent {
+ fakeBlockNumber = parentNumber - bombDelayFromParent
+ }
+ // for the exponential factor
+ periodCount := new(big.Int).SetUint64(fakeBlockNumber)
+ periodCount.Div(periodCount, expDiffPeriod)
+
+ // the exponential factor, commonly referred to as "the bomb"
+ // diff = diff + 2^(periodCount - 2)
+ if periodCount.Cmp(big1) > 0 {
+ y.Sub(periodCount, big2)
+ y.Exp(big2, y, nil)
+ x.Add(x, y)
+ }
+ return x
+ }
+}
+
+// calcDifficultyHomestead is the difficulty adjustment algorithm. It returns
+// the difficulty that a new block should have when created at time given the
+// parent block's time and difficulty. The calculation uses the Homestead rules.
+func calcDifficultyHomestead(time, parentTime uint64, parentDifficulty *big.Int, parentNumber uint64, _ libcommon.Hash) *big.Int {
+ // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2.md
+ // algorithm:
+ // diff = (parent_diff +
+ // (parent_diff / 2048 * max(1 - (block_timestamp - parent_timestamp) // 10, -99))
+ // ) + 2^(periodCount - 2)
+
+ bigTime := new(big.Int).SetUint64(time)
+ bigParentTime := new(big.Int).SetUint64(parentTime)
+
+ // holds intermediate values to make the algo easier to read & audit
+ x := new(big.Int)
+ y := new(big.Int)
+
+ // 1 - (block_timestamp - parent_timestamp) // 10
+ x.Sub(bigTime, bigParentTime)
+ x.Div(x, big10)
+ x.Sub(big1, x)
+
+ // max(1 - (block_timestamp - parent_timestamp) // 10, -99)
+ if x.Cmp(bigMinus99) < 0 {
+ x.Set(bigMinus99)
+ }
+ // (parent_diff + parent_diff // 2048 * max(1 - (block_timestamp - parent_timestamp) // 10, -99))
+ y.Div(parentDifficulty, params.DifficultyBoundDivisor)
+ x.Mul(y, x)
+ x.Add(parentDifficulty, x)
+
+ // minimum difficulty can ever be (before exponential factor)
+ if x.Cmp(params.MinimumDifficulty) < 0 {
+ x.Set(params.MinimumDifficulty)
+ }
+ // for the exponential factor
+ periodCount := new(big.Int).SetUint64(parentNumber + 1)
+ periodCount.Div(periodCount, expDiffPeriod)
+
+ // the exponential factor, commonly referred to as "the bomb"
+ // diff = diff + 2^(periodCount - 2)
+ if periodCount.Cmp(big1) > 0 {
+ y.Sub(periodCount, big2)
+ y.Exp(big2, y, nil)
+ x.Add(x, y)
+ }
+ return x
+}
+
+// calcDifficultyFrontier is the difficulty adjustment algorithm. It returns the
+// difficulty that a new block should have when created at time given the parent
+// block's time and difficulty. The calculation uses the Frontier rules.
+func calcDifficultyFrontier(time, parentTime uint64, parentDifficulty *big.Int, parentNumber uint64, _ libcommon.Hash) *big.Int {
+ diff := new(big.Int)
+ adjust := new(big.Int).Div(parentDifficulty, params.DifficultyBoundDivisor)
+ bigTime := new(big.Int)
+ bigParentTime := new(big.Int)
+
+ bigTime.SetUint64(time)
+ bigParentTime.SetUint64(parentTime)
+
+ if bigTime.Sub(bigTime, bigParentTime).Cmp(params.DurationLimit) < 0 {
+ diff.Add(parentDifficulty, adjust)
+ } else {
+ diff.Sub(parentDifficulty, adjust)
+ }
+ if diff.Cmp(params.MinimumDifficulty) < 0 {
+ diff.Set(params.MinimumDifficulty)
+ }
+
+ periodCount := new(big.Int).SetUint64(parentNumber + 1)
+ periodCount.Div(periodCount, expDiffPeriod)
+ if periodCount.Cmp(big1) > 0 {
+ // diff = diff + 2^(periodCount - 2)
+ expDiff := periodCount.Sub(periodCount, big2)
+ expDiff.Exp(big2, expDiff, nil)
+ diff.Add(diff, expDiff)
+ diff = math.BigMax(diff, params.MinimumDifficulty)
+ }
+ return diff
+}
+
+// VerifySeal implements consensus.Engine, checking whether the given block satisfies
+// the PoW difficulty requirements.
+func (ethash *Ethash) VerifySeal(_ consensus.ChainHeaderReader, header *types.Header) error {
+ return ethash.verifySeal(header, false)
+}
+
+// Exported for fuzzing
+var FrontierDifficultyCalulator = calcDifficultyFrontier
+var HomesteadDifficultyCalulator = calcDifficultyHomestead
+var DynamicDifficultyCalculator = makeDifficultyCalculator
+
+// verifySeal checks whether a block satisfies the PoW difficulty requirements,
+// either using the usual ethash cache for it, or alternatively using a full DAG
+// to make remote mining fast.
+func (ethash *Ethash) verifySeal(header *types.Header, fulldag bool) error { //nolint:unparam
+ // If we're running a shared PoW, delegate verification to it
+ if ethash.shared != nil {
+ return ethash.shared.verifySeal(header, fulldag)
+ }
+
+ // Ensure that we have a valid difficulty for the block
+ if header.Difficulty.Sign() <= 0 {
+ return errInvalidDifficulty
+ }
+ // Recompute the digest and PoW values
+ number := header.Number.Uint64()
+
+ var (
+ digest []byte
+ result []byte
+ )
+ // If fast-but-heavy PoW verification was requested, use an ethash dataset
+ if fulldag {
+ dataset := ethash.dataset(number, true)
+ if dataset.generated() {
+ digest, result = hashimotoFull(dataset.dataset, ethash.SealHash(header).Bytes(), header.Nonce.Uint64())
+
+ // Datasets are unmapped in a finalizer. Ensure that the dataset stays alive
+ // until after the call to hashimotoFull so it's not unmapped while being used.
+ runtime.KeepAlive(dataset)
+ } else {
+ // Dataset not yet generated, don't hang, use a cache instead
+ fulldag = false
+ }
+ }
+ // If slow-but-light PoW verification was requested (or DAG not yet ready), use an ethash cache
+ if !fulldag {
+ cache := ethash.cache(number)
+
+ size := datasetSize(number)
+ if ethash.config.PowMode == ethashcfg.ModeTest {
+ size = 32 * 1024
+ }
+ digest, result = hashimotoLight(size, cache.cache, ethash.SealHash(header).Bytes(), header.Nonce.Uint64())
+
+ // Caches are unmapped in a finalizer. Ensure that the cache stays alive
+ // until after the call to hashimotoLight so it's not unmapped while being used.
+ runtime.KeepAlive(cache)
+ }
+ // Verify the calculated values against the ones provided in the header
+ if !bytes.Equal(header.MixDigest[:], digest) {
+ return errInvalidMixDigest
+ }
+ target := new(big.Int).Div(two256, header.Difficulty)
+ if new(big.Int).SetBytes(result).Cmp(target) > 0 {
+ return errInvalidPoW
+ }
+ return nil
+}
+
+// Prepare implements consensus.Engine, initializing the difficulty field of a
+// header to conform to the ethash protocol. The changes are done inline.
+func (ethash *Ethash) Prepare(chain consensus.ChainHeaderReader, header *types.Header, state *state.IntraBlockState) error {
+ parent := chain.GetHeader(header.ParentHash, header.Number.Uint64()-1)
+ if parent == nil {
+ return consensus.ErrUnknownAncestor
+ }
+ header.Difficulty = ethash.CalcDifficulty(chain, header.Time, parent.Time, parent.Difficulty, parent.Number.Uint64(), parent.Hash(), parent.UncleHash, parent.AuRaStep)
+ return nil
+}
+
+func (ethash *Ethash) Initialize(config *chain.Config, chain consensus.ChainHeaderReader, header *types.Header,
+ state *state.IntraBlockState, syscall consensus.SysCallCustom, logger log.Logger, tracer *tracing.Hooks) {
+ if config.DAOForkBlock != nil && config.DAOForkBlock.Cmp(header.Number) == 0 {
+ misc.ApplyDAOHardFork(state)
+ }
+}
+
+// Finalize implements consensus.Engine, accumulating the block and uncle rewards,
+// setting the final state on the header
+func (ethash *Ethash) Finalize(config *chain.Config, header *types.Header, state *state.IntraBlockState,
+ txs types.Transactions, uncles []*types.Header, r types.Receipts, withdrawals []*types.Withdrawal, requests types.Requests,
+ chain consensus.ChainReader, syscall consensus.SystemCall, logger log.Logger,
+) (types.Transactions, types.Receipts, types.Requests, error) {
+ // Accumulate any block and uncle rewards and commit the final state root
+ accumulateRewards(config, state, header, uncles)
+ return txs, r, nil, nil
+}
+
+// FinalizeAndAssemble implements consensus.Engine, accumulating the block and
+// uncle rewards, setting the final state and assembling the block.
+func (ethash *Ethash) FinalizeAndAssemble(chainConfig *chain.Config, header *types.Header, state *state.IntraBlockState,
+ txs types.Transactions, uncles []*types.Header, r types.Receipts, withdrawals []*types.Withdrawal, requests types.Requests,
+ chain consensus.ChainReader, syscall consensus.SystemCall, call consensus.Call, logger log.Logger,
+) (*types.Block, types.Transactions, types.Receipts, error) {
+
+ // Finalize block
+ outTxs, outR, _, err := ethash.Finalize(chainConfig, header, state, txs, uncles, r, withdrawals, requests, chain, syscall, logger)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+ // Header seems complete, assemble into a block and return
+ return types.NewBlock(header, outTxs, uncles, outR, withdrawals, requests), outTxs, outR, nil
+}
+
+// SealHash returns the hash of a block prior to it being sealed.
+func (ethash *Ethash) SealHash(header *types.Header) (hash libcommon.Hash) {
+ hasher := sha3.NewLegacyKeccak256()
+
+ enc := []interface{}{
+ header.ParentHash,
+ header.UncleHash,
+ header.Coinbase,
+ header.Root,
+ header.TxHash,
+ header.ReceiptHash,
+ header.Bloom,
+ header.Difficulty,
+ header.Number,
+ header.GasLimit,
+ header.GasUsed,
+ header.Time,
+ header.Extra,
+ }
+ if header.BaseFee != nil {
+ enc = append(enc, header.BaseFee)
+ }
+ rlp.Encode(hasher, enc)
+ hasher.Sum(hash[:0])
+ return hash
+}
+
+func (ethash *Ethash) IsServiceTransaction(sender libcommon.Address, syscall consensus.SystemCall) bool {
+ return false
+}
+
+func (ethash *Ethash) CalculateRewards(config *chain.Config, header *types.Header, uncles []*types.Header, _ consensus.SystemCall,
+) ([]consensus.Reward, error) {
+ minerReward, uncleRewards := AccumulateRewards(config, header, uncles)
+ rewards := make([]consensus.Reward, 1+len(uncles))
+ rewards[0].Beneficiary = header.Coinbase
+ rewards[0].Kind = consensus.RewardAuthor
+ rewards[0].Amount = minerReward
+ for i, uncle := range uncles {
+ rewards[i+1].Beneficiary = uncle.Coinbase
+ rewards[i+1].Kind = consensus.RewardUncle
+ rewards[i+1].Amount = uncleRewards[i]
+ }
+ return rewards, nil
+}
+
+// AccumulateRewards returns rewards for a given block. The mining reward consists
+// of the static blockReward plus a reward for each included uncle (if any). Individual
+// uncle rewards are also returned in an array.
+func AccumulateRewards(config *chain.Config, header *types.Header, uncles []*types.Header) (uint256.Int, []uint256.Int) {
+ // Select the correct block reward based on chain progression
+ blockReward := FrontierBlockReward
+ if config.IsByzantium(header.Number.Uint64()) {
+ blockReward = ByzantiumBlockReward
+ }
+ if config.IsConstantinople(header.Number.Uint64()) {
+ blockReward = ConstantinopleBlockReward
+ }
+ // Accumulate the rewards for the miner and any included uncles
+ uncleRewards := []uint256.Int{}
+ reward := new(uint256.Int).Set(blockReward)
+ r := new(uint256.Int)
+ headerNum, _ := uint256.FromBig(header.Number)
+ for _, uncle := range uncles {
+ uncleNum, _ := uint256.FromBig(uncle.Number)
+ r.Add(uncleNum, u256.Num8)
+ r.Sub(r, headerNum)
+ r.Mul(r, blockReward)
+ r.Div(r, u256.Num8)
+ uncleRewards = append(uncleRewards, *r)
+
+ r.Div(blockReward, u256.Num32)
+ reward.Add(reward, r)
+ }
+ return *reward, uncleRewards
+}
+
+// accumulateRewards retrieves rewards for a block and applies them to the coinbase accounts for miner and uncle miners
+func accumulateRewards(config *chain.Config, state *state.IntraBlockState, header *types.Header, uncles []*types.Header) {
+ minerReward, uncleRewards := AccumulateRewards(config, header, uncles)
+ for i, uncle := range uncles {
+ if i < len(uncleRewards) {
+ state.AddBalance(uncle.Coinbase, &uncleRewards[i], tracing.BalanceIncreaseRewardMineUncle)
+ }
+ }
+ state.AddBalance(header.Coinbase, &minerReward, tracing.BalanceIncreaseRewardMineBlock)
+}
diff --git a/consensus/ethash/consensus_test.go b/consensus/ethash/consensus_test.go
new file mode 100644
index 00000000000..17e6f0082a9
--- /dev/null
+++ b/consensus/ethash/consensus_test.go
@@ -0,0 +1,202 @@
+// Copyright 2017 The go-ethereum Authors
+// (original work)
+// Copyright 2024 The Erigon Authors
+// (modifications)
+// This file is part of Erigon.
+//
+// Erigon 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.
+//
+// Erigon 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 Erigon. If not, see .
+
+package ethash
+
+import (
+ "encoding/binary"
+ "math/big"
+ "math/rand"
+ "os"
+ "path/filepath"
+ "testing"
+
+ "github.com/goccy/go-json"
+
+ "github.com/erigontech/erigon-lib/chain"
+ libcommon "github.com/erigontech/erigon-lib/common"
+
+ "github.com/erigontech/erigon/common/math"
+ "github.com/erigontech/erigon/core/types"
+ "github.com/erigontech/erigon/params"
+)
+
+type diffTest struct {
+ ParentTimestamp uint64
+ ParentDifficulty *big.Int
+ CurrentTimestamp uint64
+ CurrentBlocknumber *big.Int
+ CurrentDifficulty *big.Int
+}
+
+func (d *diffTest) UnmarshalJSON(b []byte) (err error) {
+ var ext struct {
+ ParentTimestamp string
+ ParentDifficulty string
+ CurrentTimestamp string
+ CurrentBlocknumber string
+ CurrentDifficulty string
+ }
+ if err := json.Unmarshal(b, &ext); err != nil {
+ return err
+ }
+
+ d.ParentTimestamp = math.MustParseUint64(ext.ParentTimestamp)
+ d.ParentDifficulty = math.MustParseBig256(ext.ParentDifficulty)
+ d.CurrentTimestamp = math.MustParseUint64(ext.CurrentTimestamp)
+ d.CurrentBlocknumber = math.MustParseBig256(ext.CurrentBlocknumber)
+ d.CurrentDifficulty = math.MustParseBig256(ext.CurrentDifficulty)
+
+ return nil
+}
+
+func TestCalcDifficulty(t *testing.T) {
+ file, err := os.Open(filepath.Join("..", "..", "tests", "testdata", "BasicTests", "difficulty.json"))
+ if err != nil {
+ t.Skip(err)
+ }
+ defer file.Close()
+
+ tests := make(map[string]diffTest)
+ err = json.NewDecoder(file).Decode(&tests)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ config := &chain.Config{HomesteadBlock: big.NewInt(1150000)}
+
+ for name, test := range tests {
+ number := new(big.Int).Sub(test.CurrentBlocknumber, big.NewInt(1))
+ diff := CalcDifficulty(config, test.CurrentTimestamp,
+ test.ParentTimestamp,
+ test.ParentDifficulty,
+ number.Uint64(),
+ types.EmptyUncleHash,
+ )
+ if diff.Cmp(test.CurrentDifficulty) != 0 {
+ t.Error(name, "failed. Expected", test.CurrentDifficulty, "and calculated", diff)
+ }
+ }
+}
+
+func randSlice(min, max uint32) []byte {
+ var b = make([]byte, 4)
+ rand.Read(b)
+ a := binary.LittleEndian.Uint32(b)
+ size := min + a%(max-min)
+ out := make([]byte, size)
+ rand.Read(out)
+ return out
+}
+
+func TestDifficultyCalculators(t *testing.T) {
+ rand.Seed(2)
+
+ wrap := func(f func(time, parentTime uint64, parentDifficulty *big.Int, parentNumber uint64, parentUncleHash libcommon.Hash) *big.Int) func(time, parentTime uint64, parentDifficulty *big.Int, parentNumber uint64, parentUncleHash libcommon.Hash) *big.Int {
+ return func(time, parentTime uint64, parentDifficulty *big.Int, parentNumber uint64, parentUncleHash libcommon.Hash) *big.Int {
+ return f(time, parentTime, parentDifficulty, parentNumber, parentUncleHash)
+ }
+ }
+ for i := 0; i < 5000; i++ {
+ // 1 to 300 seconds diff
+ var timeDelta = uint64(1 + rand.Uint32()%3000)
+ diffBig := big.NewInt(0).SetBytes(randSlice(2, 10))
+ if diffBig.Cmp(params.MinimumDifficulty) < 0 {
+ diffBig.Set(params.MinimumDifficulty)
+ }
+ //rand.Read(difficulty)
+ header := &types.Header{
+ Difficulty: diffBig,
+ Number: new(big.Int).SetUint64(rand.Uint64() % 50_000_000),
+ Time: rand.Uint64() - timeDelta,
+ }
+ if rand.Uint32()&1 == 0 {
+ header.UncleHash = types.EmptyUncleHash
+ }
+ bombDelay := rand.Uint64() % 50_000_000
+ for i, pair := range []struct {
+ bigFn func(time, parentTime uint64, parentDifficulty *big.Int, parentNumber uint64, uncleHash libcommon.Hash) *big.Int
+ u256Fn func(time uint64, parent *types.Header) *big.Int
+ }{
+ {wrap(FrontierDifficultyCalulator), CalcDifficultyFrontierU256},
+ {wrap(HomesteadDifficultyCalulator), CalcDifficultyHomesteadU256},
+ {DynamicDifficultyCalculator(bombDelay), MakeDifficultyCalculatorU256(bombDelay)},
+ } {
+ time := header.Time + timeDelta
+
+ want := pair.bigFn(time, header.Time, header.Difficulty, header.Number.Uint64(), header.UncleHash)
+ have := pair.u256Fn(time, header)
+ if want.BitLen() > 256 {
+ continue
+ }
+ if want.Cmp(have) != 0 {
+ t.Fatalf("pair %d: want %x have %x\nparent.Number: %x\np.Time: %x\nc.Time: %x\nBombdelay: %v\n", i, want, have,
+ header.Number, header.Time, time, bombDelay)
+ }
+ }
+ }
+}
+
+func BenchmarkDifficultyCalculator(b *testing.B) {
+ x1 := makeDifficultyCalculator(1000000)
+ x2 := MakeDifficultyCalculatorU256(1000000)
+ h := &types.Header{
+ ParentHash: libcommon.Hash{},
+ UncleHash: types.EmptyUncleHash,
+ Difficulty: big.NewInt(0xffffff),
+ Number: big.NewInt(500000),
+ Time: 1000000,
+ }
+ b.Run("big-frontier", func(b *testing.B) {
+ b.ReportAllocs()
+ for i := 0; i < b.N; i++ {
+ calcDifficultyFrontier(1000014, h.Time, h.Difficulty, h.Number.Uint64(), h.UncleHash)
+ }
+ })
+ b.Run("u256-frontier", func(b *testing.B) {
+ b.ReportAllocs()
+ for i := 0; i < b.N; i++ {
+ CalcDifficultyFrontierU256(1000014, h)
+ }
+ })
+ b.Run("big-homestead", func(b *testing.B) {
+ b.ReportAllocs()
+ for i := 0; i < b.N; i++ {
+ calcDifficultyHomestead(1000014, h.Time, h.Difficulty, h.Number.Uint64(), h.UncleHash)
+ }
+ })
+ b.Run("u256-homestead", func(b *testing.B) {
+ b.ReportAllocs()
+ for i := 0; i < b.N; i++ {
+ CalcDifficultyHomesteadU256(1000014, h)
+ }
+ })
+ b.Run("big-generic", func(b *testing.B) {
+ b.ReportAllocs()
+ for i := 0; i < b.N; i++ {
+ x1(1000014, h.Time, h.Difficulty, h.Number.Uint64(), h.UncleHash)
+ }
+ })
+ b.Run("u256-generic", func(b *testing.B) {
+ b.ReportAllocs()
+ for i := 0; i < b.N; i++ {
+ x2(1000014, h)
+ }
+ })
+}
diff --git a/consensus/ethash/difficulty.go b/consensus/ethash/difficulty.go
new file mode 100644
index 00000000000..51b5e3c9020
--- /dev/null
+++ b/consensus/ethash/difficulty.go
@@ -0,0 +1,197 @@
+// Copyright 2020 The go-ethereum Authors
+// (original work)
+// Copyright 2024 The Erigon Authors
+// (modifications)
+// This file is part of Erigon.
+//
+// Erigon 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.
+//
+// Erigon 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 Erigon. If not, see .
+
+package ethash
+
+import (
+ "math/big"
+
+ "github.com/holiman/uint256"
+
+ "github.com/erigontech/erigon/core/types"
+)
+
+const (
+ // frontierDurationLimit is for Frontier:
+ // The decision boundary on the blocktime duration used to determine
+ // whether difficulty should go up or down.
+ frontierDurationLimit = 13
+ // minimumDifficulty The minimum that the difficulty may ever be.
+ minimumDifficulty = 131072
+ // expDiffPeriod is the exponential difficulty period
+ expDiffPeriodUint = 100000
+ // difficultyBoundDivisorBitShift is the bound divisor of the difficulty (2048),
+ // This constant is the right-shifts to use for the division.
+ difficultyBoundDivisor = 11
+)
+
+// CalcDifficultyFrontierU256 is the difficulty adjustment algorithm. It returns the
+// difficulty that a new block should have when created at time given the parent
+// block's time and difficulty. The calculation uses the Frontier rules.
+func CalcDifficultyFrontierU256(time uint64, parent *types.Header) *big.Int {
+ /*
+ Algorithm
+ block_diff = pdiff + pdiff / 2048 * (1 if time - ptime < 13 else -1) + int(2^((num // 100000) - 2))
+
+ Where:
+ - pdiff = parent.difficulty
+ - ptime = parent.time
+ - time = block.timestamp
+ - num = block.number
+ */
+
+ pDiff := uint256.NewInt(0)
+ pDiff.SetFromBig(parent.Difficulty) // pDiff: pdiff
+ adjust := pDiff.Clone()
+ adjust.Rsh(adjust, difficultyBoundDivisor) // adjust: pDiff / 2048
+
+ if time-parent.Time < frontierDurationLimit {
+ pDiff.Add(pDiff, adjust)
+ } else {
+ pDiff.Sub(pDiff, adjust)
+ }
+ if pDiff.LtUint64(minimumDifficulty) {
+ pDiff.SetUint64(minimumDifficulty)
+ }
+ // 'pdiff' now contains:
+ // pdiff + pdiff / 2048 * (1 if time - ptime < 13 else -1)
+
+ if periodCount := (parent.Number.Uint64() + 1) / expDiffPeriodUint; periodCount > 1 {
+ // diff = diff + 2^(periodCount - 2)
+ expDiff := adjust.SetOne()
+ expDiff.Lsh(expDiff, uint(periodCount-2)) // expdiff: 2 ^ (periodCount -2)
+ pDiff.Add(pDiff, expDiff)
+ }
+ return pDiff.ToBig()
+}
+
+// CalcDifficultyHomesteadU256 is the difficulty adjustment algorithm. It returns
+// the difficulty that a new block should have when created at time given the
+// parent block's time and difficulty. The calculation uses the Homestead rules.
+func CalcDifficultyHomesteadU256(time uint64, parent *types.Header) *big.Int {
+ /*
+ https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2.md
+ Algorithm:
+ block_diff = pdiff + pdiff / 2048 * max(1 - (time - ptime) / 10, -99) + 2 ^ int((num / 100000) - 2))
+
+ Our modification, to use unsigned ints:
+ block_diff = pdiff - pdiff / 2048 * max((time - ptime) / 10 - 1, 99) + 2 ^ int((num / 100000) - 2))
+
+ Where:
+ - pdiff = parent.difficulty
+ - ptime = parent.time
+ - time = block.timestamp
+ - num = block.number
+ */
+
+ pDiff := uint256.NewInt(0)
+ pDiff.SetFromBig(parent.Difficulty) // pDiff: pdiff
+ adjust := pDiff.Clone()
+ adjust.Rsh(adjust, difficultyBoundDivisor) // adjust: pDiff / 2048
+
+ x := (time - parent.Time) / 10 // (time - ptime) / 10)
+ var neg = true
+ if x == 0 {
+ x = 1
+ neg = false
+ } else if x >= 100 {
+ x = 99
+ } else {
+ x = x - 1
+ }
+ z := new(uint256.Int).SetUint64(x)
+ adjust.Mul(adjust, z) // adjust: (pdiff / 2048) * max((time - ptime) / 10 - 1, 99)
+ if neg {
+ pDiff.Sub(pDiff, adjust) // pdiff - pdiff / 2048 * max((time - ptime) / 10 - 1, 99)
+ } else {
+ pDiff.Add(pDiff, adjust) // pdiff + pdiff / 2048 * max((time - ptime) / 10 - 1, 99)
+ }
+ if pDiff.LtUint64(minimumDifficulty) {
+ pDiff.SetUint64(minimumDifficulty)
+ }
+ // for the exponential factor, a.k.a "the bomb"
+ // diff = diff + 2^(periodCount - 2)
+ if periodCount := (1 + parent.Number.Uint64()) / expDiffPeriodUint; periodCount > 1 {
+ expFactor := adjust.Lsh(adjust.SetOne(), uint(periodCount-2))
+ pDiff.Add(pDiff, expFactor)
+ }
+ return pDiff.ToBig()
+}
+
+// MakeDifficultyCalculatorU256 creates a difficultyCalculator with the given bomb-delay.
+// the difficulty is calculated with Byzantium rules, which differs from Homestead in
+// how uncles affect the calculation
+func MakeDifficultyCalculatorU256(bombDelay uint64) func(time uint64, parent *types.Header) *big.Int {
+ // Note, the calculations below looks at the parent number, which is 1 below
+ // the block number. Thus we remove one from the delay given
+ bombDelayFromParent := bombDelay - 1
+ return func(time uint64, parent *types.Header) *big.Int {
+ /*
+ https://github.com/ethereum/EIPs/issues/100
+ pDiff = parent.difficulty
+ BLOCK_DIFF_FACTOR = 9
+ a = pDiff + (pDiff // BLOCK_DIFF_FACTOR) * adj_factor
+ b = min(parent.difficulty, MIN_DIFF)
+ child_diff = max(a,b )
+ */
+ x := (time - parent.Time) / 9 // (block_timestamp - parent_timestamp) // 9
+ c := uint64(1) // if parent.unclehash == emptyUncleHashHash
+ if parent.UncleHash != types.EmptyUncleHash {
+ c = 2
+ }
+ xNeg := x >= c
+ if xNeg {
+ // x is now _negative_ adjustment factor
+ x = x - c // - ( (t-p)/p -( 2 or 1) )
+ } else {
+ x = c - x // (2 or 1) - (t-p)/9
+ }
+ if x > 99 {
+ x = 99 // max(x, 99)
+ }
+ // parent_diff + (parent_diff / 2048 * max((2 if len(parent.uncles) else 1) - ((timestamp - parent.timestamp) // 9), -99))
+ y := new(uint256.Int)
+ y.SetFromBig(parent.Difficulty) // y: p_diff
+ pDiff := y.Clone() // pdiff: p_diff
+ z := new(uint256.Int).SetUint64(x) //z : +-adj_factor (either pos or negative)
+ y.Rsh(y, difficultyBoundDivisor) // y: p__diff / 2048
+ z.Mul(y, z) // z: (p_diff / 2048 ) * (+- adj_factor)
+
+ if xNeg {
+ y.Sub(pDiff, z) // y: parent_diff + parent_diff/2048 * adjustment_factor
+ } else {
+ y.Add(pDiff, z) // y: parent_diff + parent_diff/2048 * adjustment_factor
+ }
+ // minimum difficulty can ever be (before exponential factor)
+ if y.LtUint64(minimumDifficulty) {
+ y.SetUint64(minimumDifficulty)
+ }
+ // calculate a fake block number for the ice-age delay
+ // Specification: https://eips.ethereum.org/EIPS/eip-1234
+ var pNum = parent.Number.Uint64()
+ if pNum >= bombDelayFromParent {
+ if fakeBlockNumber := pNum - bombDelayFromParent; fakeBlockNumber >= 2*expDiffPeriodUint {
+ z.SetOne()
+ z.Lsh(z, uint(fakeBlockNumber/expDiffPeriodUint-2))
+ y.Add(z, y)
+ }
+ }
+ return y.ToBig()
+ }
+}
diff --git a/consensus/ethash/ethash.go b/consensus/ethash/ethash.go
new file mode 100644
index 00000000000..00beccf4941
--- /dev/null
+++ b/consensus/ethash/ethash.go
@@ -0,0 +1,588 @@
+// Copyright 2017 The go-ethereum Authors
+// (original work)
+// Copyright 2024 The Erigon Authors
+// (modifications)
+// This file is part of Erigon.
+//
+// Erigon 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.
+//
+// Erigon 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 Erigon. If not, see .
+
+// Package ethash implements the ethash proof-of-work consensus engine.
+package ethash
+
+import (
+ "errors"
+ "fmt"
+ "math/big"
+ "math/rand"
+ "os"
+ "path/filepath"
+ "reflect"
+ "runtime"
+ "strconv"
+ "sync"
+ "sync/atomic"
+ "unsafe"
+
+ "github.com/edsrzf/mmap-go"
+ "github.com/hashicorp/golang-lru/v2/simplelru"
+
+ "github.com/erigontech/erigon-lib/log/v3"
+
+ "github.com/erigontech/erigon/common/debug"
+ cmath "github.com/erigontech/erigon/common/math"
+ "github.com/erigontech/erigon/consensus"
+ "github.com/erigontech/erigon/consensus/ethash/ethashcfg"
+ "github.com/erigontech/erigon/core/vm/evmtypes"
+ "github.com/erigontech/erigon/rpc"
+)
+
+const doNotStoreCachesOnDisk = ""
+
+var ErrInvalidDumpMagic = errors.New("invalid dump magic")
+
+var (
+ // two256 is a big integer representing 2^256
+ two256 = new(big.Int).Exp(big.NewInt(2), big.NewInt(256), big.NewInt(0))
+
+ // sharedEthash is a full instance that can be shared between multiple users.
+ sharedEthashOnce sync.Once
+ sharedEthash *Ethash
+
+ // algorithmRevision is the data structure version used for file naming.
+ algorithmRevision = 23
+
+ // dumpMagic is a dataset dump header to sanity check a data dump.
+ dumpMagic = []uint32{0xbaddcafe, 0xfee1dead}
+)
+
+// sharedEthash is a full instance that can be shared between multiple users.
+func GetSharedEthash() *Ethash {
+ sharedEthashOnce.Do(func() {
+ sharedConfig := ethashcfg.Config{
+ PowMode: ethashcfg.ModeNormal,
+ CachesInMem: 3,
+ DatasetsInMem: 1,
+ }
+ sharedEthash = New(sharedConfig, nil, false)
+ })
+ return sharedEthash
+}
+
+// isLittleEndian returns whether the local system is running in little or big
+// endian byte order.
+func isLittleEndian() bool {
+ n := uint32(0x01020304)
+ return *(*byte)(unsafe.Pointer(&n)) == 0x04
+}
+
+// memoryMap tries to memory map a file of uint32s for read only access.
+func memoryMap(path string, lock bool) (*os.File, mmap.MMap, []uint32, error) {
+ file, err := os.OpenFile(path, os.O_RDONLY, 0644)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+ mem, buffer, err := memoryMapFile(file, false)
+ if err != nil {
+ file.Close()
+ return nil, nil, nil, err
+ }
+ for i, magic := range dumpMagic {
+ if buffer[i] != magic {
+ mem.Unmap()
+ file.Close()
+ return nil, nil, nil, ErrInvalidDumpMagic
+ }
+ }
+ if lock {
+ if err2 := mem.Lock(); err2 != nil {
+ mem.Unmap() //nolint:errcheck
+ file.Close()
+ return nil, nil, nil, err2
+ }
+ }
+ return file, mem, buffer[len(dumpMagic):], err
+}
+
+// memoryMapFile tries to memory map an already opened file descriptor.
+func memoryMapFile(file *os.File, write bool) (mmap.MMap, []uint32, error) {
+ // Try to memory map the file
+ flag := mmap.RDONLY
+ if write {
+ flag = mmap.RDWR
+ }
+ mem, err := mmap.Map(file, flag, 0)
+ if err != nil {
+ return nil, nil, err
+ }
+ // The file is now memory-mapped. Create a []uint32 view of the file.
+ var view []uint32
+ header := (*reflect.SliceHeader)(unsafe.Pointer(&view))
+ header.Data = (*reflect.SliceHeader)(unsafe.Pointer(&mem)).Data
+ header.Len /= 4
+ header.Cap /= 4
+
+ return mem, view, nil
+}
+
+// memoryMapAndGenerate tries to memory map a temporary file of uint32s for write
+// access, fill it with the data from a generator and then move it into the final
+// path requested.
+func memoryMapAndGenerate(path string, size uint64, lock bool, generator func(buffer []uint32)) (*os.File, mmap.MMap, []uint32, error) {
+ // Ensure the data folder exists
+ if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
+ return nil, nil, nil, err
+ }
+ suffix, err := cmath.RandInt64()
+ if err != nil {
+ return nil, nil, nil, fmt.Errorf("failed to get random integer: %v", err)
+ }
+ // Create a huge temporary empty file to fill with data
+ temp := path + "." + strconv.Itoa(int(suffix))
+
+ dump, err := os.Create(temp)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+ if err = dump.Truncate(int64(len(dumpMagic))*4 + int64(size)); err != nil {
+ return nil, nil, nil, err
+ }
+ // Memory map the file for writing and fill it with the generator
+ mem, buffer, err := memoryMapFile(dump, true)
+ if err != nil {
+ dump.Close()
+ return nil, nil, nil, err
+ }
+ copy(buffer, dumpMagic)
+
+ data := buffer[len(dumpMagic):]
+ generator(data)
+
+ if err := mem.Unmap(); err != nil {
+ return nil, nil, nil, err
+ }
+ if err := dump.Close(); err != nil {
+ return nil, nil, nil, err
+ }
+ if err := os.Rename(temp, path); err != nil {
+ return nil, nil, nil, err
+ }
+ return memoryMap(path, lock)
+}
+
+// lru tracks caches or datasets by their last use time, keeping at most N of them.
+type lru struct {
+ what string
+ new func(epoch uint64) interface{}
+ mu sync.Mutex
+ // Items are kept in a LRU cache, but there is a special case:
+ // We always keep an item for (highest seen epoch) + 1 as the 'future item'.
+ cache *simplelru.LRU[uint64, any]
+ future uint64
+ futureItem interface{}
+}
+
+// newlru create a new least-recently-used cache for either the verification caches
+// or the mining datasets.
+func newlru(what string, maxItems int, new func(epoch uint64) interface{}) *lru {
+ if maxItems <= 0 {
+ maxItems = 1
+ }
+ cache, _ := simplelru.NewLRU[uint64, any](maxItems, func(key uint64, value interface{}) {
+ log.Trace("Evicted ethash "+what, "epoch", key)
+ })
+ return &lru{what: what, new: new, cache: cache}
+}
+
+// get retrieves or creates an item for the given epoch. The first return value is always
+// non-nil. The second return value is non-nil if lru thinks that an item will be useful in
+// the near future.
+func (lru *lru) get(epoch uint64) (item, future interface{}) {
+ lru.mu.Lock()
+ defer lru.mu.Unlock()
+
+ // Get or create the item for the requested epoch.
+ item, ok := lru.cache.Get(epoch)
+ if !ok {
+ if lru.future > 0 && lru.future == epoch {
+ item = lru.futureItem
+ } else {
+ log.Trace("Requiring new ethash "+lru.what, "epoch", epoch)
+ item = lru.new(epoch)
+ }
+ lru.cache.Add(epoch, item)
+ }
+ // Update the 'future item' if epoch is larger than previously seen.
+ if epoch < maxEpoch-1 && lru.future < epoch+1 {
+ log.Trace("Requiring new future ethash "+lru.what, "epoch", epoch+1)
+ future = lru.new(epoch + 1)
+ lru.future = epoch + 1
+ lru.futureItem = future
+ }
+ return item, future
+}
+
+// cache wraps an ethash cache with some metadata to allow easier concurrent use.
+type cache struct {
+ epoch uint64 // Epoch for which this cache is relevant
+ dump *os.File // File descriptor of the memory mapped cache
+ mmap mmap.MMap // Memory map itself to unmap before releasing
+ cache []uint32 // The actual cache data content (may be memory mapped)
+ once sync.Once // Ensures the cache is generated only once
+}
+
+// newCache creates a new ethash verification cache and returns it as a plain Go
+// interface to be usable in an LRU cache.
+func newCache(epoch uint64) interface{} {
+ return &cache{epoch: epoch}
+}
+
+// generate ensures that the cache content is generated before use.
+func (c *cache) generate(dir string, limit int, lock bool, test bool) {
+ c.once.Do(func() {
+ defer debug.LogPanic()
+ size := cacheSize(c.epoch*epochLength + 1)
+ seed := seedHash(c.epoch*epochLength + 1)
+ if test {
+ size = 1024
+ }
+ // If we don't store anything on disk, generate and return.
+ if dir == "" {
+ c.cache = make([]uint32, size/4)
+ generateCache(c.cache, c.epoch, seed)
+ return
+ }
+ // Disk storage is needed, this will get fancy
+ var endian string
+ if !isLittleEndian() {
+ endian = ".be"
+ }
+ path := filepath.Join(dir, fmt.Sprintf("cache-R%d-%x%s", algorithmRevision, seed[:8], endian))
+ logger := log.New("epoch", c.epoch)
+
+ // We're about to mmap the file, ensure that the mapping is cleaned up when the
+ // cache becomes unused.
+ runtime.SetFinalizer(c, (*cache).finalizer)
+
+ // Try to load the file from disk and memory map it
+ var err error
+ c.dump, c.mmap, c.cache, err = memoryMap(path, lock)
+ if err == nil {
+ logger.Debug("Loaded old ethash cache from disk")
+ return
+ }
+ logger.Debug("Failed to load old ethash cache", "err", err)
+
+ // No previous cache available, create a new cache file to fill
+ c.dump, c.mmap, c.cache, err = memoryMapAndGenerate(path, size, lock, func(buffer []uint32) { generateCache(buffer, c.epoch, seed) })
+ if err != nil {
+ logger.Error("Failed to generate mapped ethash cache", "err", err)
+
+ c.cache = make([]uint32, size/4)
+ generateCache(c.cache, c.epoch, seed)
+ }
+ // Iterate over all previous instances and delete old ones
+ for ep := int(c.epoch) - limit; ep >= 0; ep-- {
+ seed := seedHash(uint64(ep)*epochLength + 1)
+ path := filepath.Join(dir, fmt.Sprintf("cache-R%d-%x%s", algorithmRevision, seed[:8], endian))
+ os.Remove(path)
+ }
+ })
+}
+
+// finalizer unmaps the memory and closes the file.
+func (c *cache) finalizer() {
+ if c.mmap != nil {
+ c.mmap.Unmap()
+ c.dump.Close()
+ c.mmap, c.dump = nil, nil
+ }
+}
+
+// dataset wraps an ethash dataset with some metadata to allow easier concurrent use.
+type dataset struct {
+ epoch uint64 // Epoch for which this cache is relevant
+ dump *os.File // File descriptor of the memory mapped cache
+ mmap mmap.MMap // Memory map itself to unmap before releasing
+ dataset []uint32 // The actual cache data content
+ once sync.Once // Ensures the cache is generated only once
+ done uint32 // Atomic flag to determine generation status
+}
+
+// newDataset creates a new ethash mining dataset and returns it as a plain Go
+// interface to be usable in an LRU cache.
+func newDataset(epoch uint64) interface{} {
+ return &dataset{epoch: epoch}
+}
+
+// generate ensures that the dataset content is generated before use.
+func (d *dataset) generate(dir string, limit int, lock bool, test bool) {
+ d.once.Do(func() {
+ // Mark the dataset generated after we're done. This is needed for remote
+ defer atomic.StoreUint32(&d.done, 1)
+
+ csize := cacheSize(d.epoch*epochLength + 1)
+ dsize := datasetSize(d.epoch*epochLength + 1)
+ seed := seedHash(d.epoch*epochLength + 1)
+ if test {
+ csize = 1024
+ dsize = 32 * 1024
+ }
+ // If we don't store anything on disk, generate and return
+ if dir == "" {
+ cache := make([]uint32, csize/4)
+ generateCache(cache, d.epoch, seed)
+
+ d.dataset = make([]uint32, dsize/4)
+ generateDataset(d.dataset, d.epoch, cache)
+
+ return
+ }
+ // Disk storage is needed, this will get fancy
+ var endian string
+ if !isLittleEndian() {
+ endian = ".be"
+ }
+ path := filepath.Join(dir, fmt.Sprintf("full-R%d-%x%s", algorithmRevision, seed[:8], endian))
+ logger := log.New("epoch", d.epoch)
+
+ // We're about to mmap the file, ensure that the mapping is cleaned up when the
+ // cache becomes unused.
+ runtime.SetFinalizer(d, (*dataset).finalizer)
+
+ // Try to load the file from disk and memory map it
+ var err error
+ d.dump, d.mmap, d.dataset, err = memoryMap(path, lock)
+ if err == nil {
+ logger.Debug("Loaded old ethash dataset from disk")
+ return
+ }
+ logger.Debug("Failed to load old ethash dataset", "err", err)
+
+ // No previous dataset available, create a new dataset file to fill
+ cache := make([]uint32, csize/4)
+ generateCache(cache, d.epoch, seed)
+
+ d.dump, d.mmap, d.dataset, err = memoryMapAndGenerate(path, dsize, lock, func(buffer []uint32) { generateDataset(buffer, d.epoch, cache) })
+ if err != nil {
+ logger.Error("Failed to generate mapped ethash dataset", "err", err)
+
+ d.dataset = make([]uint32, dsize/2)
+ generateDataset(d.dataset, d.epoch, cache)
+ }
+ // Iterate over all previous instances and delete old ones
+ for ep := int(d.epoch) - limit; ep >= 0; ep-- {
+ seed := seedHash(uint64(ep)*epochLength + 1)
+ path := filepath.Join(dir, fmt.Sprintf("full-R%d-%x%s", algorithmRevision, seed[:8], endian))
+ os.Remove(path)
+ }
+ })
+}
+
+// generated returns whether this particular dataset finished generating already
+// or not (it may not have been started at all). This is useful for remote miners
+// to default to verification caches instead of blocking on DAG generations.
+func (d *dataset) generated() bool {
+ return atomic.LoadUint32(&d.done) == 1
+}
+
+// finalizer closes any file handlers and memory maps open.
+func (d *dataset) finalizer() {
+ if d.mmap != nil {
+ d.mmap.Unmap()
+ d.dump.Close()
+ d.mmap, d.dump = nil, nil
+ }
+}
+
+// Ethash is a consensus engine based on proof-of-work implementing the ethash
+// algorithm.
+type Ethash struct {
+ config ethashcfg.Config
+
+ caches *lru // In memory caches to avoid regenerating too often
+ datasets *lru // In memory datasets to avoid regenerating too often
+
+ // Mining related fields
+ rand *rand.Rand // Properly seeded random source for nonces
+ hashrate *hashRateMeter // Meter tracking the average hashrate
+ remote *remoteSealer
+
+ // The fields below are hooks for testing
+ shared *Ethash // Shared PoW verifier to avoid cache regeneration
+
+ lock sync.Mutex // Ensures thread safety for the in-memory caches and mining fields
+ closeOnce sync.Once // Ensures exit channel will not be closed twice.
+}
+
+// New creates a full sized ethash PoW scheme and starts a background thread for
+// remote mining, also optionally notifying a batch of remote services of new work
+// packages.
+func New(config ethashcfg.Config, notify []string, noverify bool) *Ethash {
+ if config.Log == nil {
+ config.Log = log.Root()
+ }
+ if config.CachesInMem <= 0 {
+ config.Log.Warn("One ethash cache must always be in memory", "requested", config.CachesInMem)
+ config.CachesInMem = 1
+ }
+ if config.DatasetDir != "" && config.DatasetsOnDisk > 0 {
+ config.Log.Info("Disk storage enabled for ethash DAGs", "dir", config.DatasetDir, "count", config.DatasetsOnDisk)
+ }
+ ethash := &Ethash{
+ config: config,
+ caches: newlru("cache", config.CachesInMem, newCache),
+ datasets: newlru("dataset", config.DatasetsInMem, newDataset),
+ hashrate: newHashRateMeter(),
+ }
+ if config.PowMode == ethashcfg.ModeShared {
+ ethash.shared = GetSharedEthash()
+ }
+ ethash.remote = startRemoteSealer(ethash, notify, noverify)
+ return ethash
+}
+
+// NewTester creates a small sized ethash PoW scheme useful only for testing
+// purposes.
+func NewTester(notify []string, noverify bool) *Ethash {
+ return New(ethashcfg.Config{PowMode: ethashcfg.ModeTest}, notify, noverify)
+}
+
+// NewShared creates a full sized ethash PoW shared between all requesters running
+// in the same process.
+func NewShared() *Ethash {
+ return &Ethash{shared: GetSharedEthash()}
+}
+
+// Close closes the exit channel to notify all backend threads exiting.
+func (ethash *Ethash) Close() error {
+ ethash.closeOnce.Do(func() {
+ // Short circuit if the exit channel is not allocated.
+ if ethash.remote == nil {
+ return
+ }
+ close(ethash.remote.requestExit)
+ <-ethash.remote.exitCh
+ })
+ return nil
+}
+
+// cache tries to retrieve a verification cache for the specified block number
+// by first checking against a list of in-memory caches, then against caches
+// stored on disk, and finally generating one if none can be found.
+func (ethash *Ethash) cache(block uint64) *cache {
+ epoch := block / epochLength
+ currentI, futureI := ethash.caches.get(epoch)
+ current := currentI.(*cache)
+
+ // Wait for generation finish.
+ current.generate(doNotStoreCachesOnDisk, 0, ethash.config.CachesLockMmap, ethash.config.PowMode == ethashcfg.ModeTest)
+
+ // If we need a new future cache, now's a good time to regenerate it.
+ if futureI != nil {
+ future := futureI.(*cache)
+ go future.generate(doNotStoreCachesOnDisk, 0, ethash.config.CachesLockMmap, ethash.config.PowMode == ethashcfg.ModeTest)
+ }
+ return current
+}
+
+// dataset tries to retrieve a mining dataset for the specified block number
+// by first checking against a list of in-memory datasets, then against DAGs
+// stored on disk, and finally generating one if none can be found.
+//
+// If async is specified, not only the future but the current DAG is also
+// generates on a background thread.
+func (ethash *Ethash) dataset(block uint64, async bool) *dataset {
+ // Retrieve the requested ethash dataset
+ epoch := block / epochLength
+ currentI, futureI := ethash.datasets.get(epoch)
+ current := currentI.(*dataset)
+
+ // If async is specified, generate everything in a background thread
+ if async && !current.generated() {
+ go func() {
+ defer debug.LogPanic()
+ current.generate(ethash.config.DatasetDir, ethash.config.DatasetsOnDisk, ethash.config.DatasetsLockMmap, ethash.config.PowMode == ethashcfg.ModeTest)
+
+ if futureI != nil {
+ future := futureI.(*dataset)
+ future.generate(ethash.config.DatasetDir, ethash.config.DatasetsOnDisk, ethash.config.DatasetsLockMmap, ethash.config.PowMode == ethashcfg.ModeTest)
+ }
+ }()
+ } else {
+ // Either blocking generation was requested, or already done
+ current.generate(ethash.config.DatasetDir, ethash.config.DatasetsOnDisk, ethash.config.DatasetsLockMmap, ethash.config.PowMode == ethashcfg.ModeTest)
+
+ if futureI != nil {
+ future := futureI.(*dataset)
+ go future.generate(ethash.config.DatasetDir, ethash.config.DatasetsOnDisk, ethash.config.DatasetsLockMmap, ethash.config.PowMode == ethashcfg.ModeTest)
+ }
+ }
+ return current
+}
+
+// Hashrate implements PoW, returning the measured rate of the search invocations
+// per second over the last minute.
+// Note the returned hashrate includes local hashrate, but also includes the total
+// hashrate of all remote miner.
+func (ethash *Ethash) Hashrate() float64 {
+ // Short circuit if we are run the ethash in normal/test mode.
+ if (ethash.config.PowMode != ethashcfg.ModeNormal && ethash.config.PowMode != ethashcfg.ModeTest) || ethash.remote == nil {
+ return ethash.hashrate.Rate()
+ }
+ var res = make(chan uint64, 1)
+
+ select {
+ case ethash.remote.fetchRateCh <- res:
+ case <-ethash.remote.exitCh:
+ // Return local hashrate only if ethash is stopped.
+ return ethash.hashrate.Rate()
+ }
+
+ // Gather total submitted hash rate of remote sealers.
+ return ethash.hashrate.Rate() + float64(<-res)
+}
+
+// APIs implements consensus.Engine, returning the user facing RPC APIs.
+func (ethash *Ethash) APIs(chain consensus.ChainHeaderReader) []rpc.API {
+ // In order to ensure backward compatibility, we exposes ethash RPC APIs
+ // to both eth and ethash namespaces.
+ return []rpc.API{
+ {
+ Namespace: "eth",
+ Version: "1.0",
+ Service: &API{ethash},
+ Public: true,
+ },
+ {
+ Namespace: "ethash",
+ Version: "1.0",
+ Service: &API{ethash},
+ Public: true,
+ },
+ }
+}
+
+// SeedHash is the seed to use for generating a verification cache and the mining
+// dataset.
+func SeedHash(block uint64) []byte {
+ return seedHash(block)
+}
+
+func (ethash *Ethash) GetTransferFunc() evmtypes.TransferFunc {
+ return consensus.Transfer
+}
+
+func (ethash *Ethash) GetPostApplyMessageFunc() evmtypes.PostApplyMessageFunc {
+ return nil
+}
diff --git a/consensus/ethash/ethash_test.go b/consensus/ethash/ethash_test.go
new file mode 100644
index 00000000000..9f6dc18217a
--- /dev/null
+++ b/consensus/ethash/ethash_test.go
@@ -0,0 +1,114 @@
+// Copyright 2017 The go-ethereum Authors
+// (original work)
+// Copyright 2024 The Erigon Authors
+// (modifications)
+// This file is part of Erigon.
+//
+// Erigon 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.
+//
+// Erigon 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 Erigon. If not, see .
+
+package ethash
+
+import (
+ "math/big"
+ "testing"
+ "time"
+
+ libcommon "github.com/erigontech/erigon-lib/common"
+ "github.com/erigontech/erigon-lib/common/hexutil"
+
+ "github.com/erigontech/erigon/core/types"
+)
+
+func TestRemoteSealer(t *testing.T) {
+ ethash := NewTester(nil, false)
+ defer ethash.Close()
+
+ api := &API{ethash}
+ if _, err := api.GetWork(); err != errNoMiningWork {
+ t.Error("expect to return an error indicate there is no mining work")
+ }
+ header := &types.Header{Number: big.NewInt(1), Difficulty: big.NewInt(100)}
+ block := types.NewBlockWithHeader(header)
+ blockWithReceipts := &types.BlockWithReceipts{Block: block}
+ sealhash := ethash.SealHash(header)
+
+ // Push new work.
+ results := make(chan *types.BlockWithReceipts)
+ if err := ethash.Seal(nil, blockWithReceipts, results, nil); err != nil {
+ t.Fatal(err)
+ }
+ var (
+ work [4]string
+ err error
+ )
+ if work, err = api.GetWork(); err != nil || work[0] != sealhash.Hex() {
+ t.Error("expect to return a mining work has same hash")
+ }
+
+ if res := api.SubmitWork(types.BlockNonce{}, sealhash, libcommon.Hash{}); res {
+ t.Error("expect to return false when submit a fake solution")
+ }
+ // Push new block with same block number to replace the original one.
+ header = &types.Header{Number: big.NewInt(1), Difficulty: big.NewInt(1000)}
+ block = types.NewBlockWithHeader(header)
+ blockWithReceipts = &types.BlockWithReceipts{Block: block}
+ sealhash = ethash.SealHash(header)
+ err = ethash.Seal(nil, blockWithReceipts, results, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if work, err = api.GetWork(); err != nil || work[0] != sealhash.Hex() {
+ t.Error("expect to return the latest pushed work")
+ }
+}
+
+func TestHashRate(t *testing.T) {
+ var (
+ hashrate = []hexutil.Uint64{100, 200, 300}
+ expect uint64
+ ids = []libcommon.Hash{libcommon.HexToHash("a"), libcommon.HexToHash("b"), libcommon.HexToHash("c")}
+ )
+ ethash := NewTester(nil, false)
+ defer ethash.Close()
+
+ if tot := ethash.Hashrate(); tot != 0 {
+ t.Error("expect the result should be zero")
+ }
+
+ api := &API{ethash}
+ for i := 0; i < len(hashrate); i += 1 {
+ if res := api.SubmitHashRate(hashrate[i], ids[i]); !res {
+ t.Error("remote miner submit hashrate failed")
+ }
+ expect += uint64(hashrate[i])
+ }
+ if tot := ethash.Hashrate(); tot != float64(expect) {
+ t.Error("expect total hashrate should be same")
+ }
+}
+
+func TestClosedRemoteSealer(t *testing.T) {
+ ethash := NewTester(nil, false)
+ time.Sleep(1 * time.Second) // ensure exit channel is listening
+ _ = ethash.Close()
+
+ api := &API{ethash}
+ if _, err := api.GetWork(); err != errEthashStopped {
+ t.Error("expect to return an error to indicate ethash is stopped")
+ }
+
+ if res := api.SubmitHashRate(hexutil.Uint64(100), libcommon.HexToHash("a")); res {
+ t.Error("expect to return false when submit hashrate to a stopped ethash")
+ }
+}
diff --git a/consensus/ethash/ethashcfg/ethashcfg.go b/consensus/ethash/ethashcfg/ethashcfg.go
new file mode 100644
index 00000000000..976c12905b3
--- /dev/null
+++ b/consensus/ethash/ethashcfg/ethashcfg.go
@@ -0,0 +1,50 @@
+// Copyright 2024 The Erigon Authors
+// This file is part of Erigon.
+//
+// Erigon 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.
+//
+// Erigon 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 Erigon. If not, see .
+
+package ethashcfg
+
+import (
+ "github.com/erigontech/erigon-lib/log/v3"
+)
+
+// Config are the configuration parameters of the ethash.
+type Config struct {
+ CachesInMem int
+ CachesLockMmap bool
+ DatasetDir string
+ DatasetsInMem int
+ DatasetsOnDisk int
+ DatasetsLockMmap bool
+ PowMode Mode
+
+ // When set, notifications sent by the remote sealer will
+ // be block header JSON objects instead of work package arrays.
+ NotifyFull bool
+
+ Log log.Logger `toml:"-"`
+}
+
+// Mode defines the type and amount of PoW verification an ethash engine makes.
+type Mode uint
+
+const (
+ ModeNormal Mode = iota
+ ModeShared
+ ModeTest
+
+ ModeFake
+ ModeFullFake
+)
diff --git a/consensus/ethash/fake.go b/consensus/ethash/fake.go
new file mode 100644
index 00000000000..4c60400c744
--- /dev/null
+++ b/consensus/ethash/fake.go
@@ -0,0 +1,170 @@
+// Copyright 2024 The Erigon Authors
+// This file is part of Erigon.
+//
+// Erigon 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.
+//
+// Erigon 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 Erigon. If not, see .
+
+package ethash
+
+import (
+ "time"
+
+ mapset "github.com/deckarep/golang-set/v2"
+
+ libcommon "github.com/erigontech/erigon-lib/common"
+ "github.com/erigontech/erigon-lib/log/v3"
+
+ "github.com/erigontech/erigon/consensus"
+ "github.com/erigontech/erigon/consensus/ethash/ethashcfg"
+ "github.com/erigontech/erigon/core/types"
+)
+
+type FakeEthash struct {
+ Ethash
+ fakeFail uint64 // Block number which fails PoW check even in fake mode
+ fakeDelay time.Duration // Time delay to sleep for before returning from verify
+}
+
+// NewFakeFailer creates a ethash consensus engine with a fake PoW scheme that
+// accepts all blocks as valid apart from the single one specified, though they
+// still have to conform to the Ethereum consensus rules.
+func NewFakeFailer(fail uint64) *FakeEthash {
+ return &FakeEthash{
+ Ethash: newFakeEth(ethashcfg.ModeFake),
+ fakeFail: fail,
+ }
+}
+
+// NewFaker creates a ethash consensus engine with a fake PoW scheme that accepts
+// all blocks' seal as valid, though they still have to conform to the Ethereum
+// consensus rules.
+func NewFaker() *FakeEthash {
+ return &FakeEthash{
+ Ethash: newFakeEth(ethashcfg.ModeFake),
+ }
+}
+
+// NewFakeDelayer creates a ethash consensus engine with a fake PoW scheme that
+// accepts all blocks as valid, but delays verifications by some time, though
+// they still have to conform to the Ethereum consensus rules.
+func NewFakeDelayer(delay time.Duration) *FakeEthash {
+ return &FakeEthash{
+ Ethash: newFakeEth(ethashcfg.ModeFake),
+ fakeDelay: delay,
+ }
+}
+
+func newFakeEth(mode ethashcfg.Mode) Ethash {
+ return Ethash{
+ config: ethashcfg.Config{
+ PowMode: mode,
+ Log: log.Root(),
+ },
+ }
+}
+
+func (f *FakeEthash) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Header, seal bool) error {
+ err := f.Ethash.VerifyHeader(chain, header, false)
+ if err != nil {
+ return err
+ }
+
+ if seal {
+ return f.VerifySeal(chain, header)
+ }
+
+ return nil
+}
+
+func (f *FakeEthash) VerifyUncles(chain consensus.ChainReader, header *types.Header, uncles []*types.Header) error {
+ if len(uncles) > maxUncles {
+ return errTooManyUncles
+ }
+ if len(uncles) == 0 {
+ return nil
+ }
+
+ uncleBlocks, ancestors := getUncles(chain, header)
+
+ for _, uncle := range uncles {
+ if err := f.VerifyUncle(chain, header, uncle, uncleBlocks, ancestors, true); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (f *FakeEthash) VerifyUncle(chain consensus.ChainHeaderReader, block *types.Header, uncle *types.Header, uncles mapset.Set[libcommon.Hash], ancestors map[libcommon.Hash]*types.Header, seal bool) error {
+ err := f.Ethash.VerifyUncle(chain, block, uncle, uncles, ancestors, false)
+ if err != nil {
+ return err
+ }
+
+ if seal {
+ return f.VerifySeal(chain, uncle)
+ }
+ return nil
+}
+
+// If we're running a fake PoW, accept any seal as valid
+func (f *FakeEthash) VerifySeal(_ consensus.ChainHeaderReader, header *types.Header) error {
+ if f.fakeDelay > 0 {
+ time.Sleep(f.fakeDelay)
+ }
+
+ if f.fakeFail == header.Number.Uint64() {
+ return errInvalidPoW
+ }
+
+ return nil
+}
+
+// If we're running a fake PoW, simply return a 0 nonce immediately
+func (f *FakeEthash) Seal(_ consensus.ChainHeaderReader, blockWithReceipts *types.BlockWithReceipts, results chan<- *types.BlockWithReceipts, stop <-chan struct{}) error {
+ block := blockWithReceipts.Block
+ receipts := blockWithReceipts.Receipts
+ header := block.Header()
+ header.Nonce, header.MixDigest = types.BlockNonce{}, libcommon.Hash{}
+
+ select {
+ case results <- &types.BlockWithReceipts{Block: block.WithSeal(header), Receipts: receipts}:
+ default:
+ f.Ethash.config.Log.Warn("Sealing result is not read by miner", "mode", "fake", "sealhash", f.SealHash(block.Header()))
+ }
+
+ return nil
+}
+
+type FullFakeEthash FakeEthash
+
+// NewFullFaker creates an ethash consensus engine with a full fake scheme that
+// accepts all blocks as valid, without checking any consensus rules whatsoever.
+func NewFullFaker() *FullFakeEthash {
+ return &FullFakeEthash{
+ Ethash: Ethash{
+ config: ethashcfg.Config{
+ PowMode: ethashcfg.ModeFullFake,
+ Log: log.Root(),
+ },
+ },
+ }
+}
+
+// If we're running a full engine faking, accept any input as valid
+func (f *FullFakeEthash) VerifyHeader(_ consensus.ChainHeaderReader, _ *types.Header, _ bool) error {
+ return nil
+}
+
+func (f *FullFakeEthash) VerifyUncles(_ consensus.ChainReader, _ *types.Header, _ []*types.Header) error {
+ return nil
+}
diff --git a/consensus/ethash/meter.go b/consensus/ethash/meter.go
new file mode 100644
index 00000000000..1414dac98fb
--- /dev/null
+++ b/consensus/ethash/meter.go
@@ -0,0 +1,209 @@
+// Copyright 2024 The Erigon Authors
+// This file is part of Erigon.
+//
+// Erigon 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.
+//
+// Erigon 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 Erigon. If not, see .
+
+package ethash
+
+import (
+ "math"
+ "sync"
+ "sync/atomic"
+ "time"
+
+ "github.com/erigontech/erigon/common/debug"
+)
+
+func newHashRateMeter() *hashRateMeter {
+ m := newMeter()
+ arbiter.mu.Lock()
+ defer arbiter.mu.Unlock()
+ arbiter.meters[m] = struct{}{}
+ if !arbiter.started {
+ arbiter.started = true
+ go arbiter.tick()
+ }
+ return m
+}
+
+// meterSnapshot is a read-only copy of another Meter.
+type meterSnapshot struct {
+ // WARNING: The `temp` field is accessed atomically.
+ // On 32 bit platforms, only 64-bit aligned fields can be atomic. The struct is
+ // guaranteed to be so aligned, so take advantage of that. For more information,
+ // see https://golang.org/pkg/sync/atomic/#pkg-note-BUG.
+ temp int64
+ count int64
+ rate float64
+}
+
+// Count returns the count of events at the time the snapshot was taken.
+func (m *meterSnapshot) Count() int64 { return m.count }
+
+// Mark panics.
+func (*meterSnapshot) Mark(n int64) {
+ panic("Mark called on a MeterSnapshot")
+}
+
+// Rate1 returns the one-minute moving average rate of events per second at the
+// time the snapshot was taken.
+func (m *meterSnapshot) Rate() float64 { return m.rate }
+
+// Stop is a no-op.
+func (m *meterSnapshot) Stop() {}
+
+// StandardMeter is the standard implementation of a Meter.
+type hashRateMeter struct {
+ lock sync.RWMutex
+ snapshot *meterSnapshot
+ a1 *ewma
+ startTime time.Time
+ stopped uint32
+}
+
+func newMeter() *hashRateMeter {
+ return &hashRateMeter{
+ snapshot: &meterSnapshot{},
+ a1: &ewma{alpha: 1 - math.Exp(-5.0/60.0/1)},
+ startTime: time.Now(),
+ }
+}
+
+// Stop stops the meter, Mark() will be a no-op if you use it after being stopped.
+func (m *hashRateMeter) Stop() {
+ stopped := atomic.SwapUint32(&m.stopped, 1)
+ if stopped != 1 {
+ arbiter.mu.Lock()
+ delete(arbiter.meters, m)
+ arbiter.mu.Unlock()
+ }
+}
+
+// Count returns the number of events recorded.
+// It updates the meter to be as accurate as possible
+func (m *hashRateMeter) Count() int64 {
+ m.lock.Lock()
+ defer m.lock.Unlock()
+ m.updateMeter()
+ return m.snapshot.count
+}
+
+// Mark records the occurrence of n events.
+func (m *hashRateMeter) Mark(n int64) {
+ m.lock.Lock()
+ m.snapshot.temp = n
+ m.lock.Unlock()
+}
+
+// Rate returns the one-minute moving average rate of events per second.
+func (m *hashRateMeter) Rate() float64 {
+ m.lock.RLock()
+ defer m.lock.RUnlock()
+ return m.snapshot.rate
+}
+
+// Snapshot returns a read-only copy of the meter.
+func (m *hashRateMeter) Snapshot() *meterSnapshot {
+ m.lock.RLock()
+ snapshot := *m.snapshot
+ m.lock.RUnlock()
+ return &snapshot
+}
+
+func (m *hashRateMeter) updateSnapshot() {
+ // should run with write lock held on m.lock
+ snapshot := m.snapshot
+ snapshot.rate = m.a1.Rate()
+}
+
+func (m *hashRateMeter) updateMeter() {
+ // should only run with write lock held on m.lock
+ n := atomic.SwapInt64(&m.snapshot.temp, 0)
+ m.snapshot.count += n
+ m.a1.Update(n)
+}
+
+func (m *hashRateMeter) tick() {
+ m.lock.Lock()
+ defer m.lock.Unlock()
+ m.updateMeter()
+ m.a1.Tick()
+ m.updateSnapshot()
+}
+
+// meterArbiter ticks meters every 5s from a single goroutine.
+// meters are references in a set for future stopping.
+type meterArbiter struct {
+ mu sync.RWMutex
+ started bool
+ meters map[*hashRateMeter]struct{}
+ ticker *time.Ticker
+}
+
+var arbiter = meterArbiter{ticker: time.NewTicker(5 * time.Second), meters: make(map[*hashRateMeter]struct{})}
+
+// Ticks meters on the scheduled interval
+func (ma *meterArbiter) tick() {
+ defer debug.LogPanic()
+ for range ma.ticker.C {
+ ma.tickMeters()
+ }
+}
+
+func (ma *meterArbiter) tickMeters() {
+ ma.mu.RLock()
+ defer ma.mu.RUnlock()
+ for meter := range ma.meters {
+ meter.tick()
+ }
+}
+
+// ewma is the standard implementation of an EWMA and tracks the number
+// of uncounted events and processes them on each tick. It uses the
+// sync/atomic package to manage uncounted events.
+type ewma struct {
+ uncounted int64 // /!\ this should be the first member to ensure 64-bit alignment
+ alpha float64
+ rate float64
+ init bool
+ mutex sync.Mutex
+}
+
+// Rate returns the moving average rate of events per second.
+func (a *ewma) Rate() float64 {
+ a.mutex.Lock()
+ defer a.mutex.Unlock()
+ return a.rate * float64(time.Second)
+}
+
+// Tick ticks the clock to update the moving average. It assumes it is called
+// every five seconds.
+func (a *ewma) Tick() {
+ count := atomic.LoadInt64(&a.uncounted)
+ atomic.AddInt64(&a.uncounted, -count)
+ instantRate := float64(count) / float64(5*time.Second)
+ a.mutex.Lock()
+ defer a.mutex.Unlock()
+ if a.init {
+ a.rate += a.alpha * (instantRate - a.rate)
+ } else {
+ a.init = true
+ a.rate = instantRate
+ }
+}
+
+// Update adds n uncounted events.
+func (a *ewma) Update(n int64) {
+ atomic.AddInt64(&a.uncounted, n)
+}
diff --git a/consensus/ethash/sealer.go b/consensus/ethash/sealer.go
new file mode 100644
index 00000000000..a1083191b0e
--- /dev/null
+++ b/consensus/ethash/sealer.go
@@ -0,0 +1,338 @@
+// Copyright 2017 The go-ethereum Authors
+// (original work)
+// Copyright 2024 The Erigon Authors
+// (modifications)
+// This file is part of Erigon.
+//
+// Erigon 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.
+//
+// Erigon 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 Erigon. If not, see .
+
+package ethash
+
+import (
+ "bytes"
+ "context"
+ crand "crypto/rand"
+ "errors"
+ "math"
+ "math/big"
+ "math/rand"
+ "net/http"
+ "sync"
+ "time"
+
+ "github.com/goccy/go-json"
+
+ libcommon "github.com/erigontech/erigon-lib/common"
+ "github.com/erigontech/erigon-lib/common/hexutil"
+
+ "github.com/erigontech/erigon/common"
+ "github.com/erigontech/erigon/consensus"
+ "github.com/erigontech/erigon/core/types"
+)
+
+const (
+ // staleThreshold is the maximum depth of the acceptable stale but valid ethash solution.
+ staleThreshold = 7
+)
+
+var (
+ errNoMiningWork = errors.New("no mining work available yet")
+ errInvalidSealResult = errors.New("invalid or stale proof-of-work solution")
+)
+
+// Seal implements consensus.Engine, attempting to find a nonce that satisfies
+// the block's difficulty requirements.
+func (ethash *Ethash) Seal(chain consensus.ChainHeaderReader, block *types.BlockWithReceipts, results chan<- *types.BlockWithReceipts, stop <-chan struct{}) error {
+ // If we're running a shared PoW, delegate sealing to it
+ if ethash.shared != nil {
+ return ethash.shared.Seal(chain, block, results, stop)
+ }
+ ethash.lock.Lock()
+ if ethash.rand == nil {
+ seed, err := crand.Int(crand.Reader, big.NewInt(math.MaxInt64))
+ if err != nil {
+ ethash.lock.Unlock()
+ return err
+ }
+ ethash.rand = rand.New(rand.NewSource(seed.Int64())) // nolint
+ }
+ ethash.lock.Unlock()
+ // Push new work to remote sealer
+ ethash.remote.workCh <- &sealTask{block: block, results: results}
+ return nil
+}
+
+// This is the timeout for HTTP requests to notify external miners.
+const remoteSealerTimeout = 1 * time.Second
+
+type remoteSealer struct {
+ works map[libcommon.Hash]*types.BlockWithReceipts
+ rates map[libcommon.Hash]hashrate
+ currentBlock *types.Block
+ currentWork [4]string
+ notifyCtx context.Context
+ cancelNotify context.CancelFunc // cancels all notification requests
+ reqWG sync.WaitGroup // tracks notification request goroutines
+
+ ethash *Ethash
+ noverify bool
+ notifyURLs []string
+ results chan<- *types.BlockWithReceipts
+ workCh chan *sealTask // Notification channel to push new work and relative result channel to remote sealer
+ fetchWorkCh chan *sealWork // Channel used for remote sealer to fetch mining work
+ submitWorkCh chan *mineResult // Channel used for remote sealer to submit their mining result
+ fetchRateCh chan chan uint64 // Channel used to gather submitted hash rate for local or remote sealer.
+ submitRateCh chan *hashrate // Channel used for remote sealer to submit their mining hashrate
+ requestExit chan struct{}
+ exitCh chan struct{}
+}
+
+// sealTask wraps a seal block with relative result channel for remote sealer thread.
+type sealTask struct {
+ block *types.BlockWithReceipts
+ results chan<- *types.BlockWithReceipts
+}
+
+// mineResult wraps the pow solution parameters for the specified block.
+type mineResult struct {
+ nonce types.BlockNonce
+ mixDigest libcommon.Hash
+ hash libcommon.Hash
+
+ errc chan error
+}
+
+// hashrate wraps the hash rate submitted by the remote sealer.
+type hashrate struct {
+ id libcommon.Hash
+ ping time.Time
+ rate uint64
+
+ done chan struct{}
+}
+
+// sealWork wraps a seal work package for remote sealer.
+type sealWork struct {
+ errc chan error
+ res chan [4]string
+}
+
+func startRemoteSealer(ethash *Ethash, urls []string, noverify bool) *remoteSealer {
+ ctx, cancel := context.WithCancel(context.Background())
+ s := &remoteSealer{
+ ethash: ethash,
+ noverify: noverify,
+ notifyURLs: urls,
+ notifyCtx: ctx,
+ cancelNotify: cancel,
+ works: make(map[libcommon.Hash]*types.BlockWithReceipts),
+ rates: make(map[libcommon.Hash]hashrate),
+ workCh: make(chan *sealTask),
+ fetchWorkCh: make(chan *sealWork),
+ submitWorkCh: make(chan *mineResult),
+ fetchRateCh: make(chan chan uint64),
+ submitRateCh: make(chan *hashrate),
+ requestExit: make(chan struct{}),
+ exitCh: make(chan struct{}),
+ }
+ go s.loop()
+ return s
+}
+
+func (s *remoteSealer) loop() {
+ defer func() {
+ s.ethash.config.Log.Trace("Ethash remote sealer is exiting")
+ s.cancelNotify()
+ s.reqWG.Wait()
+ close(s.exitCh)
+ }()
+
+ ticker := time.NewTicker(5 * time.Second)
+ defer ticker.Stop()
+
+ for {
+ select {
+ case work := <-s.workCh:
+ // Update current work with new received block.
+ // Note same work can be past twice, happens when changing CPU threads.
+ s.results = work.results
+ s.makeWork(work.block)
+ s.notifyWork()
+
+ case work := <-s.fetchWorkCh:
+ // Return current mining work to remote miner.
+ if s.currentBlock == nil {
+ work.errc <- errNoMiningWork
+ } else {
+ work.res <- s.currentWork
+ }
+
+ case result := <-s.submitWorkCh:
+ // Verify submitted PoW solution based on maintained mining blocks.
+ if s.submitWork(result.nonce, result.mixDigest, result.hash) {
+ result.errc <- nil
+ } else {
+ result.errc <- errInvalidSealResult
+ }
+
+ case result := <-s.submitRateCh:
+ // Trace remote sealer's hash rate by submitted value.
+ s.rates[result.id] = hashrate{rate: result.rate, ping: time.Now()}
+ close(result.done)
+
+ case req := <-s.fetchRateCh:
+ // Gather all hash rate submitted by remote sealer.
+ var total uint64
+ for _, rate := range s.rates {
+ // this could overflow
+ total += rate.rate
+ }
+ req <- total
+
+ case <-ticker.C:
+ // Clear stale submitted hash rate.
+ for id, rate := range s.rates {
+ if time.Since(rate.ping) > 10*time.Second {
+ delete(s.rates, id)
+ }
+ }
+ // Clear stale pending blocks
+ if s.currentBlock != nil {
+ for hash, block := range s.works {
+ if block.Block.NumberU64()+staleThreshold <= s.currentBlock.NumberU64() {
+ delete(s.works, hash)
+ }
+ }
+ }
+
+ case <-s.requestExit:
+ return
+ }
+ }
+}
+
+// makeWork creates a work package for external miner.
+//
+// The work package consists of 3 strings:
+//
+// result[0], 32 bytes hex encoded current block header pow-hash
+// result[1], 32 bytes hex encoded seed hash used for DAG
+// result[2], 32 bytes hex encoded boundary condition ("target"), 2^256/difficulty
+// result[3], hex encoded block number
+func (s *remoteSealer) makeWork(blockWithReceipts *types.BlockWithReceipts) {
+ block := blockWithReceipts.Block
+ hash := s.ethash.SealHash(block.Header())
+ s.currentWork[0] = hash.Hex()
+ s.currentWork[1] = libcommon.BytesToHash(SeedHash(block.NumberU64())).Hex()
+ s.currentWork[2] = libcommon.BytesToHash(new(big.Int).Div(two256, block.Difficulty()).Bytes()).Hex()
+ s.currentWork[3] = hexutil.EncodeBig(block.Number())
+
+ // Trace the seal work fetched by remote sealer.
+ s.currentBlock = block
+ s.works[hash] = blockWithReceipts
+}
+
+// notifyWork notifies all the specified mining endpoints of the availability of
+// new work to be processed.
+func (s *remoteSealer) notifyWork() {
+ work := s.currentWork
+
+ // Encode the JSON payload of the notification. When NotifyFull is set,
+ // this is the complete block header, otherwise it is a JSON array.
+ var blob []byte
+ if s.ethash.config.NotifyFull {
+ blob, _ = json.Marshal(s.currentBlock.Header())
+ } else {
+ blob, _ = json.Marshal(work)
+ }
+
+ s.reqWG.Add(len(s.notifyURLs))
+ for _, url := range s.notifyURLs {
+ go s.sendNotification(s.notifyCtx, url, blob, work)
+ }
+}
+
+func (s *remoteSealer) sendNotification(ctx context.Context, url string, json []byte, work [4]string) {
+ defer s.reqWG.Done()
+
+ req, err := http.NewRequest("POST", url, bytes.NewReader(json))
+ if err != nil {
+ s.ethash.config.Log.Warn("Can't create remote miner notification", "err", err)
+ return
+ }
+ ctx, cancel := context.WithTimeout(ctx, remoteSealerTimeout)
+ defer cancel()
+ req = req.WithContext(ctx)
+ req.Header.Set("Content-Type", "application/json")
+
+ resp, err := http.DefaultClient.Do(req)
+ if err != nil {
+ s.ethash.config.Log.Warn("Failed to notify remote miner", "err", err)
+ } else {
+ s.ethash.config.Log.Trace("Notified remote miner", "miner", url, "hash", work[0], "target", work[2])
+ resp.Body.Close()
+ }
+}
+
+// submitWork verifies the submitted pow solution, returning
+// whether the solution was accepted or not (not can be both a bad pow as well as
+// any other error, like no pending work or stale mining result).
+func (s *remoteSealer) submitWork(nonce types.BlockNonce, mixDigest libcommon.Hash, sealhash libcommon.Hash) bool {
+ if s.currentBlock == nil {
+ s.ethash.config.Log.Warn("Pending work without block", "sealhash", sealhash)
+ return false
+ }
+ // Make sure the work submitted is present
+ block := s.works[sealhash]
+ if block == nil {
+ s.ethash.config.Log.Warn("Work submitted but none pending", "sealhash", sealhash, "curnumber", s.currentBlock.NumberU64())
+ return false
+ }
+ // Verify the correctness of submitted result.
+ header := block.Block.Header()
+ header.Nonce = nonce
+ header.MixDigest = mixDigest
+
+ start := time.Now()
+ if !s.noverify {
+ if err := s.ethash.verifySeal(header, true); err != nil {
+ s.ethash.config.Log.Warn("Invalid proof-of-work submitted", "sealhash", sealhash, "elapsed", common.PrettyDuration(time.Since(start)), "err", err)
+ return false
+ }
+ }
+ // Make sure the result channel is assigned.
+ if s.results == nil {
+ s.ethash.config.Log.Warn("Ethash result channel is empty, submitted mining result is rejected")
+ return false
+ }
+ s.ethash.config.Log.Trace("Verified correct proof-of-work", "sealhash", sealhash, "elapsed", common.PrettyDuration(time.Since(start)))
+
+ // Solutions seems to be valid, return to the miner and notify acceptance.
+ solution := block.Block.WithSeal(header)
+
+ // The submitted solution is within the scope of acceptance.
+ if solution.NumberU64()+staleThreshold > s.currentBlock.NumberU64() {
+ select {
+ case s.results <- &types.BlockWithReceipts{Block: solution, Receipts: block.Receipts}:
+ s.ethash.config.Log.Trace("Work submitted is acceptable", "number", solution.NumberU64(), "sealhash", sealhash, "hash", solution.Hash())
+ return true
+ default:
+ s.ethash.config.Log.Warn("Sealing result is not read by miner", "mode", "remote", "sealhash", sealhash)
+ return false
+ }
+ }
+ // The submitted block is too old to accept, drop it.
+ s.ethash.config.Log.Warn("Work submitted is too old", "number", solution.NumberU64(), "sealhash", sealhash, "hash", solution.Hash())
+ return false
+}
diff --git a/consensus/ethash/sealer_test.go b/consensus/ethash/sealer_test.go
new file mode 100644
index 00000000000..f552adb13ea
--- /dev/null
+++ b/consensus/ethash/sealer_test.go
@@ -0,0 +1,328 @@
+// Copyright 2018 The go-ethereum Authors
+// (original work)
+// Copyright 2024 The Erigon Authors
+// (modifications)
+// This file is part of Erigon.
+//
+// Erigon 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.
+//
+// Erigon 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 Erigon. If not, see .
+
+package ethash
+
+import (
+ "io"
+ "math/big"
+ "net/http"
+ "net/http/httptest"
+ "strconv"
+ "testing"
+ "time"
+
+ "github.com/goccy/go-json"
+
+ libcommon "github.com/erigontech/erigon-lib/common"
+ "github.com/erigontech/erigon/consensus/ethash/ethashcfg"
+
+ "github.com/erigontech/erigon/turbo/testlog"
+
+ "github.com/erigontech/erigon-lib/log/v3"
+
+ "github.com/erigontech/erigon/core/types"
+)
+
+// Tests whether remote HTTP servers are correctly notified of new work.
+func TestRemoteNotify(t *testing.T) {
+ // Start a simple web server to capture notifications.
+ sink := make(chan [3]string)
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ blob, err := io.ReadAll(req.Body)
+ if err != nil {
+ t.Errorf("failed to read miner notification: %v", err)
+ }
+ var work [3]string
+ if err := json.Unmarshal(blob, &work); err != nil {
+ t.Errorf("failed to unmarshal miner notification: %v", err)
+ }
+ sink <- work
+ }))
+ defer server.Close()
+
+ // Create the custom ethash engine.
+ ethash := NewTester([]string{server.URL}, false)
+ defer ethash.Close()
+
+ // Stream a work task and ensure the notification bubbles out.
+ header := &types.Header{Number: big.NewInt(1), Difficulty: big.NewInt(100)}
+ block := types.NewBlockWithHeader(header)
+ blockWithReceipts := &types.BlockWithReceipts{Block: block}
+
+ if err := ethash.Seal(nil, blockWithReceipts, nil, nil); err != nil {
+ t.Fatal(err)
+ }
+ select {
+ case work := <-sink:
+ if want := ethash.SealHash(header).Hex(); work[0] != want {
+ t.Errorf("work packet hash mismatch: have %s, want %s", work[0], want)
+ }
+ if want := libcommon.BytesToHash(SeedHash(header.Number.Uint64())).Hex(); work[1] != want {
+ t.Errorf("work packet seed mismatch: have %s, want %s", work[1], want)
+ }
+ target := new(big.Int).Div(new(big.Int).Lsh(big.NewInt(1), 256), header.Difficulty)
+ if want := libcommon.BytesToHash(target.Bytes()).Hex(); work[2] != want {
+ t.Errorf("work packet target mismatch: have %s, want %s", work[2], want)
+ }
+ case <-time.After(3 * time.Second):
+ t.Fatalf("notification timed out")
+ }
+}
+
+// Tests whether remote HTTP servers are correctly notified of new work. (Full pending block body / --miner.notify.full)
+func TestRemoteNotifyFull(t *testing.T) {
+ // Start a simple web server to capture notifications.
+ sink := make(chan map[string]interface{})
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ blob, err := io.ReadAll(req.Body)
+ if err != nil {
+ t.Errorf("failed to read miner notification: %v", err)
+ }
+ var work map[string]interface{}
+ if err := json.Unmarshal(blob, &work); err != nil {
+ t.Errorf("failed to unmarshal miner notification: %v", err)
+ }
+ sink <- work
+ }))
+ defer server.Close()
+
+ // Create the custom ethash engine.
+ config := ethashcfg.Config{
+ PowMode: ethashcfg.ModeTest,
+ NotifyFull: true,
+ Log: testlog.Logger(t, log.LvlWarn),
+ }
+ ethash := New(config, []string{server.URL}, false)
+ defer ethash.Close()
+
+ // Stream a work task and ensure the notification bubbles out.
+ header := &types.Header{Number: big.NewInt(1), Difficulty: big.NewInt(100)}
+ block := types.NewBlockWithHeader(header)
+ blockWithReceipts := &types.BlockWithReceipts{Block: block}
+
+ if err := ethash.Seal(nil, blockWithReceipts, nil, nil); err != nil {
+ t.Fatal(err)
+ }
+ select {
+ case work := <-sink:
+ if want := "0x" + strconv.FormatUint(header.Number.Uint64(), 16); work["number"] != want {
+ t.Errorf("pending block number mismatch: have %v, want %v", work["number"], want)
+ }
+ if want := "0x" + header.Difficulty.Text(16); work["difficulty"] != want {
+ t.Errorf("pending block difficulty mismatch: have %s, want %s", work["difficulty"], want)
+ }
+ case <-time.After(3 * time.Second):
+ t.Fatalf("notification timed out")
+ }
+}
+
+// Tests that pushing work packages fast to the miner doesn't cause any data race
+// issues in the notifications.
+func TestRemoteMultiNotify(t *testing.T) {
+ t.Skip("Often fails spuriously, needs to be investiaged")
+
+ // Start a simple web server to capture notifications.
+ sink := make(chan [3]string, 64)
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ blob, err := io.ReadAll(req.Body)
+ if err != nil {
+ t.Errorf("failed to read miner notification: %v", err)
+ }
+ var work [3]string
+ if err := json.Unmarshal(blob, &work); err != nil {
+ t.Errorf("failed to unmarshal miner notification: %v", err)
+ }
+ sink <- work
+ }))
+ defer server.Close()
+
+ // Create the custom ethash engine.
+ ethash := NewTester([]string{server.URL}, false)
+ ethash.config.Log = testlog.Logger(t, log.LvlWarn)
+ defer ethash.Close()
+
+ // Provide a results reader.
+ // Otherwise the unread results will be logged asynchronously
+ // and this can happen after the test is finished, causing a panic.
+ results := make(chan *types.BlockWithReceipts, cap(sink))
+
+ // Stream a lot of work task and ensure all the notifications bubble out.
+ for i := 0; i < cap(sink); i++ {
+ header := &types.Header{Number: big.NewInt(int64(i)), Difficulty: big.NewInt(100)}
+ block := types.NewBlockWithHeader(header)
+ blockWithReceipts := &types.BlockWithReceipts{Block: block}
+ err := ethash.Seal(nil, blockWithReceipts, results, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ for i := 0; i < cap(sink); i++ {
+ select {
+ case <-sink:
+ <-results
+ case <-time.After(10 * time.Second):
+ t.Fatalf("notification %d timed out", i)
+ }
+ }
+}
+
+// Tests that pushing work packages fast to the miner doesn't cause any data race
+// issues in the notifications. Full pending block body / --miner.notify.full)
+func TestRemoteMultiNotifyFull(t *testing.T) {
+ t.Skip("Often fails spuriously, needs to be investiaged")
+ // Start a simple web server to capture notifications.
+ sink := make(chan map[string]interface{}, 64)
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ blob, err := io.ReadAll(req.Body)
+ if err != nil {
+ t.Errorf("failed to read miner notification: %v", err)
+ }
+ var work map[string]interface{}
+ if err := json.Unmarshal(blob, &work); err != nil {
+ t.Errorf("failed to unmarshal miner notification: %v", err)
+ }
+ sink <- work
+ }))
+ defer server.Close()
+
+ // Create the custom ethash engine.
+ config := ethashcfg.Config{
+ PowMode: ethashcfg.ModeTest,
+ NotifyFull: true,
+ Log: testlog.Logger(t, log.LvlWarn),
+ }
+ ethash := New(config, []string{server.URL}, false)
+ defer ethash.Close()
+
+ // Provide a results reader.
+ // Otherwise the unread results will be logged asynchronously
+ // and this can happen after the test is finished, causing a panic.
+ results := make(chan *types.BlockWithReceipts, cap(sink))
+
+ // Stream a lot of work task and ensure all the notifications bubble out.
+ for i := 0; i < cap(sink); i++ {
+ header := &types.Header{Number: big.NewInt(int64(i)), Difficulty: big.NewInt(100)}
+ block := types.NewBlockWithHeader(header)
+ blockWithReceipts := &types.BlockWithReceipts{Block: block}
+ err := ethash.Seal(nil, blockWithReceipts, results, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ for i := 0; i < cap(sink); i++ {
+ select {
+ case <-sink:
+ <-results
+ case <-time.After(10 * time.Second):
+ t.Fatalf("notification %d timed out", i)
+ }
+ }
+}
+
+// Tests whether stale solutions are correctly processed.
+func TestStaleSubmission(t *testing.T) {
+ ethash := NewTester(nil, true)
+ defer ethash.Close()
+ api := &API{ethash}
+
+ fakeNonce, fakeDigest := types.BlockNonce{0x01, 0x02, 0x03}, libcommon.HexToHash("deadbeef")
+
+ testcases := []struct {
+ headers []*types.Header
+ submitIndex int
+ submitRes bool
+ }{
+ // Case1: submit solution for the latest mining package
+ {
+ []*types.Header{
+ {ParentHash: libcommon.BytesToHash([]byte{0xa}), Number: big.NewInt(1), Difficulty: big.NewInt(100000000)},
+ },
+ 0,
+ true,
+ },
+ // Case2: submit solution for the previous package but have same parent.
+ {
+ []*types.Header{
+ {ParentHash: libcommon.BytesToHash([]byte{0xb}), Number: big.NewInt(2), Difficulty: big.NewInt(100000000)},
+ {ParentHash: libcommon.BytesToHash([]byte{0xb}), Number: big.NewInt(2), Difficulty: big.NewInt(100000001)},
+ },
+ 0,
+ true,
+ },
+ // Case3: submit stale but acceptable solution
+ {
+ []*types.Header{
+ {ParentHash: libcommon.BytesToHash([]byte{0xc}), Number: big.NewInt(3), Difficulty: big.NewInt(100000000)},
+ {ParentHash: libcommon.BytesToHash([]byte{0xd}), Number: big.NewInt(9), Difficulty: big.NewInt(100000000)},
+ },
+ 0,
+ true,
+ },
+ // Case4: submit very old solution
+ {
+ []*types.Header{
+ {ParentHash: libcommon.BytesToHash([]byte{0xe}), Number: big.NewInt(10), Difficulty: big.NewInt(100000000)},
+ {ParentHash: libcommon.BytesToHash([]byte{0xf}), Number: big.NewInt(17), Difficulty: big.NewInt(100000000)},
+ },
+ 0,
+ false,
+ },
+ }
+ results := make(chan *types.BlockWithReceipts, 16)
+
+ for id, c := range testcases {
+ for _, h := range c.headers {
+ blockWithReceipts := &types.BlockWithReceipts{Block: types.NewBlockWithHeader(h)}
+ err := ethash.Seal(nil, blockWithReceipts, results, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ }
+ if res := api.SubmitWork(fakeNonce, ethash.SealHash(c.headers[c.submitIndex]), fakeDigest); res != c.submitRes {
+ t.Errorf("case %d submit result mismatch, want %t, get %t", id+1, c.submitRes, res)
+ }
+ if !c.submitRes {
+ continue
+ }
+ select {
+ case resWithReceipts := <-results:
+ res := resWithReceipts.Block
+ if res.Nonce() != fakeNonce {
+ t.Errorf("case %d block nonce mismatch, want %x, get %x", id+1, fakeNonce, res.Nonce())
+ }
+ if res.MixDigest() != fakeDigest {
+ t.Errorf("case %d block digest mismatch, want %x, get %x", id+1, fakeDigest, res.MixDigest())
+ }
+ if res.Difficulty().Uint64() != c.headers[c.submitIndex].Difficulty.Uint64() {
+ t.Errorf("case %d block difficulty mismatch, want %d, get %d", id+1, c.headers[c.submitIndex].Difficulty, res.Difficulty())
+ }
+ if res.NumberU64() != c.headers[c.submitIndex].Number.Uint64() {
+ t.Errorf("case %d block number mismatch, want %d, get %d", id+1, c.headers[c.submitIndex].Number.Uint64(), res.NumberU64())
+ }
+ if res.ParentHash() != c.headers[c.submitIndex].ParentHash {
+ t.Errorf("case %d block parent hash mismatch, want %s, get %s", id+1, c.headers[c.submitIndex].ParentHash.Hex(), res.ParentHash().Hex())
+ }
+ case <-time.NewTimer(time.Second).C:
+ t.Errorf("case %d fetch ethash result timeout", id+1)
+ }
+ }
+}
diff --git a/consensus/mainnet/mainnet.go b/consensus/mainnet/mainnet.go
deleted file mode 100644
index 309d05d9305..00000000000
--- a/consensus/mainnet/mainnet.go
+++ /dev/null
@@ -1,437 +0,0 @@
-// Copyright 2017 The go-ethereum Authors
-// (original work)
-// Copyright 2024 The Erigon Authors
-// (modifications)
-// This file is part of Erigon.
-//
-// Erigon 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.
-//
-// Erigon 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 Erigon. If not, see .
-
-package mainnet
-
-import (
- "errors"
- "fmt"
- "math/big"
- "time"
-
- mapset "github.com/deckarep/golang-set/v2"
- "github.com/holiman/uint256"
-
- "github.com/erigontech/erigon-lib/chain"
- "github.com/erigontech/erigon-lib/common"
- libcommon "github.com/erigontech/erigon-lib/common"
- "github.com/erigontech/erigon-lib/log/v3"
-
- "github.com/erigontech/erigon/common/u256"
- "github.com/erigontech/erigon/consensus"
- "github.com/erigontech/erigon/consensus/misc"
- "github.com/erigontech/erigon/core/state"
- "github.com/erigontech/erigon/core/tracing"
- "github.com/erigontech/erigon/core/types"
- "github.com/erigontech/erigon/core/vm/evmtypes"
- "github.com/erigontech/erigon/params"
- "github.com/erigontech/erigon/rpc"
-)
-
-var (
- // b proof-of-work protocol constants.
- maxUncles = 2 // Maximum number of uncles allowed in a single block
- allowedFutureBlockTimeSeconds = int64(15) // Max seconds from current time allowed for blocks, before they're considered future blocks
-
- // Various error messages to mark blocks invalid.
- errOlderBlockTime = errors.New("timestamp older than parent")
- errInvalidPoW = errors.New("invalid proof-of-work")
- ErrInvalidDumpMagic = errors.New("invalid dump magic")
-
- // Block rewards based on block progression
- FrontierBlockReward = uint256.NewInt(5e+18) // Block reward in wei for successfully mining a block
- ByzantiumBlockReward = uint256.NewInt(3e+18) // Block reward in wei for successfully mining a block upward from Byzantium
- ConstantinopleBlockReward = uint256.NewInt(2e+18) // Block reward in wei for successfully mining a block upward from Constantinople
-)
-
-// b is a consensus engine based on proof-of-work implementing the b
-// algorithm.
-type baseMainnet struct {
-}
-
-// Type returns underlying consensus engine
-func (baseMainnet) Type() chain.ConsensusName {
- return chain.EtHashConsensus
-}
-
-// Author implements consensus.Engine, returning the header's coinbase as the
-// proof-of-work verified author of the block.
-// This is thread-safe (only access the header.Coinbase)
-func (baseMainnet) Author(header *types.Header) (libcommon.Address, error) {
- return header.Coinbase, nil
-}
-
-// VerifyHeader checks whether a header conforms to the consensus rules of the
-// stock Ethereum b engine.
-func (b baseMainnet) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Header, seal bool) error {
- // Short circuit if the header is known, or its parent not
- number := header.Number.Uint64()
- if chain.GetHeader(header.Hash(), number) != nil {
- return nil
- }
- if number == 0 {
- return nil
- }
- parent := chain.GetHeader(header.ParentHash, number-1)
- if parent == nil {
- log.Error("consensus.ErrUnknownAncestor", "parentNum", number-1, "hash", header.ParentHash.String())
- return consensus.ErrUnknownAncestor
- }
- // Sanity checks passed, do a proper verification
- return b.verifyHeader(chain, header, parent, false, seal)
-}
-
-// VerifyUncles verifies that the given block's uncles conform to the consensus
-// rules of the stock Ethereum b engine.
-func (baseMainnet) VerifyUncles(chain consensus.ChainReader, header *types.Header, uncles []*types.Header) error {
- return nil
-}
-
-func (baseMainnet) VerifyUncle(chain consensus.ChainHeaderReader, header *types.Header, uncle *types.Header, uncles mapset.Set[libcommon.Hash], ancestors map[libcommon.Hash]*types.Header, seal bool) error {
- return nil
-}
-
-func VerifyHeaderBasics(chain consensus.ChainHeaderReader, header, parent *types.Header, checkTimestamp, skipGasLimit bool) error {
- // Ensure that the header's extra-data section is of a reasonable size
- if uint64(len(header.Extra)) > params.MaximumExtraDataSize {
- return fmt.Errorf("extra-data too long: %d > %d", len(header.Extra), params.MaximumExtraDataSize)
- }
- // Verify the header's timestamp
- if checkTimestamp {
- unixNow := time.Now().Unix()
- if header.Time > uint64(unixNow+allowedFutureBlockTimeSeconds) {
- return consensus.ErrFutureBlock
- }
- }
- if header.Time <= parent.Time {
- return errOlderBlockTime
- }
- // Verify that the gas limit is <= 2^63-1
- if header.GasLimit > params.MaxGasLimit {
- return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, params.MaxGasLimit)
- }
- // Verify that the gasUsed is <= gasLimit
- if header.GasUsed > header.GasLimit {
- return fmt.Errorf("invalid gasUsed: have %d, gasLimit %d", header.GasUsed, header.GasLimit)
- }
- // Verify the block's gas usage and (if applicable) verify the base fee.
- if !chain.Config().IsLondon(header.Number.Uint64()) {
- // Verify BaseFee not present before EIP-1559 fork.
- if header.BaseFee != nil {
- return fmt.Errorf("invalid baseFee before fork: have %d, expected 'nil'", header.BaseFee)
- }
- if !skipGasLimit {
- if err := misc.VerifyGaslimit(parent.GasLimit, header.GasLimit); err != nil {
- return err
- }
- }
- } else if err := misc.VerifyEip1559Header(chain.Config(), parent, header, skipGasLimit); err != nil {
- // Verify the header's EIP-1559 attributes.
- return err
- }
-
- if err := misc.VerifyAbsenceOfCancunHeaderFields(header); err != nil {
- return err
- }
-
- // Verify that the block number is parent's +1
- if diff := new(big.Int).Sub(header.Number, parent.Number); diff.Cmp(big.NewInt(1)) != 0 {
- return consensus.ErrInvalidNumber
- }
-
- if header.WithdrawalsHash != nil {
- return consensus.ErrUnexpectedWithdrawals
- }
-
- if header.RequestsRoot != nil {
- return consensus.ErrUnexpectedRequests
- }
-
- // If all checks passed, validate any special fields for hard forks
- if err := misc.VerifyDAOHeaderExtraData(chain.Config(), header); err != nil {
- return err
- }
- return nil
-}
-
-// verifyHeader checks whether a header conforms to the consensus rules of the
-// stock Ethereum b engine.
-// See YP section 4.3.4. "Block Header Validity"
-func (b baseMainnet) verifyHeader(chain consensus.ChainHeaderReader, header, parent *types.Header, uncle bool, seal bool) error {
- if err := VerifyHeaderBasics(chain, header, parent, !uncle /*checkTimestamp*/, false /*skipGasLimit*/); err != nil {
- return err
- }
- return nil
-}
-
-// CalcDifficulty is the difficulty adjustment algorithm. It returns
-// the difficulty that a new block should have when created at time
-// given the parent block's time and difficulty.
-func (b baseMainnet) CalcDifficulty(chain consensus.ChainHeaderReader, time, parentTime uint64, parentDifficulty *big.Int, parentNumber uint64, _, parentUncleHash libcommon.Hash, _ uint64) *big.Int {
- return common.Big1 // Just a placeholder, guaranteed to be valid since td will always increase
-}
-
-// VerifySeal implements consensus.Engine, checking whether the given block satisfies
-// the PoW difficulty requirements.
-func (b baseMainnet) VerifySeal(_ consensus.ChainHeaderReader, header *types.Header) error {
- return nil
-}
-
-// Prepare implements consensus.Engine, initializing the difficulty field of a
-// header to conform to the b protocol. The changes are done inline.
-func (b baseMainnet) Prepare(chain consensus.ChainHeaderReader, header *types.Header, state *state.IntraBlockState) error {
- return nil
-}
-
-func (b baseMainnet) Initialize(config *chain.Config, chain consensus.ChainHeaderReader, header *types.Header,
- state *state.IntraBlockState, syscall consensus.SysCallCustom, logger log.Logger, tracer *tracing.Hooks) {
- if config.DAOForkBlock != nil && config.DAOForkBlock.Cmp(header.Number) == 0 {
- misc.ApplyDAOHardFork(state)
- }
-}
-
-// Finalize implements consensus.Engine, accumulating the block and uncle rewards,
-// setting the final state on the header
-func (b *baseMainnet) Finalize(config *chain.Config, header *types.Header, state *state.IntraBlockState,
- txs types.Transactions, uncles []*types.Header, r types.Receipts, withdrawals []*types.Withdrawal, requests types.Requests,
- chain consensus.ChainReader, syscall consensus.SystemCall, logger log.Logger,
-) (types.Transactions, types.Receipts, types.Requests, error) {
- // Accumulate any block and uncle rewards and commit the final state root
- accumulateRewards(config, state, header, uncles)
- return txs, r, nil, nil
-}
-
-// FinalizeAndAssemble implements consensus.Engine, accumulating the block and
-// uncle rewards, setting the final state and assembling the block.
-func (b baseMainnet) FinalizeAndAssemble(chainConfig *chain.Config, header *types.Header, state *state.IntraBlockState,
- txs types.Transactions, uncles []*types.Header, r types.Receipts, withdrawals []*types.Withdrawal, requests types.Requests,
- chain consensus.ChainReader, syscall consensus.SystemCall, call consensus.Call, logger log.Logger,
-) (*types.Block, types.Transactions, types.Receipts, error) {
-
- // Finalize block
- outTxs, outR, _, err := b.Finalize(chainConfig, header, state, txs, uncles, r, withdrawals, requests, chain, syscall, logger)
- if err != nil {
- return nil, nil, nil, err
- }
- // Header seems complete, assemble into a block and return
- return types.NewBlockForAsembling(header, outTxs, uncles, outR, withdrawals, requests), outTxs, outR, nil
-}
-
-// AccumulateRewards returns rewards for a given block. The mining reward consists
-// of the static blockReward plus a reward for each included uncle (if any). Individual
-// uncle rewards are also returned in an array.
-func AccumulateRewards(config *chain.Config, header *types.Header, uncles []*types.Header) (uint256.Int, []uint256.Int) {
- // Select the correct block reward based on chain progression
- blockReward := FrontierBlockReward
- if config.IsByzantium(header.Number.Uint64()) {
- blockReward = ByzantiumBlockReward
- }
- if config.IsConstantinople(header.Number.Uint64()) {
- blockReward = ConstantinopleBlockReward
- }
- // Accumulate the rewards for the miner and any included uncles
- uncleRewards := []uint256.Int{}
- reward := new(uint256.Int).Set(blockReward)
- r := new(uint256.Int)
- headerNum, _ := uint256.FromBig(header.Number)
- for _, uncle := range uncles {
- uncleNum, _ := uint256.FromBig(uncle.Number)
- r.Add(uncleNum, u256.Num8)
- r.Sub(r, headerNum)
- r.Mul(r, blockReward)
- r.Div(r, u256.Num8)
- uncleRewards = append(uncleRewards, *r)
-
- r.Div(blockReward, u256.Num32)
- reward.Add(reward, r)
- }
- return *reward, uncleRewards
-}
-
-// accumulateRewards retrieves rewards for a block and applies them to the coinbase accounts for miner and uncle miners
-func accumulateRewards(config *chain.Config, state *state.IntraBlockState, header *types.Header, uncles []*types.Header) {
- minerReward, uncleRewards := AccumulateRewards(config, header, uncles)
- for i, uncle := range uncles {
- if i < len(uncleRewards) {
- state.AddBalance(uncle.Coinbase, &uncleRewards[i], tracing.BalanceIncreaseRewardMineUncle)
- }
- }
- state.AddBalance(header.Coinbase, &minerReward, tracing.BalanceIncreaseRewardMineBlock)
-}
-
-func (baseMainnet) CalculateRewards(config *chain.Config, header *types.Header, uncles []*types.Header, syscall consensus.SystemCall) ([]consensus.Reward, error) {
- minerReward, uncleRewards := AccumulateRewards(config, header, uncles)
- rewards := make([]consensus.Reward, 1+len(uncles))
- rewards[0].Beneficiary = header.Coinbase
- rewards[0].Kind = consensus.RewardAuthor
- rewards[0].Amount = minerReward
- for i, uncle := range uncles {
- rewards[i+1].Beneficiary = uncle.Coinbase
- rewards[i+1].Kind = consensus.RewardUncle
- rewards[i+1].Amount = uncleRewards[i]
- }
- return rewards, nil
-}
-
-type MainnetConfig struct {
-}
-
-// Seal implements consensus.Engine, attempting to find a nonce that satisfies
-// the block's difficulty requirements.
-func (baseMainnet) Seal(chain consensus.ChainHeaderReader, block *types.BlockWithReceipts, results chan<- *types.BlockWithReceipts, stop <-chan struct{}) error {
- return nil
-}
-
-// SealHash returns the hash of a block prior to it being sealed.
-func (baseMainnet) SealHash(header *types.Header) libcommon.Hash {
- return header.Hash()
-}
-
-// APIs implements consensus.Engine, returning the user facing RPC API to allow
-// controlling the signer voting.
-func (c baseMainnet) APIs(chain consensus.ChainHeaderReader) []rpc.API {
- return []rpc.API{
- //{
- //Namespace: "clique",
- //Version: "1.0",
- //Service: &API{chain: chain, clique: c},
- //Public: false,
- //}
- }
-}
-
-func (baseMainnet) Close() error {
- return nil
-}
-
-func (baseMainnet) GetPostApplyMessageFunc() evmtypes.PostApplyMessageFunc {
- return nil
-}
-
-func (baseMainnet) GetTransferFunc() evmtypes.TransferFunc {
- return consensus.Transfer
-}
-
-func (baseMainnet) IsServiceTransaction(sender libcommon.Address, syscall consensus.SystemCall) bool {
- return false
-}
-
-type MainnetConsensus struct {
- baseMainnet
- fakeFail uint64 // Block number which fails PoW check even in fake mode
- fakeDelay time.Duration // Time delay to sleep for before returning from verify
-}
-
-// NewFakeFailer creates an b consensus engine with a fake PoW scheme that
-// accepts all blocks as valid apart from the single one specified, though they
-// still have to conform to the Ethereum consensus rules.
-func NewFakeFailer(fail uint64) *MainnetConsensus {
- return &MainnetConsensus{fakeFail: fail}
-}
-
-// NewMainnetConsensus creates an b consensus engine with a fake PoW scheme
-// that accepts all blocks' seal as valid, though they still have to conform to
-// the Ethereum consensus rules.
-func NewMainnetConsensus() *MainnetConsensus {
- return &MainnetConsensus{}
-}
-
-// NewFakeDelayer creates an b consensus engine with a fake PoW scheme that
-// accepts all blocks as valid, but delays verifications by some time, though
-// they still have to conform to the Ethereum consensus rules.
-func NewFakeDelayer(delay time.Duration) *MainnetConsensus {
- return &MainnetConsensus{
- fakeDelay: delay,
- }
-}
-
-func (f *MainnetConsensus) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Header, seal bool) error {
- err := f.baseMainnet.VerifyHeader(chain, header, false)
- if err != nil {
- return err
- }
-
- if seal {
- return f.VerifySeal(chain, header)
- }
-
- return nil
-}
-
-func (f *MainnetConsensus) VerifyUncles(chain consensus.ChainReader, header *types.Header, uncles []*types.Header) error {
- return nil
-}
-
-func (f *MainnetConsensus) VerifyUncle(chain consensus.ChainHeaderReader, block *types.Header, uncle *types.Header, uncles mapset.Set[libcommon.Hash], ancestors map[libcommon.Hash]*types.Header, seal bool) error {
- err := f.baseMainnet.VerifyUncle(chain, block, uncle, uncles, ancestors, false)
- if err != nil {
- return err
- }
-
- if seal {
- return f.VerifySeal(chain, uncle)
- }
- return nil
-}
-
-// If we're running a fake PoW, accept any seal as valid
-func (f *MainnetConsensus) VerifySeal(_ consensus.ChainHeaderReader, header *types.Header) error {
- if f.fakeDelay > 0 {
- time.Sleep(f.fakeDelay)
- }
-
- if f.fakeFail == header.Number.Uint64() {
- return errInvalidPoW
- }
-
- return nil
-}
-
-// If we're running a fake PoW, simply return a 0 nonce immediately
-func (f *MainnetConsensus) Seal(_ consensus.ChainHeaderReader, blockWithReceipts *types.BlockWithReceipts, results chan<- *types.BlockWithReceipts, stop <-chan struct{}) error {
- block := blockWithReceipts.Block
- receipts := blockWithReceipts.Receipts
- header := block.Header()
- header.Nonce, header.MixDigest = types.BlockNonce{}, libcommon.Hash{}
-
- select {
- case results <- &types.BlockWithReceipts{Block: block.WithSeal(header), Receipts: receipts}:
- default:
- log.Warn("Sealing result is not read by miner", "mode", "fake", "sealhash", block.Hash())
- }
-
- return nil
-}
-
-type FullFakeb MainnetConsensus
-
-// NewFullFaker creates an b consensus engine with a full fake scheme that
-// accepts all blocks as valid, without checking any consensus rules whatsoever.
-func NewFullFaker() *FullFakeb {
- return &FullFakeb{}
-}
-
-// If we're running a full engine faking, accept any input as valid
-func (f *FullFakeb) VerifyHeader(_ consensus.ChainHeaderReader, _ *types.Header, _ bool) error {
- return nil
-}
-
-func (f *FullFakeb) VerifyUncles(_ consensus.ChainReader, _ *types.Header, _ []*types.Header) error {
- return nil
-}
diff --git a/core/block_validator_test.go b/core/block_validator_test.go
index d05358ee5f6..86ea15f66d8 100644
--- a/core/block_validator_test.go
+++ b/core/block_validator_test.go
@@ -27,7 +27,7 @@ import (
"github.com/erigontech/erigon-lib/kv"
"github.com/erigontech/erigon/consensus"
- "github.com/erigontech/erigon/consensus/mainnet"
+ "github.com/erigontech/erigon/consensus/ethash"
"github.com/erigontech/erigon/core"
"github.com/erigontech/erigon/core/types"
"github.com/erigontech/erigon/eth/stagedsync"
@@ -42,7 +42,7 @@ func TestHeaderVerification(t *testing.T) {
// Create a simple chain to verify
var (
gspec = &types.Genesis{Config: params.TestChainConfig}
- engine = mainnet.NewMainnetConsensus()
+ engine = ethash.NewFaker()
)
logger := testlog.Logger(t, log.LvlInfo)
checkStateRoot := true
@@ -64,9 +64,9 @@ func TestHeaderVerification(t *testing.T) {
}
var engine consensus.Engine
if valid {
- engine = mainnet.NewMainnetConsensus()
+ engine = ethash.NewFaker()
} else {
- engine = mainnet.NewFakeFailer(chain.Headers[i].Number.Uint64())
+ engine = ethash.NewFakeFailer(chain.Headers[i].Number.Uint64())
}
err = engine.VerifyHeader(chainReader, chain.Headers[i], true)
if (err == nil) != valid {
@@ -81,7 +81,7 @@ func TestHeaderVerification(t *testing.T) {
t.Fatalf("test %d: error inserting the block: %v", i, err)
}
- _ = engine.Close()
+ engine.Close()
}
}
@@ -91,7 +91,7 @@ func TestHeaderWithSealVerification(t *testing.T) {
// Create a simple chain to verify
var (
gspec = &types.Genesis{Config: params.TestChainAuraConfig}
- engine = mainnet.NewMainnetConsensus()
+ engine = ethash.NewFaker()
)
logger := testlog.Logger(t, log.LvlInfo)
checkStateRoot := true
@@ -114,9 +114,9 @@ func TestHeaderWithSealVerification(t *testing.T) {
}
var engine consensus.Engine
if valid {
- engine = mainnet.NewMainnetConsensus()
+ engine = ethash.NewFaker()
} else {
- engine = mainnet.NewFakeFailer(chain.Headers[i].Number.Uint64())
+ engine = ethash.NewFakeFailer(chain.Headers[i].Number.Uint64())
}
err = engine.VerifyHeader(chainReader, chain.Headers[i], true)
if (err == nil) != valid {
@@ -131,6 +131,6 @@ func TestHeaderWithSealVerification(t *testing.T) {
t.Fatalf("test %d: error inserting the block: %v", i, err)
}
- _ = engine.Close()
+ engine.Close()
}
}
diff --git a/core/rlp_test.go b/core/rlp_test.go
index 1323448ede9..646fc3aa84b 100644
--- a/core/rlp_test.go
+++ b/core/rlp_test.go
@@ -29,11 +29,11 @@ import (
libcommon "github.com/erigontech/erigon-lib/common"
"github.com/erigontech/erigon-lib/log/v3"
- "github.com/erigontech/erigon/consensus/mainnet"
"github.com/erigontech/erigon/core"
"github.com/erigontech/erigon/turbo/stages/mock"
"github.com/erigontech/erigon/common/u256"
+ "github.com/erigontech/erigon/consensus/ethash"
"github.com/erigontech/erigon/core/types"
"github.com/erigontech/erigon/crypto"
"github.com/erigontech/erigon/params"
@@ -44,7 +44,7 @@ func getBlock(tb testing.TB, transactions int, uncles int, dataSize int, tmpDir
var (
aa = libcommon.HexToAddress("0x000000000000000000000000000000000000aaaa")
// Generate a canonical chain to act as the main dataset
- engine = mainnet.NewMainnetConsensus()
+ engine = ethash.NewFaker()
// A sender who makes transactions, has some funds
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
address = crypto.PubkeyToAddress(key.PublicKey)
diff --git a/eth/backend.go b/eth/backend.go
index a1bf34d3ec5..9eb37b0c834 100644
--- a/eth/backend.go
+++ b/eth/backend.go
@@ -91,7 +91,7 @@ import (
"github.com/erigontech/erigon/common/debug"
"github.com/erigontech/erigon/consensus"
"github.com/erigontech/erigon/consensus/clique"
- "github.com/erigontech/erigon/consensus/mainnet"
+ "github.com/erigontech/erigon/consensus/ethash"
"github.com/erigontech/erigon/consensus/merge"
"github.com/erigontech/erigon/consensus/misc"
"github.com/erigontech/erigon/core"
@@ -534,7 +534,7 @@ func New(ctx context.Context, stack *node.Node, config *ethconfig.Config, logger
} else if chainConfig.Bor != nil {
consensusConfig = chainConfig.Bor
} else {
- consensusConfig = &mainnet.MainnetConfig{}
+ consensusConfig = &config.Ethash
}
var heimdallClient heimdall.HeimdallClient
@@ -696,6 +696,11 @@ func New(ctx context.Context, stack *node.Node, config *ethconfig.Config, logger
), stagedsync.MiningUnwindOrder, stagedsync.MiningPruneOrder,
logger)
+ var ethashApi *ethash.API
+ if casted, ok := backend.engine.(*ethash.Ethash); ok {
+ ethashApi = casted.APIs(nil)[1].Service.(*ethash.API)
+ }
+
// setup snapcfg
if err := loadSnapshotsEitherFromDiskIfNeeded(dirs, chainConfig.ChainName); err != nil {
return nil, err
@@ -747,7 +752,7 @@ func New(ctx context.Context, stack *node.Node, config *ethconfig.Config, logger
agg.SetSnapshotBuildSema(blockSnapBuildSema)
blockRetire := freezeblocks.NewBlockRetire(1, dirs, blockReader, blockWriter, backend.chainDB, backend.chainConfig, backend.notifications.Events, blockSnapBuildSema, logger)
- miningRPC = privateapi.NewMiningServer(ctx, backend, logger)
+ miningRPC = privateapi.NewMiningServer(ctx, backend, ethashApi, logger)
var creds credentials.TransportCredentials
if stack.Config().PrivateApiAddr != "" {
diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go
index dd4639a79a4..f1d90c25141 100644
--- a/eth/ethconfig/config.go
+++ b/eth/ethconfig/config.go
@@ -22,6 +22,10 @@ package ethconfig
import (
"math/big"
+ "os"
+ "os/user"
+ "path/filepath"
+ "runtime"
"strings"
"time"
@@ -34,6 +38,7 @@ import (
"github.com/erigontech/erigon-lib/txpool/txpoolcfg"
"github.com/erigontech/erigon/cl/beacon/beacon_router_configuration"
"github.com/erigontech/erigon/cl/clparams"
+ "github.com/erigontech/erigon/consensus/ethash/ethashcfg"
"github.com/erigontech/erigon/core/types"
"github.com/erigontech/erigon/eth/ethconfig/estimate"
"github.com/erigontech/erigon/eth/gasprice/gaspricecfg"
@@ -76,6 +81,13 @@ var Defaults = Config{
//LoopBlockLimit: 100_000,
PruneLimit: 100,
},
+ Ethash: ethashcfg.Config{
+ CachesInMem: 2,
+ CachesLockMmap: false,
+ DatasetsInMem: 1,
+ DatasetsOnDisk: 2,
+ DatasetsLockMmap: false,
+ },
NetworkID: 1,
Prune: prune.DefaultMode,
Miner: params.MiningConfig{
@@ -97,6 +109,30 @@ var Defaults = Config{
},
}
+func init() {
+ home := os.Getenv("HOME")
+ if home == "" {
+ if user, err := user.Current(); err == nil {
+ home = user.HomeDir
+ }
+ }
+ if runtime.GOOS == "darwin" {
+ Defaults.Ethash.DatasetDir = filepath.Join(home, "Library", "erigon-ethash")
+ } else if runtime.GOOS == "windows" {
+ localappdata := os.Getenv("LOCALAPPDATA")
+ if localappdata != "" {
+ Defaults.Ethash.DatasetDir = filepath.Join(localappdata, "erigon-thash")
+ } else {
+ Defaults.Ethash.DatasetDir = filepath.Join(home, "AppData", "Local", "erigon-ethash")
+ }
+ } else {
+ if xdgDataDir := os.Getenv("XDG_DATA_HOME"); xdgDataDir != "" {
+ Defaults.Ethash.DatasetDir = filepath.Join(xdgDataDir, "erigon-ethash")
+ }
+ Defaults.Ethash.DatasetDir = filepath.Join(home, ".local/share/erigon-ethash") //nolint:gocritic
+ }
+}
+
//go:generate gencodec -dir . -type Config -formats toml -out gen_config.go
type BlocksFreezing struct {
@@ -168,6 +204,9 @@ type Config struct {
// Mining options
Miner params.MiningConfig
+ // Ethash options
+ Ethash ethashcfg.Config
+
Clique params.ConsensusSnapshotConfig
Aura chain.AuRaConfig
diff --git a/eth/ethconfig/gen_config.go b/eth/ethconfig/gen_config.go
index 20c63981618..ddbdba296de 100644
--- a/eth/ethconfig/gen_config.go
+++ b/eth/ethconfig/gen_config.go
@@ -13,6 +13,7 @@ import (
"github.com/erigontech/erigon-lib/txpool/txpoolcfg"
"github.com/erigontech/erigon/cl/beacon/beacon_router_configuration"
"github.com/erigontech/erigon/cl/clparams"
+ "github.com/erigontech/erigon/consensus/ethash/ethashcfg"
"github.com/erigontech/erigon/core/types"
"github.com/erigontech/erigon/eth/gasprice/gaspricecfg"
"github.com/erigontech/erigon/ethdb/prune"
@@ -37,6 +38,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
ExternalSnapshotDownloaderAddr string
Whitelist map[uint64]common.Hash `toml:"-"`
Miner params.MiningConfig
+ Ethash ethashcfg.Config
Clique params.ConsensusSnapshotConfig
Aura chain.AuRaConfig
DeprecatedTxPool DeprecatedTxPoolConfig
@@ -89,6 +91,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
enc.ExternalSnapshotDownloaderAddr = c.ExternalSnapshotDownloaderAddr
enc.Whitelist = c.Whitelist
enc.Miner = c.Miner
+ enc.Ethash = c.Ethash
enc.Clique = c.Clique
enc.Aura = c.Aura
enc.DeprecatedTxPool = c.DeprecatedTxPool
@@ -145,6 +148,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
ExternalSnapshotDownloaderAddr *string
Whitelist map[uint64]common.Hash `toml:"-"`
Miner *params.MiningConfig
+ Ethash *ethashcfg.Config
Clique *params.ConsensusSnapshotConfig
Aura *chain.AuRaConfig
DeprecatedTxPool *DeprecatedTxPoolConfig
@@ -230,7 +234,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
if dec.Miner != nil {
c.Miner = *dec.Miner
}
-
+ if dec.Ethash != nil {
+ c.Ethash = *dec.Ethash
+ }
if dec.Clique != nil {
c.Clique = *dec.Clique
}
diff --git a/eth/ethconsensusconfig/config.go b/eth/ethconsensusconfig/config.go
index 788c38147ad..f05a14e117d 100644
--- a/eth/ethconsensusconfig/config.go
+++ b/eth/ethconsensusconfig/config.go
@@ -33,7 +33,8 @@ import (
"github.com/erigontech/erigon/consensus"
"github.com/erigontech/erigon/consensus/aura"
"github.com/erigontech/erigon/consensus/clique"
- "github.com/erigontech/erigon/consensus/mainnet"
+ "github.com/erigontech/erigon/consensus/ethash"
+ "github.com/erigontech/erigon/consensus/ethash/ethashcfg"
"github.com/erigontech/erigon/consensus/merge"
"github.com/erigontech/erigon/node"
"github.com/erigontech/erigon/node/nodecfg"
@@ -50,9 +51,27 @@ func CreateConsensusEngine(ctx context.Context, nodeConfig *nodecfg.Config, chai
var eng consensus.Engine
switch consensusCfg := config.(type) {
- case *mainnet.MainnetConfig:
- logger.Warn("Ethash used in fake mode")
- eng = mainnet.NewMainnetConsensus()
+ case *ethashcfg.Config:
+ switch consensusCfg.PowMode {
+ case ethashcfg.ModeFake:
+ logger.Warn("Ethash used in fake mode")
+ eng = ethash.NewFaker()
+ case ethashcfg.ModeTest:
+ logger.Warn("Ethash used in test mode")
+ eng = ethash.NewTester(nil, noVerify)
+ case ethashcfg.ModeShared:
+ logger.Warn("Ethash used in shared mode")
+ eng = ethash.NewShared()
+ default:
+ eng = ethash.New(ethashcfg.Config{
+ CachesInMem: consensusCfg.CachesInMem,
+ CachesLockMmap: consensusCfg.CachesLockMmap,
+ DatasetDir: consensusCfg.DatasetDir,
+ DatasetsInMem: consensusCfg.DatasetsInMem,
+ DatasetsOnDisk: consensusCfg.DatasetsOnDisk,
+ DatasetsLockMmap: consensusCfg.DatasetsLockMmap,
+ }, notify, noVerify)
+ }
case *params.ConsensusSnapshotConfig:
if chainConfig.Clique != nil {
if consensusCfg.InMemory {
@@ -134,7 +153,9 @@ func CreateConsensusEngineBareBones(ctx context.Context, chainConfig *chain.Conf
} else if chainConfig.Bor != nil {
consensusConfig = chainConfig.Bor
} else {
- consensusConfig = &mainnet.MainnetConfig{}
+ var ethashCfg ethashcfg.Config
+ ethashCfg.PowMode = ethashcfg.ModeFake
+ consensusConfig = ðashCfg
}
return CreateConsensusEngine(ctx, &nodecfg.Config{}, chainConfig, consensusConfig, nil /* notify */, true, /* noVerify */
diff --git a/eth/filters/filter_system_test.go b/eth/filters/filter_system_test.go
index d9135400144..3e345c46b8a 100644
--- a/eth/filters/filter_system_test.go
+++ b/eth/filters/filter_system_test.go
@@ -37,7 +37,7 @@ func TestBlockSubscription(t *testing.T) {
backend = &testBackend{db: db}
api = NewPublicFilterAPI(backend, deadline)
genesis = (&core.Genesis{Config: params.TestChainConfig}).MustCommitDeprecated(db)
- chain, _ = core.GenerateChain(params.TestChainConfig, genesis, mainnet.NewMainnetConsensus(), db.RwKV(), 10, func(i int, gen *core.BlockGen) {}, false)
+ chain, _ = core.GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), db.RwKV(), 10, func(i int, gen *core.BlockGen) {}, false)
chainEvents = []core.ChainEvent{}
)
diff --git a/ethdb/privateapi/mining.go b/ethdb/privateapi/mining.go
index bcfd519367a..b8f9d6e3ea7 100644
--- a/ethdb/privateapi/mining.go
+++ b/ethdb/privateapi/mining.go
@@ -19,14 +19,19 @@ package privateapi
import (
"bytes"
"context"
+ "errors"
"sync"
+ "github.com/erigontech/erigon-lib/common/hexutil"
+
"google.golang.org/protobuf/types/known/emptypb"
+ libcommon "github.com/erigontech/erigon-lib/common"
proto_txpool "github.com/erigontech/erigon-lib/gointerfaces/txpoolproto"
types2 "github.com/erigontech/erigon-lib/gointerfaces/typesproto"
"github.com/erigontech/erigon-lib/log/v3"
+ "github.com/erigontech/erigon/consensus/ethash"
"github.com/erigontech/erigon/core/types"
"github.com/erigontech/erigon/rlp"
)
@@ -41,6 +46,7 @@ type MiningServer struct {
pendingLogsStreams PendingLogsStreams
pendingBlockStreams PendingBlockStreams
minedBlockStreams MinedBlockStreams
+ ethash *ethash.API
isMining IsMining
logger log.Logger
}
@@ -49,14 +55,57 @@ type IsMining interface {
IsMining() bool
}
-func NewMiningServer(ctx context.Context, isMining IsMining, logger log.Logger) *MiningServer {
- return &MiningServer{ctx: ctx, isMining: isMining, logger: logger}
+func NewMiningServer(ctx context.Context, isMining IsMining, ethashApi *ethash.API, logger log.Logger) *MiningServer {
+ return &MiningServer{ctx: ctx, isMining: isMining, ethash: ethashApi, logger: logger}
}
func (s *MiningServer) Version(context.Context, *emptypb.Empty) (*types2.VersionReply, error) {
return MiningAPIVersion, nil
}
+func (s *MiningServer) GetWork(context.Context, *proto_txpool.GetWorkRequest) (*proto_txpool.GetWorkReply, error) {
+ if s.ethash == nil {
+ return nil, errors.New("not supported, consensus engine is not ethash")
+ }
+ res, err := s.ethash.GetWork()
+ if err != nil {
+ return nil, err
+ }
+ return &proto_txpool.GetWorkReply{HeaderHash: res[0], SeedHash: res[1], Target: res[2], BlockNumber: res[3]}, nil
+}
+
+func (s *MiningServer) SubmitWork(_ context.Context, req *proto_txpool.SubmitWorkRequest) (*proto_txpool.SubmitWorkReply, error) {
+ if s.ethash == nil {
+ return nil, errors.New("not supported, consensus engine is not ethash")
+ }
+ var nonce types.BlockNonce
+ copy(nonce[:], req.BlockNonce)
+ ok := s.ethash.SubmitWork(nonce, libcommon.BytesToHash(req.PowHash), libcommon.BytesToHash(req.Digest))
+ return &proto_txpool.SubmitWorkReply{Ok: ok}, nil
+}
+
+func (s *MiningServer) SubmitHashRate(_ context.Context, req *proto_txpool.SubmitHashRateRequest) (*proto_txpool.SubmitHashRateReply, error) {
+ if s.ethash == nil {
+ return nil, errors.New("not supported, consensus engine is not ethash")
+ }
+ ok := s.ethash.SubmitHashRate(hexutil.Uint64(req.Rate), libcommon.BytesToHash(req.Id))
+ return &proto_txpool.SubmitHashRateReply{Ok: ok}, nil
+}
+
+func (s *MiningServer) GetHashRate(_ context.Context, req *proto_txpool.HashRateRequest) (*proto_txpool.HashRateReply, error) {
+ if s.ethash == nil {
+ return nil, errors.New("not supported, consensus engine is not ethash")
+ }
+ return &proto_txpool.HashRateReply{HashRate: s.ethash.GetHashrate()}, nil
+}
+
+func (s *MiningServer) Mining(_ context.Context, req *proto_txpool.MiningRequest) (*proto_txpool.MiningReply, error) {
+ if s.ethash == nil {
+ return nil, errors.New("not supported, consensus engine is not ethash")
+ }
+ return &proto_txpool.MiningReply{Enabled: s.isMining.IsMining(), Running: true}, nil
+}
+
func (s *MiningServer) OnPendingLogs(req *proto_txpool.OnPendingLogsRequest, reply proto_txpool.Mining_OnPendingLogsServer) error {
remove := s.pendingLogsStreams.Add(reply)
defer remove()
diff --git a/go.mod b/go.mod
index 261e2cea638..3713429b399 100644
--- a/go.mod
+++ b/go.mod
@@ -32,6 +32,7 @@ require (
github.com/deckarep/golang-set v1.8.0
github.com/deckarep/golang-set/v2 v2.3.1
github.com/dop251/goja v0.0.0-20220405120441-9037c2b61cbf
+ github.com/edsrzf/mmap-go v1.1.0
github.com/emicklei/dot v1.6.2
github.com/erigontech/erigon-lib v0.0.0-00010101000000-000000000000
github.com/fjl/gencodec v0.0.0-20220412091415-8bb9e558978c
@@ -107,7 +108,6 @@ require (
)
require (
- github.com/edsrzf/mmap-go v1.1.0 // indirect
github.com/elastic/go-freelru v0.13.0 // indirect
github.com/erigontech/speedtest v0.0.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
diff --git a/polygon/bor/fake.go b/polygon/bor/fake.go
index ac5f01f3073..ce9b74eb57b 100644
--- a/polygon/bor/fake.go
+++ b/polygon/bor/fake.go
@@ -20,19 +20,19 @@ import (
"github.com/erigontech/erigon-lib/chain"
"github.com/erigontech/erigon-lib/log/v3"
"github.com/erigontech/erigon/consensus"
- "github.com/erigontech/erigon/consensus/mainnet"
+ "github.com/erigontech/erigon/consensus/ethash"
"github.com/erigontech/erigon/core/state"
"github.com/erigontech/erigon/core/types"
)
type FakeBor struct {
- *mainnet.MainnetConsensus
+ *ethash.FakeEthash
}
// NewFaker creates a bor consensus engine with a FakeEthash
func NewFaker() *FakeBor {
return &FakeBor{
- MainnetConsensus: mainnet.NewMainnetConsensus(),
+ FakeEthash: ethash.NewFaker(),
}
}
@@ -40,5 +40,5 @@ func (f *FakeBor) Finalize(config *chain.Config, header *types.Header, state *st
txs types.Transactions, uncles []*types.Header, r types.Receipts, withdrawals []*types.Withdrawal, requests types.Requests,
chain consensus.ChainReader, syscall consensus.SystemCall, logger log.Logger,
) (types.Transactions, types.Receipts, types.Requests, error) {
- return f.MainnetConsensus.Finalize(config, header, state, txs, uncles, r, withdrawals, requests, chain, syscall, logger)
+ return f.FakeEthash.Finalize(config, header, state, txs, uncles, r, withdrawals, requests, chain, syscall, logger)
}
diff --git a/tests/difficulty_test.go b/tests/difficulty_test.go
new file mode 100644
index 00000000000..daab9e53e8e
--- /dev/null
+++ b/tests/difficulty_test.go
@@ -0,0 +1,62 @@
+// Copyright 2017 The go-ethereum Authors
+// (original work)
+// Copyright 2024 The Erigon Authors
+// (modifications)
+// This file is part of Erigon.
+//
+// Erigon 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.
+//
+// Erigon 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 Erigon. If not, see .
+
+//go:build integration
+
+package tests
+
+import (
+ "encoding/json"
+ "fmt"
+ "testing"
+)
+
+func TestDifficulty(t *testing.T) {
+ //t.Parallel()
+
+ dt := new(testMatcher)
+
+ dt.walk(t, difficultyTestDir, func(t *testing.T, name string, superTest map[string]json.RawMessage) {
+ for fork, rawTests := range superTest {
+ if fork == "_info" {
+ continue
+ }
+ var tests map[string]DifficultyTest
+ if err := json.Unmarshal(rawTests, &tests); err != nil {
+ t.Error(err)
+ continue
+ }
+
+ cfg, ok := Forks[fork]
+ if !ok {
+ t.Error(UnsupportedForkError{fork})
+ continue
+ }
+
+ for subname, subtest := range tests {
+ key := fmt.Sprintf("%s/%s", fork, subname)
+ t.Run(key, func(t *testing.T) {
+ if err := dt.checkFailure(t, subtest.Run(cfg)); err != nil {
+ t.Error(err)
+ }
+ })
+ }
+ }
+ })
+}
diff --git a/tests/difficulty_test_util.go b/tests/difficulty_test_util.go
new file mode 100644
index 00000000000..13e52074bba
--- /dev/null
+++ b/tests/difficulty_test_util.go
@@ -0,0 +1,78 @@
+// Copyright 2017 The go-ethereum Authors
+// (original work)
+// Copyright 2024 The Erigon Authors
+// (modifications)
+// This file is part of Erigon.
+//
+// Erigon 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.
+//
+// Erigon 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 Erigon. If not, see .
+
+package tests
+
+import (
+ "fmt"
+ "math/big"
+
+ "github.com/erigontech/erigon-lib/chain"
+ libcommon "github.com/erigontech/erigon-lib/common"
+
+ "github.com/erigontech/erigon/common/math"
+ "github.com/erigontech/erigon/consensus/ethash"
+ "github.com/erigontech/erigon/core/types"
+)
+
+//go:generate gencodec -type DifficultyTest -field-override difficultyTestMarshaling -out gen_difficultytest.go
+
+type DifficultyTest struct {
+ ParentTimestamp uint64 `json:"parentTimestamp"`
+ ParentDifficulty *big.Int `json:"parentDifficulty"`
+ ParentUncles uint64 `json:"parentUncles"`
+ CurrentTimestamp uint64 `json:"currentTimestamp"`
+ CurrentBlockNumber uint64 `json:"currentBlockNumber"`
+ CurrentDifficulty *big.Int `json:"currentDifficulty"`
+}
+
+type difficultyTestMarshaling struct {
+ ParentTimestamp math.HexOrDecimal64
+ ParentDifficulty *math.HexOrDecimal256
+ CurrentTimestamp math.HexOrDecimal64
+ CurrentDifficulty *math.HexOrDecimal256
+ ParentUncles uint64
+ CurrentBlockNumber math.HexOrDecimal64
+}
+
+func (test *DifficultyTest) Run(config *chain.Config) error {
+ parentNumber := new(big.Int).SetUint64(test.CurrentBlockNumber - 1)
+ parent := &types.Header{
+ Difficulty: test.ParentDifficulty,
+ Time: test.ParentTimestamp,
+ Number: parentNumber,
+ }
+
+ if test.ParentUncles == 0 {
+ parent.UncleHash = types.EmptyUncleHash
+ } else {
+ parent.UncleHash = libcommon.HexToHash("ab") // some dummy != EmptyUncleHash
+ }
+
+ actual := ethash.CalcDifficulty(config, test.CurrentTimestamp, parent.Time, parent.Difficulty, parent.Number.Uint64(), parent.UncleHash)
+ exp := test.CurrentDifficulty
+
+ if actual.Cmp(exp) != 0 {
+ return fmt.Errorf("parent[time %v diff %v unclehash:%x] child[time %v number %v] diff %v != expected %v",
+ test.ParentTimestamp, test.ParentDifficulty, test.ParentUncles,
+ test.CurrentTimestamp, test.CurrentBlockNumber, actual, exp)
+ }
+ return nil
+
+}
diff --git a/tests/fuzzers/difficulty/debug/main.go b/tests/fuzzers/difficulty/debug/main.go
new file mode 100644
index 00000000000..5aa17bf76c7
--- /dev/null
+++ b/tests/fuzzers/difficulty/debug/main.go
@@ -0,0 +1,22 @@
+package main
+
+import (
+ "fmt"
+ "os"
+
+ "github.com/erigontech/erigon/tests/fuzzers/difficulty"
+)
+
+func main() {
+ if len(os.Args) != 2 {
+ fmt.Fprintf(os.Stderr, "Usage: debug ")
+ os.Exit(1)
+ }
+ crasher := os.Args[1]
+ data, err := os.ReadFile(crasher)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "error loading crasher %v: %v", crasher, err)
+ os.Exit(1)
+ }
+ difficulty.Fuzz(data)
+}
diff --git a/tests/fuzzers/difficulty/difficulty-fuzz.go b/tests/fuzzers/difficulty/difficulty-fuzz.go
new file mode 100644
index 00000000000..360d8581bd6
--- /dev/null
+++ b/tests/fuzzers/difficulty/difficulty-fuzz.go
@@ -0,0 +1,153 @@
+// Copyright 2020 The go-ethereum Authors
+// (original work)
+// Copyright 2024 The Erigon Authors
+// (modifications)
+// This file is part of Erigon.
+//
+// Erigon 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.
+//
+// Erigon 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 Erigon. If not, see .
+
+package difficulty
+
+import (
+ "bytes"
+ "encoding/binary"
+ "fmt"
+ "io"
+ "math/big"
+
+ libcommon "github.com/erigontech/erigon-lib/common"
+
+ "github.com/erigontech/erigon/consensus/ethash"
+ "github.com/erigontech/erigon/core/types"
+)
+
+type fuzzer struct {
+ input io.Reader
+ exhausted bool
+}
+
+func (f *fuzzer) read(size int) []byte {
+ out := make([]byte, size)
+ if _, err := f.input.Read(out); err != nil {
+ f.exhausted = true
+ }
+ return out
+}
+
+func (f *fuzzer) readSlice(min, max int) []byte {
+ var a uint16
+ //nolint:errcheck
+ binary.Read(f.input, binary.LittleEndian, &a)
+ size := min + int(a)%(max-min)
+ out := make([]byte, size)
+ if _, err := f.input.Read(out); err != nil {
+ f.exhausted = true
+ }
+ return out
+}
+
+func (f *fuzzer) readUint64(min, max uint64) uint64 {
+ if min == max {
+ return min
+ }
+ var a uint64
+ if err := binary.Read(f.input, binary.LittleEndian, &a); err != nil {
+ f.exhausted = true
+ }
+ a = min + a%(max-min)
+ return a
+}
+func (f *fuzzer) readBool() bool {
+ return f.read(1)[0]&0x1 == 0
+}
+
+// The function must return
+// 1 if the fuzzer should increase priority of the
+//
+// given input during subsequent fuzzing (for example, the input is lexically
+// correct and was parsed successfully);
+//
+// -1 if the input must not be added to corpus even if gives new coverage; and
+// 0 otherwise
+// other values are reserved for future use.
+func Fuzz(data []byte) int {
+ f := fuzzer{
+ input: bytes.NewReader(data),
+ exhausted: false,
+ }
+ return f.fuzz()
+}
+
+var minDifficulty = big.NewInt(0x2000)
+
+type calculator func(time uint64, parentTime uint64, parentDifficulty *big.Int, parentNumber uint64, parentUncleHash libcommon.Hash) *big.Int
+type calculatorU256 func(time uint64, parent *types.Header) *big.Int
+
+func (f *fuzzer) fuzz() int {
+ // A parent header
+ header := &types.Header{}
+ if f.readBool() {
+ header.UncleHash = types.EmptyUncleHash
+ }
+ // Difficulty can range between 0x2000 (2 bytes) and up to 32 bytes
+ {
+ diff := new(big.Int).SetBytes(f.readSlice(2, 32))
+ if diff.Cmp(minDifficulty) < 0 {
+ diff.Set(minDifficulty)
+ }
+ header.Difficulty = diff
+ }
+ // Number can range between 0 and up to 32 bytes (but not so that the child exceeds it)
+ {
+ // However, if we use astronomic numbers, then the bomb exp karatsuba calculation
+ // in the legacy methods)
+ // times out, so we limit it to fit within reasonable bounds
+ number := new(big.Int).SetBytes(f.readSlice(0, 4)) // 4 bytes: 32 bits: block num max 4 billion
+ header.Number = number
+ }
+ // Both parent and child time must fit within uint64
+ var time uint64
+ {
+ childTime := f.readUint64(1, 0xFFFFFFFFFFFFFFFF)
+ //fmt.Printf("childTime: %x\n",childTime)
+ delta := f.readUint64(1, childTime)
+ //fmt.Printf("delta: %v\n", delta)
+ pTime := childTime - delta
+ header.Time = pTime
+ time = childTime
+ }
+ // Bomb delay will never exceed uint64
+ bombDelay := f.readUint64(1, 0xFFFFFFFFFFFFFFFe)
+
+ if f.exhausted {
+ return 0
+ }
+
+ for i, pair := range []struct {
+ bigFn calculator
+ u256Fn calculatorU256
+ }{
+ {ethash.FrontierDifficultyCalulator, ethash.CalcDifficultyFrontierU256},
+ {ethash.HomesteadDifficultyCalulator, ethash.CalcDifficultyHomesteadU256},
+ {ethash.DynamicDifficultyCalculator(bombDelay), ethash.MakeDifficultyCalculatorU256(bombDelay)},
+ } {
+ want := pair.bigFn(time, header.Time, header.Difficulty, header.Number.Uint64(), header.UncleHash)
+ have := pair.u256Fn(time, header)
+ if want.Cmp(have) != 0 {
+ panic(fmt.Sprintf("pair %d: want %x have %x\nparent.Number: %x\np.Time: %x\nc.Time: %x\nBombdelay: %v\n", i, want, have,
+ header.Number, header.Time, time, bombDelay))
+ }
+ }
+ return 1
+}
diff --git a/tests/gen_difficultytest.go b/tests/gen_difficultytest.go
new file mode 100644
index 00000000000..8b9732b4f0e
--- /dev/null
+++ b/tests/gen_difficultytest.go
@@ -0,0 +1,67 @@
+// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
+
+package tests
+
+import (
+ "encoding/json"
+ "math/big"
+
+ "github.com/erigontech/erigon/common/math"
+)
+
+var _ = (*difficultyTestMarshaling)(nil)
+
+// MarshalJSON marshals as JSON.
+func (d DifficultyTest) MarshalJSON() ([]byte, error) {
+ type DifficultyTest struct {
+ ParentTimestamp math.HexOrDecimal64 `json:"parentTimestamp"`
+ ParentDifficulty *math.HexOrDecimal256 `json:"parentDifficulty"`
+ ParentUncles math.HexOrDecimal64 `json:"parentUncles"`
+ CurrentTimestamp math.HexOrDecimal64 `json:"currentTimestamp"`
+ CurrentBlockNumber math.HexOrDecimal64 `json:"currentBlockNumber"`
+ CurrentDifficulty *math.HexOrDecimal256 `json:"currentDifficulty"`
+ }
+ var enc DifficultyTest
+ enc.ParentTimestamp = math.HexOrDecimal64(d.ParentTimestamp)
+ enc.ParentDifficulty = (*math.HexOrDecimal256)(d.ParentDifficulty)
+ enc.ParentUncles = math.HexOrDecimal64(d.ParentUncles)
+ enc.CurrentTimestamp = math.HexOrDecimal64(d.CurrentTimestamp)
+ enc.CurrentBlockNumber = math.HexOrDecimal64(d.CurrentBlockNumber)
+ enc.CurrentDifficulty = (*math.HexOrDecimal256)(d.CurrentDifficulty)
+ return json.Marshal(&enc)
+}
+
+// UnmarshalJSON unmarshals from JSON.
+func (d *DifficultyTest) UnmarshalJSON(input []byte) error {
+ type DifficultyTest struct {
+ ParentTimestamp *math.HexOrDecimal64 `json:"parentTimestamp"`
+ ParentDifficulty *math.HexOrDecimal256 `json:"parentDifficulty"`
+ ParentUncles *math.HexOrDecimal64 `json:"parentUncles"`
+ CurrentTimestamp *math.HexOrDecimal64 `json:"currentTimestamp"`
+ CurrentBlockNumber *math.HexOrDecimal64 `json:"currentBlockNumber"`
+ CurrentDifficulty *math.HexOrDecimal256 `json:"currentDifficulty"`
+ }
+ var dec DifficultyTest
+ if err := json.Unmarshal(input, &dec); err != nil {
+ return err
+ }
+ if dec.ParentTimestamp != nil {
+ d.ParentTimestamp = uint64(*dec.ParentTimestamp)
+ }
+ if dec.ParentDifficulty != nil {
+ d.ParentDifficulty = (*big.Int)(dec.ParentDifficulty)
+ }
+ if dec.ParentUncles != nil {
+ d.ParentUncles = uint64(*dec.ParentUncles)
+ }
+ if dec.CurrentTimestamp != nil {
+ d.CurrentTimestamp = uint64(*dec.CurrentTimestamp)
+ }
+ if dec.CurrentBlockNumber != nil {
+ d.CurrentBlockNumber = uint64(*dec.CurrentBlockNumber)
+ }
+ if dec.CurrentDifficulty != nil {
+ d.CurrentDifficulty = (*big.Int)(dec.CurrentDifficulty)
+ }
+ return nil
+}
diff --git a/tests/init_test.go b/tests/init_test.go
index 78483aff52b..441e223e944 100644
--- a/tests/init_test.go
+++ b/tests/init_test.go
@@ -43,6 +43,7 @@ var (
stateTestDir = filepath.Join(baseDir, "GeneralStateTests")
transactionTestDir = filepath.Join(baseDir, "TransactionTests")
rlpTestDir = filepath.Join(baseDir, "RLPTests")
+ difficultyTestDir = filepath.Join(baseDir, "DifficultyTests")
)
func readJSON(reader io.Reader, value interface{}) error {
diff --git a/turbo/cli/default_flags.go b/turbo/cli/default_flags.go
index f29d2481a1d..1edd6924754 100644
--- a/turbo/cli/default_flags.go
+++ b/turbo/cli/default_flags.go
@@ -25,6 +25,7 @@ import (
// DefaultFlags contains all flags that are used and supported by Erigon binary.
var DefaultFlags = []cli.Flag{
&utils.DataDirFlag,
+ &utils.EthashDatasetDirFlag,
&utils.ExternalConsensusFlag,
&utils.TxPoolDisableFlag,
&utils.TxPoolLocalsFlag,
diff --git a/turbo/jsonrpc/erigon_receipts_test.go b/turbo/jsonrpc/erigon_receipts_test.go
index d0cf91940eb..8549f07d84c 100644
--- a/turbo/jsonrpc/erigon_receipts_test.go
+++ b/turbo/jsonrpc/erigon_receipts_test.go
@@ -111,7 +111,7 @@ func TestErigonGetLatestLogs(t *testing.T) {
BlockNumber: 10,
TxHash: libcommon.HexToHash("0xb6449d8e167a8826d050afe4c9f07095236ff769a985f02649b1023c2ded2059"),
TxIndex: 0,
- BlockHash: libcommon.HexToHash("0xdcf97af5587f84aaee59591be0e83796baa527a5553ab14254b07001a8fe6c0a"),
+ BlockHash: libcommon.HexToHash("0x6804117de2f3e6ee32953e78ced1db7b20214e0d8c745a03b8fecf7cc8ee76ef"),
Index: 0,
Removed: false,
Timestamp: 100,
@@ -214,8 +214,8 @@ func TestGetBlockReceiptsByBlockHash(t *testing.T) {
expect := map[uint64]string{
0: `[]`,
- 1: `[{"blockHash":"0x3598211c4ccc494e7744180b43165a531dd133c9aa4e68b4d97d9e07b082a73d","blockNumber":"0x1","contractAddress":null,"cumulativeGasUsed":"0x5208","effectiveGasPrice":"0x0","from":"0x71562b71999873db5b286df957af199ec94617f7","gasUsed":"0x5208","logs":[],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","to":"0x703c4b2bd70c169f5717101caee543299fc946c7","transactionHash":"0x9ca7a9e6bf23353fc5ac37f5c5676db1accec4af83477ac64cdcaa37f3a837f9","transactionIndex":"0x0","type":"0x0"}]`,
- 2: `[{"blockHash":"0xe3c0a4af50dc33587ced611c68cbd7ecfdf0e66c4d5d53ef18544ebcb06ab3d2","blockNumber":"0x2","contractAddress":null,"cumulativeGasUsed":"0x5208","effectiveGasPrice":"0x0","from":"0x71562b71999873db5b286df957af199ec94617f7","gasUsed":"0x5208","logs":[],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","to":"0x703c4b2bd70c169f5717101caee543299fc946c7","transactionHash":"0xf190eed1578cdcfe69badd05b7ef183397f336dc3de37baa4adbfb4bc657c11e","transactionIndex":"0x0","type":"0x0"},{"blockHash":"0xe3c0a4af50dc33587ced611c68cbd7ecfdf0e66c4d5d53ef18544ebcb06ab3d2","blockNumber":"0x2","contractAddress":null,"cumulativeGasUsed":"0xa410","effectiveGasPrice":"0x0","from":"0x703c4b2bd70c169f5717101caee543299fc946c7","gasUsed":"0x5208","logs":[],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","to":"0x0d3ab14bbad3d99f4203bd7a11acb94882050e7e","transactionHash":"0x309a030e44058e435a2b01302006880953e2c9319009db97013eb130d7a24eab","transactionIndex":"0x1","type":"0x0"}]`,
+ 1: `[{"blockHash":"0x63b978611c906a61e4a8333fedeea8d62a1c869fc9a19acf6ed0cc5139247eda","blockNumber":"0x1","contractAddress":null,"cumulativeGasUsed":"0x5208","effectiveGasPrice":"0x0","from":"0x71562b71999873db5b286df957af199ec94617f7","gasUsed":"0x5208","logs":[],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","to":"0x703c4b2bd70c169f5717101caee543299fc946c7","transactionHash":"0x9ca7a9e6bf23353fc5ac37f5c5676db1accec4af83477ac64cdcaa37f3a837f9","transactionIndex":"0x0","type":"0x0"}]`,
+ 2: `[{"blockHash":"0xd3294fcc342ff74be4ae07fb25cd3b2fbb6c2b7830f212ee0723da956e70e099","blockNumber":"0x2","contractAddress":null,"cumulativeGasUsed":"0x5208","effectiveGasPrice":"0x0","from":"0x71562b71999873db5b286df957af199ec94617f7","gasUsed":"0x5208","logs":[],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","to":"0x703c4b2bd70c169f5717101caee543299fc946c7","transactionHash":"0xf190eed1578cdcfe69badd05b7ef183397f336dc3de37baa4adbfb4bc657c11e","transactionIndex":"0x0","type":"0x0"},{"blockHash":"0xd3294fcc342ff74be4ae07fb25cd3b2fbb6c2b7830f212ee0723da956e70e099","blockNumber":"0x2","contractAddress":null,"cumulativeGasUsed":"0xa410","effectiveGasPrice":"0x0","from":"0x703c4b2bd70c169f5717101caee543299fc946c7","gasUsed":"0x5208","logs":[],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","to":"0x0d3ab14bbad3d99f4203bd7a11acb94882050e7e","transactionHash":"0x309a030e44058e435a2b01302006880953e2c9319009db97013eb130d7a24eab","transactionIndex":"0x1","type":"0x0"}]`,
3: `[]`,
4: `[]`,
}
diff --git a/turbo/jsonrpc/eth_block.go b/turbo/jsonrpc/eth_block.go
index 07a519a6ca6..79c0ec6a1fb 100644
--- a/turbo/jsonrpc/eth_block.go
+++ b/turbo/jsonrpc/eth_block.go
@@ -224,8 +224,14 @@ func (api *APIImpl) GetBlockByNumber(ctx context.Context, number rpc.BlockNumber
if b == nil {
return nil, nil
}
-
additionalFields := make(map[string]interface{})
+ td, err := rawdb.ReadTd(tx, b.Hash(), b.NumberU64())
+ if err != nil {
+ return nil, err
+ }
+ if td != nil {
+ additionalFields["totalDifficulty"] = (*hexutil.Big)(td)
+ }
chainConfig, err := api.chainConfig(ctx, tx)
if err != nil {
diff --git a/turbo/jsonrpc/eth_block_test.go b/turbo/jsonrpc/eth_block_test.go
index 34768094ecb..0b155d3cf32 100644
--- a/turbo/jsonrpc/eth_block_test.go
+++ b/turbo/jsonrpc/eth_block_test.go
@@ -46,7 +46,7 @@ func TestGetBlockByNumberWithLatestTag(t *testing.T) {
m, _, _ := rpcdaemontest.CreateTestSentry(t)
api := NewEthAPI(newBaseApiForTest(m), m.DB, nil, nil, nil, 5000000, 1e18, 100_000, false, 100_000, 128, log.New())
b, err := api.GetBlockByNumber(context.Background(), rpc.LatestBlockNumber, false)
- expected := common.HexToHash("0xe99e4072055929794042a257bdb34787aba52ee220f4e60867acc57d56474dab")
+ expected := common.HexToHash("0x5883164d4100b95e1d8e931b8b9574586a1dea7507941e6ad3c1e3a2591485fd")
if err != nil {
t.Errorf("error getting block number with latest tag: %s", err)
}
@@ -60,7 +60,7 @@ func TestGetBlockByNumberWithLatestTag_WithHeadHashInDb(t *testing.T) {
if err != nil {
t.Errorf("could not begin read write transaction: %s", err)
}
- latestBlockHash := common.HexToHash("0xe99e4072055929794042a257bdb34787aba52ee220f4e60867acc57d56474dab")
+ latestBlockHash := common.HexToHash("0x6804117de2f3e6ee32953e78ced1db7b20214e0d8c745a03b8fecf7cc8ee76ef")
latestBlock, err := m.BlockReader.BlockByHash(ctx, tx, latestBlockHash)
if err != nil {
tx.Rollback()
@@ -128,7 +128,7 @@ func TestGetBlockByNumber_WithFinalizedTag_WithFinalizedBlockInDb(t *testing.T)
if err != nil {
t.Errorf("could not begin read write transaction: %s", err)
}
- latestBlockHash := common.HexToHash("0xe99e4072055929794042a257bdb34787aba52ee220f4e60867acc57d56474dab")
+ latestBlockHash := common.HexToHash("0x6804117de2f3e6ee32953e78ced1db7b20214e0d8c745a03b8fecf7cc8ee76ef")
latestBlock, err := m.BlockReader.BlockByHash(ctx, tx, latestBlockHash)
if err != nil {
tx.Rollback()
@@ -167,7 +167,7 @@ func TestGetBlockByNumber_WithSafeTag_WithSafeBlockInDb(t *testing.T) {
if err != nil {
t.Errorf("could not begin read write transaction: %s", err)
}
- latestBlockHash := common.HexToHash("0xe99e4072055929794042a257bdb34787aba52ee220f4e60867acc57d56474dab")
+ latestBlockHash := common.HexToHash("0x6804117de2f3e6ee32953e78ced1db7b20214e0d8c745a03b8fecf7cc8ee76ef")
latestBlock, err := m.BlockReader.BlockByHash(ctx, tx, latestBlockHash)
if err != nil {
tx.Rollback()
@@ -195,7 +195,7 @@ func TestGetBlockTransactionCountByHash(t *testing.T) {
ctx := context.Background()
api := NewEthAPI(newBaseApiForTest(m), m.DB, nil, nil, nil, 5000000, 1e18, 100_000, false, 100_000, 128, log.New())
- blockHash := common.HexToHash("0xe99e4072055929794042a257bdb34787aba52ee220f4e60867acc57d56474dab")
+ blockHash := common.HexToHash("0x6804117de2f3e6ee32953e78ced1db7b20214e0d8c745a03b8fecf7cc8ee76ef")
tx, err := m.DB.BeginRw(ctx)
if err != nil {
@@ -227,7 +227,7 @@ func TestGetBlockTransactionCountByHash_ZeroTx(t *testing.T) {
m, _, _ := rpcdaemontest.CreateTestSentry(t)
ctx := context.Background()
api := NewEthAPI(newBaseApiForTest(m), m.DB, nil, nil, nil, 5000000, 1e18, 100_000, false, 100_000, 128, log.New())
- blockHash := common.HexToHash("0xe99e4072055929794042a257bdb34787aba52ee220f4e60867acc57d56474dab")
+ blockHash := common.HexToHash("0x5883164d4100b95e1d8e931b8b9574586a1dea7507941e6ad3c1e3a2591485fd")
tx, err := m.DB.BeginRw(ctx)
if err != nil {
@@ -259,7 +259,7 @@ func TestGetBlockTransactionCountByNumber(t *testing.T) {
m, _, _ := rpcdaemontest.CreateTestSentry(t)
ctx := context.Background()
api := NewEthAPI(newBaseApiForTest(m), m.DB, nil, nil, nil, 5000000, 1e18, 100_000, false, 100_000, 128, log.New())
- blockHash := common.HexToHash("0xe99e4072055929794042a257bdb34787aba52ee220f4e60867acc57d56474dab")
+ blockHash := common.HexToHash("0x6804117de2f3e6ee32953e78ced1db7b20214e0d8c745a03b8fecf7cc8ee76ef")
tx, err := m.DB.BeginRw(ctx)
if err != nil {
@@ -292,7 +292,7 @@ func TestGetBlockTransactionCountByNumber_ZeroTx(t *testing.T) {
ctx := context.Background()
api := NewEthAPI(newBaseApiForTest(m), m.DB, nil, nil, nil, 5000000, 1e18, 100_000, false, 100_000, 128, log.New())
- blockHash := common.HexToHash("0xe99e4072055929794042a257bdb34787aba52ee220f4e60867acc57d56474dab")
+ blockHash := common.HexToHash("0x5883164d4100b95e1d8e931b8b9574586a1dea7507941e6ad3c1e3a2591485fd")
tx, err := m.DB.BeginRw(ctx)
if err != nil {
diff --git a/turbo/jsonrpc/eth_call_test.go b/turbo/jsonrpc/eth_call_test.go
index 1baf09143c0..a6f0ff87c25 100644
--- a/turbo/jsonrpc/eth_call_test.go
+++ b/turbo/jsonrpc/eth_call_test.go
@@ -79,8 +79,8 @@ func TestEthCallNonCanonical(t *testing.T) {
if _, err := api.Call(context.Background(), ethapi.CallArgs{
From: &from,
To: &to,
- }, rpc.BlockNumberOrHashWithHash(libcommon.HexToHash("0x94b548e36d4363fc189627d454867d70902c2ffdd97347fd91bf9f1725177c43"), true), nil); err != nil {
- if fmt.Sprintf("%v", err) != "hash 94b548e36d4363fc189627d454867d70902c2ffdd97347fd91bf9f1725177c43 is not currently canonical" {
+ }, rpc.BlockNumberOrHashWithHash(libcommon.HexToHash("0x3fcb7c0d4569fddc89cbea54b42f163e0c789351d98810a513895ab44b47020b"), true), nil); err != nil {
+ if fmt.Sprintf("%v", err) != "hash 3fcb7c0d4569fddc89cbea54b42f163e0c789351d98810a513895ab44b47020b is not currently canonical" {
t.Errorf("wrong error: %v", err)
}
}
diff --git a/turbo/jsonrpc/eth_mining_test.go b/turbo/jsonrpc/eth_mining_test.go
index fabde68ccb6..f51e3c178cf 100644
--- a/turbo/jsonrpc/eth_mining_test.go
+++ b/turbo/jsonrpc/eth_mining_test.go
@@ -21,6 +21,7 @@ import (
"testing"
"time"
+ "github.com/erigontech/erigon/consensus/ethash"
"github.com/erigontech/erigon/rpc/rpccfg"
"github.com/stretchr/testify/require"
@@ -42,7 +43,8 @@ func TestPendingBlock(t *testing.T) {
mining := txpool.NewMiningClient(conn)
ff := rpchelper.New(ctx, rpchelper.DefaultFiltersConfig, nil, nil, mining, func() {}, m.Log)
stateCache := kvcache.New(kvcache.DefaultCoherentConfig)
- api := NewEthAPI(NewBaseApi(ff, stateCache, m.BlockReader, false, rpccfg.DefaultEvmCallTimeout, nil, m.Dirs, nil), nil, nil, nil, mining, 5000000, 1e18, 100_000, false, 100_000, 128, log.New())
+ engine := ethash.NewFaker()
+ api := NewEthAPI(NewBaseApi(ff, stateCache, m.BlockReader, false, rpccfg.DefaultEvmCallTimeout, engine, m.Dirs, nil), nil, nil, nil, mining, 5000000, 1e18, 100_000, false, 100_000, 128, log.New())
expect := uint64(12345)
b, err := rlp.EncodeToBytes(types.NewBlockWithHeader(&types.Header{Number: new(big.Int).SetUint64(expect)}))
require.NoError(t, err)
diff --git a/turbo/jsonrpc/gen_traces_test.go b/turbo/jsonrpc/gen_traces_test.go
index 39ad8f9e5cb..9463ee73ee9 100644
--- a/turbo/jsonrpc/gen_traces_test.go
+++ b/turbo/jsonrpc/gen_traces_test.go
@@ -157,7 +157,7 @@ func TestGeneratedTraceApi(t *testing.T) {
"to": "0x00000000000000000000000000000000000002ff",
"value": "0x0"
},
- "blockHash": "0x017dcc421ef89fbc067cac073e05ef76a1a22df7ab62e7210ec885a537705062",
+ "blockHash": "0x2c7ee9236a9eb58cbaf6473f458ddb41716c6735f4a63eacf0f8b759685f1dbc",
"blockNumber": 1,
"result": {
"gasUsed": "0x161c",
@@ -178,7 +178,7 @@ func TestGeneratedTraceApi(t *testing.T) {
"to": "0x00000000000000000000000000000000000001ff",
"value": "0x0"
},
- "blockHash": "0x017dcc421ef89fbc067cac073e05ef76a1a22df7ab62e7210ec885a537705062",
+ "blockHash": "0x2c7ee9236a9eb58cbaf6473f458ddb41716c6735f4a63eacf0f8b759685f1dbc",
"blockNumber": 1,
"result": {
"gasUsed": "0xa7b",
@@ -201,7 +201,7 @@ func TestGeneratedTraceApi(t *testing.T) {
"to": "0x00000000000000000000000000000000000000ff",
"value": "0x0"
},
- "blockHash": "0x017dcc421ef89fbc067cac073e05ef76a1a22df7ab62e7210ec885a537705062",
+ "blockHash": "0x2c7ee9236a9eb58cbaf6473f458ddb41716c6735f4a63eacf0f8b759685f1dbc",
"blockNumber": 1,
"result": {
"gasUsed": "0x16",
@@ -225,7 +225,7 @@ func TestGeneratedTraceApi(t *testing.T) {
"to": "0x00000000000000000000000000000000000001ff",
"value": "0x0"
},
- "blockHash": "0x017dcc421ef89fbc067cac073e05ef76a1a22df7ab62e7210ec885a537705062",
+ "blockHash": "0x2c7ee9236a9eb58cbaf6473f458ddb41716c6735f4a63eacf0f8b759685f1dbc",
"blockNumber": 1,
"result": {
"gasUsed": "0xb1",
@@ -248,7 +248,7 @@ func TestGeneratedTraceApi(t *testing.T) {
"to": "0x00000000000000000000000000000000000000ff",
"value": "0x0"
},
- "blockHash": "0x017dcc421ef89fbc067cac073e05ef76a1a22df7ab62e7210ec885a537705062",
+ "blockHash": "0x2c7ee9236a9eb58cbaf6473f458ddb41716c6735f4a63eacf0f8b759685f1dbc",
"blockNumber": 1,
"result": {
"gasUsed": "0x10",
@@ -269,7 +269,7 @@ func TestGeneratedTraceApi(t *testing.T) {
"rewardType": "block",
"value": "0x1bc16d674ec80000"
},
- "blockHash": "0x017dcc421ef89fbc067cac073e05ef76a1a22df7ab62e7210ec885a537705062",
+ "blockHash": "0x2c7ee9236a9eb58cbaf6473f458ddb41716c6735f4a63eacf0f8b759685f1dbc",
"blockNumber": 1,
"result": null,
"subtraces": 0,
@@ -312,7 +312,7 @@ func TestGeneratedTraceApiCollision(t *testing.T) {
"to": "0x000000000000000000000000000000000000bbbb",
"value": "0x0"
},
- "blockHash": "0x3941891beac58fdeafe9c76a031d52983b7f7cc06decfb3a37a67ebd6f58c72f",
+ "blockHash": "0xc78e9674685b04e1300d62cafdae0708d030a9bc0ff7aa9eb9315da23de650dc",
"blockNumber": 1,
"result": {
"gasUsed": "0x131bb",
@@ -331,7 +331,7 @@ func TestGeneratedTraceApiCollision(t *testing.T) {
"init": "0x600360035560046004556158ff6000526002601ef3",
"value": "0x0"
},
- "blockHash": "0x3941891beac58fdeafe9c76a031d52983b7f7cc06decfb3a37a67ebd6f58c72f",
+ "blockHash": "0xc78e9674685b04e1300d62cafdae0708d030a9bc0ff7aa9eb9315da23de650dc",
"blockNumber": 1,
"error": "contract address collision",
"result": null,
diff --git a/turbo/jsonrpc/trace_filtering.go b/turbo/jsonrpc/trace_filtering.go
index 8fe543f92f9..78699917d23 100644
--- a/turbo/jsonrpc/trace_filtering.go
+++ b/turbo/jsonrpc/trace_filtering.go
@@ -32,7 +32,7 @@ import (
"github.com/erigontech/erigon-lib/kv/stream"
"github.com/erigontech/erigon-lib/log/v3"
"github.com/erigontech/erigon/consensus"
- "github.com/erigontech/erigon/consensus/mainnet"
+ "github.com/erigontech/erigon/consensus/ethash"
"github.com/erigontech/erigon/core"
"github.com/erigontech/erigon/core/rawdb"
"github.com/erigontech/erigon/core/state"
@@ -458,7 +458,7 @@ func (api *TraceAPIImpl) filterV3(ctx context.Context, dbtx kv.TemporalTx, fromB
continue
}
// Block reward section, handle specially
- minerReward, uncleRewards := mainnet.AccumulateRewards(chainConfig, lastHeader, body.Uncles)
+ minerReward, uncleRewards := ethash.AccumulateRewards(chainConfig, lastHeader, body.Uncles)
if _, ok := toAddresses[lastHeader.Coinbase]; ok || includeAll {
nSeen++
var tr ParityTrace
diff --git a/turbo/stages/blockchain_test.go b/turbo/stages/blockchain_test.go
index be57142dfa7..96b954b8e7e 100644
--- a/turbo/stages/blockchain_test.go
+++ b/turbo/stages/blockchain_test.go
@@ -48,7 +48,7 @@ import (
types2 "github.com/erigontech/erigon-lib/types"
"github.com/erigontech/erigon/common/u256"
- "github.com/erigontech/erigon/consensus/mainnet"
+ "github.com/erigontech/erigon/consensus/ethash"
"github.com/erigontech/erigon/core"
"github.com/erigontech/erigon/core/rawdb"
"github.com/erigontech/erigon/core/state"
@@ -411,6 +411,14 @@ func testReorg(t *testing.T, first, second []int64, td int64) {
})
require.NoError(err)
require.Equal(b, msg.GetData())
+
+ // Make sure the chain total difficulty is the correct one
+ want := new(big.Int).Add(m.Genesis.Difficulty(), big.NewInt(td))
+ have, err := rawdb.ReadTdByHash(tx, rawdb.ReadCurrentHeader(tx).Hash())
+ require.NoError(err)
+ if have.Cmp(want) != 0 {
+ t.Errorf("total difficulty mismatch: have %v, want %v", have, want)
+ }
}
}
@@ -2194,7 +2202,7 @@ func TestEIP1559Transition(t *testing.T) {
actual := statedb.GetBalance(block.Coinbase())
expected := new(uint256.Int).Add(
new(uint256.Int).SetUint64(block.GasUsed()*block.Transactions()[0].GetPrice().Uint64()),
- mainnet.ConstantinopleBlockReward,
+ ethash.ConstantinopleBlockReward,
)
if actual.Cmp(expected) != 0 {
t.Fatalf("miner balance incorrect: expected %d, got %d", expected, actual)
@@ -2236,7 +2244,7 @@ func TestEIP1559Transition(t *testing.T) {
actual := statedb.GetBalance(block.Coinbase())
expected := new(uint256.Int).Add(
new(uint256.Int).SetUint64(block.GasUsed()*effectiveTip),
- mainnet.ConstantinopleBlockReward,
+ ethash.ConstantinopleBlockReward,
)
if actual.Cmp(expected) != 0 {
t.Fatalf("miner balance incorrect: expected %d, got %d", expected, actual)
diff --git a/turbo/stages/bodydownload/body_test.go b/turbo/stages/bodydownload/body_test.go
index 130a2711fa3..4164eb977e7 100644
--- a/turbo/stages/bodydownload/body_test.go
+++ b/turbo/stages/bodydownload/body_test.go
@@ -22,6 +22,8 @@ import (
"github.com/erigontech/erigon/turbo/stages/bodydownload"
"github.com/erigontech/erigon/turbo/stages/mock"
"github.com/stretchr/testify/require"
+
+ "github.com/erigontech/erigon/consensus/ethash"
)
func TestCreateBodyDownload(t *testing.T) {
@@ -30,7 +32,7 @@ func TestCreateBodyDownload(t *testing.T) {
tx, err := m.DB.BeginRo(m.Ctx)
require.NoError(t, err)
defer tx.Rollback()
- bd := bodydownload.NewBodyDownload(nil, 128, 100, m.BlockReader, m.Log)
+ bd := bodydownload.NewBodyDownload(ethash.NewFaker(), 128, 100, m.BlockReader, m.Log)
if _, _, _, _, err := bd.UpdateFromDb(tx); err != nil {
t.Fatalf("update from db: %v", err)
}
diff --git a/turbo/stages/mock/mock_sentry.go b/turbo/stages/mock/mock_sentry.go
index 180abde21a2..4364b1e5d8c 100644
--- a/turbo/stages/mock/mock_sentry.go
+++ b/turbo/stages/mock/mock_sentry.go
@@ -28,7 +28,6 @@ import (
"time"
"github.com/c2h5oh/datasize"
- "github.com/erigontech/erigon/consensus/mainnet"
"github.com/erigontech/erigon/turbo/jsonrpc/receipts"
lru "github.com/hashicorp/golang-lru/arc/v2"
"github.com/holiman/uint256"
@@ -60,6 +59,7 @@ import (
types2 "github.com/erigontech/erigon-lib/types"
"github.com/erigontech/erigon-lib/wrap"
"github.com/erigontech/erigon/consensus"
+ "github.com/erigontech/erigon/consensus/ethash"
"github.com/erigontech/erigon/core"
"github.com/erigontech/erigon/core/rawdb"
"github.com/erigontech/erigon/core/rawdb/blockio"
@@ -255,7 +255,7 @@ func MockWithGenesisPruneMode(tb testing.TB, gspec *types.Genesis, key *ecdsa.Pr
case gspec.Config.Bor != nil:
engine = bor.NewFaker()
default:
- engine = mainnet.NewMainnetConsensus()
+ engine = ethash.NewFaker()
}
checkStateRoot := true
@@ -632,7 +632,7 @@ func MockWithTxPool(t *testing.T) *MockSentry {
}
checkStateRoot := true
- return MockWithEverything(t, gspec, key, prune.DefaultMode, mainnet.NewMainnetConsensus(), blockBufferSize, true, false, checkStateRoot)
+ return MockWithEverything(t, gspec, key, prune.DefaultMode, ethash.NewFaker(), blockBufferSize, true, false, checkStateRoot)
}
func MockWithZeroTTD(t *testing.T, withPosDownloader bool) *MockSentry {