10 log "github.com/sirupsen/logrus"
11 dbm "github.com/tendermint/tmlibs/db"
13 "github.com/bytom/errors"
14 "github.com/bytom/protocol/bc"
17 // pre-define error types
19 ErrInsufficient = errors.New("reservation found insufficient funds")
20 ErrImmature = errors.New("reservation found immature funds")
21 ErrReserved = errors.New("reservation found outputs already reserved")
22 ErrMatchUTXO = errors.New("can't find utxo with given hash")
23 ErrReservation = errors.New("couldn't find reservation")
26 // UTXO describes an individual account utxo.
36 ControlProgramIndex uint64
41 // reservation describes a reservation of a set of UTXOs
42 type reservation struct {
49 type utxoKeeper struct {
50 // `sync/atomic` expects the first word in an allocated struct to be 64-bit
51 // aligned on both ARM and x86-32. See https://goo.gl/zW7dgq for more details.
55 currentHeight func() uint64
57 unconfirmed map[bc.Hash]*UTXO
58 reserved map[bc.Hash]uint64
59 reservations map[uint64]*reservation
62 func newUtxoKeeper(f func() uint64, walletdb dbm.DB) *utxoKeeper {
66 unconfirmed: make(map[bc.Hash]*UTXO),
67 reserved: make(map[bc.Hash]uint64),
68 reservations: make(map[uint64]*reservation),
74 func (uk *utxoKeeper) AddUnconfirmedUtxo(utxos []*UTXO) {
78 for _, utxo := range utxos {
79 uk.unconfirmed[utxo.OutputID] = utxo
83 // Cancel canceling the reservation with the provided ID.
84 func (uk *utxoKeeper) Cancel(rid uint64) {
90 // ListUnconfirmed return all the unconfirmed utxos
91 func (uk *utxoKeeper) ListUnconfirmed() []*UTXO {
96 for _, utxo := range uk.unconfirmed {
97 utxos = append(utxos, utxo)
102 func (uk *utxoKeeper) RemoveUnconfirmedUtxo(hashes []*bc.Hash) {
104 defer uk.mtx.Unlock()
106 for _, hash := range hashes {
107 delete(uk.unconfirmed, *hash)
111 func (uk *utxoKeeper) Reserve(accountID string, assetID *bc.AssetID, amount uint64, useUnconfirmed bool, exp time.Time) (*reservation, error) {
113 defer uk.mtx.Unlock()
115 utxos, immatureAmount := uk.findUtxos(accountID, assetID, useUnconfirmed)
116 optUtxos, optAmount, reservedAmount := uk.optUTXOs(utxos, amount)
117 if optAmount+reservedAmount+immatureAmount < amount {
118 return nil, ErrInsufficient
121 if optAmount+reservedAmount < amount {
122 return nil, ErrImmature
125 if optAmount < amount {
126 return nil, ErrReserved
129 result := &reservation{
130 id: atomic.AddUint64(&uk.nextIndex, 1),
132 change: optAmount - amount,
136 uk.reservations[result.id] = result
137 for _, u := range optUtxos {
138 uk.reserved[u.OutputID] = result.id
143 func (uk *utxoKeeper) ReserveParticular(outHash bc.Hash, useUnconfirmed bool, exp time.Time) (*reservation, error) {
145 defer uk.mtx.Unlock()
147 if _, ok := uk.reserved[outHash]; ok {
148 return nil, ErrReserved
151 u, err := uk.findUtxo(outHash, useUnconfirmed)
156 if u.ValidHeight > uk.currentHeight() {
157 return nil, ErrImmature
160 result := &reservation{
161 id: atomic.AddUint64(&uk.nextIndex, 1),
165 uk.reservations[result.id] = result
166 uk.reserved[u.OutputID] = result.id
170 func (uk *utxoKeeper) cancel(rid uint64) {
171 res, ok := uk.reservations[rid]
176 delete(uk.reservations, rid)
177 for _, utxo := range res.utxos {
178 delete(uk.reserved, utxo.OutputID)
182 func (uk *utxoKeeper) expireWorker() {
183 ticker := time.NewTicker(1000 * time.Millisecond)
184 for now := range ticker.C {
185 uk.expireReservation(now)
188 func (uk *utxoKeeper) expireReservation(t time.Time) {
190 defer uk.mtx.Unlock()
192 for rid, res := range uk.reservations {
193 if res.expiry.Before(t) {
199 func (uk *utxoKeeper) findUtxos(accountID string, assetID *bc.AssetID, useUnconfirmed bool) ([]*UTXO, uint64) {
200 immatureAmount := uint64(0)
201 currentHeight := uk.currentHeight()
203 appendUtxo := func(u *UTXO) {
204 if u.AccountID != accountID || u.AssetID != *assetID {
207 if u.ValidHeight > currentHeight {
208 immatureAmount += u.Amount
210 utxos = append(utxos, u)
214 utxoIter := uk.db.IteratorPrefix([]byte(UTXOPreFix))
215 defer utxoIter.Release()
216 for utxoIter.Next() {
218 if err := json.Unmarshal(utxoIter.Value(), u); err != nil {
219 log.WithField("err", err).Error("utxoKeeper findUtxos fail on unmarshal utxo")
225 return utxos, immatureAmount
228 for _, u := range uk.unconfirmed {
231 return utxos, immatureAmount
234 func (uk *utxoKeeper) findUtxo(outHash bc.Hash, useUnconfirmed bool) (*UTXO, error) {
235 if u, ok := uk.unconfirmed[outHash]; useUnconfirmed && ok {
240 if data := uk.db.Get(StandardUTXOKey(outHash)); data != nil {
241 return u, json.Unmarshal(data, u)
243 if data := uk.db.Get(ContractUTXOKey(outHash)); data != nil {
244 return u, json.Unmarshal(data, u)
246 return nil, ErrMatchUTXO
249 func (uk *utxoKeeper) optUTXOs(utxos []*UTXO, amount uint64) ([]*UTXO, uint64, uint64) {
250 sort.Slice(utxos, func(i, j int) bool {
251 return utxos[i].Amount < utxos[j].Amount
254 var optAmount, reservedAmount uint64
255 optUtxos := []*UTXO{}
256 for _, u := range utxos {
257 if _, ok := uk.reserved[u.OutputID]; ok {
258 reservedAmount += u.Amount
262 optAmount += u.Amount
263 optUtxos = append(optUtxos, u)
264 if optAmount >= amount {
268 return optUtxos, optAmount, reservedAmount