Skip to content

Commit

Permalink
[IBFT] Introduce quorum calculation switch (0xPolygon#549)
Browse files Browse the repository at this point in the history
* This PR introduces a flag for specifying the block number at which to use a QuorumOptimal calculation for a valid number of votes in a particular ValidatorSet. (see PR 0xPolygon#513 and issue 0xPolygon#547 )
  • Loading branch information
dbrajovic authored May 16, 2022
1 parent 2e44243 commit b0f6f0c
Show file tree
Hide file tree
Showing 10 changed files with 325 additions and 34 deletions.
3 changes: 3 additions & 0 deletions command/ibft/ibft.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"github.com/0xPolygon/polygon-edge/command/helper"
"github.com/0xPolygon/polygon-edge/command/ibft/candidates"
"github.com/0xPolygon/polygon-edge/command/ibft/propose"
"github.com/0xPolygon/polygon-edge/command/ibft/quorum"
"github.com/0xPolygon/polygon-edge/command/ibft/snapshot"
"github.com/0xPolygon/polygon-edge/command/ibft/status"
_switch "github.com/0xPolygon/polygon-edge/command/ibft/switch"
Expand Down Expand Up @@ -35,5 +36,7 @@ func registerSubcommands(baseCmd *cobra.Command) {
candidates.GetCommand(),
// ibft switch
_switch.GetCommand(),
// ibft quorum
quorum.GetCommand(),
)
}
66 changes: 66 additions & 0 deletions command/ibft/quorum/ibft_quorum.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package quorum

import (
"fmt"
"github.com/0xPolygon/polygon-edge/command"
"github.com/spf13/cobra"
)

func GetCommand() *cobra.Command {
ibftQuorumCmd := &cobra.Command{
Use: "quorum",
Short: "Specify the block number after which quorum optimal will be used for reaching consensus",
PreRunE: runPreRun,
Run: runCommand,
}

setFlags(ibftQuorumCmd)
setRequiredFlags(ibftQuorumCmd)

return ibftQuorumCmd
}

func setFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(
&params.genesisPath,
chainFlag,
fmt.Sprintf("./%s", command.DefaultGenesisFileName),
"the genesis file to update",
)

cmd.Flags().Uint64Var(
&params.from,
fromFlag,
0,
"the height to switch the quorum calculation",
)
}

func setRequiredFlags(cmd *cobra.Command) {
for _, requiredFlag := range params.getRequiredFlags() {
_ = cmd.MarkFlagRequired(requiredFlag)
}
}

func runPreRun(_ *cobra.Command, _ []string) error {
return params.initRawParams()
}

func runCommand(cmd *cobra.Command, _ []string) {
outputter := command.InitializeOutputter(cmd)
defer outputter.WriteOutput()

if err := params.updateGenesisConfig(); err != nil {
outputter.SetError(err)

return
}

if err := params.overrideGenesisConfig(); err != nil {
outputter.SetError(err)

return
}

outputter.SetCommandResult(params.getResult())
}
98 changes: 98 additions & 0 deletions command/ibft/quorum/params.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package quorum

import (
"errors"
"fmt"
"github.com/0xPolygon/polygon-edge/chain"
"github.com/0xPolygon/polygon-edge/command"
"github.com/0xPolygon/polygon-edge/command/helper"
"github.com/0xPolygon/polygon-edge/helper/common"
"os"
)

const (
fromFlag = "from"
chainFlag = "chain"
)

var (
params = &quorumParams{}
)

type quorumParams struct {
genesisConfig *chain.Chain
from uint64
genesisPath string
}

func (p *quorumParams) initChain() error {
cc, err := chain.Import(p.genesisPath)
if err != nil {
return fmt.Errorf(
"failed to load chain config from %s: %w",
p.genesisPath,
err,
)
}

p.genesisConfig = cc

return nil
}

func (p *quorumParams) initRawParams() error {
return p.initChain()
}

func (p *quorumParams) getRequiredFlags() []string {
return []string{
fromFlag,
}
}

func (p *quorumParams) updateGenesisConfig() error {
return appendIBFTQuorum(
p.genesisConfig,
p.from,
)
}

func (p *quorumParams) overrideGenesisConfig() error {
// Remove the current genesis configuration from disk
if err := os.Remove(p.genesisPath); err != nil {
return err
}

// Save the new genesis configuration
if err := helper.WriteGenesisConfigToDisk(
p.genesisConfig,
p.genesisPath,
); err != nil {
return err
}

return nil
}

func (p *quorumParams) getResult() command.CommandResult {
return &IBFTQuorumResult{
Chain: p.genesisPath,
From: common.JSONNumber{Value: p.from},
}
}

func appendIBFTQuorum(
cc *chain.Chain,
from uint64,
) error {
ibftConfig, ok := cc.Params.Engine["ibft"].(map[string]interface{})
if !ok {
return errors.New(`"ibft" setting doesn't exist in "engine" of genesis.json'`)
}

ibftConfig["quorumSizeBlockNum"] = from

cc.Params.Engine["ibft"] = ibftConfig

return nil
}
29 changes: 29 additions & 0 deletions command/ibft/quorum/result.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package quorum

import (
"bytes"
"fmt"
"github.com/0xPolygon/polygon-edge/command/helper"
"github.com/0xPolygon/polygon-edge/helper/common"
)

type IBFTQuorumResult struct {
Chain string `json:"chain"`
From common.JSONNumber `json:"from"`
}

func (r *IBFTQuorumResult) GetOutput() string {
var buffer bytes.Buffer

buffer.WriteString("\n[NEW IBFT QUORUM START]\n")

outputs := []string{
fmt.Sprintf("Chain|%s", r.Chain),
fmt.Sprintf("From|%d", r.From.Value),
}

buffer.WriteString(helper.FormatKV(outputs))
buffer.WriteString("\n")

return buffer.String()
}
75 changes: 50 additions & 25 deletions consensus/ibft/ibft.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,9 @@ type Ibft struct {

txpool txPoolInterface // Reference to the transaction pool

store *snapshotStore // Snapshot store that keeps track of all snapshots
epochSize uint64
store *snapshotStore // Snapshot store that keeps track of all snapshots
epochSize uint64
quorumSizeBlockNum uint64

msgQueue *msgQueue // Structure containing different message queues
updateCh chan struct{} // Update channel
Expand Down Expand Up @@ -133,11 +134,13 @@ func (i *Ibft) runHook(hookName HookType, height uint64, hookParam interface{})
func Factory(
params *consensus.ConsensusParams,
) (consensus.Consensus, error) {
var epochSize uint64
if definedEpochSize, ok := params.Config.Config["epochSize"]; !ok {
// No epoch size defined, use the default one
epochSize = DefaultEpochSize
} else {
// defaults for user set fields in genesis
var (
epochSize = uint64(DefaultEpochSize)
quorumSizeBlockNum = uint64(0)
)

if definedEpochSize, ok := params.Config.Config["epochSize"]; ok {
// Epoch size is defined, use the passed in one
readSize, ok := definedEpochSize.(float64)
if !ok {
Expand All @@ -147,21 +150,32 @@ func Factory(
epochSize = uint64(readSize)
}

if rawBlockNum, ok := params.Config.Config["quorumSizeBlockNum"]; ok {
// Block number specified for quorum size switch
readBlockNum, ok := rawBlockNum.(float64)
if !ok {
return nil, errors.New("invalid type assertion")
}

quorumSizeBlockNum = uint64(readBlockNum)
}

p := &Ibft{
logger: params.Logger.Named("ibft"),
config: params.Config,
Grpc: params.Grpc,
blockchain: params.Blockchain,
executor: params.Executor,
closeCh: make(chan struct{}),
txpool: params.Txpool,
state: &currentState{},
network: params.Network,
epochSize: epochSize,
sealing: params.Seal,
metrics: params.Metrics,
secretsManager: params.SecretsManager,
blockTime: time.Duration(params.BlockTime) * time.Second,
logger: params.Logger.Named("ibft"),
config: params.Config,
Grpc: params.Grpc,
blockchain: params.Blockchain,
executor: params.Executor,
closeCh: make(chan struct{}),
txpool: params.Txpool,
state: &currentState{},
network: params.Network,
epochSize: epochSize,
quorumSizeBlockNum: quorumSizeBlockNum,
sealing: params.Seal,
metrics: params.Metrics,
secretsManager: params.SecretsManager,
blockTime: time.Duration(params.BlockTime) * time.Second,
}

// Initialize the mechanism
Expand Down Expand Up @@ -916,12 +930,12 @@ func (i *Ibft) runValidateState() {
panic(fmt.Sprintf("BUG: %s", reflect.TypeOf(msg.Type)))
}

if i.state.numPrepared() >= i.state.validators.QuorumSize() {
if i.state.numPrepared() >= i.quorumSize(i.state.view.Sequence)(i.state.validators) {
// we have received enough pre-prepare messages
sendCommit()
}

if i.state.numCommitted() >= i.state.validators.QuorumSize() {
if i.state.numCommitted() >= i.quorumSize(i.state.view.Sequence)(i.state.validators) {
// we have received enough commit messages
sendCommit()

Expand Down Expand Up @@ -1107,7 +1121,7 @@ func (i *Ibft) runRoundChangeState() {
// update timer
timeout = exponentialTimeout(i.state.view.Round)
sendRoundChange(msg.View.Round)
} else if num == i.state.validators.QuorumSize() {
} else if num == i.quorumSize(i.state.view.Sequence)(i.state.validators) {
// start a new round immediately
i.state.view.Round = msg.View.Round
i.setState(AcceptState)
Expand Down Expand Up @@ -1249,13 +1263,24 @@ func (i *Ibft) VerifyHeader(parent, header *types.Header) error {
}

// verify the committed seals
if err := verifyCommitedFields(snap, header); err != nil {
if err := verifyCommitedFields(snap, header, i.quorumSize(header.Number)); err != nil {
return err
}

return nil
}

// quorumSize returns a callback that when executed on a ValidatorSet computes
// number of votes required to reach quorum based on the size of the set.
// The blockNumber argument indicates which formula was used to calculate the result (see PRs #513, #549)
func (i *Ibft) quorumSize(blockNumber uint64) QuorumImplementation {
if blockNumber < i.quorumSizeBlockNum {
return LegacyQuorumSize
}

return OptimalQuorumSize
}

// ProcessHeaders updates the snapshot based on previously verified headers
func (i *Ibft) ProcessHeaders(headers []*types.Header) error {
return i.processHeaders(headers)
Expand Down
Loading

0 comments on commit b0f6f0c

Please sign in to comment.