From 236cdf87aa129bd68fb41a8085a30ed0a8bae20f Mon Sep 17 00:00:00 2001 From: Jack Zampolin Date: Thu, 25 Apr 2019 15:11:30 -0400 Subject: [PATCH] NewProvider function complete --- lite/client/provider.go | 7 ++ lite/commit.go | 19 +++--- lite/verifying/provider.go | 131 ++++++++++++++++--------------------- types/validator_set.go | 46 +++++++++---- 4 files changed, 105 insertions(+), 98 deletions(-) diff --git a/lite/client/provider.go b/lite/client/provider.go index e0c0a331..35112011 100644 --- a/lite/client/provider.go +++ b/lite/client/provider.go @@ -56,19 +56,26 @@ func (p *provider) StatusClient() rpcclient.StatusClient { // LatestFullCommit implements Provider. 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 { err = fmt.Errorf("expected chainID %s, got %s", p.chainID, chainID) return } + + // if the heights are incorrect error if maxHeight != 0 && maxHeight < minHeight { err = fmt.Errorf("need maxHeight == 0 or minHeight <= maxHeight, got min %v and max %v", minHeight, maxHeight) return } + + // Fetch the latest block commit, err := p.fetchLatestCommit(minHeight, maxHeight) if err != nil { return } + + // Make a lite.FullCommit out of the signed header fc, err = p.fillFullCommit(commit.SignedHeader) return } diff --git a/lite/commit.go b/lite/commit.go index 6cd35417..814341a0 100644 --- a/lite/commit.go +++ b/lite/commit.go @@ -38,36 +38,37 @@ func (fc FullCommit) ValidateFull(chainID string) error { if fc.Validators.Size() == 0 { return errors.New("need FullCommit.Validators") } - if !bytes.Equal( - fc.SignedHeader.ValidatorsHash, - fc.Validators.Hash()) { + + // If the commit ValidatorHash doesn't match the block ValidatorHash return an error + if !bytes.Equal(fc.SignedHeader.ValidatorsHash, fc.Validators.Hash()) { return fmt.Errorf("header has vhash %X but valset hash is %X", fc.SignedHeader.ValidatorsHash, fc.Validators.Hash(), ) } + // Ensure that NextValidators exists and matches the header. if fc.NextValidators.Size() == 0 { return errors.New("need FullCommit.NextValidators") } - if !bytes.Equal( - fc.SignedHeader.NextValidatorsHash, - fc.NextValidators.Hash()) { + + // If the commit ValidatorHash doesn't match the block ValidatorHash return an error + if !bytes.Equal(fc.SignedHeader.NextValidatorsHash, fc.NextValidators.Hash()) { return fmt.Errorf("header has next vhash %X but next valset hash is %X", fc.SignedHeader.NextValidatorsHash, fc.NextValidators.Hash(), ) } + // Validate the header. err := fc.SignedHeader.ValidateBasic(chainID) if err != nil { return err } + // Validate the signatures on the commit. hdr, cmt := fc.SignedHeader.Header, fc.SignedHeader.Commit - return fc.Validators.VerifyCommit( - hdr.ChainID, cmt.BlockID, - hdr.Height, cmt) + return fc.Validators.VerifyCommit(hdr.ChainID, cmt.BlockID, hdr.Height, cmt) } // Height returns the height of the header. diff --git a/lite/verifying/provider.go b/lite/verifying/provider.go index 67099869..4c836a7b 100644 --- a/lite/verifying/provider.go +++ b/lite/verifying/provider.go @@ -49,40 +49,21 @@ func NewProvider(chainID, rootDir string, client lclient.SignStatusClient, logge source := lclient.NewProvider(chainID, client) vp := makeProvider(chainID, options.TrustPeriod, trust, source) vp.SetLogger(logger) - trustPeriod := options.TrustPeriod - // Get the latest trusted FC. - tlfc, err := trust.LatestFullCommit(chainID, 1, 1<<63-1) - //If there is no prior state or last state is older than the Trust Period fetch the last state. - if err != nil || time.Now().Sub(tlfc.SignedHeader.Time) > options.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 + // Get the latest source commit, or the one provided in options. + trustCommit, err := getTrustCommit(client, options) + if err != nil { + return nil, err + } + + err = vp.fillValidateAndSaveToTrust(trustCommit, nil, nil) + if err != nil { + return nil, err } // sanity check - if time.Now().Sub(tlfc.SignedHeader.Time) <= 0 { - panic(fmt.Sprintf("impossible time %v vs %v", time.Now(), tlfc.SignedHeader.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 + if time.Now().Sub(trustCommit.Time) <= 0 { + panic(fmt.Sprintf("impossible time %v vs %v", time.Now(), trustCommit.Time)) } // 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 // it's only once upon initialization of a validator so it's not a big // deal. - latestCommit, err := client.Commit(nil) - if err != nil { - return nil, err - } - err = vp.UpdateToHeight(chainID, latestCommit.SignedHeader.Height) - if err != nil { - return nil, err + if options.TrustHeight > 0 { + latestCommit, err := client.Commit(nil) + if err != nil { + return nil, err + } + err = vp.UpdateToHeight(chainID, latestCommit.SignedHeader.Height) + if err != nil { + return nil, err + } } + return vp, nil } // 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 // 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 latestBlock, err := client.Commit(nil) @@ -132,11 +117,11 @@ func getTargetCommit(client lclient.SignStatusClient, options TrustOptions) (typ } else { latestCommit := latestBlock.SignedHeader + // NOTE: This should really belong in the callback. // WARN THE USER IN ALL CAPS THAT THE LITE CLIENT IS NEW, // AND THAT WE WILL SYNC TO AND VERIFY LATEST COMMIT. - fmt.Printf("trusting source at height %v and hash %X...\n", - latestCommit.Height, latestCommit.Hash()) + fmt.Printf("trusting source at height %v and hash %X...\n", latestCommit.Height, latestCommit.Hash()) if options.Callback != nil { err := options.Callback(latestCommit.Height, latestCommit.Hash()) if err != nil { @@ -212,12 +197,11 @@ func (vp *provider) ChainID() string { // Implements UpdatingProvider // -// On success, it will store the full commit (SignedHeader + Validators) in -// vp.trusted. -// NOTE: For concurreent usage, use concurrentProvider. +// On success, it will store the full commit (SignedHeader + Validators) in vp.trusted +// NOTE: For concurreent usage, use concurrentProvider 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) if err == nil { return nil @@ -238,28 +222,31 @@ func (vp *provider) UpdateToHeight(chainID string, height int64) error { } // 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) { - // Get the valset. - if valset != nil { + // If there is no valset passed, fetch it + if valset == nil { valset, err = vp.source.ValidatorSet(vp.chainID, signedHeader.Height) if err != nil { return cmn.ErrorWrap(err, "fetching the valset") } } - // Get the next validator set. - if nextValset != nil { + // If there is no nextvalset passed, fetch it + if nextValset == nil { + // TODO: Don't loop forever, just do it 10 times for { + // fetch block at signedHeader.Height+1 nextValset, err = vp.source.ValidatorSet(vp.chainID, signedHeader.Height+1) if lerr.IsErrUnknownValidators(err) { // try again until we get it. - fmt.Printf("fetching validatorset for height %v...\n", - signedHeader.Height+1) + fmt.Printf("fetching validatorset for height %v...\n", signedHeader.Height+1) continue } else if err != nil { 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, NextValidators: nextValset, } + // Validate the full commit. This checks the cryptographic // signatures of Commit against Validators. if err := fc.ValidateFull(vp.chainID); err != nil { return cmn.ErrorWrap(err, "verifying validators from source") } + // Trust it. err = vp.trusted.SaveFullCommit(fc) if err != nil { return cmn.ErrorWrap(err, "saving full commit") } + return nil } @@ -290,35 +280,20 @@ func (vp *provider) fillValidateAndSaveToTrust(signedHeader types.SignedHeader, // Returns ErrCommitExpired when trustedFC is too old. // Panics if trustedFC.Height() >= newFC.Height(). func (vp *provider) verifyAndSave(trustedFC, newFC lite.FullCommit) error { + + // Shouldn't have trusted commits before the new commit height if trustedFC.Height() >= newFC.Height() { panic("should not happen") } + + // Check that the latest commit isn't beyond the vp.trustPeriod if vp.now().Sub(trustedFC.SignedHeader.Time) > vp.trustPeriod { 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) { - // TODO print warning - // TODO if too egregious, return error. - // return FullCommit{}, errors.New("now should not be before source time") + // If the new full commit is the next block, verify it. Otherwise use the verify future commit function + if err := trustedFC.NextValidators.VerifyCommit(vp.chainID, newFC.SignedHeader.Commit.BlockID, newFC.SignedHeader.Height, newFC.SignedHeader.Commit); err != nil { + return err } return vp.trusted.SaveFullCommit(newFC) @@ -352,13 +327,13 @@ func (vp *provider) fetchAndVerifyToHeight(h int64) (lite.FullCommit, error) { // Verify latest FullCommit against trusted FullCommits // Use a loop rather than recursion to avoid stack overflows. -FOR_LOOP: for { // Fetch latest full commit from trusted. trustedFC, err := vp.trusted.LatestFullCommit(vp.chainID, 1, h) if err != nil { return lite.FullCommit{}, err } + // We have nothing to do. if trustedFC.Height() == h { return trustedFC, nil @@ -366,6 +341,7 @@ FOR_LOOP: // Update to full commit with checks. err = vp.verifyAndSave(trustedFC, sourceFC) + // Handle special case when err is ErrTooMuchChange. if types.IsErrTooMuchChange(err) { // Divide and conquer. @@ -374,12 +350,15 @@ FOR_LOOP: panic("should not happen") } 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) if err != nil { return lite.FullCommit{}, err } // If we made it to mid, we retry. - continue FOR_LOOP + continue } else if err != nil { return lite.FullCommit{}, err } diff --git a/types/validator_set.go b/types/validator_set.go index 68beedc7..9df11e08 100644 --- a/types/validator_set.go +++ b/types/validator_set.go @@ -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. 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) { 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() { 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) { return fmt.Errorf("Invalid commit -- wrong block id: want %v got %v", blockID, commit.BlockID) } - talliedVotingPower := int64(0) + var talliedVotingPower int64 round := commit.Round() for idx, precommit := range commit.Precommits { + // Some precommits will likely be missing, skip those if precommit == nil { - continue // OK, some precommits can be missing. + continue } + + // Malicous data checking for height if precommit.Height != height { return fmt.Errorf("Invalid commit -- wrong height: want %v got %v", height, precommit.Height) } + + // Malicous data checking for round if precommit.Round != round { return fmt.Errorf("Invalid commit -- wrong round: want %v got %v", round, precommit.Round) } + + // Malicous data checking for precommit if precommit.Type != PrecommitType { return fmt.Errorf("Invalid commit -- not precommit @ index %v", idx) } - _, val := vals.GetByIndex(idx) + // Validate signature. - precommitSignBytes := precommit.SignBytes(chainID) - if !val.PubKey.VerifyBytes(precommitSignBytes, precommit.Signature) { + _, val := vals.GetByAddress(precommit.ValidatorAddress) + 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) } + // Good precommit! if blockID.Equals(precommit.BlockID) { 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 { return nil } + 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 // 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 // commit. // @@ -444,13 +464,13 @@ func (vals *ValidatorSet) VerifyCommit(chainID string, blockID BlockID, height i // set. // // NOTE: This function is strictly more restrictive than merely checking -// whether newSet.VerifyCommit(...), in fact it calls exactly that. -func (vals *ValidatorSet) VerifyFutureCommit(newSet *ValidatorSet, chainID string, +// whether newVals.VerifyCommit(...), in fact it calls exactly that. +func (vals *ValidatorSet) VerifyFutureCommit(newVals *ValidatorSet, chainID string, blockID BlockID, height int64, commit *Commit) error { oldVals := vals - // Commit must be a valid commit for newSet. - err := newSet.VerifyCommit(chainID, blockID, height, commit) + // Commit must be a valid commit for newVals. + err := newVals.VerifyCommit(chainID, blockID, height, commit) if err != nil { return err }