11 "github.com/vapor/errors"
12 "github.com/vapor/protocol/bc"
15 const desireUtxoCount = 5
17 // pre-define error types
19 ErrInsufficient = errors.New("reservation found insufficient funds")
20 ErrImmature = errors.New("reservation found immature funds")
21 ErrVoteLock = errors.New("Locked by the vote")
22 ErrReserved = errors.New("reservation found outputs already reserved")
23 ErrMatchUTXO = errors.New("can't find utxo with given hash")
24 ErrReservation = errors.New("couldn't find reservation")
27 // UTXO describes an individual account utxo.
38 ControlProgramIndex uint64
43 // reservation describes a reservation of a set of UTXOs
44 type reservation struct {
51 type utxoKeeper struct {
52 // `sync/atomic` expects the first word in an allocated struct to be 64-bit
53 // aligned on both ARM and x86-32. See https://goo.gl/zW7dgq for more details.
57 currentHeight func() uint64
59 unconfirmed map[bc.Hash]*UTXO
60 reserved map[bc.Hash]uint64
61 reservations map[uint64]*reservation
64 func newUtxoKeeper(f func() uint64, store AccountStorer) *utxoKeeper {
68 unconfirmed: make(map[bc.Hash]*UTXO),
69 reserved: make(map[bc.Hash]uint64),
70 reservations: make(map[uint64]*reservation),
76 func (uk *utxoKeeper) AddUnconfirmedUtxo(utxos []*UTXO) {
80 for _, utxo := range utxos {
81 uk.unconfirmed[utxo.OutputID] = utxo
85 // Cancel canceling the reservation with the provided ID.
86 func (uk *utxoKeeper) Cancel(rid uint64) {
92 // ListUnconfirmed return all the unconfirmed utxos
93 func (uk *utxoKeeper) ListUnconfirmed() []*UTXO {
98 for _, utxo := range uk.unconfirmed {
99 utxos = append(utxos, utxo)
104 func (uk *utxoKeeper) RemoveUnconfirmedUtxo(hashes []*bc.Hash) {
106 defer uk.mtx.Unlock()
108 for _, hash := range hashes {
109 delete(uk.unconfirmed, *hash)
113 func (uk *utxoKeeper) Reserve(accountID string, assetID *bc.AssetID, amount uint64, useUnconfirmed bool, vote []byte, exp time.Time) (*reservation, error) {
115 defer uk.mtx.Unlock()
117 utxos, immatureAmount := uk.findUtxos(accountID, assetID, useUnconfirmed, vote)
118 optUtxos, optAmount, reservedAmount := uk.optUTXOs(utxos, amount)
119 if optAmount+reservedAmount+immatureAmount < amount {
120 return nil, ErrInsufficient
123 if optAmount+reservedAmount < amount {
125 return nil, ErrVoteLock
127 return nil, ErrImmature
130 if optAmount < amount {
131 return nil, ErrReserved
134 result := &reservation{
135 id: atomic.AddUint64(&uk.nextIndex, 1),
137 change: optAmount - amount,
141 uk.reservations[result.id] = result
142 for _, u := range optUtxos {
143 uk.reserved[u.OutputID] = result.id
148 func (uk *utxoKeeper) ReserveParticular(outHash bc.Hash, useUnconfirmed bool, exp time.Time) (*reservation, error) {
150 defer uk.mtx.Unlock()
152 if _, ok := uk.reserved[outHash]; ok {
153 return nil, ErrReserved
156 u, err := uk.findUtxo(outHash, useUnconfirmed)
161 if u.ValidHeight > uk.currentHeight() {
162 return nil, ErrImmature
165 result := &reservation{
166 id: atomic.AddUint64(&uk.nextIndex, 1),
170 uk.reservations[result.id] = result
171 uk.reserved[u.OutputID] = result.id
175 func (uk *utxoKeeper) cancel(rid uint64) {
176 res, ok := uk.reservations[rid]
181 delete(uk.reservations, rid)
182 for _, utxo := range res.utxos {
183 delete(uk.reserved, utxo.OutputID)
187 func (uk *utxoKeeper) expireWorker() {
188 ticker := time.NewTicker(1000 * time.Millisecond)
191 for now := range ticker.C {
192 uk.expireReservation(now)
196 func (uk *utxoKeeper) expireReservation(t time.Time) {
198 defer uk.mtx.Unlock()
200 for rid, res := range uk.reservations {
201 if res.expiry.Before(t) {
207 func (uk *utxoKeeper) findUtxos(accountID string, assetID *bc.AssetID, useUnconfirmed bool, vote []byte) ([]*UTXO, uint64) {
208 immatureAmount := uint64(0)
209 currentHeight := uk.currentHeight()
211 appendUtxo := func(u *UTXO) {
212 if u.AccountID != accountID || u.AssetID != *assetID || !bytes.Equal(u.Vote, vote) {
215 if u.ValidHeight > currentHeight {
216 immatureAmount += u.Amount
218 utxos = append(utxos, u)
222 UTXOs := uk.store.GetUTXOs()
223 for _, UTXO := range UTXOs {
228 return utxos, immatureAmount
231 for _, u := range uk.unconfirmed {
234 return utxos, immatureAmount
237 func (uk *utxoKeeper) findUtxo(outHash bc.Hash, useUnconfirmed bool) (*UTXO, error) {
238 if u, ok := uk.unconfirmed[outHash]; useUnconfirmed && ok {
241 return uk.store.GetUTXO(outHash)
244 func (uk *utxoKeeper) optUTXOs(utxos []*UTXO, amount uint64) ([]*UTXO, uint64, uint64) {
245 //sort the utxo by amount, bigger amount in front
246 var optAmount, reservedAmount uint64
247 sort.Slice(utxos, func(i, j int) bool {
248 return utxos[i].Amount > utxos[j].Amount
251 //push all the available utxos into list
252 utxoList := list.New()
253 for _, u := range utxos {
254 if _, ok := uk.reserved[u.OutputID]; ok {
255 reservedAmount += u.Amount
261 optList := list.New()
262 for node := utxoList.Front(); node != nil; node = node.Next() {
263 //append utxo if we haven't reached the required amount
264 if optAmount < amount {
265 optList.PushBack(node.Value)
266 optAmount += node.Value.(*UTXO).Amount
270 largestNode := optList.Front()
271 replaceList := list.New()
272 replaceAmount := optAmount - largestNode.Value.(*UTXO).Amount
274 for ; node != nil && replaceList.Len() <= desireUtxoCount-optList.Len(); node = node.Next() {
275 replaceList.PushBack(node.Value)
276 if replaceAmount += node.Value.(*UTXO).Amount; replaceAmount >= amount {
277 optList.Remove(largestNode)
278 optList.PushBackList(replaceList)
279 optAmount = replaceAmount
284 //largestNode remaining the same means that there is nothing to be replaced
285 if largestNode == optList.Front() {
290 optUtxos := []*UTXO{}
291 for e := optList.Front(); e != nil; e = e.Next() {
292 optUtxos = append(optUtxos, e.Value.(*UTXO))
294 return optUtxos, optAmount, reservedAmount