github.com/okex/exchain@v1.8.0/libs/tendermint/lite/dbprovider.go (about) 1 package lite 2 3 import ( 4 "fmt" 5 "regexp" 6 "strconv" 7 8 dbm "github.com/okex/exchain/libs/tm-db" 9 amino "github.com/tendermint/go-amino" 10 11 cryptoamino "github.com/okex/exchain/libs/tendermint/crypto/encoding/amino" 12 log "github.com/okex/exchain/libs/tendermint/libs/log" 13 lerr "github.com/okex/exchain/libs/tendermint/lite/errors" 14 "github.com/okex/exchain/libs/tendermint/types" 15 ) 16 17 var _ PersistentProvider = (*DBProvider)(nil) 18 19 // DBProvider stores commits and validator sets in a DB. 20 type DBProvider struct { 21 logger log.Logger 22 label string 23 db dbm.DB 24 cdc *amino.Codec 25 limit int 26 } 27 28 func NewDBProvider(label string, db dbm.DB) *DBProvider { 29 30 // NOTE: when debugging, this type of construction might be useful. 31 //db = dbm.NewDebugDB("db provider "+tmrand.Str(4), db) 32 33 cdc := amino.NewCodec() 34 cryptoamino.RegisterAmino(cdc) 35 dbp := &DBProvider{ 36 logger: log.NewNopLogger(), 37 label: label, 38 db: db, 39 cdc: cdc, 40 } 41 return dbp 42 } 43 44 func (dbp *DBProvider) SetLogger(logger log.Logger) { 45 dbp.logger = logger.With("label", dbp.label) 46 } 47 48 func (dbp *DBProvider) SetLimit(limit int) *DBProvider { 49 dbp.limit = limit 50 return dbp 51 } 52 53 // Implements PersistentProvider. 54 func (dbp *DBProvider) SaveFullCommit(fc FullCommit) error { 55 56 dbp.logger.Info("DBProvider.SaveFullCommit()...", "fc", fc) 57 batch := dbp.db.NewBatch() 58 defer batch.Close() 59 60 // Save the fc.validators. 61 // We might be overwriting what we already have, but 62 // it makes the logic easier for now. 63 vsKey := validatorSetKey(fc.ChainID(), fc.Height()) 64 vsBz, err := dbp.cdc.MarshalBinaryLengthPrefixed(fc.Validators) 65 if err != nil { 66 return err 67 } 68 batch.Set(vsKey, vsBz) 69 70 // Save the fc.NextValidators. 71 nvsKey := validatorSetKey(fc.ChainID(), fc.Height()+1) 72 nvsBz, err := dbp.cdc.MarshalBinaryLengthPrefixed(fc.NextValidators) 73 if err != nil { 74 return err 75 } 76 batch.Set(nvsKey, nvsBz) 77 78 // Save the fc.SignedHeader 79 shKey := signedHeaderKey(fc.ChainID(), fc.Height()) 80 shBz, err := dbp.cdc.MarshalBinaryLengthPrefixed(fc.SignedHeader) 81 if err != nil { 82 return err 83 } 84 batch.Set(shKey, shBz) 85 86 // And write sync. 87 batch.WriteSync() 88 89 // Garbage collect. 90 // TODO: optimize later. 91 if dbp.limit > 0 { 92 dbp.deleteAfterN(fc.ChainID(), dbp.limit) 93 } 94 95 return nil 96 } 97 98 // Implements Provider. 99 func (dbp *DBProvider) LatestFullCommit(chainID string, minHeight, maxHeight int64) ( 100 FullCommit, error) { 101 102 dbp.logger.Info("DBProvider.LatestFullCommit()...", 103 "chainID", chainID, "minHeight", minHeight, "maxHeight", maxHeight) 104 105 if minHeight <= 0 { 106 minHeight = 1 107 } 108 if maxHeight == 0 { 109 maxHeight = 1<<63 - 1 110 } 111 112 itr, err := dbp.db.ReverseIterator( 113 signedHeaderKey(chainID, minHeight), 114 append(signedHeaderKey(chainID, maxHeight), byte(0x00)), 115 ) 116 if err != nil { 117 panic(err) 118 } 119 defer itr.Close() 120 121 for itr.Valid() { 122 key := itr.Key() 123 _, _, ok := parseSignedHeaderKey(key) 124 if !ok { 125 // Skip over other keys. 126 itr.Next() 127 continue 128 } else { 129 // Found the latest full commit signed header. 130 shBz := itr.Value() 131 sh := types.SignedHeader{} 132 err := dbp.cdc.UnmarshalBinaryLengthPrefixed(shBz, &sh) 133 if err != nil { 134 return FullCommit{}, err 135 } 136 137 lfc, err := dbp.fillFullCommit(sh) 138 if err == nil { 139 dbp.logger.Info("DBProvider.LatestFullCommit() found latest.", "height", lfc.Height()) 140 return lfc, nil 141 } 142 143 dbp.logger.Error("DBProvider.LatestFullCommit() got error", "lfc", lfc) 144 dbp.logger.Error(fmt.Sprintf("%+v", err)) 145 return lfc, err 146 147 } 148 } 149 return FullCommit{}, lerr.ErrCommitNotFound() 150 } 151 152 func (dbp *DBProvider) ValidatorSet(chainID string, height int64) (valset *types.ValidatorSet, err error) { 153 return dbp.getValidatorSet(chainID, height) 154 } 155 156 func (dbp *DBProvider) getValidatorSet(chainID string, height int64) (valset *types.ValidatorSet, err error) { 157 vsBz, err := dbp.db.Get(validatorSetKey(chainID, height)) 158 if err != nil { 159 return nil, err 160 } 161 if len(vsBz) == 0 { 162 err = lerr.ErrUnknownValidators(chainID, height) 163 return 164 } 165 err = dbp.cdc.UnmarshalBinaryLengthPrefixed(vsBz, &valset) 166 if err != nil { 167 return 168 } 169 170 // To test deep equality. This makes it easier to test for e.g. valset 171 // equivalence using assert.Equal (tests for deep equality) in our tests, 172 // which also tests for unexported/private field equivalence. 173 valset.TotalVotingPower() 174 175 return 176 } 177 178 func (dbp *DBProvider) fillFullCommit(sh types.SignedHeader) (FullCommit, error) { 179 var chainID = sh.ChainID 180 var height = sh.Height 181 var valset, nextValset *types.ValidatorSet 182 // Load the validator set. 183 valset, err := dbp.getValidatorSet(chainID, height) 184 if err != nil { 185 return FullCommit{}, err 186 } 187 // Load the next validator set. 188 nextValset, err = dbp.getValidatorSet(chainID, height+1) 189 if err != nil { 190 return FullCommit{}, err 191 } 192 // Return filled FullCommit. 193 return FullCommit{ 194 SignedHeader: sh, 195 Validators: valset, 196 NextValidators: nextValset, 197 }, nil 198 } 199 200 func (dbp *DBProvider) deleteAfterN(chainID string, after int) error { 201 202 dbp.logger.Info("DBProvider.deleteAfterN()...", "chainID", chainID, "after", after) 203 204 itr, err := dbp.db.ReverseIterator( 205 signedHeaderKey(chainID, 1), 206 append(signedHeaderKey(chainID, 1<<63-1), byte(0x00)), 207 ) 208 if err != nil { 209 panic(err) 210 } 211 defer itr.Close() 212 213 var lastHeight int64 = 1<<63 - 1 214 var numSeen = 0 215 var numDeleted = 0 216 217 for itr.Valid() { 218 key := itr.Key() 219 _, height, ok := parseChainKeyPrefix(key) 220 if !ok { 221 return fmt.Errorf("unexpected key %v", key) 222 } 223 224 if height < lastHeight { 225 lastHeight = height 226 numSeen++ 227 } 228 if numSeen > after { 229 dbp.db.Delete(key) 230 numDeleted++ 231 } 232 233 itr.Next() 234 } 235 236 dbp.logger.Info(fmt.Sprintf("DBProvider.deleteAfterN() deleted %v items", numDeleted)) 237 return nil 238 } 239 240 //---------------------------------------- 241 // key encoding 242 243 func signedHeaderKey(chainID string, height int64) []byte { 244 return []byte(fmt.Sprintf("%s/%010d/sh", chainID, height)) 245 } 246 247 func validatorSetKey(chainID string, height int64) []byte { 248 return []byte(fmt.Sprintf("%s/%010d/vs", chainID, height)) 249 } 250 251 //---------------------------------------- 252 // key parsing 253 254 var keyPattern = regexp.MustCompile(`^([^/]+)/([0-9]*)/(.*)$`) 255 256 func parseKey(key []byte) (chainID string, height int64, part string, ok bool) { 257 submatch := keyPattern.FindSubmatch(key) 258 if submatch == nil { 259 return "", 0, "", false 260 } 261 chainID = string(submatch[1]) 262 heightStr := string(submatch[2]) 263 heightInt, err := strconv.Atoi(heightStr) 264 if err != nil { 265 return "", 0, "", false 266 } 267 height = int64(heightInt) 268 part = string(submatch[3]) 269 ok = true // good! 270 return 271 } 272 273 func parseSignedHeaderKey(key []byte) (chainID string, height int64, ok bool) { 274 var part string 275 chainID, height, part, ok = parseKey(key) 276 if part != "sh" { 277 return "", 0, false 278 } 279 return 280 } 281 282 func parseChainKeyPrefix(key []byte) (chainID string, height int64, ok bool) { 283 chainID, height, _, ok = parseKey(key) 284 return 285 }