|
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())
|
|
}
|