mirror of
https://github.com/tendermint/tendermint.git
synced 2026-01-08 22:23:11 +00:00
closes #4413 and #4419 When VerifyHeaderAtHeight() is called, TrustedHeader is initially run to check if the header has already been verified and returns the Header. If the new header height is less than the lite clients latestTrustedHeader height, than backwards verification is performed else either sequence or bisection Refactored a test to reflect the changes * use trustedHeader func for already verified Headers * remove fetch missing header from TrustedHeader * check for already trusted Header in VerifyHeaderAtHeight * replace updateTrustedHeaderAndVals to updateTrustedHeaderAndNextVals * rename trustedHeader and trustedNextVals * refactored backwards and included it in VerifyHeader * cleaned up test to match changes * lite2: fixes after my own review Refs https://github.com/tendermint/tendermint/pull/4428#pullrequestreview-361730169 * fix ineffectual assignment * lite2: check that header exists in VerifyHeader * extract function Co-authored-by: Anton Kaliaev <anton.kalyaev@gmail.com>
846 lines
20 KiB
Go
846 lines
20 KiB
Go
package lite
|
|
|
|
import (
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
dbm "github.com/tendermint/tm-db"
|
|
|
|
"github.com/tendermint/tendermint/libs/log"
|
|
"github.com/tendermint/tendermint/lite2/provider"
|
|
mockp "github.com/tendermint/tendermint/lite2/provider/mock"
|
|
dbs "github.com/tendermint/tendermint/lite2/store/db"
|
|
"github.com/tendermint/tendermint/types"
|
|
)
|
|
|
|
const (
|
|
chainID = "test"
|
|
)
|
|
|
|
var (
|
|
keys = genPrivKeys(4)
|
|
vals = keys.ToValidators(20, 10)
|
|
bTime, _ = time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
|
|
h1 = keys.GenSignedHeader(chainID, 1, bTime, nil, vals, vals,
|
|
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys))
|
|
// 3/3 signed
|
|
h2 = keys.GenSignedHeaderLastBlockID(chainID, 2, bTime.Add(30*time.Minute), nil, vals, vals,
|
|
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys), types.BlockID{Hash: h1.Hash()})
|
|
// 3/3 signed
|
|
h3 = keys.GenSignedHeaderLastBlockID(chainID, 3, bTime.Add(1*time.Hour), nil, vals, vals,
|
|
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys), types.BlockID{Hash: h2.Hash()})
|
|
trustPeriod = 4 * time.Hour
|
|
trustOptions = TrustOptions{
|
|
Period: 4 * time.Hour,
|
|
Height: 1,
|
|
Hash: h1.Hash(),
|
|
}
|
|
fullNode = mockp.New(
|
|
chainID,
|
|
map[int64]*types.SignedHeader{
|
|
1: h1,
|
|
2: h2,
|
|
3: h3,
|
|
},
|
|
map[int64]*types.ValidatorSet{
|
|
1: vals,
|
|
2: vals,
|
|
3: vals,
|
|
4: vals,
|
|
},
|
|
)
|
|
deadNode = mockp.NewDeadMock(chainID)
|
|
)
|
|
|
|
func TestClient_SequentialVerification(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
otherHeaders map[int64]*types.SignedHeader // all except ^
|
|
vals map[int64]*types.ValidatorSet
|
|
initErr bool
|
|
verifyErr bool
|
|
}{
|
|
{
|
|
"good",
|
|
map[int64]*types.SignedHeader{
|
|
// trusted header
|
|
1: h1,
|
|
// interim header (3/3 signed)
|
|
2: h2,
|
|
// last header (3/3 signed)
|
|
3: h3,
|
|
},
|
|
map[int64]*types.ValidatorSet{
|
|
1: vals,
|
|
2: vals,
|
|
3: vals,
|
|
4: vals,
|
|
},
|
|
false,
|
|
false,
|
|
},
|
|
{
|
|
"bad: different first header",
|
|
map[int64]*types.SignedHeader{
|
|
// different header
|
|
1: keys.GenSignedHeader(chainID, 1, bTime.Add(1*time.Hour), nil, vals, vals,
|
|
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys)),
|
|
},
|
|
map[int64]*types.ValidatorSet{
|
|
1: vals,
|
|
},
|
|
true,
|
|
false,
|
|
},
|
|
{
|
|
"bad: 1/3 signed interim header",
|
|
map[int64]*types.SignedHeader{
|
|
// trusted header
|
|
1: h1,
|
|
// interim header (1/3 signed)
|
|
2: keys.GenSignedHeader(chainID, 2, bTime.Add(1*time.Hour), nil, vals, vals,
|
|
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), len(keys)-1, len(keys)),
|
|
// last header (3/3 signed)
|
|
3: keys.GenSignedHeader(chainID, 3, bTime.Add(2*time.Hour), nil, vals, vals,
|
|
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys)),
|
|
},
|
|
map[int64]*types.ValidatorSet{
|
|
1: vals,
|
|
2: vals,
|
|
3: vals,
|
|
4: vals,
|
|
},
|
|
false,
|
|
true,
|
|
},
|
|
{
|
|
"bad: 1/3 signed last header",
|
|
map[int64]*types.SignedHeader{
|
|
// trusted header
|
|
1: h1,
|
|
// interim header (3/3 signed)
|
|
2: keys.GenSignedHeader(chainID, 2, bTime.Add(1*time.Hour), nil, vals, vals,
|
|
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys)),
|
|
// last header (1/3 signed)
|
|
3: keys.GenSignedHeader(chainID, 3, bTime.Add(2*time.Hour), nil, vals, vals,
|
|
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), len(keys)-1, len(keys)),
|
|
},
|
|
map[int64]*types.ValidatorSet{
|
|
1: vals,
|
|
2: vals,
|
|
3: vals,
|
|
4: vals,
|
|
},
|
|
false,
|
|
true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
c, err := NewClient(
|
|
chainID,
|
|
trustOptions,
|
|
mockp.New(
|
|
chainID,
|
|
tc.otherHeaders,
|
|
tc.vals,
|
|
),
|
|
[]provider.Provider{mockp.New(
|
|
chainID,
|
|
tc.otherHeaders,
|
|
tc.vals,
|
|
)},
|
|
dbs.New(dbm.NewMemDB(), chainID),
|
|
SequentialVerification(),
|
|
)
|
|
|
|
if tc.initErr {
|
|
require.Error(t, err)
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
err = c.Start()
|
|
require.NoError(t, err)
|
|
defer c.Stop()
|
|
|
|
_, err = c.VerifyHeaderAtHeight(3, bTime.Add(3*time.Hour))
|
|
if tc.verifyErr {
|
|
assert.Error(t, err)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClient_SkippingVerification(t *testing.T) {
|
|
// required for 2nd test case
|
|
newKeys := genPrivKeys(4)
|
|
newVals := newKeys.ToValidators(10, 1)
|
|
|
|
// 1/3+ of vals, 2/3- of newVals
|
|
transitKeys := keys.Extend(3)
|
|
transitVals := transitKeys.ToValidators(10, 1)
|
|
|
|
testCases := []struct {
|
|
name string
|
|
otherHeaders map[int64]*types.SignedHeader // all except ^
|
|
vals map[int64]*types.ValidatorSet
|
|
initErr bool
|
|
verifyErr bool
|
|
}{
|
|
{
|
|
"good",
|
|
map[int64]*types.SignedHeader{
|
|
// trusted header
|
|
1: h1,
|
|
// last header (3/3 signed)
|
|
3: h3,
|
|
},
|
|
map[int64]*types.ValidatorSet{
|
|
1: vals,
|
|
2: vals,
|
|
3: vals,
|
|
4: vals,
|
|
},
|
|
false,
|
|
false,
|
|
},
|
|
{
|
|
"good, but val set changes by 2/3 (1/3 of vals is still present)",
|
|
map[int64]*types.SignedHeader{
|
|
// trusted header
|
|
1: h1,
|
|
3: transitKeys.GenSignedHeader(chainID, 3, bTime.Add(2*time.Hour), nil, transitVals, transitVals,
|
|
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(transitKeys)),
|
|
},
|
|
map[int64]*types.ValidatorSet{
|
|
1: vals,
|
|
2: vals,
|
|
3: transitVals,
|
|
4: transitVals,
|
|
},
|
|
false,
|
|
false,
|
|
},
|
|
{
|
|
"good, but val set changes 100% at height 2",
|
|
map[int64]*types.SignedHeader{
|
|
// trusted header
|
|
1: h1,
|
|
// interim header (3/3 signed)
|
|
2: keys.GenSignedHeader(chainID, 2, bTime.Add(1*time.Hour), nil, vals, newVals,
|
|
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys)),
|
|
// last header (0/4 of the original val set signed)
|
|
3: newKeys.GenSignedHeader(chainID, 3, bTime.Add(2*time.Hour), nil, newVals, newVals,
|
|
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(newKeys)),
|
|
},
|
|
map[int64]*types.ValidatorSet{
|
|
1: vals,
|
|
2: vals,
|
|
3: newVals,
|
|
4: newVals,
|
|
},
|
|
false,
|
|
false,
|
|
},
|
|
{
|
|
"bad: last header signed by newVals, interim header has no signers",
|
|
map[int64]*types.SignedHeader{
|
|
// trusted header
|
|
1: h1,
|
|
// last header (0/4 of the original val set signed)
|
|
2: keys.GenSignedHeader(chainID, 2, bTime.Add(1*time.Hour), nil, vals, newVals,
|
|
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, 0),
|
|
// last header (0/4 of the original val set signed)
|
|
3: newKeys.GenSignedHeader(chainID, 3, bTime.Add(2*time.Hour), nil, newVals, newVals,
|
|
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(newKeys)),
|
|
},
|
|
map[int64]*types.ValidatorSet{
|
|
1: vals,
|
|
2: vals,
|
|
3: newVals,
|
|
4: newVals,
|
|
},
|
|
false,
|
|
true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
c, err := NewClient(
|
|
chainID,
|
|
trustOptions,
|
|
mockp.New(
|
|
chainID,
|
|
tc.otherHeaders,
|
|
tc.vals,
|
|
),
|
|
[]provider.Provider{mockp.New(
|
|
chainID,
|
|
tc.otherHeaders,
|
|
tc.vals,
|
|
)},
|
|
dbs.New(dbm.NewMemDB(), chainID),
|
|
SkippingVerification(DefaultTrustLevel),
|
|
)
|
|
if tc.initErr {
|
|
require.Error(t, err)
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
err = c.Start()
|
|
require.NoError(t, err)
|
|
defer c.Stop()
|
|
|
|
_, err = c.VerifyHeaderAtHeight(3, bTime.Add(3*time.Hour))
|
|
if tc.verifyErr {
|
|
assert.Error(t, err)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClientRemovesNoLongerTrustedHeaders(t *testing.T) {
|
|
c, err := NewClient(
|
|
chainID,
|
|
trustOptions,
|
|
fullNode,
|
|
[]provider.Provider{fullNode},
|
|
dbs.New(dbm.NewMemDB(), chainID),
|
|
Logger(log.TestingLogger()),
|
|
)
|
|
|
|
assert.NotPanics(t, func() {
|
|
now := bTime.Add(4 * time.Hour).Add(1 * time.Second)
|
|
c.RemoveNoLongerTrustedHeaders(now)
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
err = c.Start()
|
|
require.NoError(t, err)
|
|
defer c.Stop()
|
|
|
|
// Verify new headers.
|
|
_, err = c.VerifyHeaderAtHeight(2, bTime.Add(2*time.Hour).Add(1*time.Second))
|
|
require.NoError(t, err)
|
|
now := bTime.Add(4 * time.Hour).Add(1 * time.Second)
|
|
_, err = c.VerifyHeaderAtHeight(3, now)
|
|
require.NoError(t, err)
|
|
|
|
// Remove expired headers.
|
|
c.RemoveNoLongerTrustedHeaders(now)
|
|
|
|
// Check expired headers are no longer available.
|
|
h, err := c.TrustedHeader(1, now)
|
|
assert.Error(t, err)
|
|
assert.Nil(t, h)
|
|
|
|
// Check not expired headers are available.
|
|
h, err = c.TrustedHeader(2, now)
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, h)
|
|
}
|
|
|
|
func TestClient_Cleanup(t *testing.T) {
|
|
c, err := NewClient(
|
|
chainID,
|
|
trustOptions,
|
|
fullNode,
|
|
[]provider.Provider{fullNode},
|
|
dbs.New(dbm.NewMemDB(), chainID),
|
|
Logger(log.TestingLogger()),
|
|
)
|
|
require.NoError(t, err)
|
|
err = c.Start()
|
|
require.NoError(t, err)
|
|
|
|
c.Stop()
|
|
c.Cleanup()
|
|
|
|
// Check no headers exist after Cleanup.
|
|
h, err := c.TrustedHeader(1, bTime.Add(1*time.Second))
|
|
assert.Error(t, err)
|
|
assert.Nil(t, h)
|
|
}
|
|
|
|
// trustedHeader.Height == options.Height
|
|
func TestClientRestoresTrustedHeaderAfterStartup1(t *testing.T) {
|
|
// 1. options.Hash == trustedHeader.Hash
|
|
{
|
|
trustedStore := dbs.New(dbm.NewMemDB(), chainID)
|
|
err := trustedStore.SaveSignedHeaderAndNextValidatorSet(h1, vals)
|
|
require.NoError(t, err)
|
|
|
|
c, err := NewClient(
|
|
chainID,
|
|
trustOptions,
|
|
fullNode,
|
|
[]provider.Provider{fullNode},
|
|
trustedStore,
|
|
Logger(log.TestingLogger()),
|
|
)
|
|
require.NoError(t, err)
|
|
err = c.Start()
|
|
require.NoError(t, err)
|
|
defer c.Stop()
|
|
|
|
h, err := c.TrustedHeader(1, bTime.Add(1*time.Second))
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, h)
|
|
assert.Equal(t, h.Hash(), h1.Hash())
|
|
}
|
|
|
|
// 2. options.Hash != trustedHeader.Hash
|
|
{
|
|
trustedStore := dbs.New(dbm.NewMemDB(), chainID)
|
|
err := trustedStore.SaveSignedHeaderAndNextValidatorSet(h1, vals)
|
|
require.NoError(t, err)
|
|
|
|
// header1 != header
|
|
header1 := keys.GenSignedHeader(chainID, 1, bTime.Add(1*time.Hour), nil, vals, vals,
|
|
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys))
|
|
|
|
primary := mockp.New(
|
|
chainID,
|
|
map[int64]*types.SignedHeader{
|
|
// trusted header
|
|
1: header1,
|
|
},
|
|
map[int64]*types.ValidatorSet{
|
|
1: vals,
|
|
2: vals,
|
|
},
|
|
)
|
|
|
|
c, err := NewClient(
|
|
chainID,
|
|
TrustOptions{
|
|
Period: 4 * time.Hour,
|
|
Height: 1,
|
|
Hash: header1.Hash(),
|
|
},
|
|
primary,
|
|
[]provider.Provider{primary},
|
|
trustedStore,
|
|
Logger(log.TestingLogger()),
|
|
)
|
|
require.NoError(t, err)
|
|
err = c.Start()
|
|
require.NoError(t, err)
|
|
defer c.Stop()
|
|
|
|
h, err := c.TrustedHeader(1, bTime.Add(1*time.Second))
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, h)
|
|
assert.Equal(t, h.Hash(), header1.Hash())
|
|
}
|
|
}
|
|
|
|
// trustedHeader.Height < options.Height
|
|
func TestClientRestoresTrustedHeaderAfterStartup2(t *testing.T) {
|
|
// 1. options.Hash == trustedHeader.Hash
|
|
{
|
|
trustedStore := dbs.New(dbm.NewMemDB(), chainID)
|
|
err := trustedStore.SaveSignedHeaderAndNextValidatorSet(h1, vals)
|
|
require.NoError(t, err)
|
|
|
|
c, err := NewClient(
|
|
chainID,
|
|
TrustOptions{
|
|
Period: 4 * time.Hour,
|
|
Height: 2,
|
|
Hash: h2.Hash(),
|
|
},
|
|
fullNode,
|
|
[]provider.Provider{fullNode},
|
|
trustedStore,
|
|
Logger(log.TestingLogger()),
|
|
)
|
|
require.NoError(t, err)
|
|
err = c.Start()
|
|
require.NoError(t, err)
|
|
defer c.Stop()
|
|
|
|
// Check we still have the 1st header (+header+).
|
|
h, err := c.TrustedHeader(1, bTime.Add(2*time.Hour).Add(1*time.Second))
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, h)
|
|
assert.Equal(t, h.Hash(), h1.Hash())
|
|
}
|
|
|
|
// 2. options.Hash != trustedHeader.Hash
|
|
// This could happen if previous provider was lying to us.
|
|
{
|
|
trustedStore := dbs.New(dbm.NewMemDB(), chainID)
|
|
err := trustedStore.SaveSignedHeaderAndNextValidatorSet(h1, vals)
|
|
require.NoError(t, err)
|
|
|
|
// header1 != header
|
|
diffHeader1 := keys.GenSignedHeader(chainID, 1, bTime.Add(1*time.Hour), nil, vals, vals,
|
|
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys))
|
|
|
|
diffHeader2 := keys.GenSignedHeader(chainID, 2, bTime.Add(2*time.Hour), nil, vals, vals,
|
|
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys))
|
|
|
|
primary := mockp.New(
|
|
chainID,
|
|
map[int64]*types.SignedHeader{
|
|
1: diffHeader1,
|
|
2: diffHeader2,
|
|
},
|
|
map[int64]*types.ValidatorSet{
|
|
1: vals,
|
|
2: vals,
|
|
3: vals,
|
|
},
|
|
)
|
|
|
|
c, err := NewClient(
|
|
chainID,
|
|
TrustOptions{
|
|
Period: 4 * time.Hour,
|
|
Height: 2,
|
|
Hash: diffHeader2.Hash(),
|
|
},
|
|
primary,
|
|
[]provider.Provider{primary},
|
|
trustedStore,
|
|
Logger(log.TestingLogger()),
|
|
)
|
|
require.NoError(t, err)
|
|
err = c.Start()
|
|
require.NoError(t, err)
|
|
defer c.Stop()
|
|
|
|
// Check we no longer have the invalid 1st header (+header+).
|
|
h, err := c.TrustedHeader(1, bTime.Add(2*time.Hour).Add(1*time.Second))
|
|
assert.Error(t, err)
|
|
assert.Nil(t, h)
|
|
}
|
|
}
|
|
|
|
// trustedHeader.Height > options.Height
|
|
func TestClientRestoresTrustedHeaderAfterStartup3(t *testing.T) {
|
|
// 1. options.Hash == trustedHeader.Hash
|
|
{
|
|
trustedStore := dbs.New(dbm.NewMemDB(), chainID)
|
|
err := trustedStore.SaveSignedHeaderAndNextValidatorSet(h1, vals)
|
|
require.NoError(t, err)
|
|
|
|
//header2 := keys.GenSignedHeader(chainID, 2, bTime.Add(2*time.Hour), nil, vals, vals,
|
|
// []byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys))
|
|
err = trustedStore.SaveSignedHeaderAndNextValidatorSet(h2, vals)
|
|
require.NoError(t, err)
|
|
|
|
primary := mockp.New(
|
|
chainID,
|
|
map[int64]*types.SignedHeader{
|
|
1: h1,
|
|
2: h2,
|
|
},
|
|
map[int64]*types.ValidatorSet{
|
|
1: vals,
|
|
2: vals,
|
|
3: vals,
|
|
},
|
|
)
|
|
|
|
c, err := NewClient(
|
|
chainID,
|
|
trustOptions,
|
|
primary,
|
|
[]provider.Provider{primary},
|
|
trustedStore,
|
|
Logger(log.TestingLogger()),
|
|
)
|
|
require.NoError(t, err)
|
|
err = c.Start()
|
|
require.NoError(t, err)
|
|
defer c.Stop()
|
|
|
|
// Check we still have the 1st header (+header+).
|
|
h, err := c.TrustedHeader(1, bTime.Add(2*time.Hour).Add(1*time.Second))
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, h)
|
|
assert.Equal(t, h.Hash(), h1.Hash())
|
|
|
|
// Check we no longer have 2nd header (+header2+).
|
|
h, err = c.TrustedHeader(2, bTime.Add(2*time.Hour).Add(1*time.Second))
|
|
assert.Error(t, err)
|
|
assert.Nil(t, h)
|
|
}
|
|
|
|
// 2. options.Hash != trustedHeader.Hash
|
|
// This could happen if previous provider was lying to us.
|
|
{
|
|
trustedStore := dbs.New(dbm.NewMemDB(), chainID)
|
|
err := trustedStore.SaveSignedHeaderAndNextValidatorSet(h1, vals)
|
|
require.NoError(t, err)
|
|
|
|
// header1 != header
|
|
header1 := keys.GenSignedHeader(chainID, 1, bTime.Add(1*time.Hour), nil, vals, vals,
|
|
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys))
|
|
|
|
header2 := keys.GenSignedHeader(chainID, 2, bTime.Add(2*time.Hour), nil, vals, vals,
|
|
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys))
|
|
err = trustedStore.SaveSignedHeaderAndNextValidatorSet(header2, vals)
|
|
require.NoError(t, err)
|
|
|
|
primary := mockp.New(
|
|
chainID,
|
|
map[int64]*types.SignedHeader{
|
|
1: header1,
|
|
},
|
|
map[int64]*types.ValidatorSet{
|
|
1: vals,
|
|
2: vals,
|
|
},
|
|
)
|
|
|
|
c, err := NewClient(
|
|
chainID,
|
|
TrustOptions{
|
|
Period: 4 * time.Hour,
|
|
Height: 1,
|
|
Hash: header1.Hash(),
|
|
},
|
|
primary,
|
|
[]provider.Provider{primary},
|
|
trustedStore,
|
|
Logger(log.TestingLogger()),
|
|
)
|
|
require.NoError(t, err)
|
|
err = c.Start()
|
|
require.NoError(t, err)
|
|
defer c.Stop()
|
|
|
|
// Check we have swapped invalid 1st header (+header+) with correct one (+header1+).
|
|
h, err := c.TrustedHeader(1, bTime.Add(2*time.Hour).Add(1*time.Second))
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, h)
|
|
assert.Equal(t, h.Hash(), header1.Hash())
|
|
|
|
// Check we no longer have invalid 2nd header (+header2+).
|
|
h, err = c.TrustedHeader(2, bTime.Add(2*time.Hour).Add(1*time.Second))
|
|
assert.Error(t, err)
|
|
assert.Nil(t, h)
|
|
}
|
|
}
|
|
|
|
func TestClient_Update(t *testing.T) {
|
|
c, err := NewClient(
|
|
chainID,
|
|
trustOptions,
|
|
fullNode,
|
|
[]provider.Provider{fullNode},
|
|
dbs.New(dbm.NewMemDB(), chainID),
|
|
Logger(log.TestingLogger()),
|
|
)
|
|
require.NoError(t, err)
|
|
err = c.Start()
|
|
require.NoError(t, err)
|
|
defer c.Stop()
|
|
|
|
// should result in downloading & verifying header #3
|
|
err = c.Update(bTime.Add(2 * time.Hour))
|
|
require.NoError(t, err)
|
|
|
|
h, err := c.TrustedHeader(3, bTime.Add(2*time.Hour))
|
|
assert.NoError(t, err)
|
|
require.NotNil(t, h)
|
|
assert.EqualValues(t, 3, h.Height)
|
|
}
|
|
|
|
func TestClient_Concurrency(t *testing.T) {
|
|
c, err := NewClient(
|
|
chainID,
|
|
trustOptions,
|
|
fullNode,
|
|
[]provider.Provider{fullNode},
|
|
dbs.New(dbm.NewMemDB(), chainID),
|
|
UpdatePeriod(0),
|
|
Logger(log.TestingLogger()),
|
|
)
|
|
require.NoError(t, err)
|
|
err = c.Start()
|
|
require.NoError(t, err)
|
|
defer c.Stop()
|
|
|
|
_, err = c.VerifyHeaderAtHeight(2, bTime.Add(2*time.Hour))
|
|
require.NoError(t, err)
|
|
|
|
var wg sync.WaitGroup
|
|
for i := 0; i < 100; i++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
|
|
// NOTE: Cleanup, Stop, VerifyHeaderAtHeight and Verify are not supposed
|
|
// to be concurrenly safe.
|
|
|
|
assert.Equal(t, chainID, c.ChainID())
|
|
|
|
_, err := c.LastTrustedHeight()
|
|
assert.NoError(t, err)
|
|
|
|
_, err = c.FirstTrustedHeight()
|
|
assert.NoError(t, err)
|
|
|
|
h, err := c.TrustedHeader(1, bTime.Add(2*time.Hour))
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, h)
|
|
|
|
vals, err := c.TrustedValidatorSet(2, bTime.Add(2*time.Hour))
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, vals)
|
|
}()
|
|
}
|
|
|
|
wg.Wait()
|
|
}
|
|
|
|
func TestClientReplacesPrimaryWithWitnessIfPrimaryIsUnavailable(t *testing.T) {
|
|
c, err := NewClient(
|
|
chainID,
|
|
trustOptions,
|
|
deadNode,
|
|
[]provider.Provider{fullNode, fullNode},
|
|
dbs.New(dbm.NewMemDB(), chainID),
|
|
UpdatePeriod(0),
|
|
Logger(log.TestingLogger()),
|
|
MaxRetryAttempts(1),
|
|
)
|
|
|
|
require.NoError(t, err)
|
|
err = c.Update(bTime.Add(2 * time.Hour))
|
|
require.NoError(t, err)
|
|
|
|
assert.NotEqual(t, c.Primary(), deadNode)
|
|
assert.Equal(t, 1, len(c.Witnesses()))
|
|
}
|
|
|
|
func TestClient_BackwardsVerification(t *testing.T) {
|
|
c, err := NewClient(
|
|
chainID,
|
|
TrustOptions{
|
|
Period: 1 * time.Hour,
|
|
Height: 3,
|
|
Hash: h3.Hash(),
|
|
},
|
|
fullNode,
|
|
[]provider.Provider{fullNode},
|
|
dbs.New(dbm.NewMemDB(), chainID),
|
|
UpdatePeriod(0),
|
|
Logger(log.TestingLogger()),
|
|
)
|
|
require.NoError(t, err)
|
|
err = c.Start()
|
|
require.NoError(t, err)
|
|
defer c.Stop()
|
|
|
|
// 1) header is missing => expect no error
|
|
h, err := c.VerifyHeaderAtHeight(2, bTime.Add(1*time.Hour).Add(1*time.Second))
|
|
require.NoError(t, err)
|
|
if assert.NotNil(t, h) {
|
|
assert.EqualValues(t, 2, h.Height)
|
|
}
|
|
|
|
// 2) header is missing, but it's expired => expect error
|
|
h, err = c.VerifyHeaderAtHeight(1, bTime.Add(1*time.Hour).Add(1*time.Second))
|
|
assert.Error(t, err)
|
|
assert.NotNil(t, h)
|
|
}
|
|
|
|
func TestClient_NewClientFromTrustedStore(t *testing.T) {
|
|
// 1) Initiate DB and fill with a "trusted" header
|
|
db := dbs.New(dbm.NewMemDB(), chainID)
|
|
err := db.SaveSignedHeaderAndNextValidatorSet(h1, vals)
|
|
require.NoError(t, err)
|
|
|
|
c, err := NewClientFromTrustedStore(
|
|
chainID,
|
|
trustPeriod,
|
|
deadNode,
|
|
[]provider.Provider{deadNode},
|
|
db,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
// 2) Check header exists (deadNode is being used to ensure we're not getting
|
|
// it from primary)
|
|
h, err := c.TrustedHeader(1, bTime.Add(1*time.Second))
|
|
assert.NoError(t, err)
|
|
assert.EqualValues(t, 1, h.Height)
|
|
}
|
|
|
|
func TestClientUpdateErrorsIfAllWitnessesUnavailable(t *testing.T) {
|
|
c, err := NewClient(
|
|
chainID,
|
|
trustOptions,
|
|
fullNode,
|
|
[]provider.Provider{deadNode, deadNode, deadNode},
|
|
dbs.New(dbm.NewMemDB(), chainID),
|
|
UpdatePeriod(0),
|
|
Logger(log.TestingLogger()),
|
|
MaxRetryAttempts(1),
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
err = c.Update(bTime.Add(2 * time.Hour))
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "awaiting response from all witnesses exceeded dropout time")
|
|
}
|
|
}
|
|
|
|
func TestClientRemovesWitnessIfItSendsUsIncorrectHeader(t *testing.T) {
|
|
// straight invalid header
|
|
badProvider1 := mockp.New(
|
|
chainID,
|
|
map[int64]*types.SignedHeader{
|
|
3: {Header: nil, Commit: nil},
|
|
},
|
|
map[int64]*types.ValidatorSet{},
|
|
)
|
|
|
|
// less than 1/3 signed
|
|
badProvider2 := mockp.New(
|
|
chainID,
|
|
map[int64]*types.SignedHeader{
|
|
3: keys.GenSignedHeaderLastBlockID(chainID, 3, bTime.Add(1*time.Hour), nil, vals, vals,
|
|
[]byte("app_hash2"), []byte("cons_hash"), []byte("results_hash"),
|
|
len(keys), len(keys), types.BlockID{Hash: h2.Hash()}),
|
|
},
|
|
map[int64]*types.ValidatorSet{},
|
|
)
|
|
|
|
c, err := NewClient(
|
|
chainID,
|
|
trustOptions,
|
|
fullNode,
|
|
[]provider.Provider{badProvider1, badProvider2},
|
|
dbs.New(dbm.NewMemDB(), chainID),
|
|
UpdatePeriod(0),
|
|
Logger(log.TestingLogger()),
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
err = c.Update(bTime.Add(2 * time.Hour))
|
|
if assert.Error(t, err) {
|
|
assert.Contains(t, err.Error(), "could not find any witnesses")
|
|
}
|
|
assert.Zero(t, 0, len(c.Witnesses()))
|
|
}
|