Files
tendermint/lite2/client_test.go
Callum Waters 2c7af01cb6 lite2: return already verified headers and verify earlier headers (#4428)
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>
2020-02-20 15:45:11 +01:00

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