Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add --classic: ETC treads lightly #12

Draft
wants to merge 15 commits into
base: release/1.10
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
[submodule "tests"]
[submodule "tests/testdata"]
path = tests/testdata
url = https://github.com/ethereum/tests
shallow = true
[submodule "evm-benchmarks"]
[submodule "tests/evm-benchmarks"]
path = tests/evm-benchmarks
url = https://github.com/ipsilon/evm-benchmarks
shallow = true
[submodule "tests/testdata-etc"]
path = tests/testdata-etc
url = https://github.com/etclabscore/tests-etc.git
3 changes: 3 additions & 0 deletions cmd/geth/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,9 @@ func main() {
func prepare(ctx *cli.Context) {
// If we're running a known preset, log it for convenience.
switch {
case ctx.IsSet(utils.ClassicFlag.Name):
log.Info("Starting Geth on Ethereum Classic (ETC) mainnet...")

case ctx.IsSet(utils.RopstenFlag.Name):
log.Info("Starting Geth on Ropsten testnet...")

Expand Down
31 changes: 30 additions & 1 deletion cmd/utils/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -991,6 +991,7 @@ var (
// NetworkFlags is the flag group of all built-in supported networks.
NetworkFlags = append([]cli.Flag{
MainnetFlag,
ClassicFlag,
}, TestnetFlags...)

// DatabasePathFlags is the flag group of all database path flags.
Expand All @@ -1006,6 +1007,9 @@ var (
// then a subdirectory of the specified datadir will be used.
func MakeDataDir(ctx *cli.Context) string {
if path := ctx.String(DataDirFlag.Name); path != "" {
if ctx.Bool(ClassicFlag.Name) {
return filepath.Join(path, "classic")
}
if ctx.Bool(RopstenFlag.Name) {
// Maintain compatibility with older Geth configurations storing the
// Ropsten database in `testnet` instead of `ropsten`.
Expand Down Expand Up @@ -1069,6 +1073,8 @@ func setBootstrapNodes(ctx *cli.Context, cfg *p2p.Config) {
switch {
case ctx.IsSet(BootnodesFlag.Name):
urls = SplitAndTrim(ctx.String(BootnodesFlag.Name))
case ctx.Bool(ClassicFlag.Name):
urls = params.ClassicBootnodes
case ctx.Bool(RopstenFlag.Name):
urls = params.RopstenBootnodes
case ctx.Bool(SepoliaFlag.Name):
Expand Down Expand Up @@ -1529,6 +1535,8 @@ func SetDataDir(ctx *cli.Context, cfg *node.Config) {
}

cfg.DataDir = filepath.Join(node.DefaultDataDir(), "ropsten")
case ctx.Bool(ClassicFlag.Name) && cfg.DataDir == node.DefaultDataDir():
cfg.DataDir = filepath.Join(node.DefaultDataDir(), "classic")
case ctx.Bool(RinkebyFlag.Name) && cfg.DataDir == node.DefaultDataDir():
cfg.DataDir = filepath.Join(node.DefaultDataDir(), "rinkeby")
case ctx.Bool(GoerliFlag.Name) && cfg.DataDir == node.DefaultDataDir():
Expand Down Expand Up @@ -1727,7 +1735,7 @@ func CheckExclusive(ctx *cli.Context, args ...interface{}) {
// SetEthConfig applies eth-related command line flags to the config.
func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
// Avoid conflicting network flags
CheckExclusive(ctx, MainnetFlag, DeveloperFlag, RopstenFlag, RinkebyFlag, GoerliFlag, SepoliaFlag, KilnFlag)
CheckExclusive(ctx, MainnetFlag, ClassicFlag, DeveloperFlag, RopstenFlag, RinkebyFlag, GoerliFlag, SepoliaFlag, KilnFlag)
CheckExclusive(ctx, LightServeFlag, SyncModeFlag, "light")
CheckExclusive(ctx, DeveloperFlag, ExternalSignerFlag) // Can't use both ephemeral unlocked and external signer
if ctx.String(GCModeFlag.Name) == "archive" && ctx.Uint64(TxLookupLimitFlag.Name) != 0 {
Expand Down Expand Up @@ -1868,6 +1876,25 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
}
cfg.Genesis = core.DefaultGenesisBlock()
SetDNSDiscoveryDefaults(cfg, params.MainnetGenesisHash)
case ctx.Bool(ClassicFlag.Name):
if !ctx.IsSet(NetworkIdFlag.Name) {
cfg.NetworkId = 1
}
cfg.Genesis = core.DefaultClassicGenesisBlock()

// Only configure if not already established through flags/config
if cfg.EthDiscoveryURLs == nil {
url := params.ClassicDNS
if cfg.SyncMode == downloader.LightSync {
url = strings.ReplaceAll(url, "all", "les")
}
cfg.EthDiscoveryURLs = []string{url}
cfg.SnapDiscoveryURLs = cfg.EthDiscoveryURLs
}

// Set ECIP-1099 block number for Ethash config.
u := params.ECIP1099Block_Classic.Uint64()
cfg.Ethash.ECIP1099Block = &u
case ctx.Bool(RopstenFlag.Name):
if !ctx.IsSet(NetworkIdFlag.Name) {
cfg.NetworkId = 3
Expand Down Expand Up @@ -2149,6 +2176,8 @@ func MakeGenesis(ctx *cli.Context) *core.Genesis {
switch {
case ctx.Bool(MainnetFlag.Name):
genesis = core.DefaultGenesisBlock()
case ctx.Bool(ClassicFlag.Name):
genesis = core.DefaultClassicGenesisBlock()
case ctx.Bool(RopstenFlag.Name):
genesis = core.DefaultRopstenGenesisBlock()
case ctx.Bool(SepoliaFlag.Name):
Expand Down
14 changes: 14 additions & 0 deletions cmd/utils/flags_classic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package utils

import (
"github.com/ethereum/go-ethereum/internal/flags"
"github.com/urfave/cli/v2"
)

var (
ClassicFlag = &cli.BoolFlag{
Name: "classic",
Usage: "Ethereum Classic (ETC) mainnet",
Category: flags.EthCategory,
}
)
58 changes: 58 additions & 0 deletions common/hasher.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package common

import (
"fmt"
"hash"

"golang.org/x/crypto/sha3"
)

// keccakState wraps sha3.state. In addition to the usual hash methods, it also supports
// Read to get a variable amount of data from the hash state. Read is faster than Sum
// because it doesn't copy the internal state, but also modifies the internal state.
type keccakState interface {
hash.Hash
Read([]byte) (int, error)
}

type Hasher struct {
Sha keccakState
}

var hasherPool = make(chan *Hasher, 128)

func NewHasher() *Hasher {
var h *Hasher
select {
case h = <-hasherPool:
default:
h = &Hasher{Sha: sha3.NewLegacyKeccak256().(keccakState)}
}
return h
}

func ReturnHasherToPool(h *Hasher) {
select {
case hasherPool <- h:
default:
fmt.Printf("Allowing Hasher to be garbage collected, pool is full\n")
}
}

func HashData(data []byte) (Hash, error) {
h := NewHasher()
defer ReturnHasherToPool(h)
h.Sha.Reset()

_, err := h.Sha.Write(data)
if err != nil {
return Hash{}, err
}

var buf Hash
_, err = h.Sha.Read(buf[:])
if err != nil {
return Hash{}, err
}
return buf, nil
}
64 changes: 45 additions & 19 deletions consensus/ethash/algorithm.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,19 +50,18 @@ const (

// 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)
func cacheSize(epoch uint64) uint64 {
if epoch < maxEpoch {
return cacheSizes[epoch]
return cacheSizes[int(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
func calcCacheSize(epoch uint64) uint64 {
size := cacheInitBytes + cacheGrowthBytes*epoch - hashBytes
for !new(big.Int).SetUint64(size / hashBytes).ProbablyPrime(1) { // Always accurate for n < 2^64
size -= 2 * hashBytes
}
Expand All @@ -71,19 +70,18 @@ func calcCacheSize(epoch int) uint64 {

// 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)
func datasetSize(epoch uint64) uint64 {
if epoch < maxEpoch {
return datasetSizes[epoch]
return datasetSizes[int(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
func calcDatasetSize(epoch uint64) uint64 {
size := datasetInitBytes + datasetGrowthBytes*epoch - mixBytes
for !new(big.Int).SetUint64(size / mixBytes).ProbablyPrime(1) { // Always accurate for n < 2^64
size -= 2 * mixBytes
}
Expand Down Expand Up @@ -118,13 +116,41 @@ func makeHasher(h hash.Hash) hasher {

// seedHash is the seed to use for generating a verification cache and the mining
// dataset.
func seedHash(block uint64) []byte {
func seedHash(epoch uint64, epochLength uint64) []byte {
block := calcEpochBlock(epoch, epochLength)
seed := make([]byte, 32)
if block < epochLength {
if block < epochLengthDefault {
return seed
}

h := common.NewHasher()

for i := 0; i < int(block/epochLengthDefault); 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)
}
}

common.ReturnHasherToPool(h)

return seed
}

func seedHashOld(block uint64) []byte {
seed := make([]byte, 32)
if block < epochLengthDefault {
return seed
}
keccak256 := makeHasher(sha3.NewLegacyKeccak256())
for i := 0; i < int(block/epochLength); i++ {
for i := 0; i < int(block/epochLengthDefault); i++ {
keccak256(seed, seed)
}
return seed
Expand All @@ -136,7 +162,7 @@ func seedHash(block uint64) []byte {
// 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) {
func generateCache(dest []uint32, epoch uint64, epochLength uint64, seed []byte) {
// Print some debug logs to allow analysis on low end devices
logger := log.New("epoch", epoch)

Expand All @@ -148,7 +174,7 @@ func generateCache(dest []uint32, epoch uint64, seed []byte) {
if elapsed > 3*time.Second {
logFn = logger.Info
}
logFn("Generated ethash verification cache", "elapsed", common.PrettyDuration(elapsed))
logFn("Generated ethash verification cache", "epochLength", epochLength, "elapsed", common.PrettyDuration(elapsed))
}()
// Convert our destination slice to a byte buffer
var cache []byte
Expand All @@ -174,7 +200,7 @@ func generateCache(dest []uint32, epoch uint64, seed []byte) {
case <-done:
return
case <-time.After(3 * time.Second):
logger.Info("Generating ethash verification cache", "percentage", atomic.LoadUint32(&progress)*100/uint32(rows)/(cacheRounds+1), "elapsed", common.PrettyDuration(time.Since(start)))
logger.Info("Generating ethash verification cache", "epochLength", epochLength, "percentage", atomic.LoadUint32(&progress)*100/uint32(rows)/(cacheRounds+1), "elapsed", common.PrettyDuration(time.Since(start)))
}
}
}()
Expand Down Expand Up @@ -266,7 +292,7 @@ func generateDatasetItem(cache []uint32, index uint32, keccak512 hasher) []byte

// 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) {
func generateDataset(dest []uint32, epoch uint64, epochLength uint64, cache []uint32) {
// Print some debug logs to allow analysis on low end devices
logger := log.New("epoch", epoch)

Expand All @@ -278,7 +304,7 @@ func generateDataset(dest []uint32, epoch uint64, cache []uint32) {
if elapsed > 3*time.Second {
logFn = logger.Info
}
logFn("Generated ethash verification cache", "elapsed", common.PrettyDuration(elapsed))
logFn("Generated ethash verification dataset", "epochLength", epochLength, "elapsed", common.PrettyDuration(elapsed))
}()

// Figure out whether the bytes need to be swapped for the machine
Expand Down Expand Up @@ -324,7 +350,7 @@ func generateDataset(dest []uint32, epoch uint64, cache []uint32) {
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)))
logger.Info("Generating DAG in progress", "epochLength", epochLength, "percentage", (status*100)/(size/hashBytes), "elapsed", common.PrettyDuration(time.Since(start)))
}
}
}(i)
Expand Down
Loading