Skip to content

Commit

Permalink
feat: implementing pip-19 (#899)
Browse files Browse the repository at this point in the history
  • Loading branch information
b00f authored Jan 3, 2024
1 parent 4407619 commit 72d58bc
Show file tree
Hide file tree
Showing 8 changed files with 203 additions and 4 deletions.
5 changes: 3 additions & 2 deletions consensus/consensus_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -349,11 +349,12 @@ func (td *testData) commitBlockForAllStates(t *testing.T) (*block.Block, *certif
sb := certificate.BlockCertificateSignBytes(p.Block().Hash(), height+1, 0)
sig1 := td.consX.valKey.Sign(sb)
sig2 := td.consY.valKey.Sign(sb)
sig3 := td.consB.valKey.Sign(sb)
sig4 := td.consP.valKey.Sign(sb)

sig := bls.SignatureAggregate(sig1, sig2, sig4)
sig := bls.SignatureAggregate(sig1, sig2, sig3, sig4)
cert := certificate.NewCertificate(height+1, 0,
[]int32{tIndexX, tIndexY, tIndexB, tIndexP}, []int32{tIndexB}, sig)
[]int32{tIndexX, tIndexY, tIndexB, tIndexP}, []int32{}, sig)
blk := p.Block()

err = td.consX.bcState.CommitBlock(blk, cert)
Expand Down
10 changes: 9 additions & 1 deletion consensus/propose.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,15 @@ func (s *proposeState) decide() {
s.cpRound = 0
s.cpDecided = -1
s.cpWeakValidity = nil
s.enterNewState(s.prepareState)

score := s.bcState.AvailabilityScore(proposer.Number())
// Based on PIP-19, if the Availability Score is less than 0.9,
// we initiate the Change-Proposer phase.
if score < 0.9 {
s.startChangingProposer()
} else {
s.enterNewState(s.prepareState)
}
}

func (s *proposeState) createProposal(height uint32, round int16) {
Expand Down
3 changes: 2 additions & 1 deletion network/relay_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ func (rs *relayService) checkConnectivity() {
rs.logger.Info("try connecting relay node", "addr", ai.Addrs)
err := ConnectSync(rs.ctx, rs.host, ai)
if err != nil {
rs.logger.Warn("unable to connect to relay node", "error", err, "addr", ai.Addrs)
// TODO: Make me Warn?
rs.logger.Debug("unable to connect to relay node", "error", err, "addr", ai.Addrs)
} else {
rs.logger.Info("connect to relay node", "addr", ai.Addrs)
}
Expand Down
1 change: 1 addition & 0 deletions state/facade.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,5 @@ type Facade interface {
Close() error
CalculateFee(amount int64, payloadType payload.Type) (int64, error)
PublicKey(addr crypto.Address) (crypto.PublicKey, error)
AvailabilityScore(valNum int32) float64
}
4 changes: 4 additions & 0 deletions state/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,3 +259,7 @@ func (m *MockState) CalculateFee(_ int64, payloadType payload.Type) (int64, erro
func (m *MockState) PublicKey(addr crypto.Address) (crypto.PublicKey, error) {
return m.TestStore.PublicKey(addr)
}

func (m *MockState) AvailabilityScore(_ int32) float64 {
return 1.0
}
73 changes: 73 additions & 0 deletions state/score/score.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package score

import "github.com/pactus-project/pactus/types/certificate"

type scoreData struct {
inCommittee int // Number of times a validator was in the committee
absent int // Number of times a validator was absent from the committee (not voted)
}

type Manager struct {
certs map[uint32]*certificate.Certificate
vals map[int32]*scoreData
maxCert uint32
}

func NewScoreManager(maxCert uint32) *Manager {
return &Manager{
certs: make(map[uint32]*certificate.Certificate),
vals: make(map[int32]*scoreData),
maxCert: maxCert,
}
}

func (sm *Manager) SetCertificate(cert *certificate.Certificate) {
lastHeight := cert.Height()
sm.certs[lastHeight] = cert

for _, num := range cert.Committers() {
data, ok := sm.vals[num]
if !ok {
data = new(scoreData)
sm.vals[num] = data
}

data.inCommittee++
}

for _, num := range cert.Absentees() {
data := sm.vals[num]
sm.vals[num] = data

data.absent++
}

oldHeight := lastHeight - sm.maxCert
oldCert, ok := sm.certs[oldHeight]
if ok {
for _, num := range oldCert.Committers() {
data := sm.vals[num]
data.inCommittee--
}

for _, num := range oldCert.Absentees() {
data := sm.vals[num]
data.absent--
}

delete(sm.certs, oldHeight)
}
}

func (sm *Manager) AvailabilityScore(valNum int32) float64 {
data, ok := sm.vals[valNum]
if ok {
if data.inCommittee == 0 {
return 1.0
}

return 1 - (float64(data.absent) / float64(data.inCommittee))
}

return 1.0
}
58 changes: 58 additions & 0 deletions state/score/score_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package score

import (
"testing"

"github.com/pactus-project/pactus/types/certificate"
"github.com/stretchr/testify/assert"
)

func TestScoreManager(t *testing.T) {
maxCert := uint32(3)
sm := NewScoreManager(maxCert)

cert1 := certificate.NewCertificate(1, 0, []int32{0, 1, 2, 3}, []int32{0}, nil)
cert2 := certificate.NewCertificate(2, 0, []int32{0, 1, 2, 3}, []int32{3}, nil)
cert3 := certificate.NewCertificate(3, 0, []int32{1, 2, 3, 4}, []int32{2}, nil)
cert4 := certificate.NewCertificate(4, 0, []int32{1, 2, 3, 4}, []int32{2}, nil)
cert5 := certificate.NewCertificate(5, 0, []int32{1, 2, 3, 4}, []int32{2}, nil)

tests := []struct {
cert *certificate.Certificate
score0 float64
score1 float64
score2 float64
score3 float64
score4 float64
}{
{cert1, 0, 1, 1, 1, 1},
{cert2, 0.5, 1, 1, 0.5, 1},
{cert3, 0.5, 1, 1 - (float64(1) / float64(3)), 1 - (float64(1) / float64(3)), 1},
{cert4, 1, 1, 1 - (float64(2) / float64(3)), 1 - (float64(1) / float64(3)), 1},
{cert5, 1, 1, 0, 1, 1},
}

for i, test := range tests {
sm.SetCertificate(test.cert)

score0 := sm.AvailabilityScore(0)
assert.Equal(t, test.score0, score0, "#%v: invalid score0, expected %v, got %v",
i, test.score0, score0)

score1 := sm.AvailabilityScore(1)
assert.Equal(t, test.score1, score1, "#%v: invalid score1, expected %v, got %v",
i, test.score1, score1)

score2 := sm.AvailabilityScore(2)
assert.Equal(t, test.score2, score2, "#%v: invalid score2, expected %v, got %v",
i, test.score2, score2)

score3 := sm.AvailabilityScore(3)
assert.Equal(t, test.score3, score3, "#%v: invalid score3, expected %v, got %v",
i, test.score3, score3)

score4 := sm.AvailabilityScore(4)
assert.Equal(t, test.score4, score4, "#%v: invalid score4, expected %v, got %v",
i, test.score4, score4)
}
}
53 changes: 53 additions & 0 deletions state/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/pactus-project/pactus/sandbox"
"github.com/pactus-project/pactus/sortition"
"github.com/pactus-project/pactus/state/lastinfo"
"github.com/pactus-project/pactus/state/score"
"github.com/pactus-project/pactus/store"
"github.com/pactus-project/pactus/txpool"
"github.com/pactus-project/pactus/types/account"
Expand Down Expand Up @@ -47,6 +48,7 @@ type state struct {
lastInfo *lastinfo.LastInfo
accountMerkle *persistentmerkle.Tree
validatorMerkle *persistentmerkle.Tree
scoreMgr *score.Manager
logger *logger.SubLogger
eventCh chan event.Event
}
Expand Down Expand Up @@ -92,6 +94,33 @@ func LoadOrNewState(

txPool.SetNewSandboxAndRecheck(st.concreteSandbox())

// Restoring score manager
st.logger.Info("calculating the availability scores...")
scoreWindow := uint32(60000)
startHeight := uint32(2)
endHeight := st.lastInfo.BlockHeight()
if endHeight > scoreWindow {
startHeight = endHeight - scoreWindow
}

scoreMgr := score.NewScoreManager(scoreWindow)
for h := startHeight; h <= endHeight; h++ {
cb, err := st.store.Block(h)
if err != nil {
return nil, err
}
blk, err := cb.ToBlock()
if err != nil {
return nil, err
}
scoreMgr.SetCertificate(blk.PrevCertificate())
}
st.scoreMgr = scoreMgr

for _, num := range st.committee.Committers() {
st.logger.Debug("availability score", "val", num, "score", st.scoreMgr.AvailabilityScore(num))
}

st.logger.Debug("last info", "committers", st.committee.Committers(), "state_root", st.stateRoot())

return st, nil
Expand Down Expand Up @@ -449,6 +478,20 @@ func (st *state) CommitBlock(blk *block.Block, cert *certificate.Certificate) er
// At this point we can assign a new sandbox to tx pool
st.txPool.SetNewSandboxAndRecheck(st.concreteSandbox())

// -----------------------------------
// Updating score manager
if blk.Header().Time().After(time.Now().AddDate(0, -1, -1)) {
prevCert := blk.PrevCertificate()
if prevCert != nil {
st.scoreMgr.SetCertificate(prevCert)

// TODO: Remove me after gRPC done
for _, num := range st.committee.Committers() {
st.logger.Debug("availability score", "val", num, "score", st.scoreMgr.AvailabilityScore(num))
}
}
}

// -----------------------------------
// Publishing the events to the zmq
st.publishEvents(height, blk)
Expand Down Expand Up @@ -730,5 +773,15 @@ func (st *state) CalculateFee(amount int64, payloadType payload.Type) (int64, er
}

func (st *state) PublicKey(addr crypto.Address) (crypto.PublicKey, error) {
st.lk.RLock()
defer st.lk.RUnlock()

return st.store.PublicKey(addr)
}

func (st *state) AvailabilityScore(valNum int32) float64 {
st.lk.RLock()
defer st.lk.RUnlock()

return st.scoreMgr.AvailabilityScore(valNum)
}

0 comments on commit 72d58bc

Please sign in to comment.