11 log "github.com/sirupsen/logrus"
12 dbm "github.com/tendermint/tmlibs/db"
14 "github.com/vapor/errors"
15 "github.com/vapor/protocol/bc"
18 const desireUtxoCount = 5
20 // pre-define error types
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")
29 // UTXO describes an individual account utxo.
39 ControlProgramIndex uint64
44 // reservation describes a reservation of a set of UTXOs
45 type reservation struct {
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.
58 currentHeight func() uint64
60 unconfirmed map[bc.Hash]*UTXO
61 reserved map[bc.Hash]uint64
62 reservations map[uint64]*reservation
65 func newUtxoKeeper(f func() uint64, walletdb dbm.DB) *utxoKeeper {
69 unconfirmed: make(map[bc.Hash]*UTXO),
70 reserved: make(map[bc.Hash]uint64),
71 reservations: make(map[uint64]*reservation),
77 func (uk *utxoKeeper) AddUnconfirmedUtxo(utxos []*UTXO) {
81 for _, utxo := range utxos {
82 uk.unconfirmed[utxo.OutputID] = utxo
86 // Cancel canceling the reservation with the provided ID.
87 func (uk *utxoKeeper) Cancel(rid uint64) {
93 // ListUnconfirmed return all the unconfirmed utxos
94 func (uk *utxoKeeper) ListUnconfirmed() []*UTXO {
99 for _, utxo := range uk.unconfirmed {
100 utxos = append(utxos, utxo)
105 func (uk *utxoKeeper) RemoveUnconfirmedUtxo(hashes []*bc.Hash) {
107 defer uk.mtx.Unlock()
109 for _, hash := range hashes {
110 delete(uk.unconfirmed, *hash)
114 func (uk *utxoKeeper) Reserve(accountID string, assetID *bc.AssetID, amount uint64, useUnconfirmed bool, exp time.Time) (*reservation, error) {
116 defer uk.mtx.Unlock()
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
124 if optAmount+reservedAmount < amount {
125 return nil, ErrImmature
128 if optAmount < amount {
129 return nil, ErrReserved
132 result := &reservation{
133 id: atomic.AddUint64(&uk.nextIndex, 1),
135 change: optAmount - amount,
139 uk.reservations[result.id] = result
140 for _, u := range optUtxos {
141 uk.reserved[u.OutputID] = result.id
146 func (uk *utxoKeeper) ReserveByAddress(address string, assetID *bc.AssetID, amount uint64, useUnconfirmed bool, isReserved bool) (*reservation, error) {
148 defer uk.mtx.Unlock()
150 utxos, immatureAmount := uk.findUtxosByAddress(address, assetID, useUnconfirmed)
151 optUtxos, optAmount, reservedAmount := uk.optUTXOs(utxos, amount)
152 if optAmount+reservedAmount+immatureAmount < amount {
153 return nil, ErrInsufficient
155 if optAmount+reservedAmount < amount {
156 return nil, ErrImmature
159 if optAmount < amount {
160 return nil, ErrReserved
163 result := &reservation{
164 id: atomic.AddUint64(&uk.nextIndex, 1),
166 change: optAmount - amount,
169 uk.reservations[result.id] = result
171 for _, u := range optUtxos {
172 uk.reserved[u.OutputID] = result.id
179 func (uk *utxoKeeper) ReserveParticular(outHash bc.Hash, useUnconfirmed bool, exp time.Time) (*reservation, error) {
181 defer uk.mtx.Unlock()
183 if _, ok := uk.reserved[outHash]; ok {
184 return nil, ErrReserved
187 u, err := uk.findUtxo(outHash, useUnconfirmed)
192 if u.ValidHeight > uk.currentHeight() {
193 return nil, ErrImmature
196 result := &reservation{
197 id: atomic.AddUint64(&uk.nextIndex, 1),
201 uk.reservations[result.id] = result
202 uk.reserved[u.OutputID] = result.id
206 func (uk *utxoKeeper) cancel(rid uint64) {
207 res, ok := uk.reservations[rid]
212 delete(uk.reservations, rid)
213 for _, utxo := range res.utxos {
214 delete(uk.reserved, utxo.OutputID)
218 func (uk *utxoKeeper) expireWorker() {
219 ticker := time.NewTicker(1000 * time.Millisecond)
220 for now := range ticker.C {
221 uk.expireReservation(now)
224 func (uk *utxoKeeper) expireReservation(t time.Time) {
226 defer uk.mtx.Unlock()
228 for rid, res := range uk.reservations {
229 if res.expiry.Before(t) {
235 func (uk *utxoKeeper) findUtxos(accountID string, assetID *bc.AssetID, useUnconfirmed bool) ([]*UTXO, uint64) {
236 immatureAmount := uint64(0)
237 currentHeight := uk.currentHeight()
239 appendUtxo := func(u *UTXO) {
240 if u.AccountID != accountID || u.AssetID != *assetID {
243 if u.ValidHeight > currentHeight {
244 immatureAmount += u.Amount
246 utxos = append(utxos, u)
250 utxoIter := uk.db.IteratorPrefix([]byte(UTXOPreFix))
251 defer utxoIter.Release()
252 for utxoIter.Next() {
254 if err := json.Unmarshal(utxoIter.Value(), u); err != nil {
255 log.WithField("err", err).Error("utxoKeeper findUtxos fail on unmarshal utxo")
261 return utxos, immatureAmount
264 for _, u := range uk.unconfirmed {
267 return utxos, immatureAmount
270 func (uk *utxoKeeper) findUtxosByAddress(address string, assetID *bc.AssetID, useUnconfirmed bool) ([]*UTXO, uint64) {
271 immatureAmount := uint64(0)
272 currentHeight := uk.currentHeight()
274 appendUtxo := func(u *UTXO) {
275 if u.Address != address || u.AssetID != *assetID {
278 if u.ValidHeight > currentHeight {
279 immatureAmount += u.Amount
281 utxos = append(utxos, u)
285 utxoIter := uk.db.IteratorPrefix([]byte(UTXOPreFix))
286 defer utxoIter.Release()
287 for utxoIter.Next() {
289 if err := json.Unmarshal(utxoIter.Value(), u); err != nil {
290 log.WithField("err", err).Error("utxoKeeper findUtxos fail on unmarshal utxo")
296 return utxos, immatureAmount
299 for _, u := range uk.unconfirmed {
302 return utxos, immatureAmount
305 func (uk *utxoKeeper) findUtxo(outHash bc.Hash, useUnconfirmed bool) (*UTXO, error) {
306 if u, ok := uk.unconfirmed[outHash]; useUnconfirmed && ok {
311 if data := uk.db.Get(StandardUTXOKey(outHash)); data != nil {
312 return u, json.Unmarshal(data, u)
314 if data := uk.db.Get(ContractUTXOKey(outHash)); data != nil {
315 return u, json.Unmarshal(data, u)
317 return nil, ErrMatchUTXO
320 func (uk *utxoKeeper) optUTXOs(utxos []*UTXO, amount uint64) ([]*UTXO, uint64, uint64) {
321 //sort the utxo by amount, bigger amount in front
322 var optAmount, reservedAmount uint64
323 sort.Slice(utxos, func(i, j int) bool {
324 return utxos[i].Amount > utxos[j].Amount
327 //push all the available utxos into list
328 utxoList := list.New()
329 for _, u := range utxos {
330 if _, ok := uk.reserved[u.OutputID]; ok {
331 reservedAmount += u.Amount
337 optList := list.New()
338 for node := utxoList.Front(); node != nil; node = node.Next() {
339 //append utxo if we haven't reached the required amount
340 if optAmount < amount {
341 optList.PushBack(node.Value)
342 optAmount += node.Value.(*UTXO).Amount
346 largestNode := optList.Front()
347 replaceList := list.New()
348 replaceAmount := optAmount - largestNode.Value.(*UTXO).Amount
350 for ; node != nil && replaceList.Len() <= desireUtxoCount-optList.Len(); node = node.Next() {
351 replaceList.PushBack(node.Value)
352 if replaceAmount += node.Value.(*UTXO).Amount; replaceAmount >= amount {
353 optList.Remove(largestNode)
354 optList.PushBackList(replaceList)
355 optAmount = replaceAmount
360 //largestNode remaining the same means that there is nothing to be replaced
361 if largestNode == optList.Front() {
366 optUtxos := []*UTXO{}
367 for e := optList.Front(); e != nil; e = e.Next() {
368 optUtxos = append(optUtxos, e.Value.(*UTXO))
370 return optUtxos, optAmount, reservedAmount