diff --git a/cmd/geth/config.go b/cmd/geth/config.go index 24a6b25316..8d441e29dc 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -234,9 +234,6 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) { params.FixedTurnLength = ctx.Uint64(utils.OverrideFixedTurnLength.Name) } - // Start metrics export if enabled - utils.SetupMetrics(&cfg.Metrics) - backend, eth := utils.RegisterEthService(stack, &cfg.Eth) // Create gauge with geth system and build information diff --git a/cmd/jsutils/getchainstatus.js b/cmd/jsutils/getchainstatus.js index 17097a58d1..236baf25d1 100644 --- a/cmd/jsutils/getchainstatus.js +++ b/cmd/jsutils/getchainstatus.js @@ -53,6 +53,7 @@ const addrSlash = '0x0000000000000000000000000000000000001001'; const addrStakeHub = '0x0000000000000000000000000000000000002002'; const validatorSetAbi = [ + "function validatorExtraSet(uint256 offset) external view returns (uint256, bool, bytes)", "function getLivingValidators() external view returns (address[], bytes[])", "function numOfCabinets() external view returns (uint256)", "function maxNumOfCandidates() external view returns (uint256)", @@ -277,6 +278,11 @@ async function getSlashCount() { if (blockNum === 0) { blockNum = await provider.getBlockNumber() } + let slashScale = await validatorSet.maintainSlashScale({blockTag:blockNum}) + let maxElected = await stakeHub.maxElectedValidators({blockTag:blockNum}) + const maintainThreshold = BigInt(50) // governable, hardcode to avoid one RPC call + const felonyThreshold = BigInt(150) // governable, hardcode to avoid one RPC call + let block = await provider.getBlock(blockNum) console.log("At block", blockNum, "time", block.date) const data = await validatorSet.getLivingValidators({blockTag:blockNum}) @@ -285,9 +291,22 @@ async function getSlashCount() { let addr = data[0][i]; var moniker = await getValidatorMoniker(addr, blockNum) let info = await slashIndicator.getSlashIndicator(addr, {blockTag:blockNum}) - let count = ethers.toNumber(info[1]) - totalSlash += count - console.log("Slash:", count, addr, moniker) + let slashHeight = ethers.toNumber(info[0]) + let slashCount = ethers.toNumber(info[1]) + totalSlash += slashCount + console.log("Slash:", slashCount, addr, moniker, slashHeight) + if (slashCount >= maintainThreshold) { + let validatorExtra = await validatorSet.validatorExtraSet(i, {blockTag:blockNum}) + let enterMaintenanceHeight = validatorExtra[0] + let isMaintaining = validatorExtra[1] + // let voteAddress = validatorExtra[2] + if (isMaintaining) { + let jailHeight = (felonyThreshold - slashCount) * slashScale * maxElected + BigInt(enterMaintenanceHeight) + console.log(" in maintenance mode since", enterMaintenanceHeight, "will jail after", ethers.toNumber(jailHeight)) + } else { + console.log(" exited maintenance mode") + } + } } console.log("Total slash count", totalSlash) }; diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index ccafd81fcd..8a82107de2 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -2332,7 +2332,7 @@ type SetupMetricsOption func() func EnableBuildInfo(gitCommit, gitDate string) SetupMetricsOption { return func() { // register build info into metrics - metrics.NewRegisteredLabel("build-info", nil).Mark(map[string]interface{}{ + metrics.GetOrRegisterLabel("build-info", nil).Mark(map[string]interface{}{ "version": version.WithMeta, "git-commit": gitCommit, "git-commit-date": gitDate, @@ -2349,7 +2349,7 @@ func EnableMinerInfo(ctx *cli.Context, minerConfig *minerconfig.Config) SetupMet // register miner info into metrics minerInfo := structs.Map(minerConfig) minerInfo[UnlockedAccountFlag.Name] = ctx.String(UnlockedAccountFlag.Name) - metrics.NewRegisteredLabel("miner-info", nil).Mark(minerInfo) + metrics.GetOrRegisterLabel("miner-info", nil).Mark(minerInfo) } } } @@ -2369,7 +2369,7 @@ func RegisterFilterAPI(stack *node.Node, backend ethapi.Backend, ethcfg *ethconf func EnableNodeInfo(poolConfig *legacypool.Config, nodeInfo *p2p.NodeInfo) SetupMetricsOption { return func() { // register node info into metrics - metrics.NewRegisteredLabel("node-info", nil).Mark(map[string]interface{}{ + metrics.GetOrRegisterLabel("node-info", nil).Mark(map[string]interface{}{ "Enode": nodeInfo.Enode, "ENR": nodeInfo.ENR, "ID": nodeInfo.ID, @@ -2389,7 +2389,7 @@ func EnableNodeTrack(ctx *cli.Context, cfg *ethconfig.Config, stack *node.Node) nodeInfo := stack.Server().NodeInfo() return func() { // register node info into metrics - metrics.NewRegisteredLabel("node-stats", nil).Mark(map[string]interface{}{ + metrics.GetOrRegisterLabel("node-stats", nil).Mark(map[string]interface{}{ "NodeType": parseNodeType(), "ENR": nodeInfo.ENR, "Mining": ctx.Bool(MiningEnabledFlag.Name), diff --git a/consensus/parlia/parlia.go b/consensus/parlia/parlia.go index 0800bbad22..14939fff76 100644 --- a/consensus/parlia/parlia.go +++ b/consensus/parlia/parlia.go @@ -648,7 +648,7 @@ func (p *Parlia) verifyHeader(chain consensus.ChainHeaderReader, header *types.H prague := chain.Config().IsPrague(header.Number, header.Time) if !prague { if header.RequestsHash != nil { - return fmt.Errorf("invalid RequestsHash, have %#x, expected nil", header.ParentBeaconRoot) + return fmt.Errorf("invalid RequestsHash, have %#x, expected nil", header.RequestsHash) } } else { if header.RequestsHash == nil { @@ -1559,6 +1559,23 @@ func (p *Parlia) Delay(chain consensus.ChainReader, header *types.Header, leftOv return &delay } +// AssembleSignature assemble the signature for block header +func (p *Parlia) AssembleSignature(block *types.Block) (*types.Block, error) { + header := block.Header() + // Don't hold the val fields for the entire sealing procedure + p.lock.RLock() + val, signFn := p.val, p.signFn + p.lock.RUnlock() + sig, err := signFn(accounts.Account{Address: val}, accounts.MimetypeParlia, ParliaRLP(header, p.chainConfig.ChainID)) + if err != nil { + log.Error("Sign for the block header failed when sealing", "err", err) + return nil, err + } + copy(header.Extra[len(header.Extra)-extraSeal:], sig) + block = block.WithSeal(header) + return block, nil +} + // Seal implements consensus.Engine, attempting to create a sealed block using // the local signing credentials. func (p *Parlia) Seal(chain consensus.ChainHeaderReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error { @@ -2118,6 +2135,10 @@ func (p *Parlia) backOffTime(snap *Snapshot, header *types.Header, val common.Ad backOffSteps[i], backOffSteps[j] = backOffSteps[j], backOffSteps[i] }) + for i := uint64(0); i < uint64(n); i++ { + log.Debug("backOffTime", "Number", header.Number, "val", validators[i], "delay", delay+backOffSteps[i]*wiggleTime) + } + delay += backOffSteps[idx] * wiggleTime return delay } diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 328452a6eb..5839c1e13b 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -299,7 +299,9 @@ func New(config Config, chain BlockChain) *LegacyPool { // pool, specifically, whether it is a Legacy, AccessList or Dynamic transaction. func (pool *LegacyPool) Filter(tx *types.Transaction) bool { switch tx.Type() { - case types.LegacyTxType, types.AccessListTxType, types.DynamicFeeTxType: + // TODO(Nathan): add SetCodeTxType into LegacyPool for test + // finally will rollback and be consistent with upstream + case types.LegacyTxType, types.AccessListTxType, types.DynamicFeeTxType, types.SetCodeTxType: return true default: return false @@ -692,6 +694,12 @@ func (pool *LegacyPool) validateTxBasics(tx *types.Transaction, local bool) erro MinTip: pool.gasTip.Load().ToBig(), MaxGas: pool.GetMaxGas(), } + // TODO(Nathan): ensure before prague, no SetCodeTxType will be accepted and propagated + // finally will rollback and be consistent with upstream + currentBlock := pool.chain.CurrentBlock() + if pool.chainconfig.IsPrague(currentBlock.Number, currentBlock.Time) { + opts.Accept |= 1 << types.SetCodeTxType + } if local { opts.MinTip = new(big.Int) } diff --git a/core/types/block.go b/core/types/block.go index a23601eecd..4fb68de08c 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -20,6 +20,7 @@ package types import ( "crypto/sha256" "encoding/binary" + "encoding/json" "fmt" "io" "math/big" @@ -604,6 +605,14 @@ func (b *Block) WithWitness(witness *ExecutionWitness) *Block { } } +func (b *Block) DeepCopySidecars(sidecars BlobSidecars) { + b.sidecars = make(BlobSidecars, len(sidecars)) + if len(sidecars) != 0 { + buffer, _ := json.Marshal(sidecars) + json.Unmarshal(buffer, &b.sidecars) + } +} + // Hash returns the keccak256 hash of b's header. // The hash is computed on the first call and cached thereafter. func (b *Block) Hash() common.Hash { diff --git a/core/vote/vote_manager.go b/core/vote/vote_manager.go index 891785482b..8ce2e31fcb 100644 --- a/core/vote/vote_manager.go +++ b/core/vote/vote_manager.go @@ -4,7 +4,6 @@ import ( "bytes" "fmt" "math/big" - "sync" "time" "github.com/ethereum/go-ethereum/common" @@ -31,6 +30,7 @@ var notContinuousJustified = metrics.NewRegisteredCounter("votesManager/notConti // Backend wraps all methods required for voting. type Backend interface { IsMining() bool + VoteEnabled() bool EventMux() *event.TypeMux } @@ -71,6 +71,7 @@ func NewVoteManager(eth Backend, chain *core.BlockChain, pool *VotePool, journal } log.Info("Create voteSigner successfully") voteManager.signer = voteSigner + metrics.GetOrRegisterLabel("miner-info", nil).Mark(map[string]interface{}{"VoteKey": common.Bytes2Hex(voteManager.signer.PubKey[:])}) // Create voteJournal voteJournal, err := NewVoteJournal(journalPath) @@ -107,7 +108,6 @@ func (voteManager *VoteManager) loop() { startVote := true blockCountSinceMining := 0 - var once sync.Once for { select { case ev := <-dlEventCh: @@ -136,6 +136,11 @@ func (voteManager *VoteManager) loop() { log.Debug("skip voting because mining is disabled, continue") continue } + if !voteManager.eth.VoteEnabled() { + log.Debug("skip voting because voting is disabled, continue") + continue + } + blockCountSinceMining++ if blockCountSinceMining <= blocksNumberSinceMining { log.Debug("skip voting", "blockCountSinceMining", blockCountSinceMining, "blocksNumberSinceMining", blocksNumberSinceMining) @@ -166,14 +171,6 @@ func (voteManager *VoteManager) loop() { continue } - // Add VoteKey to `miner-info` - once.Do(func() { - minerInfo := metrics.Get("miner-info") - if minerInfo != nil { - minerInfo.(metrics.Label).Value()["VoteKey"] = common.Bytes2Hex(voteManager.signer.PubKey[:]) - } - }) - // Vote for curBlockHeader block. vote := &types.VoteData{ TargetNumber: curHead.Number.Uint64(), diff --git a/core/vote/vote_pool_test.go b/core/vote/vote_pool_test.go index 8fb3f3daec..5d3b39e66a 100644 --- a/core/vote/vote_pool_test.go +++ b/core/vote/vote_pool_test.go @@ -77,6 +77,7 @@ func newTestBackend() *testBackend { return &testBackend{eventMux: new(event.TypeMux)} } func (b *testBackend) IsMining() bool { return true } +func (b *testBackend) VoteEnabled() bool { return true } func (b *testBackend) EventMux() *event.TypeMux { return b.eventMux } func (mp *mockPOSA) GetJustifiedNumberAndHash(chain consensus.ChainHeaderReader, headers []*types.Header) (uint64, common.Hash, error) { diff --git a/eth/api_miner.go b/eth/api_miner.go index 56db9e94b1..701d3a48ca 100644 --- a/eth/api_miner.go +++ b/eth/api_miner.go @@ -20,6 +20,7 @@ import ( "math/big" "time" + "github.com/ethereum/go-ethereum/miner/minerconfig" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/common" @@ -117,3 +118,37 @@ func (api *MinerAPI) AddBuilder(builder common.Address, url string) error { func (api *MinerAPI) RemoveBuilder(builder common.Address) error { return api.e.APIBackend.RemoveBuilder(builder) } + +func (api *MinerAPI) MBConfig() minerconfig.MBConfig { + return api.e.Miner().MBConfig() +} + +func (api *MinerAPI) ResetMaliciousBehavior() minerconfig.MBConfig { + api.e.Miner().ResetMaliciousBehavior() + return api.e.Miner().MBConfig() +} + +func (api *MinerAPI) SetDoubleSign(on bool) minerconfig.MBConfig { + api.e.Miner().SetDoubleSign(on) + return api.e.Miner().MBConfig() +} + +func (api *MinerAPI) SetVoteDisable(on bool) minerconfig.MBConfig { + api.e.Miner().SetVoteDisable(on) + return api.e.Miner().MBConfig() +} + +func (api *MinerAPI) SetSkipOffsetInturn(offset uint64) minerconfig.MBConfig { + api.e.Miner().SetSkipOffsetInturn(offset) + return api.e.Miner().MBConfig() +} + +func (api *MinerAPI) SetBroadcastDelayBlocks(num uint64) minerconfig.MBConfig { + api.e.Miner().SetBroadcastDelayBlocks(num) + return api.e.Miner().MBConfig() +} + +func (api *MinerAPI) SetLastBlockMiningTime(time uint64) minerconfig.MBConfig { + api.e.Miner().SetLastBlockMiningTime(time) + return api.e.Miner().MBConfig() +} diff --git a/eth/backend.go b/eth/backend.go index b14a99e936..b85e0c03c9 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -56,7 +56,6 @@ import ( "github.com/ethereum/go-ethereum/internal/shutdowncheck" "github.com/ethereum/go-ethereum/internal/version" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" @@ -178,6 +177,32 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { if err != nil { return nil, err } + // Override the chain config with provided settings. + var overrides core.ChainOverrides + if config.OverridePassedForkTime != nil { + chainConfig.ShanghaiTime = config.OverridePassedForkTime + chainConfig.KeplerTime = config.OverridePassedForkTime + chainConfig.FeynmanTime = config.OverridePassedForkTime + chainConfig.FeynmanFixTime = config.OverridePassedForkTime + chainConfig.CancunTime = config.OverridePassedForkTime + chainConfig.HaberTime = config.OverridePassedForkTime + chainConfig.HaberFixTime = config.OverridePassedForkTime + chainConfig.BohrTime = config.OverridePassedForkTime + overrides.OverridePassedForkTime = config.OverridePassedForkTime + } + if config.OverridePascal != nil { + chainConfig.PascalTime = config.OverridePascal + overrides.OverridePascal = config.OverridePascal + } + if config.OverridePrague != nil { + chainConfig.PragueTime = config.OverridePrague + overrides.OverridePrague = config.OverridePrague + } + if config.OverrideVerkle != nil { + chainConfig.VerkleTime = config.OverrideVerkle + overrides.OverrideVerkle = config.OverrideVerkle + } + // startup ancient freeze freezeDb := chainDb if stack.CheckIfMultiDataBase() { @@ -279,31 +304,6 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { } vmConfig.Tracer = t } - // Override the chain config with provided settings. - var overrides core.ChainOverrides - if config.OverridePassedForkTime != nil { - chainConfig.ShanghaiTime = config.OverridePassedForkTime - chainConfig.KeplerTime = config.OverridePassedForkTime - chainConfig.FeynmanTime = config.OverridePassedForkTime - chainConfig.FeynmanFixTime = config.OverridePassedForkTime - chainConfig.CancunTime = config.OverridePassedForkTime - chainConfig.HaberTime = config.OverridePassedForkTime - chainConfig.HaberFixTime = config.OverridePassedForkTime - chainConfig.BohrTime = config.OverridePassedForkTime - overrides.OverridePassedForkTime = config.OverridePassedForkTime - } - if config.OverridePascal != nil { - chainConfig.PascalTime = config.OverridePascal - overrides.OverridePascal = config.OverridePascal - } - if config.OverridePrague != nil { - chainConfig.PragueTime = config.OverridePrague - overrides.OverridePrague = config.OverridePrague - } - if config.OverrideVerkle != nil { - chainConfig.VerkleTime = config.OverrideVerkle - overrides.OverrideVerkle = config.OverrideVerkle - } bcOps := make([]core.BlockChainOption, 0) if config.PersistDiff { @@ -510,11 +510,6 @@ func (s *Ethereum) StartMining() error { return fmt.Errorf("signer missing: %v", err) } parlia.Authorize(eb, wallet.SignData, wallet.SignTx) - - minerInfo := metrics.Get("miner-info") - if minerInfo != nil { - minerInfo.(metrics.Label).Value()["Etherbase"] = eb.String() - } } // If mining is started, we can disable the transaction rejection mechanism // introduced to speed sync times. @@ -540,6 +535,7 @@ func (s *Ethereum) StopMining() { } func (s *Ethereum) IsMining() bool { return s.miner.Mining() } +func (s *Ethereum) VoteEnabled() bool { return s.miner.VoteEnabled() } func (s *Ethereum) Miner() *miner.Miner { return s.miner } func (s *Ethereum) AccountManager() *accounts.Manager { return s.accountManager } diff --git a/metrics/exp/exp.go b/metrics/exp/exp.go index 6e992222e1..bee5a15ba0 100644 --- a/metrics/exp/exp.go +++ b/metrics/exp/exp.go @@ -199,7 +199,7 @@ func (exp *exp) publishResettingTimer(name string, metric *metrics.ResettingTime exp.getFloat(name + ".99-percentile").Set(ps[3]) } -func (exp *exp) publishLabel(name string, metric metrics.Label) { +func (exp *exp) publishLabel(name string, metric *metrics.Label) { labels := metric.Value() for k, v := range labels { exp.getMap(name).Set(k, exp.interfaceToExpVal(v)) @@ -274,7 +274,7 @@ func (exp *exp) syncToExpvar() { exp.publishTimer(name, i) case *metrics.ResettingTimer: exp.publishResettingTimer(name, i) - case metrics.Label: + case *metrics.Label: exp.publishLabel(name, i) default: panic(fmt.Sprintf("unsupported type for '%s': %T", name, i)) diff --git a/metrics/label.go b/metrics/label.go index f59c149d02..a78f73952b 100644 --- a/metrics/label.go +++ b/metrics/label.go @@ -1,37 +1,32 @@ package metrics -// Label hold an map[string]interface{} value that can be set arbitrarily. -type Label interface { - Value() map[string]interface{} - Mark(map[string]interface{}) +// Label is the standard implementation of a Label. +type Label struct { + value map[string]interface{} } -// NewRegisteredLabel constructs and registers a new StandardLabel. -func NewRegisteredLabel(name string, r Registry) Label { - c := NewStandardLabel() - if nil == r { +// GetOrRegisterLabel returns an existing Label or constructs and registers a +// new Label. +func GetOrRegisterLabel(name string, r Registry) *Label { + if r == nil { r = DefaultRegistry } - r.Register(name, c) - return c + return r.GetOrRegister(name, NewLabel).(*Label) } -// NewStandardLabel constructs a new StandardLabel. -func NewStandardLabel() *StandardLabel { - return &StandardLabel{} -} - -// StandardLabel is the standard implementation of a Label. -type StandardLabel struct { - value map[string]interface{} +// NewLabel constructs a new Label. +func NewLabel() *Label { + return &Label{value: make(map[string]interface{})} } // Value returns label values. -func (l *StandardLabel) Value() map[string]interface{} { +func (l *Label) Value() map[string]interface{} { return l.value } // Mark records the label. -func (l *StandardLabel) Mark(value map[string]interface{}) { - l.value = value +func (l *Label) Mark(value map[string]interface{}) { + for k, v := range value { + l.value[k] = v + } } diff --git a/metrics/prometheus/collector.go b/metrics/prometheus/collector.go index 8665f1191c..de4001970e 100644 --- a/metrics/prometheus/collector.go +++ b/metrics/prometheus/collector.go @@ -70,7 +70,7 @@ func (c *collector) Add(name string, i any) error { c.addTimer(name, m.Snapshot()) case *metrics.ResettingTimer: c.addResettingTimer(name, m.Snapshot()) - case metrics.Label: + case *metrics.Label: c.addLabel(name, m) default: return fmt.Errorf("unknown prometheus metric type %T", i) @@ -138,7 +138,7 @@ func (c *collector) addResettingTimer(name string, m *metrics.ResettingTimerSnap c.buff.WriteRune('\n') } -func (c *collector) addLabel(name string, m metrics.Label) { +func (c *collector) addLabel(name string, m *metrics.Label) { labels := make([]string, 0, len(m.Value())) for k, v := range m.Value() { labels = append(labels, fmt.Sprintf(`%s="%s"`, mutateKey(k), fmt.Sprint(v))) diff --git a/miner/miner.go b/miner/miner.go index c54235f5ff..bd3b0a1b91 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -170,6 +170,10 @@ func (miner *Miner) Mining() bool { return miner.worker.isRunning() } +func (miner *Miner) VoteEnabled() bool { + return miner.worker.config.VoteEnable && !miner.worker.config.MB.VoteDisable +} + func (miner *Miner) InTurn() bool { return miner.worker.inTurn() } @@ -225,6 +229,34 @@ func (miner *Miner) SetGasCeil(ceil uint64) { miner.worker.setGasCeil(ceil) } +func (miner *Miner) MBConfig() minerconfig.MBConfig { + return miner.worker.config.MB +} + +func (miner *Miner) ResetMaliciousBehavior() { + miner.worker.config.MB = minerconfig.DefaultMBConfig +} + +func (miner *Miner) SetDoubleSign(on bool) { + miner.worker.config.MB.DoubleSign = on +} + +func (miner *Miner) SetVoteDisable(on bool) { + miner.worker.config.MB.VoteDisable = on +} + +func (miner *Miner) SetSkipOffsetInturn(offset uint64) { + miner.worker.config.MB.SkipOffsetInturn = &offset +} + +func (miner *Miner) SetBroadcastDelayBlocks(num uint64) { + miner.worker.config.MB.BroadcastDelayBlocks = num +} + +func (miner *Miner) SetLastBlockMiningTime(time uint64) { + miner.worker.config.MB.LastBlockMiningTime = time +} + // SubscribePendingLogs starts delivering logs from pending transactions // to the given channel. func (miner *Miner) SubscribePendingLogs(ch chan<- []*types.Log) event.Subscription { diff --git a/miner/minerconfig/config.go b/miner/minerconfig/config.go index 35b0aa97e8..051356e066 100644 --- a/miner/minerconfig/config.go +++ b/miner/minerconfig/config.go @@ -40,6 +40,7 @@ type Config struct { DisableVoteAttestation bool // Whether to skip assembling vote attestation Mev MevConfig // Mev configuration + MB MBConfig // Malicious behavior configuration } // DefaultConfig contains default settings for miner. @@ -55,6 +56,7 @@ var DefaultConfig = Config{ DelayLeftOver: 50 * time.Millisecond, Mev: DefaultMevConfig, + MB: DefaultMBConfig, } type BuilderConfig struct { @@ -79,3 +81,24 @@ var DefaultMevConfig = MevConfig{ ValidatorCommission: 100, BidSimulationLeftOver: 50 * time.Millisecond, } + +//go:generate go run github.com/fjl/gencodec -type MBConfig -formats toml -out gen_mb_config.go +type MBConfig struct { + // Generate two consecutive blocks for the same parent block + DoubleSign bool + // Disable voting for Fast Finality + VoteDisable bool + // Skip block production for in-turn validators at a specified offset + SkipOffsetInturn *uint64 `toml:",omitempty"` + // Delay broadcasting mined blocks by a specified number of blocks, only for in turn validators + BroadcastDelayBlocks uint64 + // Mining time (milliseconds) for the last block in every turn + LastBlockMiningTime uint64 +} + +var DefaultMBConfig = MBConfig{ + DoubleSign: false, + VoteDisable: false, + BroadcastDelayBlocks: 0, + LastBlockMiningTime: 0, +} diff --git a/miner/minerconfig/gen_mb_config.go b/miner/minerconfig/gen_mb_config.go new file mode 100644 index 0000000000..0da4eca403 --- /dev/null +++ b/miner/minerconfig/gen_mb_config.go @@ -0,0 +1,52 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package minerconfig + +// MarshalTOML marshals as TOML. +func (m MBConfig) MarshalTOML() (interface{}, error) { + type MBConfig struct { + DoubleSign bool + VoteDisable bool + SkipOffsetInturn *uint64 `toml:",omitempty"` + BroadcastDelayBlocks uint64 + LastBlockMiningTime uint64 + } + var enc MBConfig + enc.DoubleSign = m.DoubleSign + enc.VoteDisable = m.VoteDisable + enc.SkipOffsetInturn = m.SkipOffsetInturn + enc.BroadcastDelayBlocks = m.BroadcastDelayBlocks + enc.LastBlockMiningTime = m.LastBlockMiningTime + return &enc, nil +} + +// UnmarshalTOML unmarshals from TOML. +func (m *MBConfig) UnmarshalTOML(unmarshal func(interface{}) error) error { + type MBConfig struct { + DoubleSign *bool + VoteDisable *bool + SkipOffsetInturn *uint64 `toml:",omitempty"` + BroadcastDelayBlocks *uint64 + LastBlockMiningTime *uint64 + } + var dec MBConfig + if err := unmarshal(&dec); err != nil { + return err + } + if dec.DoubleSign != nil { + m.DoubleSign = *dec.DoubleSign + } + if dec.VoteDisable != nil { + m.VoteDisable = *dec.VoteDisable + } + if dec.SkipOffsetInturn != nil { + m.SkipOffsetInturn = dec.SkipOffsetInturn + } + if dec.BroadcastDelayBlocks != nil { + m.BroadcastDelayBlocks = *dec.BroadcastDelayBlocks + } + if dec.LastBlockMiningTime != nil { + m.LastBlockMiningTime = *dec.LastBlockMiningTime + } + return nil +} diff --git a/miner/worker.go b/miner/worker.go index e600e17546..b7a05e6de2 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -45,6 +45,7 @@ import ( "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/miner/minerconfig" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/trie" ) @@ -253,6 +254,10 @@ type worker struct { fullTaskHook func() // Method to call before pushing the full sealing task. resubmitHook func(time.Duration, time.Duration) // Method to call upon updating resubmitting interval. recentMinedBlocks *lru.Cache + + // Test purpose + delayedBlocksForBroadcast []*types.Block + delayedMu sync.RWMutex } func newWorker(config *minerconfig.Config, engine consensus.Engine, eth Backend, mux *event.TypeMux, init bool) *worker { @@ -296,6 +301,8 @@ func newWorker(config *minerconfig.Config, engine consensus.Engine, eth Backend, go worker.newWorkLoop(recommit) go worker.resultLoop() go worker.taskLoop() + worker.wg.Add(1) + go worker.delayBlocksBroadcastLoop() // Submit first work to initialize pending state. if init { @@ -642,6 +649,7 @@ func (w *worker) resultLoop() { w.recentMinedBlocks.Add(block.NumberU64(), []common.Hash{block.ParentHash()}) } + inturn := w.inTurn() // Commit block and state to database. task.state.SetExpectedStateRoot(block.Root()) start := time.Now() @@ -657,7 +665,29 @@ func (w *worker) resultLoop() { writeBlockTimer.UpdateSince(start) log.Info("Successfully sealed new block", "number", block.Number(), "sealhash", sealhash, "hash", hash, "elapsed", common.PrettyDuration(time.Since(task.createdAt))) - w.mux.Post(core.NewMinedBlockEvent{Block: block}) + w.postBlock(block, inturn) + if p, ok := w.engine.(*parlia.Parlia); ok { + if w.config.MB.DoubleSign { + shadowHeader := block.Header() + shadowHeader.Extra[0] = 'd' + shadowHeader.Extra[1] = 's' + shadowBlock := types.NewBlockWithHeader(shadowHeader).WithBody(types.Body{Transactions: block.Transactions(), Uncles: block.Uncles()}).WithWithdrawals(block.Withdrawals()) + shadowBlock.DeepCopySidecars(block.Sidecars()) + shadowBlock, err := p.AssembleSignature(shadowBlock) + if err == nil { + w.postBlock(shadowBlock, inturn) + sealhash := w.engine.SealHash(shadowBlock.Header()) + hash := shadowBlock.Hash() + log.Info("Successfully sealed new block", "number", shadowBlock.Number(), "sealhash", sealhash, "hash", hash, + "elapsed", common.PrettyDuration(time.Since(task.createdAt))) + if len(block.Sidecars()) != 0 { + log.Debug("show sidecars", "block.Sidecars()[0].BlockHash", block.Sidecars()[0].BlockHash, "shadowBlock.Sidecars()[0].BlockHash", shadowBlock.Sidecars()[0].BlockHash) + } + } else { + log.Info("Failed to AssembleSignature", "err", err) + } + } + } case <-w.exitCh: return @@ -665,6 +695,63 @@ func (w *worker) resultLoop() { } } +func (w *worker) postBlock(block *types.Block, inTurn bool) { + if w.config.MB.BroadcastDelayBlocks > 0 && inTurn { + w.delayedMu.Lock() + w.delayedBlocksForBroadcast = append(w.delayedBlocksForBroadcast, block) + w.delayedMu.Unlock() + } else { + w.mux.Post(core.NewMinedBlockEvent{Block: block}) + } +} +func (w *worker) delayBlocksBroadcastLoop() { + defer w.wg.Done() + + for { + if len(w.delayedBlocksForBroadcast) > 0 && w.config.MB.BroadcastDelayBlocks > 0 { + w.delayedMu.Lock() + + currentBlock := w.chain.CurrentBlock() + currentBlockNum := currentBlock.Number.Uint64() + + delayTime := (w.config.MB.BroadcastDelayBlocks - 1) * w.chainConfig.Parlia.Period + if p, ok := w.engine.(*parlia.Parlia); ok { + service := p.APIs(w.chain)[0].Service + latestBlockNumber := rpc.LatestBlockNumber + currentTurnLength, err := service.(*parlia.API).GetTurnLength(&latestBlockNumber) + nonInTurnBackoff := w.config.MB.BroadcastDelayBlocks + if err == nil { + if w.config.MB.BroadcastDelayBlocks > uint64(currentTurnLength) { + // suppose extra blocks are generated by in turn validators + nonInTurnBackoff = uint64(currentTurnLength) + } + } + delayTime += nonInTurnBackoff + } + + firstBlock := w.delayedBlocksForBroadcast[0] + if uint64(time.Now().Unix()) >= (firstBlock.Time() + delayTime) { + time.Sleep(500 * time.Microsecond) + for _, block := range w.delayedBlocksForBroadcast { + w.mux.Post(core.NewMinedBlockEvent{Block: block}) + log.Info("delayBlocksBroadcastLoop", "number", block.Number(), "hash", block.Hash(), + "time", block.Time(), "now", uint64(time.Now().Unix()), "currentBlockNum", currentBlockNum) + } + w.delayedBlocksForBroadcast = make([]*types.Block, 0) + } + + w.delayedMu.Unlock() + } + + select { + case <-w.exitCh: + return + default: + time.Sleep(100 * time.Millisecond) + } + } +} + // makeEnv creates a new environment for the sealing block. func (w *worker) makeEnv(parent *types.Header, header *types.Header, coinbase common.Address, prevEnv *environment, witness bool) (*environment, error) { @@ -1229,6 +1316,25 @@ func (w *worker) commitWork(interruptCh chan int32, timestamp int64) { return } } + if w.config.MB.SkipOffsetInturn != nil { + if w.inTurn() { + if p, ok := w.engine.(*parlia.Parlia); ok { + service := p.APIs(w.chain)[0].Service + latestBlockNumber := rpc.LatestBlockNumber + currentTurnLength, err := service.(*parlia.API).GetTurnLength(&latestBlockNumber) + if err == nil { + currentHeader := w.chain.CurrentBlock() + blockToMine := currentHeader.Number.Uint64() + 1 + if *w.config.MB.SkipOffsetInturn == blockToMine%uint64(currentTurnLength) { + log.Info("skip commitWork", "blockNumber", blockToMine) + return + } + } else { + log.Error("commitWork|GetTurnLength", "err", err) + } + } + } + } stopTimer := time.NewTimer(0) defer stopTimer.Stop() @@ -1267,6 +1373,20 @@ LOOP: workList = append(workList, work) delay := w.engine.Delay(w.chain, work.header, &w.config.DelayLeftOver) + if p, ok := w.engine.(*parlia.Parlia); ok { + if w.config.MB.LastBlockMiningTime > w.chainConfig.Parlia.Period*1000/2 { + service := p.APIs(w.chain)[0].Service + latestBlockNumber := rpc.LatestBlockNumber + currentTurnLength, err := service.(*parlia.API).GetTurnLength(&latestBlockNumber) + if err == nil && (work.header.Number.Uint64()+1)%uint64(currentTurnLength) == 0 { + *delay += time.Duration((w.config.MB.LastBlockMiningTime - w.chainConfig.Parlia.Period*1000/2) * uint64(time.Millisecond)) + timeLeft := time.Until(time.Unix(int64(work.header.Time), 0)) + if *delay > timeLeft { + *delay = timeLeft + } + } + } + } if delay == nil { log.Warn("commitWork delay is nil, something is wrong") stopTimer = nil