X-Git-Url: http://git.osdn.net/view?p=bytom%2Fvapor.git;a=blobdiff_plain;f=account%2Futxo_keeper_test.go;h=4738c6c057fa498f2d33d6495f5cf6205c8b361a;hp=bb9d01055c7941228614a306cccedc58a89f1a5c;hb=1337be95f74a1d2a1a7316737efde413f29bcb2f;hpb=db158dcf09436b003defd333f1a665e7e051d820 diff --git a/account/utxo_keeper_test.go b/account/utxo_keeper_test.go index bb9d0105..4738c6c0 100644 --- a/account/utxo_keeper_test.go +++ b/account/utxo_keeper_test.go @@ -2,13 +2,17 @@ package account import ( "encoding/json" + "io/ioutil" "os" "testing" "time" - dbm "github.com/vapor/database/leveldb" - "github.com/vapor/protocol/bc" - "github.com/vapor/testutil" + "github.com/golang/groupcache/lru" + "github.com/bytom/vapor/blockchain/txbuilder" + "github.com/bytom/vapor/crypto/ed25519/chainkd" + dbm "github.com/bytom/vapor/database/leveldb" + "github.com/bytom/vapor/protocol/bc" + "github.com/bytom/vapor/testutil" ) func TestAddUnconfirmedUtxo(t *testing.T) { @@ -273,7 +277,12 @@ func TestRemoveUnconfirmedUtxo(t *testing.T) { func TestReserve(t *testing.T) { currentHeight := func() uint64 { return 9527 } testDB := dbm.NewDB("testdb", "leveldb", "temp") - defer os.RemoveAll("temp") + defer func() { + testDB.Close() + os.RemoveAll("temp") + }() + + accountStore := newMockAccountStore(testDB) cases := []struct { before utxoKeeper @@ -281,16 +290,17 @@ func TestReserve(t *testing.T) { err error reserveAmount uint64 exp time.Time + vote []byte }{ { before: utxoKeeper{ - db: testDB, + store: accountStore, currentHeight: currentHeight, reserved: map[bc.Hash]uint64{}, reservations: map[uint64]*reservation{}, }, after: utxoKeeper{ - db: testDB, + store: accountStore, currentHeight: currentHeight, reserved: map[bc.Hash]uint64{}, reservations: map[uint64]*reservation{}, @@ -300,7 +310,7 @@ func TestReserve(t *testing.T) { }, { before: utxoKeeper{ - db: testDB, + store: accountStore, currentHeight: currentHeight, unconfirmed: map[bc.Hash]*UTXO{ bc.NewHash([32]byte{0x01}): &UTXO{ @@ -313,7 +323,7 @@ func TestReserve(t *testing.T) { reservations: map[uint64]*reservation{}, }, after: utxoKeeper{ - db: testDB, + store: accountStore, currentHeight: currentHeight, unconfirmed: map[bc.Hash]*UTXO{ bc.NewHash([32]byte{0x01}): &UTXO{ @@ -330,7 +340,7 @@ func TestReserve(t *testing.T) { }, { before: utxoKeeper{ - db: testDB, + store: accountStore, currentHeight: currentHeight, unconfirmed: map[bc.Hash]*UTXO{ bc.NewHash([32]byte{0x01}): &UTXO{ @@ -344,7 +354,7 @@ func TestReserve(t *testing.T) { reservations: map[uint64]*reservation{}, }, after: utxoKeeper{ - db: testDB, + store: accountStore, currentHeight: currentHeight, unconfirmed: map[bc.Hash]*UTXO{ bc.NewHash([32]byte{0x01}): &UTXO{ @@ -362,7 +372,7 @@ func TestReserve(t *testing.T) { }, { before: utxoKeeper{ - db: testDB, + store: accountStore, currentHeight: currentHeight, unconfirmed: map[bc.Hash]*UTXO{ bc.NewHash([32]byte{0x01}): &UTXO{ @@ -377,7 +387,7 @@ func TestReserve(t *testing.T) { reservations: map[uint64]*reservation{}, }, after: utxoKeeper{ - db: testDB, + store: accountStore, currentHeight: currentHeight, unconfirmed: map[bc.Hash]*UTXO{ bc.NewHash([32]byte{0x01}): &UTXO{ @@ -396,7 +406,7 @@ func TestReserve(t *testing.T) { }, { before: utxoKeeper{ - db: testDB, + store: accountStore, currentHeight: currentHeight, unconfirmed: map[bc.Hash]*UTXO{ bc.NewHash([32]byte{0x01}): &UTXO{ @@ -409,7 +419,7 @@ func TestReserve(t *testing.T) { reservations: map[uint64]*reservation{}, }, after: utxoKeeper{ - db: testDB, + store: accountStore, currentHeight: currentHeight, unconfirmed: map[bc.Hash]*UTXO{ bc.NewHash([32]byte{0x01}): &UTXO{ @@ -442,7 +452,7 @@ func TestReserve(t *testing.T) { }, { before: utxoKeeper{ - db: testDB, + store: accountStore, currentHeight: currentHeight, nextIndex: 1, unconfirmed: map[bc.Hash]*UTXO{ @@ -468,7 +478,7 @@ func TestReserve(t *testing.T) { reservations: map[uint64]*reservation{}, }, after: utxoKeeper{ - db: testDB, + store: accountStore, currentHeight: currentHeight, unconfirmed: map[bc.Hash]*UTXO{ bc.NewHash([32]byte{0x01}): &UTXO{ @@ -516,10 +526,60 @@ func TestReserve(t *testing.T) { err: nil, exp: time.Date(2016, 8, 10, 0, 0, 0, 0, time.UTC), }, + { + before: utxoKeeper{ + store: accountStore, + currentHeight: currentHeight, + unconfirmed: map[bc.Hash]*UTXO{ + bc.NewHash([32]byte{0x01}): &UTXO{ + OutputID: bc.NewHash([32]byte{0x01}), + AccountID: "testAccount", + Amount: 3, + Vote: []byte("af594006a40837d9f028daabb6d589df0b9138daefad5683e5233c2646279217294a8d532e60863bcf196625a35fb8ceeffa3c09610eb92dcfb655a947f13269"), + }, + }, + reserved: map[bc.Hash]uint64{}, + reservations: map[uint64]*reservation{}, + }, + after: utxoKeeper{ + store: accountStore, + currentHeight: currentHeight, + unconfirmed: map[bc.Hash]*UTXO{ + bc.NewHash([32]byte{0x01}): &UTXO{ + OutputID: bc.NewHash([32]byte{0x01}), + AccountID: "testAccount", + Amount: 3, + Vote: []byte("af594006a40837d9f028daabb6d589df0b9138daefad5683e5233c2646279217294a8d532e60863bcf196625a35fb8ceeffa3c09610eb92dcfb655a947f13269"), + }, + }, + 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, + Vote: []byte("af594006a40837d9f028daabb6d589df0b9138daefad5683e5233c2646279217294a8d532e60863bcf196625a35fb8ceeffa3c09610eb92dcfb655a947f13269"), + }, + }, + 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), + vote: []byte("af594006a40837d9f028daabb6d589df0b9138daefad5683e5233c2646279217294a8d532e60863bcf196625a35fb8ceeffa3c09610eb92dcfb655a947f13269"), + }, } for i, c := range cases { - if _, err := c.before.Reserve("testAccount", &bc.AssetID{}, c.reserveAmount, true, c.exp); err != c.err { + if _, err := c.before.Reserve("testAccount", &bc.AssetID{}, c.reserveAmount, true, c.vote, 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) @@ -531,6 +591,8 @@ func TestReserveParticular(t *testing.T) { testDB := dbm.NewDB("testdb", "leveldb", "temp") defer os.RemoveAll("temp") + accountStore := newMockAccountStore(testDB) + cases := []struct { before utxoKeeper after utxoKeeper @@ -540,7 +602,7 @@ func TestReserveParticular(t *testing.T) { }{ { before: utxoKeeper{ - db: testDB, + store: accountStore, currentHeight: currentHeight, unconfirmed: map[bc.Hash]*UTXO{ bc.NewHash([32]byte{0x01}): &UTXO{ @@ -555,7 +617,7 @@ func TestReserveParticular(t *testing.T) { reservations: map[uint64]*reservation{}, }, after: utxoKeeper{ - db: testDB, + store: accountStore, currentHeight: currentHeight, unconfirmed: map[bc.Hash]*UTXO{ bc.NewHash([32]byte{0x01}): &UTXO{ @@ -574,7 +636,7 @@ func TestReserveParticular(t *testing.T) { }, { before: utxoKeeper{ - db: testDB, + store: accountStore, currentHeight: currentHeight, unconfirmed: map[bc.Hash]*UTXO{ bc.NewHash([32]byte{0x01}): &UTXO{ @@ -588,7 +650,7 @@ func TestReserveParticular(t *testing.T) { reservations: map[uint64]*reservation{}, }, after: utxoKeeper{ - db: testDB, + store: accountStore, currentHeight: currentHeight, unconfirmed: map[bc.Hash]*UTXO{ bc.NewHash([32]byte{0x01}): &UTXO{ @@ -606,7 +668,7 @@ func TestReserveParticular(t *testing.T) { }, { before: utxoKeeper{ - db: testDB, + store: accountStore, currentHeight: currentHeight, unconfirmed: map[bc.Hash]*UTXO{ bc.NewHash([32]byte{0x01}): &UTXO{ @@ -619,7 +681,7 @@ func TestReserveParticular(t *testing.T) { reservations: map[uint64]*reservation{}, }, after: utxoKeeper{ - db: testDB, + store: accountStore, currentHeight: currentHeight, unconfirmed: map[bc.Hash]*UTXO{ bc.NewHash([32]byte{0x01}): &UTXO{ @@ -684,7 +746,12 @@ func TestExpireReservation(t *testing.T) { func TestFindUtxos(t *testing.T) { currentHeight := func() uint64 { return 9527 } testDB := dbm.NewDB("testdb", "leveldb", "temp") - defer os.RemoveAll("temp") + defer func() { + testDB.Close() + os.RemoveAll("temp") + }() + + accountStore := newMockAccountStore(testDB) cases := []struct { uk utxoKeeper @@ -692,10 +759,11 @@ func TestFindUtxos(t *testing.T) { useUnconfirmed bool wantUtxos []*UTXO immatureAmount uint64 + vote []byte }{ { uk: utxoKeeper{ - db: testDB, + store: accountStore, currentHeight: currentHeight, unconfirmed: map[bc.Hash]*UTXO{}, }, @@ -706,7 +774,7 @@ func TestFindUtxos(t *testing.T) { }, { uk: utxoKeeper{ - db: testDB, + store: accountStore, currentHeight: currentHeight, unconfirmed: map[bc.Hash]*UTXO{}, }, @@ -735,7 +803,7 @@ func TestFindUtxos(t *testing.T) { }, { uk: utxoKeeper{ - db: testDB, + store: accountStore, currentHeight: currentHeight, unconfirmed: map[bc.Hash]*UTXO{}, }, @@ -753,7 +821,7 @@ func TestFindUtxos(t *testing.T) { }, { uk: utxoKeeper{ - db: testDB, + store: accountStore, currentHeight: currentHeight, unconfirmed: map[bc.Hash]*UTXO{ bc.NewHash([32]byte{0x01}): &UTXO{ @@ -782,7 +850,7 @@ func TestFindUtxos(t *testing.T) { }, { uk: utxoKeeper{ - db: testDB, + store: accountStore, currentHeight: currentHeight, unconfirmed: map[bc.Hash]*UTXO{ bc.NewHash([32]byte{0x11}): &UTXO{ @@ -816,7 +884,7 @@ func TestFindUtxos(t *testing.T) { }, { uk: utxoKeeper{ - db: testDB, + store: accountStore, currentHeight: currentHeight, unconfirmed: map[bc.Hash]*UTXO{ bc.NewHash([32]byte{0x01}): &UTXO{ @@ -858,18 +926,42 @@ func TestFindUtxos(t *testing.T) { }, immatureAmount: 0, }, + { + uk: utxoKeeper{ + store: accountStore, + currentHeight: currentHeight, + unconfirmed: map[bc.Hash]*UTXO{}, + }, + dbUtxos: []*UTXO{ + &UTXO{ + OutputID: bc.NewHash([32]byte{0x01}), + AccountID: "testAccount", + Amount: 6, + Vote: []byte("af594006a40837d9f028daabb6d589df0b9138daefad5683e5233c2646279217294a8d532e60863bcf196625a35fb8ceeffa3c09610eb92dcfb655a947f13269"), + }, + }, + useUnconfirmed: false, + wantUtxos: []*UTXO{ + &UTXO{ + OutputID: bc.NewHash([32]byte{0x01}), + AccountID: "testAccount", + Amount: 6, + Vote: []byte("af594006a40837d9f028daabb6d589df0b9138daefad5683e5233c2646279217294a8d532e60863bcf196625a35fb8ceeffa3c09610eb92dcfb655a947f13269"), + }, + }, + immatureAmount: 0, + vote: []byte("af594006a40837d9f028daabb6d589df0b9138daefad5683e5233c2646279217294a8d532e60863bcf196625a35fb8ceeffa3c09610eb92dcfb655a947f13269"), + }, } for i, c := range cases { for _, u := range c.dbUtxos { - data, err := json.Marshal(u) - if err != nil { + if err := c.uk.store.SetStandardUTXO(u.OutputID, u); err != nil { t.Error(err) } - testDB.Set(StandardUTXOKey(u.OutputID), data) } - gotUtxos, immatureAmount := c.uk.findUtxos("testAccount", &bc.AssetID{}, c.useUnconfirmed) + gotUtxos, immatureAmount := c.uk.findUtxos("testAccount", &bc.AssetID{}, c.useUnconfirmed, c.vote) if !testutil.DeepEqual(gotUtxos, c.wantUtxos) { t.Errorf("case %d: got %v want %v", i, gotUtxos, c.wantUtxos) } @@ -878,16 +970,18 @@ func TestFindUtxos(t *testing.T) { } for _, u := range c.dbUtxos { - testDB.Delete(StandardUTXOKey(u.OutputID)) + c.uk.store.DeleteStandardUTXO(u.OutputID) } } } -func TestFindUtxo(t *testing.T) { +func TestFindUTXO(t *testing.T) { currentHeight := func() uint64 { return 9527 } testDB := dbm.NewDB("testdb", "leveldb", "temp") defer os.RemoveAll("temp") + accountStore := newMockAccountStore(testDB) + cases := []struct { uk utxoKeeper dbUtxos map[string]*UTXO @@ -898,7 +992,7 @@ func TestFindUtxo(t *testing.T) { }{ { uk: utxoKeeper{ - db: testDB, + store: accountStore, currentHeight: currentHeight, unconfirmed: map[bc.Hash]*UTXO{}, }, @@ -909,7 +1003,7 @@ func TestFindUtxo(t *testing.T) { }, { uk: utxoKeeper{ - db: testDB, + store: accountStore, currentHeight: currentHeight, unconfirmed: map[bc.Hash]*UTXO{ bc.NewHash([32]byte{0x01}): &UTXO{OutputID: bc.NewHash([32]byte{0x01})}, @@ -923,7 +1017,7 @@ func TestFindUtxo(t *testing.T) { }, { uk: utxoKeeper{ - db: testDB, + store: accountStore, currentHeight: currentHeight, unconfirmed: map[bc.Hash]*UTXO{ bc.NewHash([32]byte{0x01}): &UTXO{OutputID: bc.NewHash([32]byte{0x01})}, @@ -937,7 +1031,7 @@ func TestFindUtxo(t *testing.T) { }, { uk: utxoKeeper{ - db: testDB, + store: accountStore, currentHeight: currentHeight, unconfirmed: map[bc.Hash]*UTXO{}, }, @@ -951,7 +1045,7 @@ func TestFindUtxo(t *testing.T) { }, { uk: utxoKeeper{ - db: testDB, + store: accountStore, currentHeight: currentHeight, unconfirmed: map[bc.Hash]*UTXO{}, }, @@ -983,7 +1077,7 @@ func TestFindUtxo(t *testing.T) { } for _, u := range c.dbUtxos { - testDB.Delete(StandardUTXOKey(u.OutputID)) + c.uk.store.DeleteStandardUTXO(u.OutputID) } } } @@ -1216,3 +1310,165 @@ func checkUtxoKeeperEqual(t *testing.T, i int, a, b *utxoKeeper) { t.Errorf("case %d: reservations got %v want %v", i, a.reservations, b.reservations) } } + +const ( + utxoPrefix byte = iota //UTXOPrefix is StandardUTXOKey prefix + contractPrefix + contractIndexPrefix + accountPrefix // AccountPrefix is account ID prefix + accountIndexPrefix +) + +// leveldb key prefix +var ( + colon byte = 0x3a + accountStore = []byte("AS:") + UTXOPrefix = append(accountStore, utxoPrefix, colon) + ContractPrefix = append(accountStore, contractPrefix, colon) + ContractIndexPrefix = append(accountStore, contractIndexPrefix, colon) + AccountPrefix = append(accountStore, accountPrefix, colon) // AccountPrefix is account ID prefix + AccountIndexPrefix = append(accountStore, accountIndexPrefix, colon) +) + +const ( + sutxoPrefix byte = iota //SUTXOPrefix is ContractUTXOKey prefix + accountAliasPrefix + txPrefix //TxPrefix is wallet database transactions prefix + txIndexPrefix //TxIndexPrefix is wallet database tx index prefix + unconfirmedTxPrefix //UnconfirmedTxPrefix is txpool unconfirmed transactions prefix + globalTxIndexPrefix //GlobalTxIndexPrefix is wallet database global tx index prefix + walletKey + miningAddressKey + coinbaseAbKey + recoveryKey //recoveryKey key for db store recovery info. +) + +var ( + walletStore = []byte("WS:") + SUTXOPrefix = append(walletStore, sutxoPrefix, colon) + AccountAliasPrefix = append(walletStore, accountAliasPrefix, colon) + TxPrefix = append(walletStore, txPrefix, colon) //TxPrefix is wallet database transactions prefix + TxIndexPrefix = append(walletStore, txIndexPrefix, colon) //TxIndexPrefix is wallet database tx index prefix + UnconfirmedTxPrefix = append(walletStore, unconfirmedTxPrefix, colon) //UnconfirmedTxPrefix is txpool unconfirmed transactions prefix + GlobalTxIndexPrefix = append(walletStore, globalTxIndexPrefix, colon) //GlobalTxIndexPrefix is wallet database global tx index prefix + WalletKey = append(walletStore, walletKey) + MiningAddressKey = append(walletStore, miningAddressKey) + CoinbaseAbKey = append(walletStore, coinbaseAbKey) + RecoveryKey = append(walletStore, recoveryKey) +) + +type mockAccountStore struct { + db dbm.DB + batch dbm.Batch +} + +// NewAccountStore create new AccountStore. +func newMockAccountStore(db dbm.DB) *mockAccountStore { + return &mockAccountStore{ + db: db, + batch: nil, + } +} + +// StandardUTXOKey makes an account unspent outputs key to store +func StandardUTXOKey(id bc.Hash) []byte { + return append(UTXOPrefix, id.Bytes()...) +} + +// ContractUTXOKey makes a smart contract unspent outputs key to store +func ContractUTXOKey(id bc.Hash) []byte { + return append(SUTXOPrefix, id.Bytes()...) +} + +func (store *mockAccountStore) InitBatch() AccountStore { return nil } +func (store *mockAccountStore) CommitBatch() error { return nil } +func (store *mockAccountStore) DeleteAccount(*Account) error { return nil } +func (store *mockAccountStore) GetAccountByAlias(string) (*Account, error) { return nil, nil } +func (store *mockAccountStore) GetAccountByID(string) (*Account, error) { return nil, nil } +func (store *mockAccountStore) GetAccountIndex([]chainkd.XPub) uint64 { return 0 } +func (store *mockAccountStore) GetBip44ContractIndex(string, bool) uint64 { return 0 } +func (store *mockAccountStore) GetCoinbaseArbitrary() []byte { return nil } +func (store *mockAccountStore) GetContractIndex(string) uint64 { return 0 } +func (store *mockAccountStore) GetControlProgram(bc.Hash) (*CtrlProgram, error) { return nil, nil } +func (store *mockAccountStore) GetMiningAddress() (*CtrlProgram, error) { return nil, nil } +func (store *mockAccountStore) ListAccounts(string) ([]*Account, error) { return nil, nil } +func (store *mockAccountStore) ListControlPrograms() ([]*CtrlProgram, error) { return nil, nil } +func (store *mockAccountStore) SetAccount(*Account) error { return nil } +func (store *mockAccountStore) SetAccountIndex(*Account) { return } +func (store *mockAccountStore) SetBip44ContractIndex(string, bool, uint64) { return } +func (store *mockAccountStore) SetCoinbaseArbitrary([]byte) { return } +func (store *mockAccountStore) SetContractIndex(string, uint64) { return } +func (store *mockAccountStore) SetControlProgram(bc.Hash, *CtrlProgram) error { return nil } +func (store *mockAccountStore) SetMiningAddress(*CtrlProgram) error { return nil } + +// DeleteStandardUTXO delete utxo by outpu id +func (store *mockAccountStore) DeleteStandardUTXO(outputID bc.Hash) { + if store.batch == nil { + store.db.Delete(StandardUTXOKey(outputID)) + } else { + store.batch.Delete(StandardUTXOKey(outputID)) + } +} + +// GetUTXO get standard utxo by id +func (store *mockAccountStore) GetUTXO(outid bc.Hash) (*UTXO, error) { + u := new(UTXO) + if data := store.db.Get(StandardUTXOKey(outid)); data != nil { + return u, json.Unmarshal(data, u) + } + if data := store.db.Get(ContractUTXOKey(outid)); data != nil { + return u, json.Unmarshal(data, u) + } + return nil, ErrMatchUTXO +} + +// ListUTXOs get utxos by accountID +func (store *mockAccountStore) ListUTXOs() ([]*UTXO, error) { + utxoIter := store.db.IteratorPrefix([]byte(UTXOPrefix)) + defer utxoIter.Release() + + utxos := []*UTXO{} + for utxoIter.Next() { + utxo := new(UTXO) + if err := json.Unmarshal(utxoIter.Value(), utxo); err != nil { + return nil, err + } + utxos = append(utxos, utxo) + } + return utxos, nil +} + +// SetStandardUTXO set standard utxo +func (store *mockAccountStore) SetStandardUTXO(outputID bc.Hash, utxo *UTXO) error { + data, err := json.Marshal(utxo) + if err != nil { + return err + } + if store.batch == nil { + store.db.Set(StandardUTXOKey(outputID), data) + } else { + store.batch.Set(StandardUTXOKey(outputID), data) + } + return nil +} + +func mockAccountManager(t *testing.T) *Manager { + dirPath, err := ioutil.TempDir(".", "") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dirPath) + + testDB := dbm.NewDB("testdb", "memdb", dirPath) + accountStore := newMockAccountStore(testDB) + bestBlockHeight := func() uint64 { return 9527 } + + return &Manager{ + store: accountStore, + chain: nil, + utxoKeeper: newUtxoKeeper(bestBlockHeight, accountStore), + cache: lru.New(maxAccountCache), + aliasCache: lru.New(maxAccountCache), + delayedACPs: make(map[*txbuilder.TemplateBuilder][]*CtrlProgram), + } +}