Skip to content

Commit

Permalink
feat(parachain/collation-protocol): handle peer view change (#4307)
Browse files Browse the repository at this point in the history
On receiving peer view change, update our view of that peer, clearing all the advertisements that are no longer in current view.
This commit also moves View to types package to be reused by multiple subsystems

Fixes #4155
  • Loading branch information
kishansagathiya authored Nov 11, 2024
1 parent e030fa7 commit 075322c
Show file tree
Hide file tree
Showing 7 changed files with 206 additions and 118 deletions.
26 changes: 13 additions & 13 deletions dot/parachain/collator-protocol/message_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ func TestHandleCollationMessageDeclare(t *testing.T) {
},
peerData: map[peer.ID]PeerData{
peerID: {
view: View{},
view: parachaintypes.View{},
state: PeerStateInfo{
PeerState: Collating,
CollatingPeerState: CollatingPeerState{
Expand Down Expand Up @@ -268,7 +268,7 @@ func TestHandleCollationMessageDeclare(t *testing.T) {
},
peerData: map[peer.ID]PeerData{
peerID: {
view: View{},
view: parachaintypes.View{},
state: PeerStateInfo{
PeerState: Connected,
},
Expand All @@ -285,7 +285,7 @@ func TestHandleCollationMessageDeclare(t *testing.T) {
},
peerData: map[peer.ID]PeerData{
peerID: {
view: View{},
view: parachaintypes.View{},
state: PeerStateInfo{
PeerState: Connected,
},
Expand All @@ -312,7 +312,7 @@ func TestHandleCollationMessageDeclare(t *testing.T) {
},
peerData: map[peer.ID]PeerData{
peerID: {
view: View{},
view: parachaintypes.View{},
state: PeerStateInfo{
PeerState: Connected,
},
Expand Down Expand Up @@ -342,7 +342,7 @@ func TestHandleCollationMessageDeclare(t *testing.T) {
},
peerData: map[peer.ID]PeerData{
peerID: {
view: View{},
view: parachaintypes.View{},
state: PeerStateInfo{
PeerState: Connected,
},
Expand Down Expand Up @@ -449,7 +449,7 @@ func TestHandleCollationMessageAdvertiseCollation(t *testing.T) {
},
peerData: map[peer.ID]PeerData{
peerID: {
view: View{},
view: parachaintypes.View{},
state: PeerStateInfo{
PeerState: Connected,
},
Expand All @@ -475,7 +475,7 @@ func TestHandleCollationMessageAdvertiseCollation(t *testing.T) {
},
peerData: map[peer.ID]PeerData{
peerID: {
view: View{},
view: parachaintypes.View{},
state: PeerStateInfo{
PeerState: Collating,
CollatingPeerState: CollatingPeerState{
Expand Down Expand Up @@ -510,7 +510,7 @@ func TestHandleCollationMessageAdvertiseCollation(t *testing.T) {
},
peerData: map[peer.ID]PeerData{
peerID: {
view: View{},
view: parachaintypes.View{},
state: PeerStateInfo{
PeerState: Collating,
CollatingPeerState: CollatingPeerState{
Expand All @@ -536,7 +536,7 @@ func TestHandleCollationMessageAdvertiseCollation(t *testing.T) {
},
peerData: map[peer.ID]PeerData{
peerID: {
view: View{},
view: parachaintypes.View{},
state: PeerStateInfo{
PeerState: Collating,
CollatingPeerState: CollatingPeerState{
Expand Down Expand Up @@ -616,7 +616,7 @@ func TestInsertAdvertisement(t *testing.T) {
{
description: "fail with undeclared collator",
peerData: PeerData{
view: View{},
view: parachaintypes.View{},
state: PeerStateInfo{
PeerState: Connected,
},
Expand All @@ -626,7 +626,7 @@ func TestInsertAdvertisement(t *testing.T) {
{
description: "fail with error out of view",
peerData: PeerData{
view: View{},
view: parachaintypes.View{},
state: PeerStateInfo{
PeerState: Collating,
},
Expand All @@ -639,7 +639,7 @@ func TestInsertAdvertisement(t *testing.T) {
{
description: "fail with error duplicate advertisement",
peerData: PeerData{
view: View{},
view: parachaintypes.View{},
state: PeerStateInfo{
PeerState: Collating,
CollatingPeerState: CollatingPeerState{
Expand All @@ -662,7 +662,7 @@ func TestInsertAdvertisement(t *testing.T) {
{
description: "success case",
peerData: PeerData{
view: View{},
view: parachaintypes.View{},
state: PeerStateInfo{
PeerState: Collating,
CollatingPeerState: CollatingPeerState{
Expand Down
79 changes: 34 additions & 45 deletions dot/parachain/collator-protocol/validator_side.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (

"github.com/ChainSafe/gossamer/dot/network"
collatorprotocolmessages "github.com/ChainSafe/gossamer/dot/parachain/collator-protocol/messages"
"github.com/ChainSafe/gossamer/dot/parachain/network-bridge/events"
networkbridgeevents "github.com/ChainSafe/gossamer/dot/parachain/network-bridge/events"
networkbridgemessages "github.com/ChainSafe/gossamer/dot/parachain/network-bridge/messages"
parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types"
Expand Down Expand Up @@ -123,7 +122,7 @@ func (cpvs *CollatorProtocolValidatorSide) ProcessActiveLeavesUpdateSignal(
return nil
}

func (cpvs *CollatorProtocolValidatorSide) handleOurViewChange(view events.View) error {
func (cpvs *CollatorProtocolValidatorSide) handleOurViewChange(view parachaintypes.View) error {
// 1. Find out removed leaves (hashes) and newly added leaves
// 2. Go over each new leaves,
// - check if perspective parachain mode is enabled
Expand Down Expand Up @@ -400,7 +399,7 @@ type PendingCollation struct {
}

type PeerData struct {
view View
view parachaintypes.View
state PeerStateInfo
}

Expand Down Expand Up @@ -495,6 +494,31 @@ func (peerData *PeerData) InsertAdvertisement(
return true, nil
}

// UpdateView updates the view clearing all advertisements that are no longer in the current view.
func (peerData *PeerData) UpdateView(implicitView ImplicitView,
activeLeaves map[common.Hash]parachaintypes.ProspectiveParachainsMode, perRelayParent map[common.Hash]PerRelayParent,
newView parachaintypes.View) {

oldView := peerData.view
if peerData.state.PeerState == Collating {
// remove relay parent advertisement if it went out of implicit view
diff := oldView.Difference(newView)
for _, relayParent := range diff {
_, ok := perRelayParent[relayParent]
if !ok {
delete(peerData.state.CollatingPeerState.advertisements, relayParent)
}

keep := IsRelayParentInImplicitView(relayParent, perRelayParent[relayParent].prospectiveParachainMode,
implicitView, activeLeaves, peerData.state.CollatingPeerState.ParaID)

if !keep {
delete(peerData.state.CollatingPeerState.advertisements, relayParent)
}
}
}
}

type PeerStateInfo struct {
PeerState PeerState
// instant at which peer got connected
Expand All @@ -521,47 +545,6 @@ const (
// We use the same limit to compute the view sent to peers locally.
const MaxViewHeads uint8 = 5

// A succinct representation of a peer's view. This consists of a bounded amount of chain heads
// and the highest known finalized block number.
//
// Up to `N` (5?) chain heads.
type View struct {
// a bounded amount of chain heads
heads []common.Hash
// the highest known finalized number
finalizedNumber uint32
}

type SortableHeads []common.Hash

func (s SortableHeads) Len() int {
return len(s)
}

func (s SortableHeads) Less(i, j int) bool {
return s[i].String() > s[j].String()
}

func (s SortableHeads) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}

func ConstructView(liveHeads map[common.Hash]struct{}, finalizedNumber uint32) View {
heads := make([]common.Hash, 0, len(liveHeads))
for head := range liveHeads {
heads = append(heads, head)
}

if len(heads) >= 5 {
heads = heads[:5]
}

return View{
heads: heads,
finalizedNumber: finalizedNumber,
}
}

// Network is the interface required by parachain service for the network
type Network interface {
GossipMessage(msg network.NotificationsMessage)
Expand Down Expand Up @@ -743,7 +726,13 @@ func (cpvs CollatorProtocolValidatorSide) handleNetworkBridgeEvents(msg any) err
case networkbridgeevents.NewGossipTopology:
// NOTE: This won't happen
case networkbridgeevents.PeerViewChange:
// TODO #4155
// - advertisements by this peer that are no longer relevant have to be removed
peerData, ok := cpvs.peerData[msg.PeerID]
if ok {
peerData.UpdateView(cpvs.implicitView, cpvs.activeLeaves, cpvs.perRelayParent, msg.View)
cpvs.peerData[msg.PeerID] = peerData
}

case networkbridgeevents.OurViewChange:
return cpvs.handleOurViewChange(msg.View)
case networkbridgeevents.UpdatedAuthorityIDs:
Expand Down
56 changes: 53 additions & 3 deletions dot/parachain/collator-protocol/validator_side_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/ChainSafe/gossamer/dot/network"
"github.com/ChainSafe/gossamer/dot/parachain/backing"
collatorprotocolmessages "github.com/ChainSafe/gossamer/dot/parachain/collator-protocol/messages"
networkbridgeevents "github.com/ChainSafe/gossamer/dot/parachain/network-bridge/events"
networkbridgemessages "github.com/ChainSafe/gossamer/dot/parachain/network-bridge/messages"
"github.com/ChainSafe/gossamer/dot/parachain/overseer"
parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types"
Expand Down Expand Up @@ -104,7 +105,7 @@ func TestProcessOverseerMessage(t *testing.T) {
}},
peerData: map[peer.ID]PeerData{
peerID: {
view: View{},
view: parachaintypes.View{},
state: PeerStateInfo{
PeerState: Collating,
CollatingPeerState: CollatingPeerState{
Expand Down Expand Up @@ -168,7 +169,7 @@ func TestProcessOverseerMessage(t *testing.T) {
}(),
peerData: map[peer.ID]PeerData{
peerID: {
view: View{},
view: parachaintypes.View{},
state: PeerStateInfo{
PeerState: Collating,
CollatingPeerState: CollatingPeerState{
Expand Down Expand Up @@ -250,7 +251,7 @@ func TestProcessOverseerMessage(t *testing.T) {
}(),
peerData: map[peer.ID]PeerData{
peerID: {
view: View{},
view: parachaintypes.View{},
state: PeerStateInfo{
PeerState: Collating,
CollatingPeerState: CollatingPeerState{
Expand Down Expand Up @@ -460,3 +461,52 @@ func TestProcessBackedOverseerMessage(t *testing.T) {
})
}
}

func TestPeerViewChange(t *testing.T) {
t.Parallel()

// test that relay parent advertisement gets removed if it went out of implicit view

cpvs := CollatorProtocolValidatorSide{
activeLeaves: map[common.Hash]parachaintypes.ProspectiveParachainsMode{},
perRelayParent: map[common.Hash]PerRelayParent{
{0x01}: {
prospectiveParachainMode: parachaintypes.ProspectiveParachainsMode{
IsEnabled: false,
},
},
},
peerData: map[peer.ID]PeerData{
peer.ID("peer1"): {
// this shows our current view of peer1
view: parachaintypes.View{
Heads: []common.Hash{{0x01}},
},
state: PeerStateInfo{
PeerState: Collating,
CollatingPeerState: CollatingPeerState{
advertisements: map[common.Hash][]parachaintypes.CandidateHash{
{0x01}: {},
},
},
},
},
},
}

msg := networkbridgeevents.PeerViewChange{
PeerID: peer.ID("peer1"),
// this shows the new view of peer1, since the new view does not contain relay parent {0x01},
// we will remove the advertisement for that relay parent
View: parachaintypes.View{
Heads: []common.Hash{{0x02}},
},
}

err := cpvs.handleNetworkBridgeEvents(msg)
require.NoError(t, err)

// advertisement for relay parent {0x01} should be removed
_, ok := cpvs.peerData[peer.ID("peer1")].state.CollatingPeerState.advertisements[common.Hash{0x01}]
require.False(t, ok)
}
15 changes: 2 additions & 13 deletions dot/parachain/network-bridge/events/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,22 +46,11 @@ type NewGossipTopology struct {

type PeerViewChange struct {
PeerID peer.ID
View View
}

// View is a succinct representation of a peer's view. This consists of a bounded amount of chain heads
// and the highest known finalized block number.
//
// Up to `N` (5?) chain heads.
type View struct {
// a bounded amount of chain heads
Heads []common.Hash
// the highest known finalized number
FinalizedNumber uint32
View parachaintypes.View
}

type OurViewChange struct {
View View
View parachaintypes.View
}

type PeerMessage[Message collationprotocol.CollationProtocol | validationprotocol.ValidationProtocol] struct {
Expand Down
Loading

0 comments on commit 075322c

Please sign in to comment.