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