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

fix(dot/sync): Gossip BlockAnnounceMessage only after successfully imported #2885

Merged
merged 18 commits into from
Oct 21, 2022
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
3c2a92f
fix: gossip block announce only after block sucessfully imported
EclesioMeloJunior Oct 10, 2022
069109b
chore: imported block could be a best block
EclesioMeloJunior Oct 10, 2022
b3c1cea
chore: fix lint warns
EclesioMeloJunior Oct 11, 2022
f9c0d52
chore: simplify bool var, remove unneeded comment
EclesioMeloJunior Oct 11, 2022
abf6330
Merge branch 'development' into eclesio/fix/import-block-announce
EclesioMeloJunior Oct 11, 2022
67b3710
Merge branch 'eclesio/fix/import-block-announce' of github.com:ChainS…
EclesioMeloJunior Oct 11, 2022
35b18ff
wip: maybe propagate message when we have the block
EclesioMeloJunior Oct 11, 2022
efbf67f
Merge branch 'development' into eclesio/fix/import-block-announce
EclesioMeloJunior Oct 12, 2022
48fcdf6
chore: change test to check `propagate` var is `false`
EclesioMeloJunior Oct 13, 2022
80c8616
chore: increase code coverage + fix tests
EclesioMeloJunior Oct 13, 2022
642b847
chore: fix lint warns
EclesioMeloJunior Oct 13, 2022
a539478
chore: solving error wrapping
EclesioMeloJunior Oct 13, 2022
6eea08b
chore: remove useless comment
EclesioMeloJunior Oct 14, 2022
7cccaf9
chore: address comment
EclesioMeloJunior Oct 18, 2022
2615216
Merge branch 'development' into eclesio/fix/import-block-announce
EclesioMeloJunior Oct 20, 2022
1cde08c
chore: fix inverted `errors.Is` check
EclesioMeloJunior Oct 20, 2022
5b004be
chore
EclesioMeloJunior Oct 21, 2022
b04ed3b
Merge branch 'development' into eclesio/fix/import-block-announce
EclesioMeloJunior Oct 21, 2022
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
46 changes: 35 additions & 11 deletions dot/core/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,40 +127,64 @@ func (s *Service) StorageRoot() (common.Hash, error) {

// HandleBlockImport handles a block that was imported via the network
func (s *Service) HandleBlockImport(block *types.Block, state *rtstorage.TrieState) error {
return s.handleBlock(block, state)
err := s.handleBlock(block, state)
if err != nil {
return fmt.Errorf("handling block: %s", err)
EclesioMeloJunior marked this conversation as resolved.
Show resolved Hide resolved
}

bestBlockHash := s.blockState.BestBlockHash()
isBestBlock := bestBlockHash.Equal(block.Header.Hash())

blockAnnounce, err := createBlockAnnounce(block, isBestBlock)
if err != nil {
return fmt.Errorf("creating block announce: %w", err)
}

s.net.GossipMessage(blockAnnounce)
EclesioMeloJunior marked this conversation as resolved.
Show resolved Hide resolved
return nil
}

// HandleBlockProduced handles a block that was produced by us
// It is handled the same as an imported block in terms of state updates; the only difference
// is we send a BlockAnnounceMessage to our peers.
func (s *Service) HandleBlockProduced(block *types.Block, state *rtstorage.TrieState) error {
if err := s.handleBlock(block, state); err != nil {
return err
err := s.handleBlock(block, state)
if err != nil {
return fmt.Errorf("handling block: %w", err)
}

const isBestBlock = true
blockAnnounce, err := createBlockAnnounce(block, isBestBlock)
EclesioMeloJunior marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return fmt.Errorf("creating block announce: %w", err)
}

s.net.GossipMessage(blockAnnounce)
EclesioMeloJunior marked this conversation as resolved.
Show resolved Hide resolved
return nil
}

func createBlockAnnounce(block *types.Block, isBestBlock bool) (
blockAnnounce *network.BlockAnnounceMessage, err error) {
digest := types.NewDigest()
for i := range block.Header.Digest.Types {
digestValue, err := block.Header.Digest.Types[i].Value()
if err != nil {
return fmt.Errorf("getting value of digest type at index %d: %w", i, err)
return nil, fmt.Errorf("getting value of digest type at index %d: %w", i, err)
}
err = digest.Add(digestValue)
if err != nil {
return err
return nil, err
EclesioMeloJunior marked this conversation as resolved.
Show resolved Hide resolved
}
}

msg := &network.BlockAnnounceMessage{
return &network.BlockAnnounceMessage{
ParentHash: block.Header.ParentHash,
Number: block.Header.Number,
StateRoot: block.Header.StateRoot,
ExtrinsicsRoot: block.Header.ExtrinsicsRoot,
Digest: digest,
BestBlock: true,
}

s.net.GossipMessage(msg)
return nil
BestBlock: isBestBlock,
}, nil
}

func (s *Service) handleBlock(block *types.Block, state *rtstorage.TrieState) error {
Expand Down
5 changes: 3 additions & 2 deletions dot/core/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -480,9 +480,10 @@ func Test_Service_HandleBlockProduced(t *testing.T) {
t.Parallel()
execTest := func(t *testing.T, s *Service, block *types.Block, trieState *rtstorage.TrieState, expErr error) {
err := s.HandleBlockProduced(block, trieState)
assert.ErrorIs(t, err, expErr)
require.ErrorIs(t, err, expErr)
EclesioMeloJunior marked this conversation as resolved.
Show resolved Hide resolved
if expErr != nil {
assert.EqualError(t, err, expErr.Error())
wrapped := fmt.Errorf("handling block: %w", expErr)
assert.EqualError(t, err, wrapped.Error())
EclesioMeloJunior marked this conversation as resolved.
Show resolved Hide resolved
}
}
t.Run("nil input", func(t *testing.T) {
Expand Down
9 changes: 6 additions & 3 deletions dot/network/block_announce.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/ChainSafe/gossamer/dot/peerset"
"github.com/ChainSafe/gossamer/dot/types"
"github.com/ChainSafe/gossamer/lib/blocktree"
"github.com/ChainSafe/gossamer/lib/common"
"github.com/ChainSafe/gossamer/pkg/scale"

Expand Down Expand Up @@ -208,9 +209,11 @@ func (s *Service) handleBlockAnnounceMessage(from peer.ID, msg NotificationsMess
return false, errors.New("invalid message")
}

if err = s.syncer.HandleBlockAnnounce(from, bam); err != nil {
return false, err
// TODO announce if we already have the block
EclesioMeloJunior marked this conversation as resolved.
Show resolved Hide resolved
err = s.syncer.HandleBlockAnnounce(from, bam)
if errors.Is(err, blocktree.ErrBlockExists) {
return true, nil
}

return true, nil
return false, err
}
64 changes: 50 additions & 14 deletions dot/network/block_announce_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import (
"testing"

"github.com/ChainSafe/gossamer/dot/types"
"github.com/ChainSafe/gossamer/lib/blocktree"
"github.com/ChainSafe/gossamer/lib/common"
"github.com/ChainSafe/gossamer/pkg/scale"
gomock "github.com/golang/mock/gomock"

"github.com/libp2p/go-libp2p-core/peer"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -126,24 +128,58 @@ func TestDecodeBlockAnnounceHandshake(t *testing.T) {
func TestHandleBlockAnnounceMessage(t *testing.T) {
t.Parallel()

config := &Config{
BasePath: t.TempDir(),
Port: availablePort(t),
NoBootstrap: true,
NoMDNS: true,
testCases := map[string]struct {
propagate bool
mockSyncer func(*testing.T, peer.ID, *BlockAnnounceMessage) Syncer
}{
"block already exists": {
mockSyncer: func(t *testing.T, peer peer.ID, blockAnnounceMessage *BlockAnnounceMessage) Syncer {
ctrl := gomock.NewController(t)
syncer := NewMockSyncer(ctrl)
syncer.EXPECT().
HandleBlockAnnounce(peer, blockAnnounceMessage).
Return(blocktree.ErrBlockExists).
Times(1)
EclesioMeloJunior marked this conversation as resolved.
Show resolved Hide resolved

return syncer
},
propagate: true,
},
"block does not exists": {
propagate: false,
},
}

s := createTestService(t, config)
for tname, tt := range testCases {
tt := tt

peerID := peer.ID("noot")
msg := &BlockAnnounceMessage{
Number: 10,
Digest: types.NewDigest(),
}
t.Run(tname, func(t *testing.T) {
t.Parallel()

propagate, err := s.handleBlockAnnounceMessage(peerID, msg)
require.NoError(t, err)
require.True(t, propagate)
config := &Config{
BasePath: t.TempDir(),
Port: availablePort(t),
NoBootstrap: true,
NoMDNS: true,
}

peerID := peer.ID("noot")
msg := &BlockAnnounceMessage{
Number: 10,
Digest: types.NewDigest(),
}

if tt.mockSyncer != nil {
config.Syncer = tt.mockSyncer(t, peerID, msg)
}

s := createTestService(t, config)
EclesioMeloJunior marked this conversation as resolved.
Show resolved Hide resolved
gotPropagate, err := s.handleBlockAnnounceMessage(peerID, msg)

require.NoError(t, err)
require.Equal(t, tt.propagate, gotPropagate)
})
}
}

func TestValidateBlockAnnounceHandshake(t *testing.T) {
Expand Down
13 changes: 7 additions & 6 deletions dot/sync/chain_sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/ChainSafe/gossamer/dot/network"
"github.com/ChainSafe/gossamer/dot/peerset"
"github.com/ChainSafe/gossamer/dot/types"
"github.com/ChainSafe/gossamer/lib/blocktree"
"github.com/ChainSafe/gossamer/lib/common"
"github.com/ChainSafe/gossamer/lib/common/variadic"
)
Expand Down Expand Up @@ -251,7 +252,7 @@ func (cs *chainSync) setBlockAnnounce(from peer.ID, header *types.Header) error
}

if has {
return nil
return blocktree.ErrBlockExists
}

if err = cs.pendingBlocks.addHeader(header); err != nil {
Expand Down Expand Up @@ -627,19 +628,19 @@ func (cs *chainSync) tryDispatchWorker(w *worker) {
// if it fails due to any reason, it sets the worker `err` and returns
// this function always places the worker into the `resultCh` for result handling upon return
func (cs *chainSync) dispatchWorker(w *worker) {
if w.targetNumber == nil || w.startNumber == nil {
return
}

logger.Debugf("dispatching sync worker id %d, "+
"start number %d, target number %d, "+
"start hash %s, target hash %s, "+
"request data %d, direction %s",
w.id,
w.startNumber, w.targetNumber,
*w.startNumber, *w.targetNumber,
EclesioMeloJunior marked this conversation as resolved.
Show resolved Hide resolved
w.startHash, w.targetHash,
w.requestData, w.direction)

if w.targetNumber == nil || w.startNumber == nil {
return
}

start := time.Now()
defer func() {
end := time.Now()
Expand Down
79 changes: 77 additions & 2 deletions dot/sync/chain_sync_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/ChainSafe/gossamer/dot/network"
"github.com/ChainSafe/gossamer/dot/peerset"
"github.com/ChainSafe/gossamer/dot/types"
"github.com/ChainSafe/gossamer/lib/blocktree"
"github.com/ChainSafe/gossamer/lib/common"
"github.com/ChainSafe/gossamer/lib/common/variadic"
"github.com/ChainSafe/gossamer/lib/trie"
Expand Down Expand Up @@ -1246,7 +1247,11 @@ func Test_chainSync_start(t *testing.T) {
}
}

var blockAnnounceHeader = &types.Header{Number: 2}

EclesioMeloJunior marked this conversation as resolved.
Show resolved Hide resolved
func Test_chainSync_setBlockAnnounce(t *testing.T) {
t.Parallel()

type args struct {
from peer.ID
header *types.Header
Expand All @@ -1257,8 +1262,9 @@ func Test_chainSync_setBlockAnnounce(t *testing.T) {
wantErr error
}{
"base case": {
wantErr: blocktree.ErrBlockExists,
args: args{
header: &types.Header{Number: 2},
header: blockAnnounceHeader,
},
chainSyncBuilder: func(ctrl *gomock.Controller) chainSync {
mockBlockState := NewMockBlockState(ctrl)
Expand All @@ -1271,13 +1277,82 @@ func Test_chainSync_setBlockAnnounce(t *testing.T) {
}
},
},
"err_when_calling_has_header": {
wantErr: errors.New("checking header exists"),
args: args{
header: blockAnnounceHeader,
},
chainSyncBuilder: func(ctrl *gomock.Controller) chainSync {
mockBlockState := NewMockBlockState(ctrl)
mockBlockState.EXPECT().
HasHeader(common.MustHexToHash(
"0x05bdcc454f60a08d427d05e7f19f240fdc391f570ab76fcb96ecca0b5823d3bf")).
Return(false, errors.New("checking header exists"))
mockDisjointBlockSet := NewMockDisjointBlockSet(ctrl)
return chainSync{
blockState: mockBlockState,
pendingBlocks: mockDisjointBlockSet,
}
},
},
"adding_block_header_to_pending_blocks": {
args: args{
header: blockAnnounceHeader,
},
chainSyncBuilder: func(ctrl *gomock.Controller) chainSync {
argumentHeaderHash := common.MustHexToHash(
"0x05bdcc454f60a08d427d05e7f19f240fdc391f570ab76fcb96ecca0b5823d3bf")

mockBlockState := NewMockBlockState(ctrl)
mockBlockState.EXPECT().
HasHeader(argumentHeaderHash).
Return(false, nil)

mockBlockState.EXPECT().
BestBlockHeader().
Return(&types.Header{Number: 1}, nil)

mockDisjointBlockSet := NewMockDisjointBlockSet(ctrl)
mockDisjointBlockSet.EXPECT().
addHeader(blockAnnounceHeader).
Return(nil)

mockDisjointBlockSet.EXPECT().
addHashAndNumber(argumentHeaderHash, uint(2)).
Return(nil)

return chainSync{
blockState: mockBlockState,
pendingBlocks: mockDisjointBlockSet,
peerState: make(map[peer.ID]*peerState),
// creating an buffered channel for this specific test
// since it will put a work on the queue and an unbufered channel
// will hang until we read on this channel and the goal is to
// put the work on the channel and don't block
workQueue: make(chan *peerState, 1),
}
},
},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
t.Parallel()
EclesioMeloJunior marked this conversation as resolved.
Show resolved Hide resolved
ctrl := gomock.NewController(t)
sync := tt.chainSyncBuilder(ctrl)
err := sync.setBlockAnnounce(tt.args.from, tt.args.header)
assert.ErrorIs(t, err, tt.wantErr)
if tt.wantErr != nil {
assert.EqualError(t, err, tt.wantErr.Error())
} else {
assert.NoError(t, err)
}

if sync.workQueue != nil {
assert.LessOrEqual(t, len(sync.workQueue), 1)
if len(sync.workQueue) > 0 {
EclesioMeloJunior marked this conversation as resolved.
Show resolved Hide resolved
<-sync.workQueue
close(sync.workQueue)
EclesioMeloJunior marked this conversation as resolved.
Show resolved Hide resolved
}
}
})
}
}
Expand Down