mirror of
https://github.com/tendermint/tendermint.git
synced 2026-07-25 17:43:03 +00:00
Closes #4328 When TrustedHeader(height) is called, if the height is less than the trusted height but the header is not in the trusted store then a function finds the previous lowest height with a trusted header and performs a forwards sequential verification to the header of the height that was given. If no error is found it updates the trusted store with the header and validator set for that height and can then return them to the user. Commits: * drafted trusted header * created function to find previous trusted height * updates missing headers less than the trusted height * minor cosmetic tweaks * incorporated suggestions * lite2: implement Backwards verification and add SignedHeaderAfter func to Store interface Refs https://github.com/tendermint/tendermint/issues/4328#issuecomment-581878549 * remove unused method * write tests * start with next height in SignedHeaderAfter func * fix linter errors * address Callum's comments Co-authored-by: Anton Kaliaev <anton.kalyaev@gmail.com>
989 lines
25 KiB
Go
989 lines
25 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"
|
|
)
|
|
|
|
func TestClient_SequentialVerification(t *testing.T) {
|
|
const (
|
|
chainID = "sequential-verification"
|
|
)
|
|
|
|
var (
|
|
keys = genPrivKeys(4)
|
|
// 20, 30, 40, 50 - the first 3 don't have 2/3, the last 3 do!
|
|
vals = keys.ToValidators(20, 10)
|
|
bTime, _ = time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
|
|
header = keys.GenSignedHeader(chainID, 1, bTime, nil, vals, vals,
|
|
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys))
|
|
)
|
|
|
|
testCases := []struct {
|
|
otherHeaders map[int64]*types.SignedHeader // all except ^
|
|
vals map[int64]*types.ValidatorSet
|
|
initErr bool
|
|
verifyErr bool
|
|
}{
|
|
// good
|
|
{
|
|
map[int64]*types.SignedHeader{
|
|
// trusted header
|
|
1: header,
|
|
// 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 (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,
|
|
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: header,
|
|
// 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: header,
|
|
// 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 {
|
|
c, err := NewClient(
|
|
chainID,
|
|
TrustOptions{
|
|
Period: 4 * time.Hour,
|
|
Height: 1,
|
|
Hash: header.Hash(),
|
|
},
|
|
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)
|
|
continue
|
|
} else {
|
|
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) {
|
|
const (
|
|
chainID = "skipping-verification"
|
|
)
|
|
|
|
var (
|
|
keys = genPrivKeys(4)
|
|
// 20, 30, 40, 50 - the first 3 don't have 2/3, the last 3 do!
|
|
vals = keys.ToValidators(20, 10)
|
|
bTime, _ = time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
|
|
header = keys.GenSignedHeader(chainID, 1, bTime, nil, vals, vals,
|
|
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys))
|
|
)
|
|
|
|
// required for 2nd test case
|
|
newKeys := genPrivKeys(4)
|
|
newVals := newKeys.ToValidators(10, 1)
|
|
|
|
testCases := []struct {
|
|
otherHeaders map[int64]*types.SignedHeader // all except ^
|
|
vals map[int64]*types.ValidatorSet
|
|
initErr bool
|
|
verifyErr bool
|
|
}{
|
|
// good
|
|
{
|
|
map[int64]*types.SignedHeader{
|
|
// trusted header
|
|
1: header,
|
|
// 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,
|
|
false,
|
|
},
|
|
// good, val set changes 100% at height 2
|
|
{
|
|
map[int64]*types.SignedHeader{
|
|
// trusted header
|
|
1: header,
|
|
// 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,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
c, err := NewClient(
|
|
chainID,
|
|
TrustOptions{
|
|
Period: 4 * time.Hour,
|
|
Height: 1,
|
|
Hash: header.Hash(),
|
|
},
|
|
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)
|
|
continue
|
|
} else {
|
|
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) {
|
|
const (
|
|
chainID = "TestClientRemovesNoLongerTrustedHeaders"
|
|
)
|
|
|
|
var (
|
|
keys = genPrivKeys(4)
|
|
// 20, 30, 40, 50 - the first 3 don't have 2/3, the last 3 do!
|
|
vals = keys.ToValidators(20, 10)
|
|
bTime, _ = time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
|
|
header = keys.GenSignedHeader(chainID, 1, bTime, 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: header,
|
|
// interim header (3/3 signed)
|
|
2: keys.GenSignedHeader(chainID, 2, bTime.Add(2*time.Hour), nil, vals, vals,
|
|
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys)),
|
|
// last header (3/3 signed)
|
|
3: keys.GenSignedHeader(chainID, 3, bTime.Add(4*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,
|
|
},
|
|
)
|
|
|
|
c, err := NewClient(
|
|
chainID,
|
|
TrustOptions{
|
|
Period: 4 * time.Hour,
|
|
Height: 1,
|
|
Hash: header.Hash(),
|
|
},
|
|
primary,
|
|
[]provider.Provider{primary},
|
|
dbs.New(dbm.NewMemDB(), chainID),
|
|
Logger(log.TestingLogger()),
|
|
)
|
|
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) {
|
|
const (
|
|
chainID = "TestClient_Cleanup"
|
|
)
|
|
|
|
var (
|
|
keys = genPrivKeys(4)
|
|
// 20, 30, 40, 50 - the first 3 don't have 2/3, the last 3 do!
|
|
vals = keys.ToValidators(20, 10)
|
|
bTime, _ = time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
|
|
header = keys.GenSignedHeader(chainID, 1, bTime, 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: header,
|
|
},
|
|
map[int64]*types.ValidatorSet{
|
|
1: vals,
|
|
2: vals,
|
|
},
|
|
)
|
|
|
|
c, err := NewClient(
|
|
chainID,
|
|
TrustOptions{
|
|
Period: 4 * time.Hour,
|
|
Height: 1,
|
|
Hash: header.Hash(),
|
|
},
|
|
primary,
|
|
[]provider.Provider{primary},
|
|
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 TestClientRestoreTrustedHeaderAfterStartup1(t *testing.T) {
|
|
const (
|
|
chainID = "TestClientRestoreTrustedHeaderAfterStartup1"
|
|
)
|
|
|
|
var (
|
|
keys = genPrivKeys(4)
|
|
// 20, 30, 40, 50 - the first 3 don't have 2/3, the last 3 do!
|
|
vals = keys.ToValidators(20, 10)
|
|
bTime, _ = time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
|
|
header = keys.GenSignedHeader(chainID, 1, bTime, nil, vals, vals,
|
|
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys))
|
|
)
|
|
|
|
// 1. options.Hash == trustedHeader.Hash
|
|
{
|
|
trustedStore := dbs.New(dbm.NewMemDB(), chainID)
|
|
err := trustedStore.SaveSignedHeaderAndNextValidatorSet(header, vals)
|
|
require.NoError(t, err)
|
|
|
|
primary := mockp.New(
|
|
chainID,
|
|
map[int64]*types.SignedHeader{
|
|
// trusted header
|
|
1: header,
|
|
},
|
|
map[int64]*types.ValidatorSet{
|
|
1: vals,
|
|
2: vals,
|
|
},
|
|
)
|
|
|
|
c, err := NewClient(
|
|
chainID,
|
|
TrustOptions{
|
|
Period: 4 * time.Hour,
|
|
Height: 1,
|
|
Hash: header.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(), header.Hash())
|
|
}
|
|
|
|
// 2. options.Hash != trustedHeader.Hash
|
|
{
|
|
trustedStore := dbs.New(dbm.NewMemDB(), chainID)
|
|
err := trustedStore.SaveSignedHeaderAndNextValidatorSet(header, 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 TestClientRestoreTrustedHeaderAfterStartup2(t *testing.T) {
|
|
const (
|
|
chainID = "TestClientRestoreTrustedHeaderAfterStartup2"
|
|
)
|
|
|
|
var (
|
|
keys = genPrivKeys(4)
|
|
// 20, 30, 40, 50 - the first 3 don't have 2/3, the last 3 do!
|
|
vals = keys.ToValidators(20, 10)
|
|
bTime, _ = time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
|
|
header = keys.GenSignedHeader(chainID, 1, bTime, nil, vals, vals,
|
|
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys))
|
|
)
|
|
|
|
// 1. options.Hash == trustedHeader.Hash
|
|
{
|
|
trustedStore := dbs.New(dbm.NewMemDB(), chainID)
|
|
err := trustedStore.SaveSignedHeaderAndNextValidatorSet(header, 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))
|
|
|
|
primary := mockp.New(
|
|
chainID,
|
|
map[int64]*types.SignedHeader{
|
|
1: header,
|
|
2: header2,
|
|
},
|
|
map[int64]*types.ValidatorSet{
|
|
1: vals,
|
|
2: vals,
|
|
3: vals,
|
|
},
|
|
)
|
|
c, err := NewClient(
|
|
chainID,
|
|
TrustOptions{
|
|
Period: 4 * time.Hour,
|
|
Height: 2,
|
|
Hash: header2.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 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(), header.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(header, 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))
|
|
|
|
primary := mockp.New(
|
|
chainID,
|
|
map[int64]*types.SignedHeader{
|
|
1: header1,
|
|
2: header2,
|
|
},
|
|
map[int64]*types.ValidatorSet{
|
|
1: vals,
|
|
2: vals,
|
|
3: vals,
|
|
},
|
|
)
|
|
|
|
c, err := NewClient(
|
|
chainID,
|
|
TrustOptions{
|
|
Period: 4 * time.Hour,
|
|
Height: 2,
|
|
Hash: header2.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 TestClientRestoreTrustedHeaderAfterStartup3(t *testing.T) {
|
|
const (
|
|
chainID = "TestClientRestoreTrustedHeaderAfterStartup3"
|
|
)
|
|
|
|
var (
|
|
keys = genPrivKeys(4)
|
|
// 20, 30, 40, 50 - the first 3 don't have 2/3, the last 3 do!
|
|
vals = keys.ToValidators(20, 10)
|
|
bTime, _ = time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
|
|
header = keys.GenSignedHeader(chainID, 1, bTime, nil, vals, vals,
|
|
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys))
|
|
)
|
|
|
|
// 1. options.Hash == trustedHeader.Hash
|
|
{
|
|
trustedStore := dbs.New(dbm.NewMemDB(), chainID)
|
|
err := trustedStore.SaveSignedHeaderAndNextValidatorSet(header, 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(header2, vals)
|
|
require.NoError(t, err)
|
|
|
|
primary := mockp.New(
|
|
chainID,
|
|
map[int64]*types.SignedHeader{
|
|
1: header,
|
|
2: header2,
|
|
},
|
|
map[int64]*types.ValidatorSet{
|
|
1: vals,
|
|
2: vals,
|
|
3: vals,
|
|
},
|
|
)
|
|
|
|
c, err := NewClient(
|
|
chainID,
|
|
TrustOptions{
|
|
Period: 4 * time.Hour,
|
|
Height: 1,
|
|
Hash: header.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 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(), header.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(header, 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) {
|
|
const (
|
|
chainID = "TestClient_Update"
|
|
)
|
|
|
|
var (
|
|
keys = genPrivKeys(4)
|
|
// 20, 30, 40, 50 - the first 3 don't have 2/3, the last 3 do!
|
|
vals = keys.ToValidators(20, 10)
|
|
bTime, _ = time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
|
|
header = keys.GenSignedHeader(chainID, 1, bTime, 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: header,
|
|
// interim header (3/3 signed)
|
|
2: keys.GenSignedHeader(chainID, 2, bTime.Add(30*time.Minute), nil, vals, vals,
|
|
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys)),
|
|
// last header (3/3 signed)
|
|
3: keys.GenSignedHeader(chainID, 3, 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,
|
|
2: vals,
|
|
3: vals,
|
|
4: vals,
|
|
},
|
|
)
|
|
|
|
c, err := NewClient(
|
|
chainID,
|
|
TrustOptions{
|
|
Period: 4 * time.Hour,
|
|
Height: 1,
|
|
Hash: header.Hash(),
|
|
},
|
|
primary,
|
|
[]provider.Provider{primary},
|
|
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) {
|
|
const (
|
|
chainID = "TestClient_Concurrency"
|
|
)
|
|
|
|
var (
|
|
keys = genPrivKeys(4)
|
|
// 20, 30, 40, 50 - the first 3 don't have 2/3, the last 3 do!
|
|
vals = keys.ToValidators(20, 10)
|
|
bTime, _ = time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
|
|
header = keys.GenSignedHeader(chainID, 1, bTime, 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: header,
|
|
// interim header (3/3 signed)
|
|
2: keys.GenSignedHeader(chainID, 2, bTime.Add(30*time.Minute), nil, vals, vals,
|
|
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys)),
|
|
// last header (3/3 signed)
|
|
3: keys.GenSignedHeader(chainID, 3, 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,
|
|
2: vals,
|
|
3: vals,
|
|
4: vals,
|
|
},
|
|
)
|
|
|
|
c, err := NewClient(
|
|
chainID,
|
|
TrustOptions{
|
|
Period: 4 * time.Hour,
|
|
Height: 1,
|
|
Hash: header.Hash(),
|
|
},
|
|
primary,
|
|
[]provider.Provider{primary},
|
|
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 TestProvider_Replacement(t *testing.T) {
|
|
const (
|
|
chainID = "TestProvider_Replacement"
|
|
)
|
|
|
|
var (
|
|
keys = genPrivKeys(4)
|
|
// 20, 30, 40, 50 - the first 3 don't have 2/3, the last 3 do!
|
|
vals = keys.ToValidators(20, 10)
|
|
bTime, _ = time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
|
|
header = keys.GenSignedHeader(chainID, 1, bTime, nil, vals, vals,
|
|
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys))
|
|
primary = mockp.NewDeadMock(chainID)
|
|
witness = mockp.New(
|
|
chainID,
|
|
map[int64]*types.SignedHeader{
|
|
// trusted header
|
|
1: header,
|
|
// interim header (3/3 signed)
|
|
2: keys.GenSignedHeader(chainID, 2, bTime.Add(30*time.Minute), nil, vals, vals,
|
|
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys)),
|
|
// last header (3/3 signed)
|
|
3: keys.GenSignedHeader(chainID, 3, 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,
|
|
2: vals,
|
|
3: vals,
|
|
4: vals,
|
|
},
|
|
)
|
|
)
|
|
|
|
c, err := NewClient(
|
|
chainID,
|
|
TrustOptions{
|
|
Period: 4 * time.Hour,
|
|
Height: 1,
|
|
Hash: header.Hash(),
|
|
},
|
|
primary,
|
|
[]provider.Provider{witness},
|
|
dbs.New(dbm.NewMemDB(), chainID),
|
|
UpdatePeriod(0),
|
|
Logger(log.TestingLogger()),
|
|
)
|
|
require.NoError(t, err)
|
|
err = c.Start()
|
|
require.NoError(t, err)
|
|
defer c.Stop()
|
|
|
|
assert.NotEqual(t, c.Primary(), primary)
|
|
assert.Equal(t, 0, len(c.Witnesses()))
|
|
}
|
|
|
|
func TestProvider_TrustedHeaderFetchesMissingHeader(t *testing.T) {
|
|
const (
|
|
chainID = "TestProvider_TrustedHeaderFetchesMissingHeader"
|
|
)
|
|
|
|
var (
|
|
keys = genPrivKeys(4)
|
|
// 20, 30, 40, 50 - the first 3 don't have 2/3, the last 3 do!
|
|
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))
|
|
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()})
|
|
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()})
|
|
primary = 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,
|
|
},
|
|
)
|
|
)
|
|
|
|
c, err := NewClient(
|
|
chainID,
|
|
TrustOptions{
|
|
Period: 1 * time.Hour,
|
|
Height: 3,
|
|
Hash: h3.Hash(),
|
|
},
|
|
primary,
|
|
[]provider.Provider{primary},
|
|
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.TrustedHeader(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.TrustedHeader(1, bTime.Add(1*time.Hour).Add(1*time.Second))
|
|
assert.Error(t, err)
|
|
assert.Nil(t, h)
|
|
}
|