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