- package statesync
-
- import (
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/mock"
- "github.com/stretchr/testify/require"
-
- "github.com/tendermint/tendermint/p2p"
- p2pmocks "github.com/tendermint/tendermint/p2p/mocks"
- "github.com/tendermint/tendermint/statesync/mocks"
- )
-
- func TestSnapshot_Key(t *testing.T) {
- testcases := map[string]struct {
- modify func(*snapshot)
- }{
- "new height": {func(s *snapshot) { s.Height = 9 }},
- "new format": {func(s *snapshot) { s.Format = 9 }},
- "new chunk count": {func(s *snapshot) { s.Chunks = 9 }},
- "new hash": {func(s *snapshot) { s.Hash = []byte{9} }},
- "no metadata": {func(s *snapshot) { s.Metadata = nil }},
- }
- for name, tc := range testcases {
- tc := tc
- t.Run(name, func(t *testing.T) {
- s := snapshot{
- Height: 3,
- Format: 1,
- Chunks: 7,
- Hash: []byte{1, 2, 3},
- Metadata: []byte{255},
- }
- before := s.Key()
- tc.modify(&s)
- after := s.Key()
- assert.NotEqual(t, before, after)
- })
- }
- }
-
- func TestSnapshotPool_Add(t *testing.T) {
- stateProvider := &mocks.StateProvider{}
- stateProvider.On("AppHash", mock.Anything, uint64(1)).Return([]byte("app_hash"), nil)
-
- peer := &p2pmocks.Peer{}
- peer.On("ID").Return(p2p.ID("id"))
-
- // Adding to the pool should work
- pool := newSnapshotPool(stateProvider)
- added, err := pool.Add(peer, &snapshot{
- Height: 1,
- Format: 1,
- Chunks: 1,
- Hash: []byte{1},
- })
- require.NoError(t, err)
- assert.True(t, added)
-
- // Adding again from a different peer should return false
- otherPeer := &p2pmocks.Peer{}
- otherPeer.On("ID").Return(p2p.ID("other"))
- added, err = pool.Add(peer, &snapshot{
- Height: 1,
- Format: 1,
- Chunks: 1,
- Hash: []byte{1},
- })
- require.NoError(t, err)
- assert.False(t, added)
-
- // The pool should have populated the snapshot with the trusted app hash
- snapshot := pool.Best()
- require.NotNil(t, snapshot)
- assert.Equal(t, []byte("app_hash"), snapshot.trustedAppHash)
-
- stateProvider.AssertExpectations(t)
- }
-
- func TestSnapshotPool_GetPeer(t *testing.T) {
- stateProvider := &mocks.StateProvider{}
- stateProvider.On("AppHash", mock.Anything, mock.Anything).Return([]byte("app_hash"), nil)
- pool := newSnapshotPool(stateProvider)
-
- s := &snapshot{Height: 1, Format: 1, Chunks: 1, Hash: []byte{1}}
- peerA := &p2pmocks.Peer{}
- peerA.On("ID").Return(p2p.ID("a"))
- peerB := &p2pmocks.Peer{}
- peerB.On("ID").Return(p2p.ID("b"))
-
- _, err := pool.Add(peerA, s)
- require.NoError(t, err)
- _, err = pool.Add(peerB, s)
- require.NoError(t, err)
- _, err = pool.Add(peerA, &snapshot{Height: 2, Format: 1, Chunks: 1, Hash: []byte{1}})
- require.NoError(t, err)
-
- // GetPeer currently picks a random peer, so lets run it until we've seen both.
- seenA := false
- seenB := false
- for !seenA || !seenB {
- peer := pool.GetPeer(s)
- switch peer.ID() {
- case p2p.ID("a"):
- seenA = true
- case p2p.ID("b"):
- seenB = true
- }
- }
-
- // GetPeer should return nil for an unknown snapshot
- peer := pool.GetPeer(&snapshot{Height: 9, Format: 9})
- assert.Nil(t, peer)
- }
-
- func TestSnapshotPool_GetPeers(t *testing.T) {
- stateProvider := &mocks.StateProvider{}
- stateProvider.On("AppHash", mock.Anything, mock.Anything).Return([]byte("app_hash"), nil)
- pool := newSnapshotPool(stateProvider)
-
- s := &snapshot{Height: 1, Format: 1, Chunks: 1, Hash: []byte{1}}
- peerA := &p2pmocks.Peer{}
- peerA.On("ID").Return(p2p.ID("a"))
- peerB := &p2pmocks.Peer{}
- peerB.On("ID").Return(p2p.ID("b"))
-
- _, err := pool.Add(peerA, s)
- require.NoError(t, err)
- _, err = pool.Add(peerB, s)
- require.NoError(t, err)
- _, err = pool.Add(peerA, &snapshot{Height: 2, Format: 1, Chunks: 1, Hash: []byte{2}})
- require.NoError(t, err)
-
- peers := pool.GetPeers(s)
- assert.Len(t, peers, 2)
- assert.EqualValues(t, "a", peers[0].ID())
- assert.EqualValues(t, "b", peers[1].ID())
- }
-
- func TestSnapshotPool_Ranked_Best(t *testing.T) {
- stateProvider := &mocks.StateProvider{}
- stateProvider.On("AppHash", mock.Anything, mock.Anything).Return([]byte("app_hash"), nil)
- pool := newSnapshotPool(stateProvider)
-
- // snapshots in expected order (best to worst). Highest height wins, then highest format.
- // Snapshots with different chunk hashes are considered different, and the most peers is
- // tie-breaker.
- expectSnapshots := []struct {
- snapshot *snapshot
- peers []string
- }{
- {&snapshot{Height: 2, Format: 2, Chunks: 4, Hash: []byte{1, 3}}, []string{"a", "b", "c"}},
- {&snapshot{Height: 2, Format: 2, Chunks: 5, Hash: []byte{1, 2}}, []string{"a"}},
- {&snapshot{Height: 2, Format: 1, Chunks: 3, Hash: []byte{1, 2}}, []string{"a", "b"}},
- {&snapshot{Height: 1, Format: 2, Chunks: 5, Hash: []byte{1, 2}}, []string{"a", "b"}},
- {&snapshot{Height: 1, Format: 1, Chunks: 4, Hash: []byte{1, 2}}, []string{"a", "b", "c"}},
- }
-
- // Add snapshots in reverse order, to make sure the pool enforces some order.
- for i := len(expectSnapshots) - 1; i >= 0; i-- {
- for _, peerID := range expectSnapshots[i].peers {
- peer := &p2pmocks.Peer{}
- peer.On("ID").Return(p2p.ID(peerID))
- _, err := pool.Add(peer, expectSnapshots[i].snapshot)
- require.NoError(t, err)
- }
- }
-
- // Ranked should return the snapshots in the same order
- ranked := pool.Ranked()
- assert.Len(t, ranked, len(expectSnapshots))
- for i := range ranked {
- assert.Equal(t, expectSnapshots[i].snapshot, ranked[i])
- }
-
- // Check that best snapshots are returned in expected order
- for i := range expectSnapshots {
- snapshot := expectSnapshots[i].snapshot
- require.Equal(t, snapshot, pool.Best())
- pool.Reject(snapshot)
- }
- assert.Nil(t, pool.Best())
- }
-
- func TestSnapshotPool_Reject(t *testing.T) {
- stateProvider := &mocks.StateProvider{}
- stateProvider.On("AppHash", mock.Anything, mock.Anything).Return([]byte("app_hash"), nil)
- pool := newSnapshotPool(stateProvider)
- peer := &p2pmocks.Peer{}
- peer.On("ID").Return(p2p.ID("id"))
-
- snapshots := []*snapshot{
- {Height: 2, Format: 2, Chunks: 1, Hash: []byte{1, 2}},
- {Height: 2, Format: 1, Chunks: 1, Hash: []byte{1, 2}},
- {Height: 1, Format: 2, Chunks: 1, Hash: []byte{1, 2}},
- {Height: 1, Format: 1, Chunks: 1, Hash: []byte{1, 2}},
- }
- for _, s := range snapshots {
- _, err := pool.Add(peer, s)
- require.NoError(t, err)
- }
-
- pool.Reject(snapshots[0])
- assert.Equal(t, snapshots[1:], pool.Ranked())
-
- added, err := pool.Add(peer, snapshots[0])
- require.NoError(t, err)
- assert.False(t, added)
-
- added, err = pool.Add(peer, &snapshot{Height: 3, Format: 3, Chunks: 1, Hash: []byte{1}})
- require.NoError(t, err)
- assert.True(t, added)
- }
-
- func TestSnapshotPool_RejectFormat(t *testing.T) {
- stateProvider := &mocks.StateProvider{}
- stateProvider.On("AppHash", mock.Anything, mock.Anything).Return([]byte("app_hash"), nil)
- pool := newSnapshotPool(stateProvider)
- peer := &p2pmocks.Peer{}
- peer.On("ID").Return(p2p.ID("id"))
-
- snapshots := []*snapshot{
- {Height: 2, Format: 2, Chunks: 1, Hash: []byte{1, 2}},
- {Height: 2, Format: 1, Chunks: 1, Hash: []byte{1, 2}},
- {Height: 1, Format: 2, Chunks: 1, Hash: []byte{1, 2}},
- {Height: 1, Format: 1, Chunks: 1, Hash: []byte{1, 2}},
- }
- for _, s := range snapshots {
- _, err := pool.Add(peer, s)
- require.NoError(t, err)
- }
-
- pool.RejectFormat(1)
- assert.Equal(t, []*snapshot{snapshots[0], snapshots[2]}, pool.Ranked())
-
- added, err := pool.Add(peer, &snapshot{Height: 3, Format: 1, Chunks: 1, Hash: []byte{1}})
- require.NoError(t, err)
- assert.False(t, added)
- assert.Equal(t, []*snapshot{snapshots[0], snapshots[2]}, pool.Ranked())
-
- added, err = pool.Add(peer, &snapshot{Height: 3, Format: 3, Chunks: 1, Hash: []byte{1}})
- require.NoError(t, err)
- assert.True(t, added)
- }
-
- func TestSnapshotPool_RejectPeer(t *testing.T) {
- stateProvider := &mocks.StateProvider{}
- stateProvider.On("AppHash", mock.Anything, mock.Anything).Return([]byte("app_hash"), nil)
- pool := newSnapshotPool(stateProvider)
-
- peerA := &p2pmocks.Peer{}
- peerA.On("ID").Return(p2p.ID("a"))
- peerB := &p2pmocks.Peer{}
- peerB.On("ID").Return(p2p.ID("b"))
-
- s1 := &snapshot{Height: 1, Format: 1, Chunks: 1, Hash: []byte{1}}
- s2 := &snapshot{Height: 2, Format: 1, Chunks: 1, Hash: []byte{2}}
- s3 := &snapshot{Height: 3, Format: 1, Chunks: 1, Hash: []byte{2}}
-
- _, err := pool.Add(peerA, s1)
- require.NoError(t, err)
- _, err = pool.Add(peerA, s2)
- require.NoError(t, err)
-
- _, err = pool.Add(peerB, s2)
- require.NoError(t, err)
- _, err = pool.Add(peerB, s3)
- require.NoError(t, err)
-
- pool.RejectPeer(peerA.ID())
-
- assert.Empty(t, pool.GetPeers(s1))
-
- peers2 := pool.GetPeers(s2)
- assert.Len(t, peers2, 1)
- assert.EqualValues(t, "b", peers2[0].ID())
-
- peers3 := pool.GetPeers(s2)
- assert.Len(t, peers3, 1)
- assert.EqualValues(t, "b", peers3[0].ID())
-
- // it should no longer be possible to add the peer back
- _, err = pool.Add(peerA, s1)
- require.NoError(t, err)
- assert.Empty(t, pool.GetPeers(s1))
- }
-
- func TestSnapshotPool_RemovePeer(t *testing.T) {
- stateProvider := &mocks.StateProvider{}
- stateProvider.On("AppHash", mock.Anything, mock.Anything).Return([]byte("app_hash"), nil)
- pool := newSnapshotPool(stateProvider)
-
- peerA := &p2pmocks.Peer{}
- peerA.On("ID").Return(p2p.ID("a"))
- peerB := &p2pmocks.Peer{}
- peerB.On("ID").Return(p2p.ID("b"))
-
- s1 := &snapshot{Height: 1, Format: 1, Chunks: 1, Hash: []byte{1}}
- s2 := &snapshot{Height: 2, Format: 1, Chunks: 1, Hash: []byte{2}}
-
- _, err := pool.Add(peerA, s1)
- require.NoError(t, err)
- _, err = pool.Add(peerA, s2)
- require.NoError(t, err)
- _, err = pool.Add(peerB, s1)
- require.NoError(t, err)
-
- pool.RemovePeer(peerA.ID())
-
- peers1 := pool.GetPeers(s1)
- assert.Len(t, peers1, 1)
- assert.EqualValues(t, "b", peers1[0].ID())
-
- peers2 := pool.GetPeers(s2)
- assert.Empty(t, peers2)
-
- // it should still be possible to add the peer back
- _, err = pool.Add(peerA, s1)
- require.NoError(t, err)
- peers1 = pool.GetPeers(s1)
- assert.Len(t, peers1, 2)
- assert.EqualValues(t, "a", peers1[0].ID())
- assert.EqualValues(t, "b", peers1[1].ID())
- }
|