mirror of
https://github.com/fluencelabs/tendermint
synced 2025-05-13 15:21:20 +00:00
NewProvider function complete
This commit is contained in:
parent
88b69a956f
commit
236cdf87aa
@ -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
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user