github.com/core-coin/go-core/v2@v2.1.9/les/lespay/server/clientdb.go (about) 1 // Copyright 2020 by the Authors 2 // This file is part of the go-core library. 3 // 4 // The go-core library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-core library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-core library. If not, see <http://www.gnu.org/licenses/>. 16 17 package server 18 19 import ( 20 "bytes" 21 "encoding/binary" 22 "time" 23 24 lru "github.com/hashicorp/golang-lru" 25 26 "github.com/core-coin/go-core/v2/xcbdb" 27 28 "github.com/core-coin/go-core/v2/common" 29 "github.com/core-coin/go-core/v2/common/mclock" 30 "github.com/core-coin/go-core/v2/les/utils" 31 "github.com/core-coin/go-core/v2/log" 32 "github.com/core-coin/go-core/v2/p2p/enode" 33 "github.com/core-coin/go-core/v2/rlp" 34 ) 35 36 const ( 37 balanceCacheLimit = 8192 // the maximum number of cached items in service token balance queue 38 39 // nodeDBVersion is the version identifier of the node data in db 40 // 41 // Changelog: 42 // Version 0 => 1 43 // * Replace `lastTotal` with `meta` in positive balance: version 0=>1 44 // 45 // Version 1 => 2 46 // * Positive Balance and negative balance is changed: 47 // * Cumulative time is replaced with expiration 48 nodeDBVersion = 2 49 50 // dbCleanupCycle is the cycle of db for useless data cleanup 51 dbCleanupCycle = time.Hour 52 ) 53 54 var ( 55 positiveBalancePrefix = []byte("pb:") // dbVersion(uint16 big endian) + positiveBalancePrefix + id -> balance 56 negativeBalancePrefix = []byte("nb:") // dbVersion(uint16 big endian) + negativeBalancePrefix + ip -> balance 57 expirationKey = []byte("expiration:") // dbVersion(uint16 big endian) + expirationKey -> posExp, negExp 58 ) 59 60 type nodeDB struct { 61 db xcbdb.KeyValueStore 62 cache *lru.Cache 63 auxbuf []byte // 37-byte auxiliary buffer for key encoding 64 verbuf [2]byte // 2-byte auxiliary buffer for db version 65 evictCallBack func(mclock.AbsTime, bool, utils.ExpiredValue) bool // Callback to determine whether the balance can be evicted. 66 clock mclock.Clock 67 closeCh chan struct{} 68 cleanupHook func() // Test hook used for testing 69 } 70 71 func newNodeDB(db xcbdb.KeyValueStore, clock mclock.Clock) *nodeDB { 72 cache, _ := lru.New(balanceCacheLimit) 73 ndb := &nodeDB{ 74 db: db, 75 cache: cache, 76 auxbuf: make([]byte, 37), 77 clock: clock, 78 closeCh: make(chan struct{}), 79 } 80 binary.BigEndian.PutUint16(ndb.verbuf[:], uint16(nodeDBVersion)) 81 go ndb.expirer() 82 return ndb 83 } 84 85 func (db *nodeDB) close() { 86 close(db.closeCh) 87 } 88 89 func (db *nodeDB) getPrefix(neg bool) []byte { 90 prefix := positiveBalancePrefix 91 if neg { 92 prefix = negativeBalancePrefix 93 } 94 return append(db.verbuf[:], prefix...) 95 } 96 97 func (db *nodeDB) key(id []byte, neg bool) []byte { 98 prefix := positiveBalancePrefix 99 if neg { 100 prefix = negativeBalancePrefix 101 } 102 if len(prefix)+len(db.verbuf)+len(id) > len(db.auxbuf) { 103 db.auxbuf = append(db.auxbuf, make([]byte, len(prefix)+len(db.verbuf)+len(id)-len(db.auxbuf))...) 104 } 105 copy(db.auxbuf[:len(db.verbuf)], db.verbuf[:]) 106 copy(db.auxbuf[len(db.verbuf):len(db.verbuf)+len(prefix)], prefix) 107 copy(db.auxbuf[len(prefix)+len(db.verbuf):len(prefix)+len(db.verbuf)+len(id)], id) 108 return db.auxbuf[:len(prefix)+len(db.verbuf)+len(id)] 109 } 110 111 func (db *nodeDB) getExpiration() (utils.Fixed64, utils.Fixed64) { 112 blob, err := db.db.Get(append(db.verbuf[:], expirationKey...)) 113 if err != nil || len(blob) != 16 { 114 return 0, 0 115 } 116 return utils.Fixed64(binary.BigEndian.Uint64(blob[:8])), utils.Fixed64(binary.BigEndian.Uint64(blob[8:16])) 117 } 118 119 func (db *nodeDB) setExpiration(pos, neg utils.Fixed64) { 120 var buff [16]byte 121 binary.BigEndian.PutUint64(buff[:8], uint64(pos)) 122 binary.BigEndian.PutUint64(buff[8:16], uint64(neg)) 123 db.db.Put(append(db.verbuf[:], expirationKey...), buff[:16]) 124 } 125 126 func (db *nodeDB) getOrNewBalance(id []byte, neg bool) utils.ExpiredValue { 127 key := db.key(id, neg) 128 item, exist := db.cache.Get(string(key)) 129 if exist { 130 return item.(utils.ExpiredValue) 131 } 132 var b utils.ExpiredValue 133 enc, err := db.db.Get(key) 134 if err != nil || len(enc) == 0 { 135 return b 136 } 137 if err := rlp.DecodeBytes(enc, &b); err != nil { 138 log.Crit("Failed to decode positive balance", "err", err) 139 } 140 db.cache.Add(string(key), b) 141 return b 142 } 143 144 func (db *nodeDB) setBalance(id []byte, neg bool, b utils.ExpiredValue) { 145 key := db.key(id, neg) 146 enc, err := rlp.EncodeToBytes(&(b)) 147 if err != nil { 148 log.Crit("Failed to encode positive balance", "err", err) 149 } 150 db.db.Put(key, enc) 151 db.cache.Add(string(key), b) 152 } 153 154 func (db *nodeDB) delBalance(id []byte, neg bool) { 155 key := db.key(id, neg) 156 db.db.Delete(key) 157 db.cache.Remove(string(key)) 158 } 159 160 // getPosBalanceIDs returns a lexicographically ordered list of IDs of accounts 161 // with a positive balance 162 func (db *nodeDB) getPosBalanceIDs(start, stop enode.ID, maxCount int) (result []enode.ID) { 163 if maxCount <= 0 { 164 return 165 } 166 prefix := db.getPrefix(false) 167 keylen := len(prefix) + len(enode.ID{}) 168 169 it := db.db.NewIterator(prefix, start.Bytes()) 170 defer it.Release() 171 172 for it.Next() { 173 var id enode.ID 174 if len(it.Key()) != keylen { 175 return 176 } 177 copy(id[:], it.Key()[keylen-len(id):]) 178 if bytes.Compare(id.Bytes(), stop.Bytes()) >= 0 { 179 return 180 } 181 result = append(result, id) 182 if len(result) == maxCount { 183 return 184 } 185 } 186 return 187 } 188 189 // forEachBalance iterates all balances and passes values to callback. 190 func (db *nodeDB) forEachBalance(neg bool, callback func(id enode.ID, balance utils.ExpiredValue) bool) { 191 prefix := db.getPrefix(neg) 192 keylen := len(prefix) + len(enode.ID{}) 193 194 it := db.db.NewIterator(prefix, nil) 195 defer it.Release() 196 197 for it.Next() { 198 var id enode.ID 199 if len(it.Key()) != keylen { 200 return 201 } 202 copy(id[:], it.Key()[keylen-len(id):]) 203 204 var b utils.ExpiredValue 205 if err := rlp.DecodeBytes(it.Value(), &b); err != nil { 206 continue 207 } 208 if !callback(id, b) { 209 return 210 } 211 } 212 } 213 214 func (db *nodeDB) expirer() { 215 for { 216 select { 217 case <-db.clock.After(dbCleanupCycle): 218 db.expireNodes() 219 case <-db.closeCh: 220 return 221 } 222 } 223 } 224 225 // expireNodes iterates the whole node db and checks whether the 226 // token balances can be deleted. 227 func (db *nodeDB) expireNodes() { 228 var ( 229 visited int 230 deleted int 231 start = time.Now() 232 ) 233 for _, neg := range []bool{false, true} { 234 iter := db.db.NewIterator(db.getPrefix(neg), nil) 235 for iter.Next() { 236 visited++ 237 var balance utils.ExpiredValue 238 if err := rlp.DecodeBytes(iter.Value(), &balance); err != nil { 239 log.Crit("Failed to decode negative balance", "err", err) 240 } 241 if db.evictCallBack != nil && db.evictCallBack(db.clock.Now(), neg, balance) { 242 deleted++ 243 db.db.Delete(iter.Key()) 244 } 245 } 246 } 247 // Invoke testing hook if it's not nil. 248 if db.cleanupHook != nil { 249 db.cleanupHook() 250 } 251 log.Debug("Expire nodes", "visited", visited, "deleted", deleted, "elapsed", common.PrettyDuration(time.Since(start))) 252 }