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