Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(parachain/collation-protocol): handle peer view change #4307

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading