12 log "github.com/sirupsen/logrus"
14 dbm "github.com/vapor/database/leveldb"
15 "github.com/vapor/errors"
16 "github.com/vapor/protocol/bc"
19 const desireUtxoCount = 5
21 // pre-define error types
23 ErrInsufficient = errors.New("reservation found insufficient funds")
24 ErrImmature = errors.New("reservation found immature funds")
25 ErrVoteLock = errors.New("Locked by the vote")
26 ErrReserved = errors.New("reservation found outputs already reserved")
27 ErrMatchUTXO = errors.New("can't find utxo with given hash")
28 ErrReservation = errors.New("couldn't find reservation")
31 // UTXO describes an individual account utxo.
42 ControlProgramIndex uint64
47 // reservation describes a reservation of a set of UTXOs
48 type reservation struct {
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.
61 currentHeight func() uint64
63 unconfirmed map[bc.Hash]*UTXO
64 reserved map[bc.Hash]uint64
65 reservations map[uint64]*reservation
68 func newUtxoKeeper(f func() uint64, walletdb dbm.DB) *utxoKeeper {
72 unconfirmed: make(map[bc.Hash]*UTXO),
73 reserved: make(map[bc.Hash]uint64),
74 reservations: make(map[uint64]*reservation),
80 func (uk *utxoKeeper) AddUnconfirmedUtxo(utxos []*UTXO) {
84 for _, utxo := range utxos {
85 uk.unconfirmed[utxo.OutputID] = utxo
89 // Cancel canceling the reservation with the provided ID.
90 func (uk *utxoKeeper) Cancel(rid uint64) {
96 // ListUnconfirmed return all the unconfirmed utxos
97 func (uk *utxoKeeper) ListUnconfirmed() []*UTXO {
102 for _, utxo := range uk.unconfirmed {
103 utxos = append(utxos, utxo)
108 func (uk *utxoKeeper) RemoveUnconfirmedUtxo(hashes []*bc.Hash) {
110 defer uk.mtx.Unlock()
112 for _, hash := range hashes {
113 delete(uk.unconfirmed, *hash)
117 func (uk *utxoKeeper) Reserve(accountID string, assetID *bc.AssetID, amount uint64, useUnconfirmed bool, vote []byte, exp time.Time) (*reservation, error) {
119 defer uk.mtx.Unlock()
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
127 if optAmount+reservedAmount < amount {
129 return nil, ErrVoteLock
131 return nil, ErrImmature
134 if optAmount < amount {
135 return nil, ErrReserved
138 result := &reservation{
139 id: atomic.AddUint64(&uk.nextIndex, 1),
141 change: optAmount - amount,
145 uk.reservations[result.id] = result
146 for _, u := range optUtxos {
147 uk.reserved[u.OutputID] = result.id
152 func (uk *utxoKeeper) ReserveParticular(outHash bc.Hash, useUnconfirmed bool, exp time.Time) (*reservation, error) {
154 defer uk.mtx.Unlock()
156 if _, ok := uk.reserved[outHash]; ok {
157 return nil, ErrReserved
160 u, err := uk.findUtxo(outHash, useUnconfirmed)
165 if u.ValidHeight > uk.currentHeight() {
166 return nil, ErrImmature
169 result := &reservation{
170 id: atomic.AddUint64(&uk.nextIndex, 1),
174 uk.reservations[result.id] = result
175 uk.reserved[u.OutputID] = result.id
179 func (uk *utxoKeeper) cancel(rid uint64) {
180 res, ok := uk.reservations[rid]
185 delete(uk.reservations, rid)
186 for _, utxo := range res.utxos {
187 delete(uk.reserved, utxo.OutputID)
191 func (uk *utxoKeeper) expireWorker() {
192 ticker := time.NewTicker(1000 * time.Millisecond)
195 for now := range ticker.C {
196 uk.expireReservation(now)
200 func (uk *utxoKeeper) expireReservation(t time.Time) {
202 defer uk.mtx.Unlock()
204 for rid, res := range uk.reservations {
205 if res.expiry.Before(t) {
211 func (uk *utxoKeeper) findUtxos(accountID string, assetID *bc.AssetID, useUnconfirmed bool, vote []byte) ([]*UTXO, uint64) {
212 immatureAmount := uint64(0)
213 currentHeight := uk.currentHeight()
215 appendUtxo := func(u *UTXO) {
216 if u.AccountID != accountID || u.AssetID != *assetID || !bytes.Equal(u.Vote, vote) {
219 if u.ValidHeight > currentHeight {
220 immatureAmount += u.Amount
222 utxos = append(utxos, u)
226 utxoIter := uk.db.IteratorPrefix([]byte(UTXOPreFix))
227 defer utxoIter.Release()
228 for utxoIter.Next() {
230 if err := json.Unmarshal(utxoIter.Value(), u); err != nil {
231 log.WithFields(log.Fields{"module": logModule, "err": err}).Error("utxoKeeper findUtxos fail on unmarshal utxo")
237 return utxos, immatureAmount
240 for _, u := range uk.unconfirmed {
243 return utxos, immatureAmount
246 func (uk *utxoKeeper) findUtxo(outHash bc.Hash, useUnconfirmed bool) (*UTXO, error) {
247 if u, ok := uk.unconfirmed[outHash]; useUnconfirmed && ok {
252 if data := uk.db.Get(StandardUTXOKey(outHash)); data != nil {
253 return u, json.Unmarshal(data, u)
255 if data := uk.db.Get(ContractUTXOKey(outHash)); data != nil {
256 return u, json.Unmarshal(data, u)
258 return nil, ErrMatchUTXO
261 func (uk *utxoKeeper) optUTXOs(utxos []*UTXO, amount uint64) ([]*UTXO, uint64, uint64) {
262 //sort the utxo by amount, bigger amount in front
263 var optAmount, reservedAmount uint64
264 sort.Slice(utxos, func(i, j int) bool {
265 return utxos[i].Amount > utxos[j].Amount
268 //push all the available utxos into list
269 utxoList := list.New()
270 for _, u := range utxos {
271 if _, ok := uk.reserved[u.OutputID]; ok {
272 reservedAmount += u.Amount
278 optList := list.New()
279 for node := utxoList.Front(); node != nil; node = node.Next() {
280 //append utxo if we haven't reached the required amount
281 if optAmount < amount {
282 optList.PushBack(node.Value)
283 optAmount += node.Value.(*UTXO).Amount
287 largestNode := optList.Front()
288 replaceList := list.New()
289 replaceAmount := optAmount - largestNode.Value.(*UTXO).Amount
291 for ; node != nil && replaceList.Len() <= desireUtxoCount-optList.Len(); node = node.Next() {
292 replaceList.PushBack(node.Value)
293 if replaceAmount += node.Value.(*UTXO).Amount; replaceAmount >= amount {
294 optList.Remove(largestNode)
295 optList.PushBackList(replaceList)
296 optAmount = replaceAmount
301 //largestNode remaining the same means that there is nothing to be replaced
302 if largestNode == optList.Front() {
307 optUtxos := []*UTXO{}
308 for e := optList.Front(); e != nil; e = e.Next() {
309 optUtxos = append(optUtxos, e.Value.(*UTXO))
311 return optUtxos, optAmount, reservedAmount