github.com/vipernet-xyz/tendermint-core@v0.32.0/lite/dynamic_verifier.go (about) 1 package lite 2 3 import ( 4 "bytes" 5 "fmt" 6 "sync" 7 8 log "github.com/tendermint/tendermint/libs/log" 9 lerr "github.com/tendermint/tendermint/lite/errors" 10 "github.com/tendermint/tendermint/types" 11 ) 12 13 const sizeOfPendingMap = 1024 14 15 var _ Verifier = (*DynamicVerifier)(nil) 16 17 // DynamicVerifier implements an auto-updating Verifier. It uses a 18 // "source" provider to obtain the needed FullCommits to securely sync with 19 // validator set changes. It stores properly validated data on the 20 // "trusted" local system. 21 // TODO: make this single threaded and create a new 22 // ConcurrentDynamicVerifier that wraps it with concurrency. 23 // see https://github.com/tendermint/tendermint/issues/3170 24 type DynamicVerifier struct { 25 chainID string 26 logger log.Logger 27 28 // Already validated, stored locally 29 trusted PersistentProvider 30 31 // New info, like a node rpc, or other import method. 32 source Provider 33 34 // pending map to synchronize concurrent verification requests 35 mtx sync.Mutex 36 pendingVerifications map[int64]chan struct{} 37 } 38 39 // NewDynamicVerifier returns a new DynamicVerifier. It uses the 40 // trusted provider to store validated data and the source provider to 41 // obtain missing data (e.g. FullCommits). 42 // 43 // The trusted provider should be a DBProvider. 44 // The source provider should be a client.HTTPProvider. 45 func NewDynamicVerifier(chainID string, trusted PersistentProvider, source Provider) *DynamicVerifier { 46 return &DynamicVerifier{ 47 logger: log.NewNopLogger(), 48 chainID: chainID, 49 trusted: trusted, 50 source: source, 51 pendingVerifications: make(map[int64]chan struct{}, sizeOfPendingMap), 52 } 53 } 54 55 func (dv *DynamicVerifier) SetLogger(logger log.Logger) { 56 logger = logger.With("module", "lite") 57 dv.logger = logger 58 dv.trusted.SetLogger(logger) 59 dv.source.SetLogger(logger) 60 } 61 62 // Implements Verifier. 63 func (dv *DynamicVerifier) ChainID() string { 64 return dv.chainID 65 } 66 67 // Implements Verifier. 68 // 69 // If the validators have changed since the last known time, it looks to 70 // dv.trusted and dv.source to prove the new validators. On success, it will 71 // try to store the SignedHeader in dv.trusted if the next 72 // validator can be sourced. 73 func (dv *DynamicVerifier) Verify(shdr types.SignedHeader) error { 74 75 // Performs synchronization for multi-threads verification at the same height. 76 dv.mtx.Lock() 77 if pending := dv.pendingVerifications[shdr.Height]; pending != nil { 78 dv.mtx.Unlock() 79 <-pending // pending is chan struct{} 80 } else { 81 pending := make(chan struct{}) 82 dv.pendingVerifications[shdr.Height] = pending 83 defer func() { 84 close(pending) 85 dv.mtx.Lock() 86 delete(dv.pendingVerifications, shdr.Height) 87 dv.mtx.Unlock() 88 }() 89 dv.mtx.Unlock() 90 } 91 92 //Get the exact trusted commit for h, and if it is 93 // equal to shdr, then it's already trusted, so 94 // just return nil. 95 trustedFCSameHeight, err := dv.trusted.LatestFullCommit(dv.chainID, shdr.Height, shdr.Height) 96 if err == nil { 97 // If loading trust commit successfully, and trust commit equal to shdr, then don't verify it, 98 // just return nil. 99 if bytes.Equal(trustedFCSameHeight.SignedHeader.Hash(), shdr.Hash()) { 100 dv.logger.Info(fmt.Sprintf("Load full commit at height %d from cache, there is not need to verify.", shdr.Height)) 101 return nil 102 } 103 } else if !lerr.IsErrCommitNotFound(err) { 104 // Return error if it is not CommitNotFound error 105 dv.logger.Info(fmt.Sprintf("Encountered unknown error in loading full commit at height %d.", shdr.Height)) 106 return err 107 } 108 109 // Get the latest known full commit <= h-1 from our trusted providers. 110 // The full commit at h-1 contains the valset to sign for h. 111 prevHeight := shdr.Height - 1 112 trustedFC, err := dv.trusted.LatestFullCommit(dv.chainID, 1, prevHeight) 113 if err != nil { 114 return err 115 } 116 117 // sync up to the prevHeight and assert our latest NextValidatorSet 118 // is the ValidatorSet for the SignedHeader 119 if trustedFC.Height() == prevHeight { 120 // Return error if valset doesn't match. 121 if !bytes.Equal( 122 trustedFC.NextValidators.Hash(), 123 shdr.Header.ValidatorsHash) { 124 return lerr.ErrUnexpectedValidators( 125 trustedFC.NextValidators.Hash(), 126 shdr.Header.ValidatorsHash) 127 } 128 } else { 129 // If valset doesn't match, try to update 130 if !bytes.Equal( 131 trustedFC.NextValidators.Hash(), 132 shdr.Header.ValidatorsHash) { 133 // ... update. 134 trustedFC, err = dv.updateToHeight(prevHeight) 135 if err != nil { 136 return err 137 } 138 // Return error if valset _still_ doesn't match. 139 if !bytes.Equal(trustedFC.NextValidators.Hash(), 140 shdr.Header.ValidatorsHash) { 141 return lerr.ErrUnexpectedValidators( 142 trustedFC.NextValidators.Hash(), 143 shdr.Header.ValidatorsHash) 144 } 145 } 146 } 147 148 // Verify the signed header using the matching valset. 149 cert := NewBaseVerifier(dv.chainID, trustedFC.Height()+1, trustedFC.NextValidators) 150 err = cert.Verify(shdr) 151 if err != nil { 152 return err 153 } 154 155 // By now, the SignedHeader is fully validated and we're synced up to 156 // SignedHeader.Height - 1. To sync to SignedHeader.Height, we need 157 // the validator set at SignedHeader.Height + 1 so we can verify the 158 // SignedHeader.NextValidatorSet. 159 // TODO: is the ValidateFull below mostly redundant with the BaseVerifier.Verify above? 160 // See https://github.com/tendermint/tendermint/issues/3174. 161 162 // Get the next validator set. 163 nextValset, err := dv.source.ValidatorSet(dv.chainID, shdr.Height+1) 164 if lerr.IsErrUnknownValidators(err) { 165 // Ignore this error. 166 return nil 167 } else if err != nil { 168 return err 169 } 170 171 // Create filled FullCommit. 172 nfc := FullCommit{ 173 SignedHeader: shdr, 174 Validators: trustedFC.NextValidators, 175 NextValidators: nextValset, 176 } 177 // Validate the full commit. This checks the cryptographic 178 // signatures of Commit against Validators. 179 if err := nfc.ValidateFull(dv.chainID); err != nil { 180 return err 181 } 182 // Trust it. 183 return dv.trusted.SaveFullCommit(nfc) 184 } 185 186 // verifyAndSave will verify if this is a valid source full commit given the 187 // best match trusted full commit, and if good, persist to dv.trusted. 188 // Returns ErrNotEnoughVotingPowerSigned when >2/3 of trustedFC did not sign sourceFC. 189 // Panics if trustedFC.Height() >= sourceFC.Height(). 190 func (dv *DynamicVerifier) verifyAndSave(trustedFC, sourceFC FullCommit) error { 191 if trustedFC.Height() >= sourceFC.Height() { 192 panic("should not happen") 193 } 194 err := trustedFC.NextValidators.VerifyFutureCommit( 195 sourceFC.Validators, 196 dv.chainID, sourceFC.SignedHeader.Commit.BlockID, 197 sourceFC.SignedHeader.Height, sourceFC.SignedHeader.Commit, 198 ) 199 if err != nil { 200 return err 201 } 202 203 return dv.trusted.SaveFullCommit(sourceFC) 204 } 205 206 // updateToHeight will use divide-and-conquer to find a path to h. 207 // Returns nil error iff we successfully verify and persist a full commit 208 // for height h, using repeated applications of bisection if necessary. 209 // 210 // Returns ErrCommitNotFound if source provider doesn't have the commit for h. 211 func (dv *DynamicVerifier) updateToHeight(h int64) (FullCommit, error) { 212 213 // Fetch latest full commit from source. 214 sourceFC, err := dv.source.LatestFullCommit(dv.chainID, h, h) 215 if err != nil { 216 return FullCommit{}, err 217 } 218 219 // If sourceFC.Height() != h, we can't do it. 220 if sourceFC.Height() != h { 221 return FullCommit{}, lerr.ErrCommitNotFound() 222 } 223 224 // Validate the full commit. This checks the cryptographic 225 // signatures of Commit against Validators. 226 if err := sourceFC.ValidateFull(dv.chainID); err != nil { 227 return FullCommit{}, err 228 } 229 230 // Verify latest FullCommit against trusted FullCommits 231 FOR_LOOP: 232 for { 233 // Fetch latest full commit from trusted. 234 trustedFC, err := dv.trusted.LatestFullCommit(dv.chainID, 1, h) 235 if err != nil { 236 return FullCommit{}, err 237 } 238 // We have nothing to do. 239 if trustedFC.Height() == h { 240 return trustedFC, nil 241 } 242 243 // Try to update to full commit with checks. 244 err = dv.verifyAndSave(trustedFC, sourceFC) 245 if err == nil { 246 // All good! 247 return sourceFC, nil 248 } 249 250 // Handle special case when err is ErrNotEnoughVotingPowerSigned. 251 if types.IsErrNotEnoughVotingPowerSigned(err) { 252 // Divide and conquer. 253 start, end := trustedFC.Height(), sourceFC.Height() 254 if !(start < end) { 255 panic("should not happen") 256 } 257 mid := (start + end) / 2 258 _, err = dv.updateToHeight(mid) 259 if err != nil { 260 return FullCommit{}, err 261 } 262 // If we made it to mid, we retry. 263 continue FOR_LOOP 264 } 265 return FullCommit{}, err 266 } 267 } 268 269 func (dv *DynamicVerifier) LastTrustedHeight() int64 { 270 fc, err := dv.trusted.LatestFullCommit(dv.chainID, 1, 1<<63-1) 271 if err != nil { 272 panic("should not happen") 273 } 274 return fc.Height() 275 }