import (
"encoding/json"
+ "io/ioutil"
"os"
"testing"
"time"
+ "github.com/golang/groupcache/lru"
+ "github.com/vapor/blockchain/txbuilder"
+ "github.com/vapor/crypto/ed25519/chainkd"
dbm "github.com/vapor/database/leveldb"
"github.com/vapor/protocol/bc"
"github.com/vapor/testutil"
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
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{},
},
{
before: utxoKeeper{
- db: testDB,
+ store: accountStore,
currentHeight: currentHeight,
unconfirmed: map[bc.Hash]*UTXO{
bc.NewHash([32]byte{0x01}): &UTXO{
reservations: map[uint64]*reservation{},
},
after: utxoKeeper{
- db: testDB,
+ store: accountStore,
currentHeight: currentHeight,
unconfirmed: map[bc.Hash]*UTXO{
bc.NewHash([32]byte{0x01}): &UTXO{
},
{
before: utxoKeeper{
- db: testDB,
+ store: accountStore,
currentHeight: currentHeight,
unconfirmed: map[bc.Hash]*UTXO{
bc.NewHash([32]byte{0x01}): &UTXO{
reservations: map[uint64]*reservation{},
},
after: utxoKeeper{
- db: testDB,
+ store: accountStore,
currentHeight: currentHeight,
unconfirmed: map[bc.Hash]*UTXO{
bc.NewHash([32]byte{0x01}): &UTXO{
},
{
before: utxoKeeper{
- db: testDB,
+ store: accountStore,
currentHeight: currentHeight,
unconfirmed: map[bc.Hash]*UTXO{
bc.NewHash([32]byte{0x01}): &UTXO{
reservations: map[uint64]*reservation{},
},
after: utxoKeeper{
- db: testDB,
+ store: accountStore,
currentHeight: currentHeight,
unconfirmed: map[bc.Hash]*UTXO{
bc.NewHash([32]byte{0x01}): &UTXO{
},
{
before: utxoKeeper{
- db: testDB,
+ store: accountStore,
currentHeight: currentHeight,
unconfirmed: map[bc.Hash]*UTXO{
bc.NewHash([32]byte{0x01}): &UTXO{
reservations: map[uint64]*reservation{},
},
after: utxoKeeper{
- db: testDB,
+ store: accountStore,
currentHeight: currentHeight,
unconfirmed: map[bc.Hash]*UTXO{
bc.NewHash([32]byte{0x01}): &UTXO{
},
{
before: utxoKeeper{
- db: testDB,
+ store: accountStore,
currentHeight: currentHeight,
nextIndex: 1,
unconfirmed: map[bc.Hash]*UTXO{
reservations: map[uint64]*reservation{},
},
after: utxoKeeper{
- db: testDB,
+ store: accountStore,
currentHeight: currentHeight,
unconfirmed: map[bc.Hash]*UTXO{
bc.NewHash([32]byte{0x01}): &UTXO{
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)
testDB := dbm.NewDB("testdb", "leveldb", "temp")
defer os.RemoveAll("temp")
+ accountStore := newMockAccountStore(testDB)
+
cases := []struct {
before utxoKeeper
after utxoKeeper
}{
{
before: utxoKeeper{
- db: testDB,
+ store: accountStore,
currentHeight: currentHeight,
unconfirmed: map[bc.Hash]*UTXO{
bc.NewHash([32]byte{0x01}): &UTXO{
reservations: map[uint64]*reservation{},
},
after: utxoKeeper{
- db: testDB,
+ store: accountStore,
currentHeight: currentHeight,
unconfirmed: map[bc.Hash]*UTXO{
bc.NewHash([32]byte{0x01}): &UTXO{
},
{
before: utxoKeeper{
- db: testDB,
+ store: accountStore,
currentHeight: currentHeight,
unconfirmed: map[bc.Hash]*UTXO{
bc.NewHash([32]byte{0x01}): &UTXO{
reservations: map[uint64]*reservation{},
},
after: utxoKeeper{
- db: testDB,
+ store: accountStore,
currentHeight: currentHeight,
unconfirmed: map[bc.Hash]*UTXO{
bc.NewHash([32]byte{0x01}): &UTXO{
},
{
before: utxoKeeper{
- db: testDB,
+ store: accountStore,
currentHeight: currentHeight,
unconfirmed: map[bc.Hash]*UTXO{
bc.NewHash([32]byte{0x01}): &UTXO{
reservations: map[uint64]*reservation{},
},
after: utxoKeeper{
- db: testDB,
+ store: accountStore,
currentHeight: currentHeight,
unconfirmed: map[bc.Hash]*UTXO{
bc.NewHash([32]byte{0x01}): &UTXO{
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
useUnconfirmed bool
wantUtxos []*UTXO
immatureAmount uint64
+ vote []byte
}{
{
uk: utxoKeeper{
- db: testDB,
+ store: accountStore,
currentHeight: currentHeight,
unconfirmed: map[bc.Hash]*UTXO{},
},
},
{
uk: utxoKeeper{
- db: testDB,
+ store: accountStore,
currentHeight: currentHeight,
unconfirmed: map[bc.Hash]*UTXO{},
},
},
{
uk: utxoKeeper{
- db: testDB,
+ store: accountStore,
currentHeight: currentHeight,
unconfirmed: map[bc.Hash]*UTXO{},
},
},
{
uk: utxoKeeper{
- db: testDB,
+ store: accountStore,
currentHeight: currentHeight,
unconfirmed: map[bc.Hash]*UTXO{
bc.NewHash([32]byte{0x01}): &UTXO{
},
{
uk: utxoKeeper{
- db: testDB,
+ store: accountStore,
currentHeight: currentHeight,
unconfirmed: map[bc.Hash]*UTXO{
bc.NewHash([32]byte{0x11}): &UTXO{
},
{
uk: utxoKeeper{
- db: testDB,
+ store: accountStore,
currentHeight: currentHeight,
unconfirmed: map[bc.Hash]*UTXO{
bc.NewHash([32]byte{0x01}): &UTXO{
},
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)
}
}
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
}{
{
uk: utxoKeeper{
- db: testDB,
+ store: accountStore,
currentHeight: currentHeight,
unconfirmed: map[bc.Hash]*UTXO{},
},
},
{
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})},
},
{
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})},
},
{
uk: utxoKeeper{
- db: testDB,
+ store: accountStore,
currentHeight: currentHeight,
unconfirmed: map[bc.Hash]*UTXO{},
},
},
{
uk: utxoKeeper{
- db: testDB,
+ store: accountStore,
currentHeight: currentHeight,
unconfirmed: map[bc.Hash]*UTXO{},
},
}
for _, u := range c.dbUtxos {
- testDB.Delete(StandardUTXOKey(u.OutputID))
+ c.uk.store.DeleteStandardUTXO(u.OutputID)
}
}
}
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),
+ }
+}