github.com/okex/exchain@v1.8.0/libs/tendermint/lite/dynamic_verifier.go (about) 1 package lite 2 3 import ( 4 "bytes" 5 "fmt" 6 "sync" 7 8 log "github.com/okex/exchain/libs/tendermint/libs/log" 9 lerr "github.com/okex/exchain/libs/tendermint/lite/errors" 10 "github.com/okex/exchain/libs/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 commit := func() error { 114 return nil 115 } 116 if err != nil { 117 // if the commit is not found in the local database , fetch the acommit from the source node 118 if !lerr.IsErrCommitNotFound(err) { 119 return err 120 } 121 if trustedFC, err = dv.source.LatestFullCommit(dv.chainID, 1, prevHeight); err != nil { 122 return err 123 } else { 124 commit = func() error { 125 return dv.trusted.SaveFullCommit(trustedFC) 126 } 127 } 128 } 129 130 // sync up to the prevHeight and assert our latest NextValidatorSet 131 // is the ValidatorSet for the SignedHeader 132 if trustedFC.Height() == prevHeight { 133 // Return error if valset doesn't match. 134 if !bytes.Equal( 135 trustedFC.NextValidators.Hash(trustedFC.Height()+1), 136 shdr.Header.ValidatorsHash) { 137 return lerr.ErrUnexpectedValidators( 138 trustedFC.NextValidators.Hash(trustedFC.Height()+1), 139 shdr.Header.ValidatorsHash) 140 } 141 } else { 142 // If valset doesn't match, try to update 143 if !bytes.Equal( 144 trustedFC.NextValidators.Hash(trustedFC.Height()+1), 145 shdr.Header.ValidatorsHash) { 146 // ... update. 147 trustedFC, err = dv.updateToHeight(prevHeight) 148 if err != nil { 149 return err 150 } 151 // Return error if valset _still_ doesn't match. 152 if !bytes.Equal(trustedFC.NextValidators.Hash(trustedFC.Height()+1), 153 shdr.Header.ValidatorsHash) { 154 return lerr.ErrUnexpectedValidators( 155 trustedFC.NextValidators.Hash(trustedFC.Height()+1), 156 shdr.Header.ValidatorsHash) 157 } 158 } 159 } 160 161 // Verify the signed header using the matching valset. 162 cert := NewBaseVerifier(dv.chainID, trustedFC.Height()+1, trustedFC.NextValidators) 163 err = cert.Verify(shdr) 164 if err != nil { 165 return err 166 } 167 168 // By now, the SignedHeader is fully validated and we're synced up to 169 // SignedHeader.Height - 1. To sync to SignedHeader.Height, we need 170 // the validator set at SignedHeader.Height + 1 so we can verify the 171 // SignedHeader.NextValidatorSet. 172 // TODO: is the ValidateFull below mostly redundant with the BaseVerifier.Verify above? 173 // See https://github.com/tendermint/tendermint/issues/3174. 174 175 // Get the next validator set. 176 nextValset, err := dv.source.ValidatorSet(dv.chainID, shdr.Height+1) 177 if lerr.IsErrUnknownValidators(err) { 178 // Ignore this error. 179 return nil 180 } else if err != nil { 181 return err 182 } 183 184 // Create filled FullCommit. 185 nfc := FullCommit{ 186 SignedHeader: shdr, 187 Validators: trustedFC.NextValidators, 188 NextValidators: nextValset, 189 } 190 // Validate the full commit. This checks the cryptographic 191 // signatures of Commit against Validators. 192 if err := nfc.ValidateFull(dv.chainID); err != nil { 193 return err 194 } 195 // Trust it. 196 if err = dv.trusted.SaveFullCommit(nfc); err != nil { 197 return err 198 } 199 return commit() 200 } 201 202 // verifyAndSave will verify if this is a valid source full commit given the 203 // best match trusted full commit, and if good, persist to dv.trusted. 204 // Returns ErrNotEnoughVotingPowerSigned when >2/3 of trustedFC did not sign sourceFC. 205 // Panics if trustedFC.Height() >= sourceFC.Height(). 206 func (dv *DynamicVerifier) verifyAndSave(trustedFC, sourceFC FullCommit) error { 207 if trustedFC.Height() >= sourceFC.Height() { 208 panic("should not happen") 209 } 210 err := trustedFC.NextValidators.VerifyFutureCommit( 211 sourceFC.Validators, 212 dv.chainID, sourceFC.SignedHeader.Commit.BlockID, 213 sourceFC.SignedHeader.Height, sourceFC.SignedHeader.Commit, 214 ) 215 if err != nil { 216 return err 217 } 218 219 return dv.trusted.SaveFullCommit(sourceFC) 220 } 221 222 // updateToHeight will use divide-and-conquer to find a path to h. 223 // Returns nil error iff we successfully verify and persist a full commit 224 // for height h, using repeated applications of bisection if necessary. 225 // 226 // Returns ErrCommitNotFound if source provider doesn't have the commit for h. 227 func (dv *DynamicVerifier) updateToHeight(h int64) (FullCommit, error) { 228 229 // Fetch latest full commit from source. 230 sourceFC, err := dv.source.LatestFullCommit(dv.chainID, h, h) 231 if err != nil { 232 return FullCommit{}, err 233 } 234 235 // If sourceFC.Height() != h, we can't do it. 236 if sourceFC.Height() != h { 237 return FullCommit{}, lerr.ErrCommitNotFound() 238 } 239 240 // Validate the full commit. This checks the cryptographic 241 // signatures of Commit against Validators. 242 if err := sourceFC.ValidateFull(dv.chainID); err != nil { 243 return FullCommit{}, err 244 } 245 246 // Verify latest FullCommit against trusted FullCommits 247 FOR_LOOP: 248 for { 249 // Fetch latest full commit from trusted. 250 trustedFC, err := dv.trusted.LatestFullCommit(dv.chainID, 1, h) 251 if err != nil { 252 return FullCommit{}, err 253 } 254 // We have nothing to do. 255 if trustedFC.Height() == h { 256 return trustedFC, nil 257 } 258 259 // Try to update to full commit with checks. 260 err = dv.verifyAndSave(trustedFC, sourceFC) 261 if err == nil { 262 // All good! 263 return sourceFC, nil 264 } 265 266 // Handle special case when err is ErrNotEnoughVotingPowerSigned. 267 if types.IsErrNotEnoughVotingPowerSigned(err) { 268 // Divide and conquer. 269 start, end := trustedFC.Height(), sourceFC.Height() 270 if !(start < end) { 271 panic("should not happen") 272 } 273 mid := (start + end) / 2 274 _, err = dv.updateToHeight(mid) 275 if err != nil { 276 return FullCommit{}, err 277 } 278 // If we made it to mid, we retry. 279 continue FOR_LOOP 280 } 281 return FullCommit{}, err 282 } 283 } 284 285 func (dv *DynamicVerifier) LastTrustedHeight() int64 { 286 fc, err := dv.trusted.LatestFullCommit(dv.chainID, 1, 1<<63-1) 287 if err != nil { 288 panic("should not happen") 289 } 290 return fc.Height() 291 }