Skip to content

Commit

Permalink
Introduce a new version of go ibft (#1392)
Browse files Browse the repository at this point in the history
 introduce a new version of go-ibft (HasQuorum is a part of go-ibft)


Co-authored-by: Stefan Negovanović <stefan@ethernal.tech>
  • Loading branch information
Nemanja0x and Stefan-Ethernal committed Jun 1, 2023
1 parent 6ff53be commit dd13966
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 190 deletions.
39 changes: 11 additions & 28 deletions consensus/ibft/consensus_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package ibft
import (
"context"
"fmt"
"math/big"
"time"

"github.com/0xPolygon/go-ibft/messages"
Expand Down Expand Up @@ -139,39 +140,21 @@ func (i *backendIBFT) MaximumFaultyNodes() uint64 {
return uint64(CalcMaxFaultyNodes(i.currentValidators))
}

func (i *backendIBFT) HasQuorum(
blockNumber uint64,
messages []*proto.Message,
msgType proto.MessageType,
) bool {
validators, err := i.forkManager.GetValidators(blockNumber)
// DISCLAIMER: IBFT will be deprecated so we set 1 as a voting power to all validators
func (i *backendIBFT) GetVotingPowers(height uint64) (map[string]*big.Int, error) {
validators, err := i.forkManager.GetValidators(height)
if err != nil {
i.logger.Error(
"failed to get validators when calculation quorum",
"height", blockNumber,
"err", err,
)

return false
return nil, err
}

quorum := i.quorumSize(blockNumber)(validators)

switch msgType {
case proto.MessageType_PREPREPARE:
return len(messages) > 0
case proto.MessageType_PREPARE:
// two cases -> first message is MessageType_PREPREPARE, and other -> MessageType_PREPREPARE is not included
if len(messages) > 0 && messages[0].Type == proto.MessageType_PREPREPARE {
return len(messages) >= quorum
}
result := make(map[string]*big.Int, validators.Len())

return len(messages) >= quorum-1
case proto.MessageType_COMMIT, proto.MessageType_ROUND_CHANGE:
return len(messages) >= quorum
default:
return false
for index := 0; index < validators.Len(); index++ {
strAddress := types.AddressToString(validators.At(uint64(index)).Addr())
result[strAddress] = big.NewInt(1) // set 1 as voting power to everyone
}

return result, nil
}

// buildBlock builds the block, based on the passed in snapshot and parent header
Expand Down
58 changes: 8 additions & 50 deletions consensus/polybft/consensus_runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -766,61 +766,19 @@ func (c *consensusRuntime) ID() []byte {
return c.config.Key.Address().Bytes()
}

// HasQuorum returns true if quorum is reached for the given blockNumber
func (c *consensusRuntime) HasQuorum(
height uint64,
messages []*proto.Message,
msgType proto.MessageType) bool {
// GetVotingPowers returns map of validators addresses and their voting powers for the specified height.
func (c *consensusRuntime) GetVotingPowers(height uint64) (map[string]*big.Int, error) {
c.lock.RLock()
defer c.lock.RUnlock()
// extract the addresses of all the signers of the messages
ppIncluded := false
signers := make(map[types.Address]struct{}, len(messages))

for _, message := range messages {
if message.Type == proto.MessageType_PREPREPARE {
ppIncluded = true
}

signers[types.BytesToAddress(message.From)] = struct{}{}
if c.fsm == nil {
return nil, errors.New("getting voting power failed - backend is not initialized")
} else if c.fsm.Height() != height {
return nil, fmt.Errorf("getting voting power failed - backend is not initialized for height %d, fsm height %d",
height, c.fsm.Height())
}

// check quorum
switch msgType {
case proto.MessageType_PREPREPARE:
return len(messages) >= 1
case proto.MessageType_PREPARE:
if ppIncluded {
return c.fsm.validators.HasQuorum(signers)
}

if len(messages) == 0 {
return false
}

propAddress, err := c.fsm.proposerSnapshot.GetLatestProposer(messages[0].View.Round, height)
if err != nil {
// This can happen if e.g. node runs sequence on lower height and proposer calculator updated
// to a newer count as a consequence of inserting block from syncer
c.logger.Debug("HasQuorum has been called but proposer could not be retrieved", "error", err)

return false
}

if _, ok := signers[propAddress]; ok {
c.logger.Warn("HasQuorum failed - proposer is among signers but it is not expected to be")

return false
}

signers[propAddress] = struct{}{} // add proposer manually

return c.fsm.validators.HasQuorum(signers)
case proto.MessageType_ROUND_CHANGE, proto.MessageType_COMMIT:
return c.fsm.validators.HasQuorum(signers)
default:
return false
}
return c.fsm.validators.GetVotingPowers(), nil
}

// BuildPrePrepareMessage builds a PREPREPARE message based on the passed in proposal
Expand Down
125 changes: 17 additions & 108 deletions consensus/polybft/consensus_runtime_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -838,126 +838,35 @@ func TestConsensusRuntime_ID(t *testing.T) {
require.NotEqual(t, runtime.ID(), key2.Address().Bytes())
}

func TestConsensusRuntime_HasQuorum(t *testing.T) {
func TestConsensusRuntime_GetVotingPowers(t *testing.T) {
t.Parallel()

const round = 5
const height = 100

validatorAccounts := validator.NewTestValidatorsWithAliases(t, []string{"A", "B", "C", "D", "E", "F"})
validatorAccounts := validator.NewTestValidatorsWithAliases(t, []string{"A", "B", "C"}, []uint64{1, 3, 2})
runtime := &consensusRuntime{}

extra := &Extra{
Checkpoint: &CheckpointData{},
}
_, err := runtime.GetVotingPowers(height)
require.Error(t, err)

lastBuildBlock := &types.Header{
Number: 1,
ExtraData: extra.MarshalRLPTo(nil),
runtime.fsm = &fsm{
validators: validatorAccounts.ToValidatorSet(),
parent: &types.Header{Number: height},
}

blockchainMock := new(blockchainMock)
blockchainMock.On("NewBlockBuilder", mock.Anything).Return(&BlockBuilder{}, nil).Once()
blockchainMock.On("GetHeaderByNumber", mock.Anything).Return(&types.Header{
Number: 0,
}, true).Once()
_, err = runtime.GetVotingPowers(height)
require.Error(t, err)

state := newTestState(t)
snapshot := NewProposerSnapshot(lastBuildBlock.Number+1, validatorAccounts.GetPublicIdentities())
config := &runtimeConfig{
Key: validatorAccounts.GetValidator("B").Key(),
blockchain: blockchainMock,
PolyBFTConfig: &PolyBFTConfig{EpochSize: 10, SprintSize: 5},
}
runtime := &consensusRuntime{
state: state,
config: config,
lastBuiltBlock: lastBuildBlock,
epoch: &epochMetadata{
Number: 1,
Validators: validatorAccounts.GetPublicIdentities()[:len(validatorAccounts.Validators)-1],
},
logger: hclog.NewNullLogger(),
proposerCalculator: NewProposerCalculatorFromSnapshot(snapshot, config, hclog.NewNullLogger()),
stateSyncManager: &dummyStateSyncManager{},
checkpointManager: &dummyCheckpointManager{},
}
runtime.fsm.parent.Number = height - 1

require.NoError(t, runtime.FSM())

proposer, err := snapshot.CalcProposer(round, lastBuildBlock.Number+1)
val, err := runtime.GetVotingPowers(height)
require.NoError(t, err)

runtime.fsm.proposerSnapshot = snapshot

messages := make([]*proto.Message, 0, len(validatorAccounts.Validators))

// Unknown message type
assert.False(t, runtime.HasQuorum(lastBuildBlock.Number+1, messages, -1))

// invalid block number
for _, msgType := range []proto.MessageType{proto.MessageType_PREPREPARE, proto.MessageType_PREPARE,
proto.MessageType_ROUND_CHANGE, proto.MessageType_COMMIT} {
assert.False(t, runtime.HasQuorum(lastBuildBlock.Number, nil, msgType))
}

// MessageType_PREPREPARE - only one message is enough
messages = append(messages, &proto.Message{
From: proposer.Bytes(),
Type: proto.MessageType_PREPREPARE,
View: &proto.View{Height: 1, Round: round},
})

assert.False(t, runtime.HasQuorum(lastBuildBlock.Number+1, nil, proto.MessageType_PREPREPARE))
assert.True(t, runtime.HasQuorum(lastBuildBlock.Number+1, messages, proto.MessageType_PREPREPARE))

// MessageType_PREPARE
messages = make([]*proto.Message, 0, len(validatorAccounts.Validators))

for _, x := range validatorAccounts.Validators {
address := x.Address()
addresses := validatorAccounts.GetPublicIdentities([]string{"A", "B", "C"}...).GetAddresses()

// proposer must not be included in prepare messages
if address != proposer {
messages = append(messages, &proto.Message{
From: address[:],
Type: proto.MessageType_PREPARE,
View: &proto.View{Height: lastBuildBlock.Number + 1, Round: round},
})
}
}

// enough quorum
assert.True(t, runtime.HasQuorum(lastBuildBlock.Number+1, messages, proto.MessageType_PREPARE))

// not enough quorum
assert.False(t, runtime.HasQuorum(lastBuildBlock.Number+1, messages[:1], proto.MessageType_PREPARE))

// include proposer which is not allowed
messages = append(messages, &proto.Message{
From: proposer[:],
Type: proto.MessageType_PREPARE,
View: &proto.View{Height: lastBuildBlock.Number + 1, Round: round},
})
assert.False(t, runtime.HasQuorum(lastBuildBlock.Number+1, messages, proto.MessageType_PREPARE))

// last message is MessageType_PREPREPARE - this should be allowed
messages[len(messages)-1].Type = proto.MessageType_PREPREPARE
assert.True(t, runtime.HasQuorum(lastBuildBlock.Number+1, messages, proto.MessageType_PREPARE))

//proto.MessageType_ROUND_CHANGE, proto.MessageType_COMMIT
for _, msgType := range []proto.MessageType{proto.MessageType_ROUND_CHANGE, proto.MessageType_COMMIT} {
messages = make([]*proto.Message, 0, len(validatorAccounts.Validators))

for _, x := range validatorAccounts.Validators {
messages = append(messages, &proto.Message{
From: x.Address().Bytes(),
Type: msgType,
View: &proto.View{Height: lastBuildBlock.Number + 1, Round: round},
})
}

assert.True(t, runtime.HasQuorum(lastBuildBlock.Number+1, messages, msgType))
assert.False(t, runtime.HasQuorum(lastBuildBlock.Number+1, messages[:1], msgType))
}
assert.Equal(t, uint64(1), val[types.AddressToString(addresses[0])].Uint64())
assert.Equal(t, uint64(3), val[types.AddressToString(addresses[1])].Uint64())
assert.Equal(t, uint64(2), val[types.AddressToString(addresses[2])].Uint64())
}

func TestConsensusRuntime_BuildRoundChangeMessage(t *testing.T) {
Expand Down
15 changes: 14 additions & 1 deletion consensus/polybft/validator/validator_set.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@ type ValidatorSet interface {
// Accounts returns the list of the ValidatorMetadata
Accounts() AccountSet

// checks if submitted signers have reached quorum
// HasQuorum checks if submitted signers have reached quorum
HasQuorum(signers map[types.Address]struct{}) bool

// GetVotingPowers retrieves map: string(address) -> vp
GetVotingPowers() map[string]*big.Int
}

type validatorSet struct {
Expand Down Expand Up @@ -80,6 +83,16 @@ func (vs validatorSet) HasQuorum(signers map[types.Address]struct{}) bool {
return hasQuorum
}

func (vs validatorSet) GetVotingPowers() map[string]*big.Int {
result := make(map[string]*big.Int, vs.Len())

for address, vp := range vs.votingPowerMap {
result[types.AddressToString(address)] = vp
}

return result
}

func (vs validatorSet) Accounts() AccountSet {
return vs.validators
}
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ require (
)

require (
github.com/0xPolygon/go-ibft v0.4.0
github.com/0xPolygon/go-ibft v0.4.1-0.20230418151118-337b82c3c3c4
github.com/docker/docker v20.10.18+incompatible
github.com/docker/go-connections v0.4.0
go.etcd.io/bbolt v1.3.7
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D
filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU=
filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
github.com/0xPolygon/go-ibft v0.4.0 h1:WwevpA/J5PI2ztQuIis/0uRlVZrJgps9NhPewoYZDus=
github.com/0xPolygon/go-ibft v0.4.0/go.mod h1:mJGwdcGvLdg9obtnzBqx1aAzuhzvGeWav5AiUWN7F3Q=
github.com/0xPolygon/go-ibft v0.4.1-0.20230418151118-337b82c3c3c4 h1:wrQjKg1RS5/K757z6E2A/oVqp/Wq3/3D0p69BhLlAs4=
github.com/0xPolygon/go-ibft v0.4.1-0.20230418151118-337b82c3c3c4/go.mod h1:mJGwdcGvLdg9obtnzBqx1aAzuhzvGeWav5AiUWN7F3Q=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
Expand Down

0 comments on commit dd13966

Please sign in to comment.