NewProvider function complete

This commit is contained in:
Jack Zampolin 2019-04-25 15:11:30 -04:00
parent 88b69a956f
commit 236cdf87aa
No known key found for this signature in database
GPG Key ID: 888DC4CF24AAFC63
4 changed files with 105 additions and 98 deletions

View File

@ -56,19 +56,26 @@ func (p *provider) StatusClient() rpcclient.StatusClient {
// LatestFullCommit implements Provider. // LatestFullCommit implements Provider.
func (p *provider) LatestFullCommit(chainID string, minHeight, maxHeight int64) (fc lite.FullCommit, err error) { func (p *provider) LatestFullCommit(chainID string, minHeight, maxHeight int64) (fc lite.FullCommit, err error) {
// If the chain-id is wrong, error
if chainID != p.chainID { if chainID != p.chainID {
err = fmt.Errorf("expected chainID %s, got %s", p.chainID, chainID) err = fmt.Errorf("expected chainID %s, got %s", p.chainID, chainID)
return return
} }
// if the heights are incorrect error
if maxHeight != 0 && maxHeight < minHeight { if maxHeight != 0 && maxHeight < minHeight {
err = fmt.Errorf("need maxHeight == 0 or minHeight <= maxHeight, got min %v and max %v", err = fmt.Errorf("need maxHeight == 0 or minHeight <= maxHeight, got min %v and max %v",
minHeight, maxHeight) minHeight, maxHeight)
return return
} }
// Fetch the latest block
commit, err := p.fetchLatestCommit(minHeight, maxHeight) commit, err := p.fetchLatestCommit(minHeight, maxHeight)
if err != nil { if err != nil {
return return
} }
// Make a lite.FullCommit out of the signed header
fc, err = p.fillFullCommit(commit.SignedHeader) fc, err = p.fillFullCommit(commit.SignedHeader)
return return
} }

View File

@ -38,36 +38,37 @@ func (fc FullCommit) ValidateFull(chainID string) error {
if fc.Validators.Size() == 0 { if fc.Validators.Size() == 0 {
return errors.New("need FullCommit.Validators") return errors.New("need FullCommit.Validators")
} }
if !bytes.Equal(
fc.SignedHeader.ValidatorsHash, // If the commit ValidatorHash doesn't match the block ValidatorHash return an error
fc.Validators.Hash()) { if !bytes.Equal(fc.SignedHeader.ValidatorsHash, fc.Validators.Hash()) {
return fmt.Errorf("header has vhash %X but valset hash is %X", return fmt.Errorf("header has vhash %X but valset hash is %X",
fc.SignedHeader.ValidatorsHash, fc.SignedHeader.ValidatorsHash,
fc.Validators.Hash(), fc.Validators.Hash(),
) )
} }
// Ensure that NextValidators exists and matches the header. // Ensure that NextValidators exists and matches the header.
if fc.NextValidators.Size() == 0 { if fc.NextValidators.Size() == 0 {
return errors.New("need FullCommit.NextValidators") return errors.New("need FullCommit.NextValidators")
} }
if !bytes.Equal(
fc.SignedHeader.NextValidatorsHash, // If the commit ValidatorHash doesn't match the block ValidatorHash return an error
fc.NextValidators.Hash()) { if !bytes.Equal(fc.SignedHeader.NextValidatorsHash, fc.NextValidators.Hash()) {
return fmt.Errorf("header has next vhash %X but next valset hash is %X", return fmt.Errorf("header has next vhash %X but next valset hash is %X",
fc.SignedHeader.NextValidatorsHash, fc.SignedHeader.NextValidatorsHash,
fc.NextValidators.Hash(), fc.NextValidators.Hash(),
) )
} }
// Validate the header. // Validate the header.
err := fc.SignedHeader.ValidateBasic(chainID) err := fc.SignedHeader.ValidateBasic(chainID)
if err != nil { if err != nil {
return err return err
} }
// Validate the signatures on the commit. // Validate the signatures on the commit.
hdr, cmt := fc.SignedHeader.Header, fc.SignedHeader.Commit hdr, cmt := fc.SignedHeader.Header, fc.SignedHeader.Commit
return fc.Validators.VerifyCommit( return fc.Validators.VerifyCommit(hdr.ChainID, cmt.BlockID, hdr.Height, cmt)
hdr.ChainID, cmt.BlockID,
hdr.Height, cmt)
} }
// Height returns the height of the header. // Height returns the height of the header.

View File

@ -49,40 +49,21 @@ func NewProvider(chainID, rootDir string, client lclient.SignStatusClient, logge
source := lclient.NewProvider(chainID, client) source := lclient.NewProvider(chainID, client)
vp := makeProvider(chainID, options.TrustPeriod, trust, source) vp := makeProvider(chainID, options.TrustPeriod, trust, source)
vp.SetLogger(logger) vp.SetLogger(logger)
trustPeriod := options.TrustPeriod
// Get the latest trusted FC. // Get the latest source commit, or the one provided in options.
tlfc, err := trust.LatestFullCommit(chainID, 1, 1<<63-1) trustCommit, err := getTrustCommit(client, options)
//If there is no prior state or last state is older than the Trust Period fetch the last state. if err != nil {
if err != nil || time.Now().Sub(tlfc.SignedHeader.Time) > options.TrustPeriod { return nil, err
// Get the latest source commit, or the one provided in options. }
targetCommit, err := getTargetCommit(client, options)
if err != nil { err = vp.fillValidateAndSaveToTrust(trustCommit, nil, nil)
return nil, err if err != nil {
} return nil, err
err = vp.fillValidateAndSaveToTrust(targetCommit, nil, nil)
if err != nil {
return nil, err
}
return vp, nil
} }
// sanity check // sanity check
if time.Now().Sub(tlfc.SignedHeader.Time) <= 0 { if time.Now().Sub(trustCommit.Time) <= 0 {
panic(fmt.Sprintf("impossible time %v vs %v", time.Now(), tlfc.SignedHeader.Time)) panic(fmt.Sprintf("impossible time %v vs %v", time.Now(), trustCommit.Time))
}
if time.Now().Sub(tlfc.SignedHeader.Time) > trustPeriod {
// Get the latest source commit, or the one provided in options.
targetCommit, err := getTargetCommit(client, options)
if err != nil {
return nil, err
}
err = vp.fillValidateAndSaveToTrust(targetCommit, nil, nil)
if err != nil {
return nil, err
}
return vp, nil
} }
// Otherwise we're syncing within the unbonding period. // Otherwise we're syncing within the unbonding period.
@ -90,22 +71,26 @@ func NewProvider(chainID, rootDir string, client lclient.SignStatusClient, logge
// UpdateToHeight() will fetch it again, and latestCommit isn't used), but // UpdateToHeight() will fetch it again, and latestCommit isn't used), but
// it's only once upon initialization of a validator so it's not a big // it's only once upon initialization of a validator so it's not a big
// deal. // deal.
latestCommit, err := client.Commit(nil) if options.TrustHeight > 0 {
if err != nil { latestCommit, err := client.Commit(nil)
return nil, err if err != nil {
} return nil, err
err = vp.UpdateToHeight(chainID, latestCommit.SignedHeader.Height) }
if err != nil { err = vp.UpdateToHeight(chainID, latestCommit.SignedHeader.Height)
return nil, err if err != nil {
return nil, err
}
} }
return vp, nil return vp, nil
} }
// getTragetCommit returns a commit trusted with weak subjectivity. It either: // getTragetCommit returns a commit trusted with weak subjectivity. It either:
// 1. Fetches a commit at height provide in options and ensure the specified is within the trust period of latest // 1. Fetches a commit at height provided in options and ensures the specified commit
// is within the trust period of latest block
// 2. Trusts the remote node and gets the latest commit // 2. Trusts the remote node and gets the latest commit
// 3. Returns an error if the height provided in trust option is too old to sync to latest. // 3. Returns an error if the height provided in trust option is too old to sync to latest.
func getTargetCommit(client lclient.SignStatusClient, options TrustOptions) (types.SignedHeader, error) { func getTrustCommit(client lclient.SignStatusClient, options TrustOptions) (types.SignedHeader, error) {
// Get the lastest commit always // Get the lastest commit always
latestBlock, err := client.Commit(nil) latestBlock, err := client.Commit(nil)
@ -132,11 +117,11 @@ func getTargetCommit(client lclient.SignStatusClient, options TrustOptions) (typ
} else { } else {
latestCommit := latestBlock.SignedHeader latestCommit := latestBlock.SignedHeader
// NOTE: This should really belong in the callback. // NOTE: This should really belong in the callback.
// WARN THE USER IN ALL CAPS THAT THE LITE CLIENT IS NEW, // WARN THE USER IN ALL CAPS THAT THE LITE CLIENT IS NEW,
// AND THAT WE WILL SYNC TO AND VERIFY LATEST COMMIT. // AND THAT WE WILL SYNC TO AND VERIFY LATEST COMMIT.
fmt.Printf("trusting source at height %v and hash %X...\n", fmt.Printf("trusting source at height %v and hash %X...\n", latestCommit.Height, latestCommit.Hash())
latestCommit.Height, latestCommit.Hash())
if options.Callback != nil { if options.Callback != nil {
err := options.Callback(latestCommit.Height, latestCommit.Hash()) err := options.Callback(latestCommit.Height, latestCommit.Hash())
if err != nil { if err != nil {
@ -212,12 +197,11 @@ func (vp *provider) ChainID() string {
// Implements UpdatingProvider // Implements UpdatingProvider
// //
// On success, it will store the full commit (SignedHeader + Validators) in // On success, it will store the full commit (SignedHeader + Validators) in vp.trusted
// vp.trusted. // NOTE: For concurreent usage, use concurrentProvider
// NOTE: For concurreent usage, use concurrentProvider.
func (vp *provider) UpdateToHeight(chainID string, height int64) error { func (vp *provider) UpdateToHeight(chainID string, height int64) error {
// If we alreeady have the commit, just return nil. // If we alreedy have the commit, just return nil
_, err := vp.trusted.LatestFullCommit(vp.chainID, height, height) _, err := vp.trusted.LatestFullCommit(vp.chainID, height, height)
if err == nil { if err == nil {
return nil return nil
@ -238,28 +222,31 @@ func (vp *provider) UpdateToHeight(chainID string, height int64) error {
} }
// If valset or nextValset are nil, fetches them. // If valset or nextValset are nil, fetches them.
// Then, validatees the full commit, then savees it. // Then, validatees the full commit, then saves it.
func (vp *provider) fillValidateAndSaveToTrust(signedHeader types.SignedHeader, valset, nextValset *types.ValidatorSet) (err error) { func (vp *provider) fillValidateAndSaveToTrust(signedHeader types.SignedHeader, valset, nextValset *types.ValidatorSet) (err error) {
// Get the valset. // If there is no valset passed, fetch it
if valset != nil { if valset == nil {
valset, err = vp.source.ValidatorSet(vp.chainID, signedHeader.Height) valset, err = vp.source.ValidatorSet(vp.chainID, signedHeader.Height)
if err != nil { if err != nil {
return cmn.ErrorWrap(err, "fetching the valset") return cmn.ErrorWrap(err, "fetching the valset")
} }
} }
// Get the next validator set. // If there is no nextvalset passed, fetch it
if nextValset != nil { if nextValset == nil {
// TODO: Don't loop forever, just do it 10 times
for { for {
// fetch block at signedHeader.Height+1
nextValset, err = vp.source.ValidatorSet(vp.chainID, signedHeader.Height+1) nextValset, err = vp.source.ValidatorSet(vp.chainID, signedHeader.Height+1)
if lerr.IsErrUnknownValidators(err) { if lerr.IsErrUnknownValidators(err) {
// try again until we get it. // try again until we get it.
fmt.Printf("fetching validatorset for height %v...\n", fmt.Printf("fetching validatorset for height %v...\n", signedHeader.Height+1)
signedHeader.Height+1)
continue continue
} else if err != nil { } else if err != nil {
return cmn.ErrorWrap(err, "fetching the next valset") return cmn.ErrorWrap(err, "fetching the next valset")
} else if nextValset != nil {
break
} }
} }
} }
@ -270,16 +257,19 @@ func (vp *provider) fillValidateAndSaveToTrust(signedHeader types.SignedHeader,
Validators: valset, Validators: valset,
NextValidators: nextValset, NextValidators: nextValset,
} }
// Validate the full commit. This checks the cryptographic // Validate the full commit. This checks the cryptographic
// signatures of Commit against Validators. // signatures of Commit against Validators.
if err := fc.ValidateFull(vp.chainID); err != nil { if err := fc.ValidateFull(vp.chainID); err != nil {
return cmn.ErrorWrap(err, "verifying validators from source") return cmn.ErrorWrap(err, "verifying validators from source")
} }
// Trust it. // Trust it.
err = vp.trusted.SaveFullCommit(fc) err = vp.trusted.SaveFullCommit(fc)
if err != nil { if err != nil {
return cmn.ErrorWrap(err, "saving full commit") return cmn.ErrorWrap(err, "saving full commit")
} }
return nil return nil
} }
@ -290,35 +280,20 @@ func (vp *provider) fillValidateAndSaveToTrust(signedHeader types.SignedHeader,
// Returns ErrCommitExpired when trustedFC is too old. // Returns ErrCommitExpired when trustedFC is too old.
// Panics if trustedFC.Height() >= newFC.Height(). // Panics if trustedFC.Height() >= newFC.Height().
func (vp *provider) verifyAndSave(trustedFC, newFC lite.FullCommit) error { func (vp *provider) verifyAndSave(trustedFC, newFC lite.FullCommit) error {
// Shouldn't have trusted commits before the new commit height
if trustedFC.Height() >= newFC.Height() { if trustedFC.Height() >= newFC.Height() {
panic("should not happen") panic("should not happen")
} }
// Check that the latest commit isn't beyond the vp.trustPeriod
if vp.now().Sub(trustedFC.SignedHeader.Time) > vp.trustPeriod { if vp.now().Sub(trustedFC.SignedHeader.Time) > vp.trustPeriod {
return lerr.ErrCommitExpired() return lerr.ErrCommitExpired()
} }
if trustedFC.Height() == newFC.Height()-1 {
err := trustedFC.NextValidators.VerifyCommit(
vp.chainID, newFC.SignedHeader.Commit.BlockID,
newFC.SignedHeader.Height, newFC.SignedHeader.Commit,
)
if err != nil {
return err
}
} else {
err := trustedFC.NextValidators.VerifyFutureCommit(
newFC.Validators,
vp.chainID, newFC.SignedHeader.Commit.BlockID,
newFC.SignedHeader.Height, newFC.SignedHeader.Commit,
)
if err != nil {
return err
}
}
if vp.now().Before(newFC.SignedHeader.Time) { // If the new full commit is the next block, verify it. Otherwise use the verify future commit function
// TODO print warning if err := trustedFC.NextValidators.VerifyCommit(vp.chainID, newFC.SignedHeader.Commit.BlockID, newFC.SignedHeader.Height, newFC.SignedHeader.Commit); err != nil {
// TODO if too egregious, return error. return err
// return FullCommit{}, errors.New("now should not be before source time")
} }
return vp.trusted.SaveFullCommit(newFC) return vp.trusted.SaveFullCommit(newFC)
@ -352,13 +327,13 @@ func (vp *provider) fetchAndVerifyToHeight(h int64) (lite.FullCommit, error) {
// Verify latest FullCommit against trusted FullCommits // Verify latest FullCommit against trusted FullCommits
// Use a loop rather than recursion to avoid stack overflows. // Use a loop rather than recursion to avoid stack overflows.
FOR_LOOP:
for { for {
// Fetch latest full commit from trusted. // Fetch latest full commit from trusted.
trustedFC, err := vp.trusted.LatestFullCommit(vp.chainID, 1, h) trustedFC, err := vp.trusted.LatestFullCommit(vp.chainID, 1, h)
if err != nil { if err != nil {
return lite.FullCommit{}, err return lite.FullCommit{}, err
} }
// We have nothing to do. // We have nothing to do.
if trustedFC.Height() == h { if trustedFC.Height() == h {
return trustedFC, nil return trustedFC, nil
@ -366,6 +341,7 @@ FOR_LOOP:
// Update to full commit with checks. // Update to full commit with checks.
err = vp.verifyAndSave(trustedFC, sourceFC) err = vp.verifyAndSave(trustedFC, sourceFC)
// Handle special case when err is ErrTooMuchChange. // Handle special case when err is ErrTooMuchChange.
if types.IsErrTooMuchChange(err) { if types.IsErrTooMuchChange(err) {
// Divide and conquer. // Divide and conquer.
@ -374,12 +350,15 @@ FOR_LOOP:
panic("should not happen") panic("should not happen")
} }
mid := (start + end) / 2 mid := (start + end) / 2
// Recursive call back into fetchAndVerifyToHeight. Once you get to an inner
// call that succeeeds, the outer calls will succeed.
_, err = vp.fetchAndVerifyToHeight(mid) _, err = vp.fetchAndVerifyToHeight(mid)
if err != nil { if err != nil {
return lite.FullCommit{}, err return lite.FullCommit{}, err
} }
// If we made it to mid, we retry. // If we made it to mid, we retry.
continue FOR_LOOP continue
} else if err != nil { } else if err != nil {
return lite.FullCommit{}, err return lite.FullCommit{}, err
} }

View File

@ -368,51 +368,71 @@ func (vals *ValidatorSet) Iterate(fn func(index int, val *Validator) bool) {
// Verify that +2/3 of the set had signed the given signBytes. // Verify that +2/3 of the set had signed the given signBytes.
func (vals *ValidatorSet) VerifyCommit(chainID string, blockID BlockID, height int64, commit *Commit) error { func (vals *ValidatorSet) VerifyCommit(chainID string, blockID BlockID, height int64, commit *Commit) error {
// If the ValidatorSet size is different than the commit.Precommits size somthing is wrong
if vals.Size() != len(commit.Precommits) { if vals.Size() != len(commit.Precommits) {
return fmt.Errorf("Invalid commit -- wrong set size: %v vs %v", vals.Size(), len(commit.Precommits)) return fmt.Errorf("Invalid commit -- wrong set size: %v vs %v", vals.Size(), len(commit.Precommits))
} }
// If the height to check is different than the commit height return an error
if height != commit.Height() { if height != commit.Height() {
return fmt.Errorf("Invalid commit -- wrong height: %v vs %v", height, commit.Height()) return fmt.Errorf("Invalid commit -- wrong height: %v vs %v", height, commit.Height())
} }
// If the blockHash is not equal to the commit block hash return an error
if !blockID.Equals(commit.BlockID) { if !blockID.Equals(commit.BlockID) {
return fmt.Errorf("Invalid commit -- wrong block id: want %v got %v", return fmt.Errorf("Invalid commit -- wrong block id: want %v got %v",
blockID, commit.BlockID) blockID, commit.BlockID)
} }
talliedVotingPower := int64(0) var talliedVotingPower int64
round := commit.Round() round := commit.Round()
for idx, precommit := range commit.Precommits { for idx, precommit := range commit.Precommits {
// Some precommits will likely be missing, skip those
if precommit == nil { if precommit == nil {
continue // OK, some precommits can be missing. continue
} }
// Malicous data checking for height
if precommit.Height != height { if precommit.Height != height {
return fmt.Errorf("Invalid commit -- wrong height: want %v got %v", height, precommit.Height) return fmt.Errorf("Invalid commit -- wrong height: want %v got %v", height, precommit.Height)
} }
// Malicous data checking for round
if precommit.Round != round { if precommit.Round != round {
return fmt.Errorf("Invalid commit -- wrong round: want %v got %v", round, precommit.Round) return fmt.Errorf("Invalid commit -- wrong round: want %v got %v", round, precommit.Round)
} }
// Malicous data checking for precommit
if precommit.Type != PrecommitType { if precommit.Type != PrecommitType {
return fmt.Errorf("Invalid commit -- not precommit @ index %v", idx) return fmt.Errorf("Invalid commit -- not precommit @ index %v", idx)
} }
_, val := vals.GetByIndex(idx)
// Validate signature. // Validate signature.
precommitSignBytes := precommit.SignBytes(chainID) _, val := vals.GetByAddress(precommit.ValidatorAddress)
if !val.PubKey.VerifyBytes(precommitSignBytes, precommit.Signature) { if val == nil {
continue
}
// verify that the valiator signed the precommit
if !val.PubKey.VerifyBytes(precommit.SignBytes(chainID), precommit.Signature) {
return fmt.Errorf("Invalid commit -- invalid signature: %v", precommit) return fmt.Errorf("Invalid commit -- invalid signature: %v", precommit)
} }
// Good precommit! // Good precommit!
if blockID.Equals(precommit.BlockID) { if blockID.Equals(precommit.BlockID) {
talliedVotingPower += val.VotingPower talliedVotingPower += val.VotingPower
} else {
// It's OK that the BlockID doesn't match. We include stray
// precommits to measure validator availability.
} }
// It's OK that the BlockID doesn't match. We include stray
// precommits to measure validator availability.
} }
if talliedVotingPower > vals.TotalVotingPower()*2/3 { if talliedVotingPower > vals.TotalVotingPower()*2/3 {
return nil return nil
} }
return errTooMuchChange{talliedVotingPower, vals.TotalVotingPower()*2/3 + 1} return errTooMuchChange{talliedVotingPower, vals.TotalVotingPower()*2/3 + 1}
} }
@ -434,7 +454,7 @@ func (vals *ValidatorSet) VerifyCommit(chainID string, blockID BlockID, height i
// > 2/3. Otherwise, the lite client isn't providing the same security // > 2/3. Otherwise, the lite client isn't providing the same security
// guarantees. // guarantees.
// //
// newSet is the validator set that signed this block. Only votes from new are // newVals is the validator set that signed this block. Only votes from new are
// sufficient for 2/3 majority in the new set as well, for it to be a valid // sufficient for 2/3 majority in the new set as well, for it to be a valid
// commit. // commit.
// //
@ -444,13 +464,13 @@ func (vals *ValidatorSet) VerifyCommit(chainID string, blockID BlockID, height i
// set. // set.
// //
// NOTE: This function is strictly more restrictive than merely checking // NOTE: This function is strictly more restrictive than merely checking
// whether newSet.VerifyCommit(...), in fact it calls exactly that. // whether newVals.VerifyCommit(...), in fact it calls exactly that.
func (vals *ValidatorSet) VerifyFutureCommit(newSet *ValidatorSet, chainID string, func (vals *ValidatorSet) VerifyFutureCommit(newVals *ValidatorSet, chainID string,
blockID BlockID, height int64, commit *Commit) error { blockID BlockID, height int64, commit *Commit) error {
oldVals := vals oldVals := vals
// Commit must be a valid commit for newSet. // Commit must be a valid commit for newVals.
err := newSet.VerifyCommit(chainID, blockID, height, commit) err := newVals.VerifyCommit(chainID, blockID, height, commit)
if err != nil { if err != nil {
return err return err
} }