Files
tendermint/lite2/verifier_test.go
Anton Kaliaev 86adc2c89f lite: follow up from #3989 (#4209)
* rename adjusted to adjacent

Refs https://github.com/tendermint/tendermint/pull/3989#discussion_r352140829

* rename ErrTooMuchChange to ErrNotEnoughVotingPowerSigned

Refs https://github.com/tendermint/tendermint/pull/3989#discussion_r352142785

* verify commit is properly signed

* remove no longer trusted headers

* restore trustedHeader and trustedNextVals

* check trustedHeader using options

Refs https://github.com/tendermint/tendermint/pull/4209#issuecomment-562462165

* use correct var when checking if headers are adjacent

in bisection func
+ replace TODO with a comment

https://github.com/tendermint/tendermint/pull/3989#discussion_r352125455

* return header in VerifyHeaderAtHeight

because that way we avoid DB call

+ add godoc comments
+ check if there are no headers yet in AutoClient

https://github.com/tendermint/tendermint/pull/3989#pullrequestreview-315454506

* TestVerifyAdjacentHeaders: add 2 more test-cases

+ add TestVerifyReturnsErrorIfTrustLevelIsInvalid

* lite: avoid overflow when parsing key in db store!

* lite: rename AutoClient#Err to Errs

* lite: add a test for AutoClient

* lite: fix keyPattern and call itr.Next in db store

* lite: add two tests for db store

* lite: add TestClientRemovesNoLongerTrustedHeaders

* lite: test Client#Cleanup

* lite: test restoring trustedHeader

https://github.com/tendermint/tendermint/pull/4209#issuecomment-562462165

* lite: comment out unused code in test_helpers

* fix TestVerifyReturnsErrorIfTrustLevelIsInvalid after merge

* change defaultRemoveNoLongerTrustedHeadersPeriod

and add docs

* write more doc

* lite: uncomment testable examples

* use stdlog.Fatal to stop AutoClient tests

* make lll linter happy

* separate errors for 2 cases

- the validator set of a skipped header cannot be trusted, i.e. <1/3rd
  of h1 validator set has signed (new error, something like
  ErrNewValSetCantBeTrusted)
- the validator set is trusted but < 2/3rds has signed
  (ErrNewHeaderCantBeTrusted)

https://github.com/tendermint/tendermint/pull/4209#discussion_r360331253

* remove all headers (even the last one) that are outside

of the trusting period. By doing this, we avoid checking the
trustedHeader's hash in checkTrustedHeaderUsingOptions (case #1).

https://github.com/tendermint/tendermint/pull/4209#discussion_r360332460

* explain restoreTrustedHeaderAndNextVals better

https://github.com/tendermint/tendermint/pull/4209#discussion_r360602328

* add ConfirmationFunction option

for optionally prompting for user input Y/n before removing headers

Refs https://github.com/tendermint/tendermint/pull/4209#discussion_r360602945

* make cleaning optional

https://github.com/tendermint/tendermint/pull/4209#discussion_r364838189

* return error when user refused to remove headers

* check for double votes in VerifyCommitTrusting

* leave only ErrNewValSetCantBeTrusted error

to differenciate between h2Vals.VerifyCommit and
h1NextVals.VerifyCommitTrusting

* fix example tests

* remove unnecessary if condition

https://github.com/tendermint/tendermint/pull/4209#discussion_r365171847

It will be handled by the above switch.

* verifyCommitBasic does not depend on vals

Co-authored-by: Marko <marbar3778@yahoo.com>
2020-01-13 11:56:48 +04:00

321 lines
9.5 KiB
Go

package lite
import (
"fmt"
"testing"
"time"
"github.com/stretchr/testify/assert"
tmmath "github.com/tendermint/tendermint/libs/math"
"github.com/tendermint/tendermint/types"
)
func TestVerifyAdjacentHeaders(t *testing.T) {
const (
chainID = "TestVerifyAdjacentHeaders"
lastHeight = 1
nextHeight = 2
)
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, lastHeight, bTime, nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys))
)
testCases := []struct {
newHeader *types.SignedHeader
newVals *types.ValidatorSet
trustingPeriod time.Duration
now time.Time
expErr error
expErrText string
}{
// same header -> no error
0: {
header,
vals,
3 * time.Hour,
bTime.Add(2 * time.Hour),
nil,
"expected new header height 1 to be greater than one of old header 1",
},
// different chainID -> error
1: {
keys.GenSignedHeader("different-chainID", nextHeight, bTime.Add(1*time.Hour), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys)),
vals,
3 * time.Hour,
bTime.Add(2 * time.Hour),
nil,
"h2.ValidateBasic failed: signedHeader belongs to another chain 'different-chainID' not 'TestVerifyAdjacentHeaders'",
},
// new header's time is before old header's time -> error
2: {
keys.GenSignedHeader(chainID, nextHeight, bTime.Add(-1*time.Hour), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys)),
vals,
3 * time.Hour,
bTime.Add(2 * time.Hour),
nil,
"to be after old header time",
},
// new header's time is from the future -> error
3: {
keys.GenSignedHeader(chainID, nextHeight, bTime.Add(3*time.Hour), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys)),
vals,
3 * time.Hour,
bTime.Add(2 * time.Hour),
nil,
"new header has a time from the future",
},
// 3/3 signed -> no error
4: {
keys.GenSignedHeader(chainID, nextHeight, bTime.Add(1*time.Hour), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys)),
vals,
3 * time.Hour,
bTime.Add(2 * time.Hour),
nil,
"",
},
// 2/3 signed -> no error
5: {
keys.GenSignedHeader(chainID, nextHeight, bTime.Add(1*time.Hour), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 1, len(keys)),
vals,
3 * time.Hour,
bTime.Add(2 * time.Hour),
nil,
"",
},
// 1/3 signed -> error
6: {
keys.GenSignedHeader(chainID, nextHeight, bTime.Add(1*time.Hour), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), len(keys)-1, len(keys)),
vals,
3 * time.Hour,
bTime.Add(2 * time.Hour),
types.ErrNotEnoughVotingPowerSigned{Got: 50, Needed: 93},
"",
},
// vals does not match with what we have -> error
7: {
keys.GenSignedHeader(chainID, nextHeight, bTime.Add(1*time.Hour), nil, keys.ToValidators(10, 1), vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys)),
keys.ToValidators(10, 1),
3 * time.Hour,
bTime.Add(2 * time.Hour),
nil,
"to match those from new header",
},
// vals are inconsistent with newHeader -> error
8: {
keys.GenSignedHeader(chainID, nextHeight, bTime.Add(1*time.Hour), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys)),
keys.ToValidators(10, 1),
3 * time.Hour,
bTime.Add(2 * time.Hour),
nil,
"to match those that were supplied",
},
// old header has expired -> error
9: {
keys.GenSignedHeader(chainID, nextHeight, bTime.Add(1*time.Hour), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys)),
keys.ToValidators(10, 1),
1 * time.Hour,
bTime.Add(1 * time.Hour),
nil,
"old header has expired",
},
}
for i, tc := range testCases {
tc := tc
t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) {
err := Verify(chainID, header, vals, tc.newHeader, tc.newVals, tc.trustingPeriod, tc.now, DefaultTrustLevel)
switch {
case tc.expErr != nil && assert.Error(t, err):
assert.Equal(t, tc.expErr, err)
case tc.expErrText != "":
assert.Contains(t, err.Error(), tc.expErrText)
default:
assert.NoError(t, err)
}
})
}
}
func TestVerifyNonAdjacentHeaders(t *testing.T) {
const (
chainID = "TestVerifyNonAdjacentHeaders"
lastHeight = 1
)
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, lastHeight, bTime, nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys))
// 30, 40, 50
twoThirds = keys[1:]
twoThirdsVals = twoThirds.ToValidators(30, 10)
// 50
oneThird = keys[len(keys)-1:]
oneThirdVals = oneThird.ToValidators(50, 10)
// 20
lessThanOneThird = keys[0:1]
lessThanOneThirdVals = lessThanOneThird.ToValidators(20, 10)
)
testCases := []struct {
newHeader *types.SignedHeader
newVals *types.ValidatorSet
trustingPeriod time.Duration
now time.Time
expErr error
expErrText string
}{
// 3/3 new vals signed, 3/3 old vals present -> no error
0: {
keys.GenSignedHeader(chainID, 3, bTime.Add(1*time.Hour), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys)),
vals,
3 * time.Hour,
bTime.Add(2 * time.Hour),
nil,
"",
},
// 2/3 new vals signed, 3/3 old vals present -> no error
1: {
keys.GenSignedHeader(chainID, 4, bTime.Add(1*time.Hour), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 1, len(keys)),
vals,
3 * time.Hour,
bTime.Add(2 * time.Hour),
nil,
"",
},
// 1/3 new vals signed, 3/3 old vals present -> error
2: {
keys.GenSignedHeader(chainID, 5, bTime.Add(1*time.Hour), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), len(keys)-1, len(keys)),
vals,
3 * time.Hour,
bTime.Add(2 * time.Hour),
types.ErrNotEnoughVotingPowerSigned{Got: 50, Needed: 93},
"",
},
// 3/3 new vals signed, 2/3 old vals present -> no error
3: {
twoThirds.GenSignedHeader(chainID, 5, bTime.Add(1*time.Hour), nil, twoThirdsVals, twoThirdsVals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(twoThirds)),
twoThirdsVals,
3 * time.Hour,
bTime.Add(2 * time.Hour),
nil,
"",
},
// 3/3 new vals signed, 1/3 old vals present -> no error
4: {
oneThird.GenSignedHeader(chainID, 5, bTime.Add(1*time.Hour), nil, oneThirdVals, oneThirdVals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(oneThird)),
oneThirdVals,
3 * time.Hour,
bTime.Add(2 * time.Hour),
nil,
"",
},
// 3/3 new vals signed, less than 1/3 old vals present -> error
5: {
lessThanOneThird.GenSignedHeader(chainID, 5, bTime.Add(1*time.Hour), nil, lessThanOneThirdVals, lessThanOneThirdVals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(lessThanOneThird)),
lessThanOneThirdVals,
3 * time.Hour,
bTime.Add(2 * time.Hour),
ErrNewValSetCantBeTrusted{types.ErrNotEnoughVotingPowerSigned{Got: 20, Needed: 46}},
"",
},
}
for i, tc := range testCases {
tc := tc
t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) {
err := Verify(chainID, header, vals, tc.newHeader, tc.newVals, tc.trustingPeriod, tc.now, DefaultTrustLevel)
switch {
case tc.expErr != nil && assert.Error(t, err):
assert.Equal(t, tc.expErr, err)
case tc.expErrText != "":
assert.Contains(t, err.Error(), tc.expErrText)
default:
assert.NoError(t, err)
}
})
}
}
func TestVerifyReturnsErrorIfTrustLevelIsInvalid(t *testing.T) {
const (
chainID = "TestVerifyReturnsErrorIfTrustLevelIsInvalid"
lastHeight = 1
)
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, lastHeight, bTime, nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys))
)
err := Verify(chainID, header, vals, header, vals, 2*time.Hour, time.Now(),
tmmath.Fraction{Numerator: 2, Denominator: 1})
assert.Error(t, err)
}
func TestValidateTrustLevel(t *testing.T) {
testCases := []struct {
lvl tmmath.Fraction
valid bool
}{
// valid
0: {tmmath.Fraction{Numerator: 1, Denominator: 1}, true},
1: {tmmath.Fraction{Numerator: 1, Denominator: 3}, true},
2: {tmmath.Fraction{Numerator: 2, Denominator: 3}, true},
3: {tmmath.Fraction{Numerator: 3, Denominator: 3}, true},
4: {tmmath.Fraction{Numerator: 4, Denominator: 5}, true},
// invalid
5: {tmmath.Fraction{Numerator: 6, Denominator: 5}, false},
6: {tmmath.Fraction{Numerator: -1, Denominator: 3}, false},
7: {tmmath.Fraction{Numerator: 0, Denominator: 1}, false},
8: {tmmath.Fraction{Numerator: -1, Denominator: -3}, false},
9: {tmmath.Fraction{Numerator: 0, Denominator: 0}, false},
10: {tmmath.Fraction{Numerator: 1, Denominator: 0}, false},
}
for _, tc := range testCases {
err := ValidateTrustLevel(tc.lvl)
if !tc.valid {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
}
}