11 log "github.com/sirupsen/logrus"
13 dbm "github.com/vapor/database/leveldb"
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.
40 ControlProgramIndex uint64
45 // reservation describes a reservation of a set of UTXOs
46 type reservation struct {
53 type utxoKeeper struct {
54 // `sync/atomic` expects the first word in an allocated struct to be 64-bit
55 // aligned on both ARM and x86-32. See https://goo.gl/zW7dgq for more details.
59 currentHeight func() uint64
61 unconfirmed map[bc.Hash]*UTXO
62 reserved map[bc.Hash]uint64
63 reservations map[uint64]*reservation
66 func newUtxoKeeper(f func() uint64, walletdb dbm.DB) *utxoKeeper {
70 unconfirmed: make(map[bc.Hash]*UTXO),
71 reserved: make(map[bc.Hash]uint64),
72 reservations: make(map[uint64]*reservation),
78 func (uk *utxoKeeper) AddUnconfirmedUtxo(utxos []*UTXO) {
82 for _, utxo := range utxos {
83 uk.unconfirmed[utxo.OutputID] = utxo
87 // Cancel canceling the reservation with the provided ID.
88 func (uk *utxoKeeper) Cancel(rid uint64) {
94 // ListUnconfirmed return all the unconfirmed utxos
95 func (uk *utxoKeeper) ListUnconfirmed() []*UTXO {
100 for _, utxo := range uk.unconfirmed {
101 utxos = append(utxos, utxo)
106 func (uk *utxoKeeper) RemoveUnconfirmedUtxo(hashes []*bc.Hash) {
108 defer uk.mtx.Unlock()
110 for _, hash := range hashes {
111 delete(uk.unconfirmed, *hash)
115 func (uk *utxoKeeper) Reserve(accountID string, assetID *bc.AssetID, amount uint64, useUnconfirmed bool, exp time.Time) (*reservation, error) {
117 defer uk.mtx.Unlock()
119 utxos, immatureAmount := uk.findUtxos(accountID, assetID, useUnconfirmed)
120 optUtxos, optAmount, reservedAmount := uk.optUTXOs(utxos, amount)
121 if optAmount+reservedAmount+immatureAmount < amount {
122 return nil, ErrInsufficient
125 if optAmount+reservedAmount < amount {
126 return nil, ErrImmature
129 if optAmount < amount {
130 return nil, ErrReserved
133 result := &reservation{
134 id: atomic.AddUint64(&uk.nextIndex, 1),
136 change: optAmount - amount,
140 uk.reservations[result.id] = result
141 for _, u := range optUtxos {
142 uk.reserved[u.OutputID] = result.id
147 func (uk *utxoKeeper) ReserveParticular(outHash bc.Hash, useUnconfirmed bool, exp time.Time) (*reservation, error) {
149 defer uk.mtx.Unlock()
151 if _, ok := uk.reserved[outHash]; ok {
152 return nil, ErrReserved
155 u, err := uk.findUtxo(outHash, useUnconfirmed)
160 if u.ValidHeight > uk.currentHeight() {
161 return nil, ErrImmature
164 result := &reservation{
165 id: atomic.AddUint64(&uk.nextIndex, 1),
169 uk.reservations[result.id] = result
170 uk.reserved[u.OutputID] = result.id
174 func (uk *utxoKeeper) cancel(rid uint64) {
175 res, ok := uk.reservations[rid]
180 delete(uk.reservations, rid)
181 for _, utxo := range res.utxos {
182 delete(uk.reserved, utxo.OutputID)
186 func (uk *utxoKeeper) expireWorker() {
187 ticker := time.NewTicker(1000 * time.Millisecond)
190 for now := range ticker.C {
191 uk.expireReservation(now)
195 func (uk *utxoKeeper) expireReservation(t time.Time) {
197 defer uk.mtx.Unlock()
199 for rid, res := range uk.reservations {
200 if res.expiry.Before(t) {
206 func (uk *utxoKeeper) findUtxos(accountID string, assetID *bc.AssetID, useUnconfirmed bool) ([]*UTXO, uint64) {
207 immatureAmount := uint64(0)
208 currentHeight := uk.currentHeight()
210 appendUtxo := func(u *UTXO) {
211 if u.AccountID != accountID || u.AssetID != *assetID {
214 if u.ValidHeight > currentHeight {
215 immatureAmount += u.Amount
217 utxos = append(utxos, u)
221 utxoIter := uk.db.IteratorPrefix([]byte(UTXOPreFix))
222 defer utxoIter.Release()
223 for utxoIter.Next() {
225 if err := json.Unmarshal(utxoIter.Value(), u); err != nil {
226 log.WithFields(log.Fields{"module": logModule, "err": err}).Error("utxoKeeper findUtxos fail on unmarshal utxo")
232 return utxos, immatureAmount
235 for _, u := range uk.unconfirmed {
238 return utxos, immatureAmount
241 func (uk *utxoKeeper) findUtxo(outHash bc.Hash, useUnconfirmed bool) (*UTXO, error) {
242 if u, ok := uk.unconfirmed[outHash]; useUnconfirmed && ok {
247 if data := uk.db.Get(StandardUTXOKey(outHash)); data != nil {
248 return u, json.Unmarshal(data, u)
250 if data := uk.db.Get(ContractUTXOKey(outHash)); data != nil {
251 return u, json.Unmarshal(data, u)
253 return nil, ErrMatchUTXO
256 func (uk *utxoKeeper) optUTXOs(utxos []*UTXO, amount uint64) ([]*UTXO, uint64, uint64) {
257 //sort the utxo by amount, bigger amount in front
258 var optAmount, reservedAmount uint64
259 sort.Slice(utxos, func(i, j int) bool {
260 return utxos[i].Amount > utxos[j].Amount
263 //push all the available utxos into list
264 utxoList := list.New()
265 for _, u := range utxos {
266 if _, ok := uk.reserved[u.OutputID]; ok {
267 reservedAmount += u.Amount
273 optList := list.New()
274 for node := utxoList.Front(); node != nil; node = node.Next() {
275 //append utxo if we haven't reached the required amount
276 if optAmount < amount {
277 optList.PushBack(node.Value)
278 optAmount += node.Value.(*UTXO).Amount
282 largestNode := optList.Front()
283 replaceList := list.New()
284 replaceAmount := optAmount - largestNode.Value.(*UTXO).Amount
286 for ; node != nil && replaceList.Len() <= desireUtxoCount-optList.Len(); node = node.Next() {
287 replaceList.PushBack(node.Value)
288 if replaceAmount += node.Value.(*UTXO).Amount; replaceAmount >= amount {
289 optList.Remove(largestNode)
290 optList.PushBackList(replaceList)
291 optAmount = replaceAmount
296 //largestNode remaining the same means that there is nothing to be replaced
297 if largestNode == optList.Front() {
302 optUtxos := []*UTXO{}
303 for e := optList.Front(); e != nil; e = e.Next() {
304 optUtxos = append(optUtxos, e.Value.(*UTXO))
306 return optUtxos, optAmount, reservedAmount