OSDN Git Service

Hulk did something
[bytom/vapor.git] / account / utxo_keeper_test.go
diff --git a/account/utxo_keeper_test.go b/account/utxo_keeper_test.go
new file mode 100644 (file)
index 0000000..bb9d010
--- /dev/null
@@ -0,0 +1,1218 @@
+package account
+
+import (
+       "encoding/json"
+       "os"
+       "testing"
+       "time"
+
+       dbm "github.com/vapor/database/leveldb"
+       "github.com/vapor/protocol/bc"
+       "github.com/vapor/testutil"
+)
+
+func TestAddUnconfirmedUtxo(t *testing.T) {
+       cases := []struct {
+               before   utxoKeeper
+               after    utxoKeeper
+               addUtxos []*UTXO
+       }{
+               {
+                       before: utxoKeeper{
+                               unconfirmed: map[bc.Hash]*UTXO{},
+                       },
+                       after: utxoKeeper{
+                               unconfirmed: map[bc.Hash]*UTXO{},
+                       },
+                       addUtxos: []*UTXO{},
+               },
+               {
+                       before: utxoKeeper{
+                               unconfirmed: map[bc.Hash]*UTXO{},
+                       },
+                       after: utxoKeeper{
+                               unconfirmed: map[bc.Hash]*UTXO{
+                                       bc.NewHash([32]byte{0x01}): &UTXO{OutputID: bc.NewHash([32]byte{0x01})},
+                               },
+                       },
+                       addUtxos: []*UTXO{
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x01})},
+                       },
+               },
+               {
+                       before: utxoKeeper{
+                               unconfirmed: map[bc.Hash]*UTXO{
+                                       bc.NewHash([32]byte{0x01}): &UTXO{OutputID: bc.NewHash([32]byte{0x01})},
+                               },
+                       },
+                       after: utxoKeeper{
+                               unconfirmed: map[bc.Hash]*UTXO{
+                                       bc.NewHash([32]byte{0x01}): &UTXO{OutputID: bc.NewHash([32]byte{0x01})},
+                               },
+                       },
+                       addUtxos: []*UTXO{
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x01})},
+                       },
+               },
+               {
+                       before: utxoKeeper{
+                               unconfirmed: map[bc.Hash]*UTXO{
+                                       bc.NewHash([32]byte{0x01}): &UTXO{OutputID: bc.NewHash([32]byte{0x01})},
+                               },
+                       },
+                       after: utxoKeeper{
+                               unconfirmed: map[bc.Hash]*UTXO{
+                                       bc.NewHash([32]byte{0x01}): &UTXO{OutputID: bc.NewHash([32]byte{0x01})},
+                                       bc.NewHash([32]byte{0x02}): &UTXO{OutputID: bc.NewHash([32]byte{0x02})},
+                                       bc.NewHash([32]byte{0x03}): &UTXO{OutputID: bc.NewHash([32]byte{0x03})},
+                               },
+                       },
+                       addUtxos: []*UTXO{
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x02})},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x03})},
+                       },
+               },
+       }
+
+       for i, c := range cases {
+               c.before.AddUnconfirmedUtxo(c.addUtxos)
+               if !testutil.DeepEqual(c.before, c.after) {
+                       t.Errorf("case %d: got %v want %v", i, c.before, c.after)
+               }
+       }
+}
+
+func TestCancel(t *testing.T) {
+       cases := []struct {
+               before    utxoKeeper
+               after     utxoKeeper
+               cancelRid uint64
+       }{
+               {
+                       before: utxoKeeper{
+                               reserved:     map[bc.Hash]uint64{},
+                               reservations: map[uint64]*reservation{},
+                       },
+                       after: utxoKeeper{
+                               reserved:     map[bc.Hash]uint64{},
+                               reservations: map[uint64]*reservation{},
+                       },
+                       cancelRid: 0,
+               },
+               {
+                       before: utxoKeeper{
+                               reserved: map[bc.Hash]uint64{
+                                       bc.NewHash([32]byte{0x01}): 1,
+                               },
+                               reservations: map[uint64]*reservation{
+                                       1: &reservation{
+                                               id: 1,
+                                               utxos: []*UTXO{
+                                                       &UTXO{OutputID: bc.NewHash([32]byte{0x01})},
+                                               },
+                                               change: 9527,
+                                               expiry: time.Date(2016, 8, 10, 0, 0, 0, 0, time.UTC),
+                                       },
+                               },
+                       },
+                       after: utxoKeeper{
+                               reserved:     map[bc.Hash]uint64{},
+                               reservations: map[uint64]*reservation{},
+                       },
+                       cancelRid: 1,
+               },
+               {
+                       before: utxoKeeper{
+                               reserved: map[bc.Hash]uint64{
+                                       bc.NewHash([32]byte{0x01}): 1,
+                               },
+                               reservations: map[uint64]*reservation{
+                                       1: &reservation{
+                                               id: 1,
+                                               utxos: []*UTXO{
+                                                       &UTXO{OutputID: bc.NewHash([32]byte{0x01})},
+                                               },
+                                               change: 9527,
+                                               expiry: time.Date(2016, 8, 10, 0, 0, 0, 0, time.UTC),
+                                       },
+                               },
+                       },
+                       after: utxoKeeper{
+                               reserved: map[bc.Hash]uint64{
+                                       bc.NewHash([32]byte{0x01}): 1,
+                               },
+                               reservations: map[uint64]*reservation{
+                                       1: &reservation{
+                                               id: 1,
+                                               utxos: []*UTXO{
+                                                       &UTXO{OutputID: bc.NewHash([32]byte{0x01})},
+                                               },
+                                               change: 9527,
+                                               expiry: time.Date(2016, 8, 10, 0, 0, 0, 0, time.UTC),
+                                       },
+                               },
+                       },
+                       cancelRid: 2,
+               },
+               {
+                       before: utxoKeeper{
+                               reserved: map[bc.Hash]uint64{
+                                       bc.NewHash([32]byte{0x01}): 1,
+                                       bc.NewHash([32]byte{0x02}): 3,
+                                       bc.NewHash([32]byte{0x03}): 3,
+                                       bc.NewHash([32]byte{0x04}): 3,
+                               },
+                               reservations: map[uint64]*reservation{
+                                       1: &reservation{
+                                               id: 1,
+                                               utxos: []*UTXO{
+                                                       &UTXO{OutputID: bc.NewHash([32]byte{0x01})},
+                                               },
+                                               change: 9527,
+                                               expiry: time.Date(2016, 8, 10, 0, 0, 0, 0, time.UTC),
+                                       },
+                                       3: &reservation{
+                                               id: 3,
+                                               utxos: []*UTXO{
+                                                       &UTXO{OutputID: bc.NewHash([32]byte{0x02})},
+                                                       &UTXO{OutputID: bc.NewHash([32]byte{0x03})},
+                                                       &UTXO{OutputID: bc.NewHash([32]byte{0x04})},
+                                               },
+                                               change: 9528,
+                                               expiry: time.Date(2016, 8, 10, 0, 0, 0, 0, time.UTC),
+                                       },
+                               },
+                       },
+                       after: utxoKeeper{
+                               reserved: map[bc.Hash]uint64{
+                                       bc.NewHash([32]byte{0x01}): 1,
+                               },
+                               reservations: map[uint64]*reservation{
+                                       1: &reservation{
+                                               id: 1,
+                                               utxos: []*UTXO{
+                                                       &UTXO{OutputID: bc.NewHash([32]byte{0x01})},
+                                               },
+                                               change: 9527,
+                                               expiry: time.Date(2016, 8, 10, 0, 0, 0, 0, time.UTC),
+                                       },
+                               },
+                       },
+                       cancelRid: 3,
+               },
+       }
+
+       for i, c := range cases {
+               c.before.cancel(c.cancelRid)
+               if !testutil.DeepEqual(c.before, c.after) {
+                       t.Errorf("case %d: got %v want %v", i, c.before, c.after)
+               }
+       }
+}
+
+func TestRemoveUnconfirmedUtxo(t *testing.T) {
+       cases := []struct {
+               before      utxoKeeper
+               after       utxoKeeper
+               removeUtxos []*bc.Hash
+       }{
+               {
+                       before: utxoKeeper{
+                               unconfirmed: map[bc.Hash]*UTXO{},
+                       },
+                       after: utxoKeeper{
+                               unconfirmed: map[bc.Hash]*UTXO{},
+                       },
+                       removeUtxos: []*bc.Hash{},
+               },
+               {
+                       before: utxoKeeper{
+                               unconfirmed: map[bc.Hash]*UTXO{
+                                       bc.Hash{V0: 1}: &UTXO{OutputID: bc.NewHash([32]byte{0x01})},
+                               },
+                       },
+                       after: utxoKeeper{
+                               unconfirmed: map[bc.Hash]*UTXO{},
+                       },
+                       removeUtxos: []*bc.Hash{
+                               &bc.Hash{V0: 1},
+                       },
+               },
+               {
+                       before: utxoKeeper{
+                               unconfirmed: map[bc.Hash]*UTXO{
+                                       bc.Hash{V0: 1}: &UTXO{OutputID: bc.NewHash([32]byte{0x01})},
+                                       bc.Hash{V0: 2}: &UTXO{OutputID: bc.NewHash([32]byte{0x02})},
+                                       bc.Hash{V0: 3}: &UTXO{OutputID: bc.NewHash([32]byte{0x03})},
+                                       bc.Hash{V0: 4}: &UTXO{OutputID: bc.NewHash([32]byte{0x04})},
+                                       bc.Hash{V0: 5}: &UTXO{OutputID: bc.NewHash([32]byte{0x05})},
+                               },
+                       },
+                       after: utxoKeeper{
+                               unconfirmed: map[bc.Hash]*UTXO{
+                                       bc.Hash{V0: 2}: &UTXO{OutputID: bc.NewHash([32]byte{0x02})},
+                                       bc.Hash{V0: 4}: &UTXO{OutputID: bc.NewHash([32]byte{0x04})},
+                               },
+                       },
+                       removeUtxos: []*bc.Hash{
+                               &bc.Hash{V0: 1},
+                               &bc.Hash{V0: 3},
+                               &bc.Hash{V0: 5},
+                       },
+               },
+       }
+
+       for i, c := range cases {
+               c.before.RemoveUnconfirmedUtxo(c.removeUtxos)
+               if !testutil.DeepEqual(c.before, c.after) {
+                       t.Errorf("case %d: got %v want %v", i, c.before, c.after)
+               }
+       }
+}
+
+func TestReserve(t *testing.T) {
+       currentHeight := func() uint64 { return 9527 }
+       testDB := dbm.NewDB("testdb", "leveldb", "temp")
+       defer os.RemoveAll("temp")
+
+       cases := []struct {
+               before        utxoKeeper
+               after         utxoKeeper
+               err           error
+               reserveAmount uint64
+               exp           time.Time
+       }{
+               {
+                       before: utxoKeeper{
+                               db:            testDB,
+                               currentHeight: currentHeight,
+                               reserved:      map[bc.Hash]uint64{},
+                               reservations:  map[uint64]*reservation{},
+                       },
+                       after: utxoKeeper{
+                               db:            testDB,
+                               currentHeight: currentHeight,
+                               reserved:      map[bc.Hash]uint64{},
+                               reservations:  map[uint64]*reservation{},
+                       },
+                       reserveAmount: 1,
+                       err:           ErrInsufficient,
+               },
+               {
+                       before: utxoKeeper{
+                               db:            testDB,
+                               currentHeight: currentHeight,
+                               unconfirmed: map[bc.Hash]*UTXO{
+                                       bc.NewHash([32]byte{0x01}): &UTXO{
+                                               OutputID:  bc.NewHash([32]byte{0x01}),
+                                               AccountID: "testAccount",
+                                               Amount:    3,
+                                       },
+                               },
+                               reserved:     map[bc.Hash]uint64{},
+                               reservations: map[uint64]*reservation{},
+                       },
+                       after: utxoKeeper{
+                               db:            testDB,
+                               currentHeight: currentHeight,
+                               unconfirmed: map[bc.Hash]*UTXO{
+                                       bc.NewHash([32]byte{0x01}): &UTXO{
+                                               OutputID:  bc.NewHash([32]byte{0x01}),
+                                               AccountID: "testAccount",
+                                               Amount:    3,
+                                       },
+                               },
+                               reserved:     map[bc.Hash]uint64{},
+                               reservations: map[uint64]*reservation{},
+                       },
+                       reserveAmount: 4,
+                       err:           ErrInsufficient,
+               },
+               {
+                       before: utxoKeeper{
+                               db:            testDB,
+                               currentHeight: currentHeight,
+                               unconfirmed: map[bc.Hash]*UTXO{
+                                       bc.NewHash([32]byte{0x01}): &UTXO{
+                                               OutputID:    bc.NewHash([32]byte{0x01}),
+                                               AccountID:   "testAccount",
+                                               Amount:      3,
+                                               ValidHeight: 9528,
+                                       },
+                               },
+                               reserved:     map[bc.Hash]uint64{},
+                               reservations: map[uint64]*reservation{},
+                       },
+                       after: utxoKeeper{
+                               db:            testDB,
+                               currentHeight: currentHeight,
+                               unconfirmed: map[bc.Hash]*UTXO{
+                                       bc.NewHash([32]byte{0x01}): &UTXO{
+                                               OutputID:    bc.NewHash([32]byte{0x01}),
+                                               AccountID:   "testAccount",
+                                               Amount:      3,
+                                               ValidHeight: 9528,
+                                       },
+                               },
+                               reserved:     map[bc.Hash]uint64{},
+                               reservations: map[uint64]*reservation{},
+                       },
+                       reserveAmount: 3,
+                       err:           ErrImmature,
+               },
+               {
+                       before: utxoKeeper{
+                               db:            testDB,
+                               currentHeight: currentHeight,
+                               unconfirmed: map[bc.Hash]*UTXO{
+                                       bc.NewHash([32]byte{0x01}): &UTXO{
+                                               OutputID:  bc.NewHash([32]byte{0x01}),
+                                               AccountID: "testAccount",
+                                               Amount:    3,
+                                       },
+                               },
+                               reserved: map[bc.Hash]uint64{
+                                       bc.NewHash([32]byte{0x01}): 0,
+                               },
+                               reservations: map[uint64]*reservation{},
+                       },
+                       after: utxoKeeper{
+                               db:            testDB,
+                               currentHeight: currentHeight,
+                               unconfirmed: map[bc.Hash]*UTXO{
+                                       bc.NewHash([32]byte{0x01}): &UTXO{
+                                               OutputID:  bc.NewHash([32]byte{0x01}),
+                                               AccountID: "testAccount",
+                                               Amount:    3,
+                                       },
+                               },
+                               reserved: map[bc.Hash]uint64{
+                                       bc.NewHash([32]byte{0x01}): 0,
+                               },
+                               reservations: map[uint64]*reservation{},
+                       },
+                       reserveAmount: 3,
+                       err:           ErrReserved,
+               },
+               {
+                       before: utxoKeeper{
+                               db:            testDB,
+                               currentHeight: currentHeight,
+                               unconfirmed: map[bc.Hash]*UTXO{
+                                       bc.NewHash([32]byte{0x01}): &UTXO{
+                                               OutputID:  bc.NewHash([32]byte{0x01}),
+                                               AccountID: "testAccount",
+                                               Amount:    3,
+                                       },
+                               },
+                               reserved:     map[bc.Hash]uint64{},
+                               reservations: map[uint64]*reservation{},
+                       },
+                       after: utxoKeeper{
+                               db:            testDB,
+                               currentHeight: currentHeight,
+                               unconfirmed: map[bc.Hash]*UTXO{
+                                       bc.NewHash([32]byte{0x01}): &UTXO{
+                                               OutputID:  bc.NewHash([32]byte{0x01}),
+                                               AccountID: "testAccount",
+                                               Amount:    3,
+                                       },
+                               },
+                               reserved: map[bc.Hash]uint64{
+                                       bc.NewHash([32]byte{0x01}): 1,
+                               },
+                               reservations: map[uint64]*reservation{
+                                       1: &reservation{
+                                               id: 1,
+                                               utxos: []*UTXO{
+                                                       &UTXO{
+                                                               OutputID:  bc.NewHash([32]byte{0x01}),
+                                                               AccountID: "testAccount",
+                                                               Amount:    3,
+                                                       },
+                                               },
+                                               change: 1,
+                                               expiry: time.Date(2016, 8, 10, 0, 0, 0, 0, time.UTC),
+                                       },
+                               },
+                       },
+                       reserveAmount: 2,
+                       err:           nil,
+                       exp:           time.Date(2016, 8, 10, 0, 0, 0, 0, time.UTC),
+               },
+               {
+                       before: utxoKeeper{
+                               db:            testDB,
+                               currentHeight: currentHeight,
+                               nextIndex:     1,
+                               unconfirmed: map[bc.Hash]*UTXO{
+                                       bc.NewHash([32]byte{0x01}): &UTXO{
+                                               OutputID:  bc.NewHash([32]byte{0x01}),
+                                               AccountID: "testAccount",
+                                               Amount:    3,
+                                       },
+                                       bc.NewHash([32]byte{0x02}): &UTXO{
+                                               OutputID:  bc.NewHash([32]byte{0x02}),
+                                               AccountID: "testAccount",
+                                               Amount:    5,
+                                       },
+                                       bc.NewHash([32]byte{0x03}): &UTXO{
+                                               OutputID:  bc.NewHash([32]byte{0x03}),
+                                               AccountID: "testAccount",
+                                               Amount:    7,
+                                       },
+                               },
+                               reserved: map[bc.Hash]uint64{
+                                       bc.NewHash([32]byte{0x01}): 1,
+                               },
+                               reservations: map[uint64]*reservation{},
+                       },
+                       after: utxoKeeper{
+                               db:            testDB,
+                               currentHeight: currentHeight,
+                               unconfirmed: map[bc.Hash]*UTXO{
+                                       bc.NewHash([32]byte{0x01}): &UTXO{
+                                               OutputID:  bc.NewHash([32]byte{0x01}),
+                                               AccountID: "testAccount",
+                                               Amount:    3,
+                                       },
+                                       bc.NewHash([32]byte{0x02}): &UTXO{
+                                               OutputID:  bc.NewHash([32]byte{0x02}),
+                                               AccountID: "testAccount",
+                                               Amount:    5,
+                                       },
+                                       bc.NewHash([32]byte{0x03}): &UTXO{
+                                               OutputID:  bc.NewHash([32]byte{0x03}),
+                                               AccountID: "testAccount",
+                                               Amount:    7,
+                                       },
+                               },
+                               reserved: map[bc.Hash]uint64{
+                                       bc.NewHash([32]byte{0x01}): 1,
+                                       bc.NewHash([32]byte{0x02}): 2,
+                                       bc.NewHash([32]byte{0x03}): 2,
+                               },
+                               reservations: map[uint64]*reservation{
+                                       2: &reservation{
+                                               id: 2,
+                                               utxos: []*UTXO{
+                                                       &UTXO{
+                                                               OutputID:  bc.NewHash([32]byte{0x03}),
+                                                               AccountID: "testAccount",
+                                                               Amount:    7,
+                                                       },
+                                                       &UTXO{
+                                                               OutputID:  bc.NewHash([32]byte{0x02}),
+                                                               AccountID: "testAccount",
+                                                               Amount:    5,
+                                                       },
+                                               },
+                                               change: 4,
+                                               expiry: time.Date(2016, 8, 10, 0, 0, 0, 0, time.UTC),
+                                       },
+                               },
+                       },
+                       reserveAmount: 8,
+                       err:           nil,
+                       exp:           time.Date(2016, 8, 10, 0, 0, 0, 0, time.UTC),
+               },
+       }
+
+       for i, c := range cases {
+               if _, err := c.before.Reserve("testAccount", &bc.AssetID{}, c.reserveAmount, true, c.exp); err != c.err {
+                       t.Errorf("case %d: got error %v want error %v", i, err, c.err)
+               }
+               checkUtxoKeeperEqual(t, i, &c.before, &c.after)
+       }
+}
+
+func TestReserveParticular(t *testing.T) {
+       currentHeight := func() uint64 { return 9527 }
+       testDB := dbm.NewDB("testdb", "leveldb", "temp")
+       defer os.RemoveAll("temp")
+
+       cases := []struct {
+               before      utxoKeeper
+               after       utxoKeeper
+               err         error
+               reserveHash bc.Hash
+               exp         time.Time
+       }{
+               {
+                       before: utxoKeeper{
+                               db:            testDB,
+                               currentHeight: currentHeight,
+                               unconfirmed: map[bc.Hash]*UTXO{
+                                       bc.NewHash([32]byte{0x01}): &UTXO{
+                                               OutputID:  bc.NewHash([32]byte{0x01}),
+                                               AccountID: "testAccount",
+                                               Amount:    3,
+                                       },
+                               },
+                               reserved: map[bc.Hash]uint64{
+                                       bc.NewHash([32]byte{0x01}): 0,
+                               },
+                               reservations: map[uint64]*reservation{},
+                       },
+                       after: utxoKeeper{
+                               db:            testDB,
+                               currentHeight: currentHeight,
+                               unconfirmed: map[bc.Hash]*UTXO{
+                                       bc.NewHash([32]byte{0x01}): &UTXO{
+                                               OutputID:  bc.NewHash([32]byte{0x01}),
+                                               AccountID: "testAccount",
+                                               Amount:    3,
+                                       },
+                               },
+                               reserved: map[bc.Hash]uint64{
+                                       bc.NewHash([32]byte{0x01}): 0,
+                               },
+                               reservations: map[uint64]*reservation{},
+                       },
+                       reserveHash: bc.NewHash([32]byte{0x01}),
+                       err:         ErrReserved,
+               },
+               {
+                       before: utxoKeeper{
+                               db:            testDB,
+                               currentHeight: currentHeight,
+                               unconfirmed: map[bc.Hash]*UTXO{
+                                       bc.NewHash([32]byte{0x01}): &UTXO{
+                                               OutputID:    bc.NewHash([32]byte{0x01}),
+                                               AccountID:   "testAccount",
+                                               Amount:      3,
+                                               ValidHeight: 9528,
+                                       },
+                               },
+                               reserved:     map[bc.Hash]uint64{},
+                               reservations: map[uint64]*reservation{},
+                       },
+                       after: utxoKeeper{
+                               db:            testDB,
+                               currentHeight: currentHeight,
+                               unconfirmed: map[bc.Hash]*UTXO{
+                                       bc.NewHash([32]byte{0x01}): &UTXO{
+                                               OutputID:    bc.NewHash([32]byte{0x01}),
+                                               AccountID:   "testAccount",
+                                               Amount:      3,
+                                               ValidHeight: 9528,
+                                       },
+                               },
+                               reserved:     map[bc.Hash]uint64{},
+                               reservations: map[uint64]*reservation{},
+                       },
+                       reserveHash: bc.NewHash([32]byte{0x01}),
+                       err:         ErrImmature,
+               },
+               {
+                       before: utxoKeeper{
+                               db:            testDB,
+                               currentHeight: currentHeight,
+                               unconfirmed: map[bc.Hash]*UTXO{
+                                       bc.NewHash([32]byte{0x01}): &UTXO{
+                                               OutputID:  bc.NewHash([32]byte{0x01}),
+                                               AccountID: "testAccount",
+                                               Amount:    3,
+                                       },
+                               },
+                               reserved:     map[bc.Hash]uint64{},
+                               reservations: map[uint64]*reservation{},
+                       },
+                       after: utxoKeeper{
+                               db:            testDB,
+                               currentHeight: currentHeight,
+                               unconfirmed: map[bc.Hash]*UTXO{
+                                       bc.NewHash([32]byte{0x01}): &UTXO{
+                                               OutputID:  bc.NewHash([32]byte{0x01}),
+                                               AccountID: "testAccount",
+                                               Amount:    3,
+                                       },
+                               },
+                               reserved: map[bc.Hash]uint64{
+                                       bc.NewHash([32]byte{0x01}): 1,
+                               },
+                               reservations: map[uint64]*reservation{
+                                       1: &reservation{
+                                               id: 1,
+                                               utxos: []*UTXO{
+                                                       &UTXO{
+                                                               OutputID:  bc.NewHash([32]byte{0x01}),
+                                                               AccountID: "testAccount",
+                                                               Amount:    3,
+                                                       },
+                                               },
+                                               change: 0,
+                                               expiry: time.Date(2016, 8, 10, 0, 0, 0, 0, time.UTC),
+                                       },
+                               },
+                       },
+                       reserveHash: bc.NewHash([32]byte{0x01}),
+                       err:         nil,
+                       exp:         time.Date(2016, 8, 10, 0, 0, 0, 0, time.UTC),
+               },
+       }
+
+       for i, c := range cases {
+               if _, err := c.before.ReserveParticular(c.reserveHash, true, c.exp); err != c.err {
+                       t.Errorf("case %d: got error %v want error %v", i, err, c.err)
+               }
+               checkUtxoKeeperEqual(t, i, &c.before, &c.after)
+       }
+}
+
+func TestExpireReservation(t *testing.T) {
+       before := &utxoKeeper{
+               reservations: map[uint64]*reservation{
+                       1: &reservation{expiry: time.Date(2016, 8, 10, 0, 0, 0, 0, time.UTC)},
+                       2: &reservation{expiry: time.Date(3016, 8, 10, 0, 0, 0, 0, time.UTC)},
+                       3: &reservation{expiry: time.Date(2016, 8, 10, 0, 0, 0, 0, time.UTC)},
+                       4: &reservation{expiry: time.Date(3016, 8, 10, 0, 0, 0, 0, time.UTC)},
+                       5: &reservation{expiry: time.Date(3016, 8, 10, 0, 0, 0, 0, time.UTC)},
+               },
+       }
+       after := &utxoKeeper{
+               reservations: map[uint64]*reservation{
+                       2: &reservation{expiry: time.Date(3016, 8, 10, 0, 0, 0, 0, time.UTC)},
+                       4: &reservation{expiry: time.Date(3016, 8, 10, 0, 0, 0, 0, time.UTC)},
+                       5: &reservation{expiry: time.Date(3016, 8, 10, 0, 0, 0, 0, time.UTC)},
+               },
+       }
+       before.expireReservation(time.Date(2017, 8, 10, 0, 0, 0, 0, time.UTC))
+       checkUtxoKeeperEqual(t, 0, before, after)
+}
+
+func TestFindUtxos(t *testing.T) {
+       currentHeight := func() uint64 { return 9527 }
+       testDB := dbm.NewDB("testdb", "leveldb", "temp")
+       defer os.RemoveAll("temp")
+
+       cases := []struct {
+               uk             utxoKeeper
+               dbUtxos        []*UTXO
+               useUnconfirmed bool
+               wantUtxos      []*UTXO
+               immatureAmount uint64
+       }{
+               {
+                       uk: utxoKeeper{
+                               db:            testDB,
+                               currentHeight: currentHeight,
+                               unconfirmed:   map[bc.Hash]*UTXO{},
+                       },
+                       dbUtxos:        []*UTXO{},
+                       useUnconfirmed: true,
+                       wantUtxos:      []*UTXO{},
+                       immatureAmount: 0,
+               },
+               {
+                       uk: utxoKeeper{
+                               db:            testDB,
+                               currentHeight: currentHeight,
+                               unconfirmed:   map[bc.Hash]*UTXO{},
+                       },
+                       dbUtxos: []*UTXO{
+                               &UTXO{
+                                       OutputID:  bc.NewHash([32]byte{0x01}),
+                                       AccountID: "testAccount",
+                                       Amount:    3,
+                               },
+                               &UTXO{
+                                       OutputID:  bc.NewHash([32]byte{0x02}),
+                                       AccountID: "testAccount",
+                                       AssetID:   bc.AssetID{V0: 6},
+                                       Amount:    3,
+                               },
+                       },
+                       useUnconfirmed: false,
+                       wantUtxos: []*UTXO{
+                               &UTXO{
+                                       OutputID:  bc.NewHash([32]byte{0x01}),
+                                       AccountID: "testAccount",
+                                       Amount:    3,
+                               },
+                       },
+                       immatureAmount: 0,
+               },
+               {
+                       uk: utxoKeeper{
+                               db:            testDB,
+                               currentHeight: currentHeight,
+                               unconfirmed:   map[bc.Hash]*UTXO{},
+                       },
+                       dbUtxos: []*UTXO{
+                               &UTXO{
+                                       OutputID:    bc.NewHash([32]byte{0x02}),
+                                       AccountID:   "testAccount",
+                                       Amount:      3,
+                                       ValidHeight: 9528,
+                               },
+                       },
+                       useUnconfirmed: false,
+                       wantUtxos:      []*UTXO{},
+                       immatureAmount: 3,
+               },
+               {
+                       uk: utxoKeeper{
+                               db:            testDB,
+                               currentHeight: currentHeight,
+                               unconfirmed: map[bc.Hash]*UTXO{
+                                       bc.NewHash([32]byte{0x01}): &UTXO{
+                                               OutputID:  bc.NewHash([32]byte{0x01}),
+                                               AccountID: "testAccount",
+                                               Amount:    3,
+                                       },
+                               },
+                       },
+                       dbUtxos: []*UTXO{
+                               &UTXO{
+                                       OutputID:  bc.NewHash([32]byte{0x02}),
+                                       AccountID: "testAccount",
+                                       Amount:    3,
+                               },
+                       },
+                       useUnconfirmed: false,
+                       wantUtxos: []*UTXO{
+                               &UTXO{
+                                       OutputID:  bc.NewHash([32]byte{0x02}),
+                                       AccountID: "testAccount",
+                                       Amount:    3,
+                               },
+                       },
+                       immatureAmount: 0,
+               },
+               {
+                       uk: utxoKeeper{
+                               db:            testDB,
+                               currentHeight: currentHeight,
+                               unconfirmed: map[bc.Hash]*UTXO{
+                                       bc.NewHash([32]byte{0x11}): &UTXO{
+                                               OutputID:  bc.NewHash([32]byte{0x01}),
+                                               AccountID: "testAccount",
+                                               Amount:    3,
+                                       },
+                               },
+                       },
+                       dbUtxos: []*UTXO{
+                               &UTXO{
+                                       OutputID:  bc.NewHash([32]byte{0x02}),
+                                       AccountID: "testAccount",
+                                       Amount:    3,
+                               },
+                       },
+                       useUnconfirmed: true,
+                       wantUtxos: []*UTXO{
+                               &UTXO{
+                                       OutputID:  bc.NewHash([32]byte{0x02}),
+                                       AccountID: "testAccount",
+                                       Amount:    3,
+                               },
+                               &UTXO{
+                                       OutputID:  bc.NewHash([32]byte{0x01}),
+                                       AccountID: "testAccount",
+                                       Amount:    3,
+                               },
+                       },
+                       immatureAmount: 0,
+               },
+               {
+                       uk: utxoKeeper{
+                               db:            testDB,
+                               currentHeight: currentHeight,
+                               unconfirmed: map[bc.Hash]*UTXO{
+                                       bc.NewHash([32]byte{0x01}): &UTXO{
+                                               OutputID:  bc.NewHash([32]byte{0x01}),
+                                               AccountID: "testAccount",
+                                               Amount:    1,
+                                       },
+                                       bc.NewHash([32]byte{0x02}): &UTXO{
+                                               OutputID:  bc.NewHash([32]byte{0x02}),
+                                               AccountID: "notMe",
+                                               Amount:    2,
+                                       },
+                               },
+                       },
+                       dbUtxos: []*UTXO{
+                               &UTXO{
+                                       OutputID:  bc.NewHash([32]byte{0x03}),
+                                       AccountID: "testAccount",
+                                       Amount:    3,
+                               },
+                               &UTXO{
+                                       OutputID:  bc.NewHash([32]byte{0x04}),
+                                       AccountID: "notMe",
+                                       Amount:    4,
+                               },
+                       },
+                       useUnconfirmed: true,
+                       wantUtxos: []*UTXO{
+                               &UTXO{
+                                       OutputID:  bc.NewHash([32]byte{0x03}),
+                                       AccountID: "testAccount",
+                                       Amount:    3,
+                               },
+                               &UTXO{
+                                       OutputID:  bc.NewHash([32]byte{0x01}),
+                                       AccountID: "testAccount",
+                                       Amount:    1,
+                               },
+                       },
+                       immatureAmount: 0,
+               },
+       }
+
+       for i, c := range cases {
+               for _, u := range c.dbUtxos {
+                       data, err := json.Marshal(u)
+                       if err != nil {
+                               t.Error(err)
+                       }
+                       testDB.Set(StandardUTXOKey(u.OutputID), data)
+               }
+
+               gotUtxos, immatureAmount := c.uk.findUtxos("testAccount", &bc.AssetID{}, c.useUnconfirmed)
+               if !testutil.DeepEqual(gotUtxos, c.wantUtxos) {
+                       t.Errorf("case %d: got %v want %v", i, gotUtxos, c.wantUtxos)
+               }
+               if immatureAmount != c.immatureAmount {
+                       t.Errorf("case %d: got %v want %v", i, immatureAmount, c.immatureAmount)
+               }
+
+               for _, u := range c.dbUtxos {
+                       testDB.Delete(StandardUTXOKey(u.OutputID))
+               }
+       }
+}
+
+func TestFindUtxo(t *testing.T) {
+       currentHeight := func() uint64 { return 9527 }
+       testDB := dbm.NewDB("testdb", "leveldb", "temp")
+       defer os.RemoveAll("temp")
+
+       cases := []struct {
+               uk             utxoKeeper
+               dbUtxos        map[string]*UTXO
+               outHash        bc.Hash
+               useUnconfirmed bool
+               wantUtxo       *UTXO
+               err            error
+       }{
+               {
+                       uk: utxoKeeper{
+                               db:            testDB,
+                               currentHeight: currentHeight,
+                               unconfirmed:   map[bc.Hash]*UTXO{},
+                       },
+                       dbUtxos:  map[string]*UTXO{},
+                       outHash:  bc.NewHash([32]byte{0x01}),
+                       wantUtxo: nil,
+                       err:      ErrMatchUTXO,
+               },
+               {
+                       uk: utxoKeeper{
+                               db:            testDB,
+                               currentHeight: currentHeight,
+                               unconfirmed: map[bc.Hash]*UTXO{
+                                       bc.NewHash([32]byte{0x01}): &UTXO{OutputID: bc.NewHash([32]byte{0x01})},
+                               },
+                       },
+                       dbUtxos:        map[string]*UTXO{},
+                       outHash:        bc.NewHash([32]byte{0x01}),
+                       wantUtxo:       nil,
+                       useUnconfirmed: false,
+                       err:            ErrMatchUTXO,
+               },
+               {
+                       uk: utxoKeeper{
+                               db:            testDB,
+                               currentHeight: currentHeight,
+                               unconfirmed: map[bc.Hash]*UTXO{
+                                       bc.NewHash([32]byte{0x01}): &UTXO{OutputID: bc.NewHash([32]byte{0x01})},
+                               },
+                       },
+                       dbUtxos:        map[string]*UTXO{},
+                       outHash:        bc.NewHash([32]byte{0x01}),
+                       wantUtxo:       &UTXO{OutputID: bc.NewHash([32]byte{0x01})},
+                       useUnconfirmed: true,
+                       err:            nil,
+               },
+               {
+                       uk: utxoKeeper{
+                               db:            testDB,
+                               currentHeight: currentHeight,
+                               unconfirmed:   map[bc.Hash]*UTXO{},
+                       },
+                       dbUtxos: map[string]*UTXO{
+                               string(StandardUTXOKey(bc.NewHash([32]byte{0x01}))): &UTXO{OutputID: bc.NewHash([32]byte{0x01})},
+                       },
+                       outHash:        bc.NewHash([32]byte{0x01}),
+                       wantUtxo:       &UTXO{OutputID: bc.NewHash([32]byte{0x01})},
+                       useUnconfirmed: false,
+                       err:            nil,
+               },
+               {
+                       uk: utxoKeeper{
+                               db:            testDB,
+                               currentHeight: currentHeight,
+                               unconfirmed:   map[bc.Hash]*UTXO{},
+                       },
+                       dbUtxos: map[string]*UTXO{
+                               string(ContractUTXOKey(bc.NewHash([32]byte{0x01}))): &UTXO{OutputID: bc.NewHash([32]byte{0x01})},
+                       },
+                       outHash:        bc.NewHash([32]byte{0x01}),
+                       wantUtxo:       &UTXO{OutputID: bc.NewHash([32]byte{0x01})},
+                       useUnconfirmed: false,
+                       err:            nil,
+               },
+       }
+
+       for i, c := range cases {
+               for k, u := range c.dbUtxos {
+                       data, err := json.Marshal(u)
+                       if err != nil {
+                               t.Error(err)
+                       }
+                       testDB.Set([]byte(k), data)
+               }
+
+               gotUtxo, err := c.uk.findUtxo(c.outHash, c.useUnconfirmed)
+               if !testutil.DeepEqual(gotUtxo, c.wantUtxo) {
+                       t.Errorf("case %d: got %v want %v", i, gotUtxo, c.wantUtxo)
+               }
+               if err != c.err {
+                       t.Errorf("case %d: got %v want %v", i, err, c.err)
+               }
+
+               for _, u := range c.dbUtxos {
+                       testDB.Delete(StandardUTXOKey(u.OutputID))
+               }
+       }
+}
+
+func TestOptUTXOs(t *testing.T) {
+       cases := []struct {
+               uk             utxoKeeper
+               input          []*UTXO
+               inputAmount    uint64
+               wantUtxos      []*UTXO
+               optAmount      uint64
+               reservedAmount uint64
+       }{
+               {
+                       uk: utxoKeeper{
+                               reserved: map[bc.Hash]uint64{
+                                       bc.NewHash([32]byte{0x01}): 1,
+                               },
+                       },
+                       input:          []*UTXO{},
+                       inputAmount:    13,
+                       wantUtxos:      []*UTXO{},
+                       optAmount:      0,
+                       reservedAmount: 0,
+               },
+               {
+                       uk: utxoKeeper{
+                               reserved: map[bc.Hash]uint64{
+                                       bc.NewHash([32]byte{0x01}): 1,
+                               },
+                       },
+                       input: []*UTXO{
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x01}), Amount: 1},
+                       },
+                       inputAmount:    13,
+                       wantUtxos:      []*UTXO{},
+                       optAmount:      0,
+                       reservedAmount: 1,
+               },
+               {
+                       uk: utxoKeeper{
+                               reserved: map[bc.Hash]uint64{
+                                       bc.NewHash([32]byte{0x01}): 1,
+                               },
+                       },
+                       input: []*UTXO{
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x01}), Amount: 1},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x02}), Amount: 3},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x03}), Amount: 5},
+                       },
+                       inputAmount: 13,
+                       wantUtxos: []*UTXO{
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x03}), Amount: 5},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x02}), Amount: 3},
+                       },
+                       optAmount:      8,
+                       reservedAmount: 1,
+               },
+               {
+                       uk: utxoKeeper{
+                               reserved: map[bc.Hash]uint64{
+                                       bc.NewHash([32]byte{0x01}): 1,
+                                       bc.NewHash([32]byte{0x02}): 2,
+                                       bc.NewHash([32]byte{0x03}): 3,
+                               },
+                       },
+                       input: []*UTXO{
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x01}), Amount: 1},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x02}), Amount: 3},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x03}), Amount: 5},
+                       },
+                       inputAmount:    1,
+                       wantUtxos:      []*UTXO{},
+                       optAmount:      0,
+                       reservedAmount: 9,
+               },
+               {
+                       uk: utxoKeeper{},
+                       input: []*UTXO{
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x01}), Amount: 1},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x02}), Amount: 3},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x03}), Amount: 5},
+                       },
+                       inputAmount: 1,
+                       wantUtxos: []*UTXO{
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x01}), Amount: 1},
+                       },
+                       optAmount:      1,
+                       reservedAmount: 0,
+               },
+               {
+                       uk: utxoKeeper{},
+                       input: []*UTXO{
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x02}), Amount: 2},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x03}), Amount: 3},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x05}), Amount: 5},
+                       },
+                       inputAmount: 5,
+                       wantUtxos: []*UTXO{
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x03}), Amount: 3},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x02}), Amount: 2},
+                       },
+                       optAmount:      5,
+                       reservedAmount: 0,
+               },
+               {
+                       uk: utxoKeeper{},
+                       input: []*UTXO{
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x01}), Amount: 1},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x02}), Amount: 1},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x03}), Amount: 1},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x04}), Amount: 1},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x05}), Amount: 1},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x06}), Amount: 1},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x08}), Amount: 6},
+                       },
+                       inputAmount: 6,
+                       wantUtxos: []*UTXO{
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x08}), Amount: 6},
+                       },
+                       optAmount:      6,
+                       reservedAmount: 0,
+               },
+               {
+                       uk: utxoKeeper{},
+                       input: []*UTXO{
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x01}), Amount: 1},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x02}), Amount: 1},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x03}), Amount: 1},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x04}), Amount: 1},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x05}), Amount: 1},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x06}), Amount: 1},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x08}), Amount: 6},
+                       },
+                       inputAmount: 5,
+                       wantUtxos: []*UTXO{
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x03}), Amount: 1},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x04}), Amount: 1},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x05}), Amount: 1},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x06}), Amount: 1},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x01}), Amount: 1},
+                       },
+                       optAmount:      5,
+                       reservedAmount: 0,
+               },
+               {
+                       uk: utxoKeeper{},
+                       input: []*UTXO{
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x01}), Amount: 1},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x03}), Amount: 3},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x05}), Amount: 5},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x07}), Amount: 7},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x11}), Amount: 11},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x13}), Amount: 13},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x23}), Amount: 23},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x31}), Amount: 31},
+                       },
+                       inputAmount: 13,
+                       wantUtxos: []*UTXO{
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x07}), Amount: 7},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x05}), Amount: 5},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x03}), Amount: 3},
+                       },
+                       optAmount:      15,
+                       reservedAmount: 0,
+               },
+               {
+                       uk: utxoKeeper{},
+                       input: []*UTXO{
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x01}), Amount: 1},
+                       },
+                       inputAmount: 1,
+                       wantUtxos: []*UTXO{
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x01}), Amount: 1},
+                       },
+                       optAmount:      1,
+                       reservedAmount: 0,
+               },
+               {
+                       uk: utxoKeeper{},
+                       input: []*UTXO{
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x01}), Amount: 1},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x02}), Amount: 2},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x03}), Amount: 3},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x04}), Amount: 4},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x05}), Amount: 5},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x06}), Amount: 6},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x07}), Amount: 7},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x08}), Amount: 8},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x09}), Amount: 9},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x10}), Amount: 10},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x11}), Amount: 11},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x12}), Amount: 12},
+                       },
+                       inputAmount: 15,
+                       wantUtxos: []*UTXO{
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x05}), Amount: 5},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x04}), Amount: 4},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x03}), Amount: 3},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x02}), Amount: 2},
+                               &UTXO{OutputID: bc.NewHash([32]byte{0x01}), Amount: 1},
+                       },
+                       optAmount:      15,
+                       reservedAmount: 0,
+               },
+       }
+
+       for i, c := range cases {
+               got, optAmount, reservedAmount := c.uk.optUTXOs(c.input, c.inputAmount)
+               if !testutil.DeepEqual(got, c.wantUtxos) {
+                       t.Errorf("case %d: utxos got %v want %v", i, got, c.wantUtxos)
+               }
+               if optAmount != c.optAmount {
+                       t.Errorf("case %d: utxos got %v want %v", i, optAmount, c.optAmount)
+               }
+               if reservedAmount != c.reservedAmount {
+                       t.Errorf("case %d: reservedAmount got %v want %v", i, reservedAmount, c.reservedAmount)
+               }
+       }
+}
+
+func checkUtxoKeeperEqual(t *testing.T, i int, a, b *utxoKeeper) {
+       if !testutil.DeepEqual(a.unconfirmed, b.unconfirmed) {
+               t.Errorf("case %d: unconfirmed got %v want %v", i, a.unconfirmed, b.unconfirmed)
+       }
+       if !testutil.DeepEqual(a.reserved, b.reserved) {
+               t.Errorf("case %d: reserved got %v want %v", i, a.reserved, b.reserved)
+       }
+       if !testutil.DeepEqual(a.reservations, b.reservations) {
+               t.Errorf("case %d: reservations got %v want %v", i, a.reservations, b.reservations)
+       }
+}