12 log "github.com/sirupsen/logrus"
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 ErrVoteLock = errors.New("Locked by the vote")
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")
30 // UTXO describes an individual account utxo.
41 ControlProgramIndex uint64
46 // reservation describes a reservation of a set of UTXOs
47 type reservation struct {
54 type utxoKeeper struct {
55 // `sync/atomic` expects the first word in an allocated struct to be 64-bit
56 // aligned on both ARM and x86-32. See https://goo.gl/zW7dgq for more details.
60 currentHeight func() uint64
62 unconfirmed map[bc.Hash]*UTXO
63 reserved map[bc.Hash]uint64
64 reservations map[uint64]*reservation
67 func newUtxoKeeper(f func() uint64, store AccountStorer) *utxoKeeper {
71 unconfirmed: make(map[bc.Hash]*UTXO),
72 reserved: make(map[bc.Hash]uint64),
73 reservations: make(map[uint64]*reservation),
79 func (uk *utxoKeeper) AddUnconfirmedUtxo(utxos []*UTXO) {
83 for _, utxo := range utxos {
84 uk.unconfirmed[utxo.OutputID] = utxo
88 // Cancel canceling the reservation with the provided ID.
89 func (uk *utxoKeeper) Cancel(rid uint64) {
95 // ListUnconfirmed return all the unconfirmed utxos
96 func (uk *utxoKeeper) ListUnconfirmed() []*UTXO {
101 for _, utxo := range uk.unconfirmed {
102 utxos = append(utxos, utxo)
107 func (uk *utxoKeeper) RemoveUnconfirmedUtxo(hashes []*bc.Hash) {
109 defer uk.mtx.Unlock()
111 for _, hash := range hashes {
112 delete(uk.unconfirmed, *hash)
116 func (uk *utxoKeeper) Reserve(accountID string, assetID *bc.AssetID, amount uint64, useUnconfirmed bool, vote []byte, exp time.Time) (*reservation, error) {
118 defer uk.mtx.Unlock()
120 utxos, immatureAmount := uk.findUtxos(accountID, assetID, useUnconfirmed, vote)
121 optUtxos, optAmount, reservedAmount := uk.optUTXOs(utxos, amount)
122 if optAmount+reservedAmount+immatureAmount < amount {
123 return nil, ErrInsufficient
126 if optAmount+reservedAmount < amount {
128 return nil, ErrVoteLock
130 return nil, ErrImmature
133 if optAmount < amount {
134 return nil, ErrReserved
137 result := &reservation{
138 id: atomic.AddUint64(&uk.nextIndex, 1),
140 change: optAmount - amount,
144 uk.reservations[result.id] = result
145 for _, u := range optUtxos {
146 uk.reserved[u.OutputID] = result.id
151 func (uk *utxoKeeper) ReserveParticular(outHash bc.Hash, useUnconfirmed bool, exp time.Time) (*reservation, error) {
153 defer uk.mtx.Unlock()
155 if _, ok := uk.reserved[outHash]; ok {
156 return nil, ErrReserved
159 u, err := uk.findUtxo(outHash, useUnconfirmed)
164 if u.ValidHeight > uk.currentHeight() {
165 return nil, ErrImmature
168 result := &reservation{
169 id: atomic.AddUint64(&uk.nextIndex, 1),
173 uk.reservations[result.id] = result
174 uk.reserved[u.OutputID] = result.id
178 func (uk *utxoKeeper) cancel(rid uint64) {
179 res, ok := uk.reservations[rid]
184 delete(uk.reservations, rid)
185 for _, utxo := range res.utxos {
186 delete(uk.reserved, utxo.OutputID)
190 func (uk *utxoKeeper) expireWorker() {
191 ticker := time.NewTicker(1000 * time.Millisecond)
194 for now := range ticker.C {
195 uk.expireReservation(now)
199 func (uk *utxoKeeper) expireReservation(t time.Time) {
201 defer uk.mtx.Unlock()
203 for rid, res := range uk.reservations {
204 if res.expiry.Before(t) {
210 func (uk *utxoKeeper) findUtxos(accountID string, assetID *bc.AssetID, useUnconfirmed bool, vote []byte) ([]*UTXO, uint64) {
211 immatureAmount := uint64(0)
212 currentHeight := uk.currentHeight()
214 appendUtxo := func(u *UTXO) {
215 if u.AccountID != accountID || u.AssetID != *assetID || !bytes.Equal(u.Vote, vote) {
218 if u.ValidHeight > currentHeight {
219 immatureAmount += u.Amount
221 utxos = append(utxos, u)
225 rawUTXOs := uk.store.GetUTXOs()
226 for _, rawUTXO := range rawUTXOs {
228 if err := json.Unmarshal(rawUTXO, utxo); err != nil {
229 log.WithFields(log.Fields{"module": logModule, "err": err}).Error("utxoKeeper findUtxos fail on unmarshal utxo")
236 return utxos, immatureAmount
239 for _, u := range uk.unconfirmed {
242 return utxos, immatureAmount
245 func (uk *utxoKeeper) findUtxo(outHash bc.Hash, useUnconfirmed bool) (*UTXO, error) {
246 if u, ok := uk.unconfirmed[outHash]; useUnconfirmed && ok {
251 if data := uk.store.GetStandardUTXO(outHash); data != nil {
252 return u, json.Unmarshal(data, u)
254 if data := uk.store.GetContractUTXO(outHash); data != nil {
255 return u, json.Unmarshal(data, u)
257 return nil, ErrMatchUTXO
260 func (uk *utxoKeeper) optUTXOs(utxos []*UTXO, amount uint64) ([]*UTXO, uint64, uint64) {
261 //sort the utxo by amount, bigger amount in front
262 var optAmount, reservedAmount uint64
263 sort.Slice(utxos, func(i, j int) bool {
264 return utxos[i].Amount > utxos[j].Amount
267 //push all the available utxos into list
268 utxoList := list.New()
269 for _, u := range utxos {
270 if _, ok := uk.reserved[u.OutputID]; ok {
271 reservedAmount += u.Amount
277 optList := list.New()
278 for node := utxoList.Front(); node != nil; node = node.Next() {
279 //append utxo if we haven't reached the required amount
280 if optAmount < amount {
281 optList.PushBack(node.Value)
282 optAmount += node.Value.(*UTXO).Amount
286 largestNode := optList.Front()
287 replaceList := list.New()
288 replaceAmount := optAmount - largestNode.Value.(*UTXO).Amount
290 for ; node != nil && replaceList.Len() <= desireUtxoCount-optList.Len(); node = node.Next() {
291 replaceList.PushBack(node.Value)
292 if replaceAmount += node.Value.(*UTXO).Amount; replaceAmount >= amount {
293 optList.Remove(largestNode)
294 optList.PushBackList(replaceList)
295 optAmount = replaceAmount
300 //largestNode remaining the same means that there is nothing to be replaced
301 if largestNode == optList.Front() {
306 optUtxos := []*UTXO{}
307 for e := optList.Front(); e != nil; e = e.Next() {
308 optUtxos = append(optUtxos, e.Value.(*UTXO))
310 return optUtxos, optAmount, reservedAmount