github.com/bytom/bytom@v1.1.2-0.20221014091027-bbcba3df6075/account/utxo_keeper.go (about) 1 package account 2 3 import ( 4 "bytes" 5 "container/list" 6 "encoding/json" 7 "sort" 8 "sync" 9 "sync/atomic" 10 "time" 11 12 log "github.com/sirupsen/logrus" 13 14 dbm "github.com/bytom/bytom/database/leveldb" 15 "github.com/bytom/bytom/errors" 16 "github.com/bytom/bytom/protocol/bc" 17 ) 18 19 const desireUtxoCount = 5 20 21 // pre-define error types 22 var ( 23 ErrInsufficient = errors.New("reservation found insufficient funds") 24 ErrImmature = errors.New("reservation found immature funds") 25 ErrReserved = errors.New("reservation found outputs already reserved") 26 ErrMatchUTXO = errors.New("can't find utxo with given hash") 27 ErrReservation = errors.New("couldn't find reservation") 28 ) 29 30 // UTXO describes an individual account utxo. 31 type UTXO struct { 32 OutputID bc.Hash 33 SourceID bc.Hash 34 AssetID bc.AssetID 35 Amount uint64 36 SourcePos uint64 37 ControlProgram []byte 38 Vote []byte 39 StateData [][]byte 40 AccountID string 41 Address string 42 ControlProgramIndex uint64 43 ValidHeight uint64 44 Change bool 45 } 46 47 // reservation describes a reservation of a set of UTXOs 48 type reservation struct { 49 id uint64 50 utxos []*UTXO 51 change uint64 52 expiry time.Time 53 } 54 55 type utxoKeeper struct { 56 // `sync/atomic` expects the first word in an allocated struct to be 64-bit 57 // aligned on both ARM and x86-32. See https://goo.gl/zW7dgq for more details. 58 nextIndex uint64 59 db dbm.DB 60 mtx sync.RWMutex 61 currentHeight func() uint64 62 63 unconfirmed map[bc.Hash]*UTXO 64 reserved map[bc.Hash]uint64 65 reservations map[uint64]*reservation 66 } 67 68 func newUtxoKeeper(f func() uint64, walletdb dbm.DB) *utxoKeeper { 69 uk := &utxoKeeper{ 70 db: walletdb, 71 currentHeight: f, 72 unconfirmed: make(map[bc.Hash]*UTXO), 73 reserved: make(map[bc.Hash]uint64), 74 reservations: make(map[uint64]*reservation), 75 } 76 go uk.expireWorker() 77 return uk 78 } 79 80 func (uk *utxoKeeper) AddUnconfirmedUtxo(utxos []*UTXO) { 81 uk.mtx.Lock() 82 defer uk.mtx.Unlock() 83 84 for _, utxo := range utxos { 85 uk.unconfirmed[utxo.OutputID] = utxo 86 } 87 } 88 89 // Cancel canceling the reservation with the provided ID. 90 func (uk *utxoKeeper) Cancel(rid uint64) { 91 uk.mtx.Lock() 92 uk.cancel(rid) 93 uk.mtx.Unlock() 94 } 95 96 // ListUnconfirmed return all the unconfirmed utxos 97 func (uk *utxoKeeper) ListUnconfirmed() []*UTXO { 98 uk.mtx.Lock() 99 defer uk.mtx.Unlock() 100 101 utxos := []*UTXO{} 102 for _, utxo := range uk.unconfirmed { 103 utxos = append(utxos, utxo) 104 } 105 return utxos 106 } 107 108 func (uk *utxoKeeper) RemoveUnconfirmedUtxo(hashes []*bc.Hash) { 109 uk.mtx.Lock() 110 defer uk.mtx.Unlock() 111 112 for _, hash := range hashes { 113 delete(uk.unconfirmed, *hash) 114 } 115 } 116 117 func (uk *utxoKeeper) Reserve(accountID string, assetID *bc.AssetID, amount uint64, useUnconfirmed bool, vote []byte, exp time.Time) (*reservation, error) { 118 uk.mtx.Lock() 119 defer uk.mtx.Unlock() 120 121 utxos, immatureAmount := uk.findUtxos(accountID, assetID, useUnconfirmed, vote) 122 optUtxos, optAmount, reservedAmount := uk.optUTXOs(utxos, amount) 123 if optAmount+reservedAmount+immatureAmount < amount { 124 return nil, ErrInsufficient 125 } 126 127 if optAmount+reservedAmount < amount { 128 return nil, ErrImmature 129 } 130 131 if optAmount < amount { 132 return nil, ErrReserved 133 } 134 135 result := &reservation{ 136 id: atomic.AddUint64(&uk.nextIndex, 1), 137 utxos: optUtxos, 138 change: optAmount - amount, 139 expiry: exp, 140 } 141 142 uk.reservations[result.id] = result 143 for _, u := range optUtxos { 144 uk.reserved[u.OutputID] = result.id 145 } 146 return result, nil 147 } 148 149 func (uk *utxoKeeper) ReserveParticular(outHash bc.Hash, useUnconfirmed bool, exp time.Time) (*reservation, error) { 150 uk.mtx.Lock() 151 defer uk.mtx.Unlock() 152 153 if _, ok := uk.reserved[outHash]; ok { 154 return nil, ErrReserved 155 } 156 157 u, err := uk.findUtxo(outHash, useUnconfirmed) 158 if err != nil { 159 return nil, err 160 } 161 162 if u.ValidHeight > uk.currentHeight() { 163 return nil, ErrImmature 164 } 165 166 result := &reservation{ 167 id: atomic.AddUint64(&uk.nextIndex, 1), 168 utxos: []*UTXO{u}, 169 expiry: exp, 170 } 171 uk.reservations[result.id] = result 172 uk.reserved[u.OutputID] = result.id 173 return result, nil 174 } 175 176 func (uk *utxoKeeper) cancel(rid uint64) { 177 res, ok := uk.reservations[rid] 178 if !ok { 179 return 180 } 181 182 delete(uk.reservations, rid) 183 for _, utxo := range res.utxos { 184 delete(uk.reserved, utxo.OutputID) 185 } 186 } 187 188 func (uk *utxoKeeper) expireWorker() { 189 ticker := time.NewTicker(1000 * time.Millisecond) 190 defer ticker.Stop() 191 192 for now := range ticker.C { 193 uk.expireReservation(now) 194 } 195 } 196 197 func (uk *utxoKeeper) expireReservation(t time.Time) { 198 uk.mtx.Lock() 199 defer uk.mtx.Unlock() 200 201 for rid, res := range uk.reservations { 202 if res.expiry.Before(t) { 203 uk.cancel(rid) 204 } 205 } 206 } 207 208 func (uk *utxoKeeper) findUtxos(accountID string, assetID *bc.AssetID, useUnconfirmed bool, vote []byte) ([]*UTXO, uint64) { 209 immatureAmount := uint64(0) 210 currentHeight := uk.currentHeight() 211 utxos := []*UTXO{} 212 appendUtxo := func(u *UTXO) { 213 if u.AccountID != accountID || u.AssetID != *assetID || !bytes.Equal(u.Vote, vote) { 214 return 215 } 216 if u.ValidHeight > currentHeight { 217 immatureAmount += u.Amount 218 } else { 219 utxos = append(utxos, u) 220 } 221 } 222 223 utxoIter := uk.db.IteratorPrefix([]byte(UTXOPreFix)) 224 defer utxoIter.Release() 225 for utxoIter.Next() { 226 u := &UTXO{} 227 if err := json.Unmarshal(utxoIter.Value(), u); err != nil { 228 log.WithFields(log.Fields{"module": logModule, "err": err}).Error("utxoKeeper findUtxos fail on unmarshal utxo") 229 continue 230 } 231 appendUtxo(u) 232 } 233 if !useUnconfirmed { 234 return utxos, immatureAmount 235 } 236 237 for _, u := range uk.unconfirmed { 238 appendUtxo(u) 239 } 240 return utxos, immatureAmount 241 } 242 243 func (uk *utxoKeeper) findUtxo(outHash bc.Hash, useUnconfirmed bool) (*UTXO, error) { 244 if u, ok := uk.unconfirmed[outHash]; useUnconfirmed && ok { 245 return u, nil 246 } 247 248 u := &UTXO{} 249 if data := uk.db.Get(StandardUTXOKey(outHash)); data != nil { 250 return u, json.Unmarshal(data, u) 251 } 252 if data := uk.db.Get(ContractUTXOKey(outHash)); data != nil { 253 return u, json.Unmarshal(data, u) 254 } 255 return nil, ErrMatchUTXO 256 } 257 258 func (uk *utxoKeeper) optUTXOs(utxos []*UTXO, amount uint64) ([]*UTXO, uint64, uint64) { 259 //sort the utxo by amount, bigger amount in front 260 var optAmount, reservedAmount uint64 261 sort.Slice(utxos, func(i, j int) bool { 262 return utxos[i].Amount > utxos[j].Amount 263 }) 264 265 //push all the available utxos into list 266 utxoList := list.New() 267 for _, u := range utxos { 268 if _, ok := uk.reserved[u.OutputID]; ok { 269 reservedAmount += u.Amount 270 continue 271 } 272 utxoList.PushBack(u) 273 } 274 275 optList := list.New() 276 for node := utxoList.Front(); node != nil; node = node.Next() { 277 //append utxo if we haven't reached the required amount 278 if optAmount < amount { 279 optList.PushBack(node.Value) 280 optAmount += node.Value.(*UTXO).Amount 281 continue 282 } 283 284 largestNode := optList.Front() 285 replaceList := list.New() 286 replaceAmount := optAmount - largestNode.Value.(*UTXO).Amount 287 288 for ; node != nil && replaceList.Len() <= desireUtxoCount-optList.Len(); node = node.Next() { 289 replaceList.PushBack(node.Value) 290 if replaceAmount += node.Value.(*UTXO).Amount; replaceAmount >= amount { 291 optList.Remove(largestNode) 292 optList.PushBackList(replaceList) 293 optAmount = replaceAmount 294 break 295 } 296 } 297 298 //largestNode remaining the same means that there is nothing to be replaced 299 if largestNode == optList.Front() { 300 break 301 } 302 } 303 304 optUtxos := []*UTXO{} 305 for e := optList.Front(); e != nil; e = e.Next() { 306 optUtxos = append(optUtxos, e.Value.(*UTXO)) 307 } 308 return optUtxos, optAmount, reservedAmount 309 }