package account
import (
- "encoding/json"
"reflect"
- "sort"
"strings"
"sync"
"github.com/vapor/crypto"
"github.com/vapor/crypto/ed25519/chainkd"
"github.com/vapor/crypto/sha3pool"
- dbm "github.com/vapor/database/leveldb"
"github.com/vapor/errors"
"github.com/vapor/protocol"
"github.com/vapor/protocol/bc"
logModule = "account"
)
-var (
- accountIndexPrefix = []byte("AccountIndex:")
- accountPrefix = []byte("Account:")
- aliasPrefix = []byte("AccountAlias:")
- contractIndexPrefix = []byte("ContractIndex")
- contractPrefix = []byte("Contract:")
- miningAddressKey = []byte("MiningAddress")
- CoinbaseAbKey = []byte("CoinbaseArbitrary")
-)
-
// pre-define errors for supporting bytom errorFormatter
var (
- ErrDuplicateAlias = errors.New("Duplicate account alias")
- ErrDuplicateIndex = errors.New("Duplicate account with same xPubs and index")
- ErrFindAccount = errors.New("Failed to find account")
- ErrMarshalAccount = errors.New("Failed to marshal account")
- ErrInvalidAddress = errors.New("Invalid address")
- ErrFindCtrlProgram = errors.New("Failed to find account control program")
- ErrDeriveRule = errors.New("Invalid key derivation rule")
- ErrContractIndex = errors.New("Exceeded maximum addresses per account")
- ErrAccountIndex = errors.New("Exceeded maximum accounts per xpub")
- ErrFindTransaction = errors.New("No transaction")
- ErrAccountIDEmpty = errors.New("account_id is empty")
+ ErrDuplicateAlias = errors.New("Duplicate account alias")
+ ErrDuplicateIndex = errors.New("Duplicate account with same xPubs and index")
+ ErrFindAccount = errors.New("Failed to find account")
+ ErrMarshalAccount = errors.New("Failed to marshal account")
+ ErrInvalidAddress = errors.New("Invalid address")
+ ErrFindCtrlProgram = errors.New("Failed to find account control program")
+ ErrDeriveRule = errors.New("Invalid key derivation rule")
+ ErrContractIndex = errors.New("Exceeded maximum addresses per account")
+ ErrAccountIndex = errors.New("Exceeded maximum accounts per xpub")
+ ErrFindTransaction = errors.New("No transaction")
+ ErrFindMiningAddress = errors.New("Failed to find mining address")
+ ErrAccountIDEmpty = errors.New("account_id is empty")
)
-// ContractKey account control promgram store prefix
-func ContractKey(hash common.Hash) []byte {
- return append(contractPrefix, hash[:]...)
-}
-
-// Key account store prefix
-func Key(name string) []byte {
- return append(accountPrefix, []byte(name)...)
-}
-
-func aliasKey(name string) []byte {
- return append(aliasPrefix, []byte(name)...)
-}
-
-func bip44ContractIndexKey(accountID string, change bool) []byte {
- key := append(contractIndexPrefix, accountID...)
- if change {
- return append(key, []byte{1}...)
- }
- return append(key, []byte{0}...)
-}
-
-func contractIndexKey(accountID string) []byte {
- return append(contractIndexPrefix, []byte(accountID)...)
-}
-
// Account is structure of Bytom account
type Account struct {
*signers.Signer
// Manager stores accounts and their associated control programs.
type Manager struct {
- db dbm.DB
+ store AccountStore
chain *protocol.Chain
utxoKeeper *utxoKeeper
}
// NewManager creates a new account manager
-func NewManager(walletDB dbm.DB, chain *protocol.Chain) *Manager {
+func NewManager(store AccountStore, chain *protocol.Chain) *Manager {
return &Manager{
- db: walletDB,
+ store: store,
chain: chain,
- utxoKeeper: newUtxoKeeper(chain.BestBlockHeight, walletDB),
+ utxoKeeper: newUtxoKeeper(chain.BestBlockHeight, store),
cache: lru.New(maxAccountCache),
aliasCache: lru.New(maxAccountCache),
delayedACPs: make(map[*txbuilder.TemplateBuilder][]*CtrlProgram),
return &Account{Signer: signer, ID: id, Alias: strings.ToLower(strings.TrimSpace(alias))}, nil
}
-func (m *Manager) saveAccount(account *Account, updateIndex bool) error {
- rawAccount, err := json.Marshal(account)
- if err != nil {
- return ErrMarshalAccount
+func (m *Manager) saveAccount(account *Account) error {
+ newStore := m.store.InitBatch()
+
+ // update account index
+ newStore.SetAccountIndex(account)
+ if err := newStore.SetAccount(account); err != nil {
+ return err
}
- storeBatch := m.db.NewBatch()
- storeBatch.Set(Key(account.ID), rawAccount)
- storeBatch.Set(aliasKey(account.Alias), []byte(account.ID))
- if updateIndex {
- storeBatch.Set(GetAccountIndexKey(account.XPubs), common.Unit64ToBytes(account.KeyIndex))
+ if err := newStore.CommitBatch(); err != nil {
+ return err
}
- storeBatch.Write()
+
return nil
}
m.accountMu.Lock()
defer m.accountMu.Unlock()
- if existed := m.db.Get(aliasKey(account.Alias)); existed != nil {
+ _, err := m.store.GetAccountByAlias(account.Alias)
+ if err == nil {
return ErrDuplicateAlias
}
- acct, err := m.GetAccountByXPubsIndex(account.XPubs, account.KeyIndex)
- if err != nil {
+ if err != ErrFindAccount {
return err
}
- if acct != nil {
+ acct, err := m.GetAccountByXPubsIndex(account.XPubs, account.KeyIndex)
+ if err != nil && err != ErrFindAccount {
+ return err
+ } else if acct != nil {
return ErrDuplicateIndex
}
- currentIndex := uint64(0)
- if rawIndexBytes := m.db.Get(GetAccountIndexKey(account.XPubs)); rawIndexBytes != nil {
- currentIndex = common.BytesToUnit64(rawIndexBytes)
- }
- return m.saveAccount(account, account.KeyIndex > currentIndex)
+ return m.saveAccount(account)
}
// Create creates and save a new Account.
m.accountMu.Lock()
defer m.accountMu.Unlock()
- if existed := m.db.Get(aliasKey(alias)); existed != nil {
+ _, err := m.store.GetAccountByAlias(alias)
+ if err == nil {
return nil, ErrDuplicateAlias
+ } else if err != ErrFindAccount {
+ return nil, err
}
acctIndex := uint64(1)
- if rawIndexBytes := m.db.Get(GetAccountIndexKey(xpubs)); rawIndexBytes != nil {
- acctIndex = common.BytesToUnit64(rawIndexBytes) + 1
+ if currentIndex := m.store.GetAccountIndex(xpubs); currentIndex != 0 {
+ acctIndex = currentIndex + 1
}
+
account, err := CreateAccount(xpubs, quorum, alias, acctIndex, deriveRule)
if err != nil {
return nil, err
}
- if err := m.saveAccount(account, true); err != nil {
+ if err := m.saveAccount(account); err != nil {
return nil, err
}
return account, nil
}
-func (m *Manager) UpdateAccountAlias(accountID string, newAlias string) (err error) {
+func (m *Manager) UpdateAccountAlias(accountID string, newAlias string) error {
m.accountMu.Lock()
defer m.accountMu.Unlock()
if err != nil {
return err
}
- oldAlias := account.Alias
+ oldAccount := *account
normalizedAlias := strings.ToLower(strings.TrimSpace(newAlias))
- if existed := m.db.Get(aliasKey(normalizedAlias)); existed != nil {
+ _, err = m.store.GetAccountByAlias(normalizedAlias)
+ if err == nil {
return ErrDuplicateAlias
}
+ if err != ErrFindAccount {
+ return err
+ }
m.cacheMu.Lock()
- m.aliasCache.Remove(oldAlias)
+ m.aliasCache.Remove(oldAccount.Alias)
m.cacheMu.Unlock()
account.Alias = normalizedAlias
- rawAccount, err := json.Marshal(account)
- if err != nil {
- return ErrMarshalAccount
+
+ newStore := m.store.InitBatch()
+
+ if err := newStore.DeleteAccount(&oldAccount); err != nil {
+ return err
+ }
+
+ if err := newStore.SetAccount(account); err != nil {
+ return err
+ }
+
+ if err := newStore.CommitBatch(); err != nil {
+ return err
}
- storeBatch := m.db.NewBatch()
- storeBatch.Delete(aliasKey(oldAlias))
- storeBatch.Set(Key(accountID), rawAccount)
- storeBatch.Set(aliasKey(normalizedAlias), []byte(accountID))
- storeBatch.Write()
return nil
}
return nil
}
-// deleteAccountControlPrograms deletes control program matching accountID
-func (m *Manager) deleteAccountControlPrograms(accountID string) error {
- cps, err := m.ListControlProgram()
- if err != nil {
- return err
- }
-
- var hash common.Hash
- for _, cp := range cps {
- if cp.AccountID == accountID {
- sha3pool.Sum256(hash[:], cp.ControlProgram)
- m.db.Delete(ContractKey(hash))
- }
- }
- m.db.Delete(bip44ContractIndexKey(accountID, false))
- m.db.Delete(bip44ContractIndexKey(accountID, true))
- m.db.Delete(contractIndexKey(accountID))
- return nil
-}
-
-// deleteAccountUtxos deletes utxos matching accountID
-func (m *Manager) deleteAccountUtxos(accountID string) error {
- accountUtxoIter := m.db.IteratorPrefix([]byte(UTXOPreFix))
- defer accountUtxoIter.Release()
- for accountUtxoIter.Next() {
- accountUtxo := &UTXO{}
- if err := json.Unmarshal(accountUtxoIter.Value(), accountUtxo); err != nil {
- return err
- }
-
- if accountID == accountUtxo.AccountID {
- m.db.Delete(StandardUTXOKey(accountUtxo.OutputID))
- }
- }
- return nil
-}
-
// DeleteAccount deletes the account's ID or alias matching account ID.
-func (m *Manager) DeleteAccount(accountID string) (err error) {
+func (m *Manager) DeleteAccount(accountID string) error {
m.accountMu.Lock()
defer m.accountMu.Unlock()
return err
}
- if err := m.deleteAccountControlPrograms(accountID); err != nil {
- return err
- }
- if err := m.deleteAccountUtxos(accountID); err != nil {
- return err
- }
-
m.cacheMu.Lock()
m.aliasCache.Remove(account.Alias)
m.cacheMu.Unlock()
- storeBatch := m.db.NewBatch()
- storeBatch.Delete(aliasKey(account.Alias))
- storeBatch.Delete(Key(account.ID))
- storeBatch.Write()
- return nil
+ return m.store.DeleteAccount(account)
}
// FindByAlias retrieves an account's Signer record by its alias
return m.FindByID(cachedID.(string))
}
- rawID := m.db.Get(aliasKey(alias))
- if rawID == nil {
- return nil, ErrFindAccount
- }
-
- accountID := string(rawID)
- m.cacheMu.Lock()
- m.aliasCache.Add(alias, accountID)
- m.cacheMu.Unlock()
- return m.FindByID(accountID)
+ return m.store.GetAccountByAlias(alias)
}
// FindByID returns an account's Signer record by its ID.
return cachedAccount.(*Account), nil
}
- rawAccount := m.db.Get(Key(id))
- if rawAccount == nil {
- return nil, ErrFindAccount
- }
-
- account := &Account{}
- if err := json.Unmarshal(rawAccount, account); err != nil {
+ account, err := m.store.GetAccountByID(id)
+ if err != nil {
return nil, err
}
// GetAccountByProgram return Account by given CtrlProgram
func (m *Manager) GetAccountByProgram(program *CtrlProgram) (*Account, error) {
- rawAccount := m.db.Get(Key(program.AccountID))
- if rawAccount == nil {
- return nil, ErrFindAccount
- }
-
- account := &Account{}
- return account, json.Unmarshal(rawAccount, account)
+ return m.store.GetAccountByID(program.AccountID)
}
// GetAccountByXPubsIndex get account by xPubs and index
return account, nil
}
}
- return nil, nil
+ return nil, ErrFindAccount
}
// GetAliasByID return the account alias by given ID
func (m *Manager) GetAliasByID(id string) string {
- rawAccount := m.db.Get(Key(id))
- if rawAccount == nil {
+ account, err := m.store.GetAccountByID(id)
+ if err != nil {
log.Warn("GetAliasByID fail to find account")
return ""
}
-
- account := &Account{}
- if err := json.Unmarshal(rawAccount, account); err != nil {
- log.Warn(err)
- }
return account.Alias
}
func (m *Manager) GetCoinbaseArbitrary() []byte {
- if arbitrary := m.db.Get(CoinbaseAbKey); arbitrary != nil {
+ if arbitrary := m.store.GetCoinbaseArbitrary(); arbitrary != nil {
return arbitrary
}
return []byte{}
log.Warningf("GetCoinbaseControlProgram: can't find any account in db")
return vmutil.DefaultCoinbaseProgram()
}
+
if err != nil {
return nil, err
}
+
return cp.ControlProgram, nil
}
// GetCoinbaseCtrlProgram will return the coinbase CtrlProgram
func (m *Manager) GetCoinbaseCtrlProgram() (*CtrlProgram, error) {
- if data := m.db.Get(miningAddressKey); data != nil {
- cp := &CtrlProgram{}
- return cp, json.Unmarshal(data, cp)
+ if cp, err := m.store.GetMiningAddress(); err == nil {
+ return cp, nil
+ } else if err != ErrFindMiningAddress {
+ return nil, err
}
- accountIter := m.db.IteratorPrefix([]byte(accountPrefix))
- defer accountIter.Release()
- if !accountIter.Next() {
- return nil, ErrFindAccount
+ account := new(Account)
+ accounts, err := m.store.ListAccounts("")
+ if err != nil {
+ return nil, err
}
- account := &Account{}
- if err := json.Unmarshal(accountIter.Value(), account); err != nil {
- return nil, err
+ if len(accounts) > 0 {
+ account = accounts[0]
+ } else {
+ return nil, ErrFindAccount
}
program, err := m.CreateAddress(account.ID, false)
return nil, err
}
- rawCP, err := json.Marshal(program)
- if err != nil {
+ if err := m.store.SetMiningAddress(program); err != nil {
return nil, err
}
- m.db.Set(miningAddressKey, rawCP)
return program, nil
}
-// GetContractIndex return the current index
-func (m *Manager) GetContractIndex(accountID string) uint64 {
- index := uint64(0)
- if rawIndexBytes := m.db.Get(contractIndexKey(accountID)); rawIndexBytes != nil {
- index = common.BytesToUnit64(rawIndexBytes)
- }
- return index
-}
-
// GetBip44ContractIndex return the current bip44 contract index
func (m *Manager) GetBip44ContractIndex(accountID string, change bool) uint64 {
- index := uint64(0)
- if rawIndexBytes := m.db.Get(bip44ContractIndexKey(accountID, change)); rawIndexBytes != nil {
- index = common.BytesToUnit64(rawIndexBytes)
- }
- return index
+ return m.store.GetBip44ContractIndex(accountID, change)
}
// GetLocalCtrlProgramByAddress return CtrlProgram by given address
var hash [32]byte
sha3pool.Sum256(hash[:], program)
- rawProgram := m.db.Get(ContractKey(hash))
- if rawProgram == nil {
- return nil, ErrFindCtrlProgram
+
+ cp, err := m.store.GetControlProgram(bc.NewHash(hash))
+ if err != nil {
+ return nil, err
}
- cp := &CtrlProgram{}
- return cp, json.Unmarshal(rawProgram, cp)
+ return cp, nil
}
// GetMiningAddress will return the mining address
if err != nil {
return "", err
}
+
return cp.Address, nil
}
// IsLocalControlProgram check is the input control program belong to local
func (m *Manager) IsLocalControlProgram(prog []byte) bool {
- var hash common.Hash
+ var hash [32]byte
sha3pool.Sum256(hash[:], prog)
- bytes := m.db.Get(ContractKey(hash))
- return bytes != nil
+ cp, err := m.store.GetControlProgram(bc.NewHash(hash))
+ if err != nil || cp == nil {
+ return false
+ }
+ return true
}
// ListAccounts will return the accounts in the db
func (m *Manager) ListAccounts(id string) ([]*Account, error) {
- accounts := []*Account{}
- accountIter := m.db.IteratorPrefix(Key(strings.TrimSpace(id)))
- defer accountIter.Release()
-
- for accountIter.Next() {
- account := &Account{}
- if err := json.Unmarshal(accountIter.Value(), &account); err != nil {
- return nil, err
- }
- accounts = append(accounts, account)
- }
- return accounts, nil
+ return m.store.ListAccounts(id)
}
// ListControlProgram return all the local control program
func (m *Manager) ListControlProgram() ([]*CtrlProgram, error) {
- cps := []*CtrlProgram{}
- cpIter := m.db.IteratorPrefix(contractPrefix)
- defer cpIter.Release()
-
- for cpIter.Next() {
- cp := &CtrlProgram{}
- if err := json.Unmarshal(cpIter.Value(), cp); err != nil {
- return nil, err
- }
- cps = append(cps, cp)
- }
- return cps, nil
+ return m.store.ListControlPrograms()
}
func (m *Manager) ListUnconfirmedUtxo(accountID string, isSmartContract bool) []*UTXO {
Address: miningAddress,
ControlProgram: program,
}
- rawCP, err := json.Marshal(cp)
- if err != nil {
- return "", err
+ if err := m.store.SetMiningAddress(cp); err != nil {
+ return cp.Address, err
}
- m.db.Set(miningAddressKey, rawCP)
return m.GetMiningAddress()
}
func (m *Manager) SetCoinbaseArbitrary(arbitrary []byte) {
- m.db.Set(CoinbaseAbKey, arbitrary)
+ m.store.SetCoinbaseArbitrary(arbitrary)
}
// CreateCtrlProgram generate an address for the select account
} else {
cp, err = createP2SH(account, path)
}
+
if err != nil {
return nil, err
}
+
cp.KeyIndex, cp.Change = addrIdx, change
return cp, nil
}
if err != nil {
return nil, err
}
- scriptHash := crypto.Sha256(signScript)
+ scriptHash := crypto.Sha256(signScript)
address, err := common.NewAddressWitnessScriptHash(scriptHash, &consensus.ActiveNetParams)
if err != nil {
return nil, err
}, nil
}
-func GetAccountIndexKey(xpubs []chainkd.XPub) []byte {
- var hash [32]byte
- var xPubs []byte
- cpy := append([]chainkd.XPub{}, xpubs[:]...)
- sort.Sort(signers.SortKeys(cpy))
- for _, xpub := range cpy {
- xPubs = append(xPubs, xpub[:]...)
- }
- sha3pool.Sum256(hash[:], xPubs)
- return append(accountIndexPrefix, hash[:]...)
+func (m *Manager) GetContractIndex(accountID string) uint64 {
+ return m.store.GetContractIndex(accountID)
}
func (m *Manager) getCurrentContractIndex(account *Account, change bool) (uint64, error) {
switch account.DeriveRule {
case signers.BIP0032:
- return m.GetContractIndex(account.ID), nil
+ return m.store.GetContractIndex(account.ID), nil
case signers.BIP0044:
- return m.GetBip44ContractIndex(account.ID, change), nil
+ return m.store.GetBip44ContractIndex(account.ID, change), nil
}
return 0, ErrDeriveRule
}
if err != nil {
return nil, err
}
+
redeemContract := addr.ScriptAddress()
program := []byte{}
switch addr.(type) {
if err != nil {
return nil, err
}
+
return program, nil
}
func (m *Manager) saveControlProgram(prog *CtrlProgram, updateIndex bool) error {
- var hash common.Hash
+ var hash [32]byte
sha3pool.Sum256(hash[:], prog.ControlProgram)
acct, err := m.GetAccountByProgram(prog)
return err
}
- accountCP, err := json.Marshal(prog)
- if err != nil {
- return err
+ newStore := m.store.InitBatch()
+
+ if err := newStore.SetControlProgram(bc.NewHash(hash), prog); err != nil {
+ return nil
}
- storeBatch := m.db.NewBatch()
- storeBatch.Set(ContractKey(hash), accountCP)
if updateIndex {
switch acct.DeriveRule {
case signers.BIP0032:
- storeBatch.Set(contractIndexKey(acct.ID), common.Unit64ToBytes(prog.KeyIndex))
+ newStore.SetContractIndex(acct.ID, prog.KeyIndex)
case signers.BIP0044:
- storeBatch.Set(bip44ContractIndexKey(acct.ID, prog.Change), common.Unit64ToBytes(prog.KeyIndex))
+ newStore.SetBip44ContractIndex(acct.ID, prog.Change, prog.KeyIndex)
}
}
- storeBatch.Write()
- return nil
+ return newStore.CommitBatch()
}
// SaveControlPrograms save account control programs
}
return nil
}
+
+func (m *Manager) SetStandardUTXO(outputID bc.Hash, utxo *UTXO) error {
+ return m.store.SetStandardUTXO(outputID, utxo)
+}
+
+func (m *Manager) DeleteStandardUTXO(outputID bc.Hash) {
+ m.store.DeleteStandardUTXO(outputID)
+}
+
+func (m *Manager) GetControlProgram(hash bc.Hash) (*CtrlProgram, error) {
+ return m.store.GetControlProgram(hash)
+}
return utxos, nil
}
-func (m *Manager) buildBtmTxChain(utxos []*UTXO, signer *signers.Signer) ([]*txbuilder.Template, *UTXO, error) {
+func (m *Manager) BuildBtmTxChain(utxos []*UTXO, signer *signers.Signer) ([]*txbuilder.Template, *UTXO, error) {
if len(utxos) == 0 {
return nil, nil, errors.New("mergeSpendActionUTXO utxos num 0")
}
if !ok {
return nil, errors.New("fail to convert the spend action")
}
+
if *act.AssetId != *consensus.BTMAssetID {
return nil, errors.New("spend chain action only support BTM")
}
return nil, err
}
- tpls, utxo, err := act.accounts.buildBtmTxChain(utxos, acct.Signer)
+ tpls, utxo, err := act.accounts.BuildBtmTxChain(utxos, acct.Signer)
if err != nil {
return nil, err
}
if err != nil {
return nil, nil, err
}
+
if u.Address == "" {
sigInst.AddWitnessKeys(signer.XPubs, path, signer.Quorum)
return txInput, sigInst, nil
if len(acps) == 0 {
return nil
}
+
return m.SaveControlPrograms(acps...)
})
}
package account
import (
- "encoding/json"
"testing"
"time"
- "github.com/vapor/blockchain/signers"
"github.com/vapor/blockchain/txbuilder"
"github.com/vapor/consensus"
- "github.com/vapor/crypto/ed25519/chainkd"
"github.com/vapor/protocol/bc"
"github.com/vapor/testutil"
)
-func TestReserveBtmUtxoChain(t *testing.T) {
- chainTxUtxoNum = 3
- utxos := []*UTXO{}
- m := mockAccountManager(t)
- for i := uint64(1); i <= 20; i++ {
- utxo := &UTXO{
- OutputID: bc.Hash{V0: i},
- AccountID: "TestAccountID",
- AssetID: *consensus.BTMAssetID,
- Amount: i * chainTxMergeGas,
- }
- utxos = append(utxos, utxo)
-
- data, err := json.Marshal(utxo)
- if err != nil {
- t.Fatal(err)
- }
-
- m.db.Set(StandardUTXOKey(utxo.OutputID), data)
- }
-
- cases := []struct {
- amount uint64
- want []uint64
- err bool
- }{
- {
- amount: 1 * chainTxMergeGas,
- want: []uint64{1},
- },
- {
- amount: 888888 * chainTxMergeGas,
- want: []uint64{},
- err: true,
- },
- {
- amount: 7 * chainTxMergeGas,
- want: []uint64{4, 3, 1},
- },
- {
- amount: 15 * chainTxMergeGas,
- want: []uint64{5, 4, 3, 2, 1, 6},
- },
- {
- amount: 163 * chainTxMergeGas,
- want: []uint64{20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 2, 1, 3},
- },
- }
-
- for i, c := range cases {
- m.utxoKeeper.expireReservation(time.Unix(999999999, 0))
- utxos, err := m.reserveBtmUtxoChain(&txbuilder.TemplateBuilder{}, "TestAccountID", c.amount, false)
-
- if err != nil != c.err {
- t.Fatalf("case %d got err %v want err = %v", i, err, c.err)
- }
-
- got := []uint64{}
- for _, utxo := range utxos {
- got = append(got, utxo.Amount/chainTxMergeGas)
- }
-
- if !testutil.DeepEqual(got, c.want) {
- t.Fatalf("case %d got %d want %d", i, got, c.want)
- }
- }
-
-}
-
-func TestBuildBtmTxChain(t *testing.T) {
- chainTxUtxoNum = 3
- m := mockAccountManager(t)
- cases := []struct {
- inputUtxo []uint64
- wantInput [][]uint64
- wantOutput [][]uint64
- wantUtxo uint64
- }{
- {
- inputUtxo: []uint64{5},
- wantInput: [][]uint64{},
- wantOutput: [][]uint64{},
- wantUtxo: 5 * chainTxMergeGas,
- },
- {
- inputUtxo: []uint64{5, 4},
- wantInput: [][]uint64{
- []uint64{5, 4},
- },
- wantOutput: [][]uint64{
- []uint64{8},
- },
- wantUtxo: 8 * chainTxMergeGas,
- },
- {
- inputUtxo: []uint64{5, 4, 1, 1},
- wantInput: [][]uint64{
- []uint64{5, 4, 1},
- []uint64{1, 9},
- },
- wantOutput: [][]uint64{
- []uint64{9},
- []uint64{9},
- },
- wantUtxo: 9 * chainTxMergeGas,
- },
- {
- inputUtxo: []uint64{22, 123, 53, 234, 23, 4, 2423, 24, 23, 43, 34, 234, 234, 24},
- wantInput: [][]uint64{
- []uint64{22, 123, 53},
- []uint64{234, 23, 4},
- []uint64{2423, 24, 23},
- []uint64{43, 34, 234},
- []uint64{234, 24, 197},
- []uint64{260, 2469, 310},
- []uint64{454, 3038},
- },
- wantOutput: [][]uint64{
- []uint64{197},
- []uint64{260},
- []uint64{2469},
- []uint64{310},
- []uint64{454},
- []uint64{3038},
- []uint64{3491},
- },
- wantUtxo: 3491 * chainTxMergeGas,
- },
- }
-
- acct, err := m.Create([]chainkd.XPub{testutil.TestXPub}, 1, "testAccount", signers.BIP0044)
- if err != nil {
- t.Fatal(err)
- }
-
- acp, err := m.CreateAddress(acct.ID, false)
- if err != nil {
- t.Fatal(err)
- }
-
- for caseIndex, c := range cases {
- utxos := []*UTXO{}
- for _, amount := range c.inputUtxo {
- utxos = append(utxos, &UTXO{
- Amount: amount * chainTxMergeGas,
- AssetID: *consensus.BTMAssetID,
- Address: acp.Address,
- ControlProgram: acp.ControlProgram,
- })
- }
-
- tpls, gotUtxo, err := m.buildBtmTxChain(utxos, acct.Signer)
- if err != nil {
- t.Fatal(err)
- }
-
- for i, tpl := range tpls {
- gotInput := []uint64{}
- for _, input := range tpl.Transaction.Inputs {
- gotInput = append(gotInput, input.Amount()/chainTxMergeGas)
- }
-
- gotOutput := []uint64{}
- for _, output := range tpl.Transaction.Outputs {
- gotOutput = append(gotOutput, output.AssetAmount().Amount/chainTxMergeGas)
- }
-
- if !testutil.DeepEqual(c.wantInput[i], gotInput) {
- t.Fatalf("case %d tx %d input got %d want %d", caseIndex, i, gotInput, c.wantInput[i])
- }
- if !testutil.DeepEqual(c.wantOutput[i], gotOutput) {
- t.Fatalf("case %d tx %d output got %d want %d", caseIndex, i, gotOutput, c.wantOutput[i])
- }
- }
-
- if c.wantUtxo != gotUtxo.Amount {
- t.Fatalf("case %d got utxo=%d want utxo=%d", caseIndex, gotUtxo.Amount, c.wantUtxo)
- }
- }
-
-}
-
func TestMergeSpendAction(t *testing.T) {
testBTM := &bc.AssetID{}
if err := testBTM.UnmarshalText([]byte("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")); err != nil {
}
}
}
+
+func TestReserveBtmUtxoChain(t *testing.T) {
+ chainTxUtxoNum = 3
+ utxos := []*UTXO{}
+ m := mockAccountManager(t)
+ for i := uint64(1); i <= 20; i++ {
+ utxo := &UTXO{
+ OutputID: bc.Hash{V0: i},
+ AccountID: "TestAccountID",
+ AssetID: *consensus.BTMAssetID,
+ Amount: i * chainTxMergeGas,
+ }
+ utxos = append(utxos, utxo)
+
+ m.store.SetStandardUTXO(utxo.OutputID, utxo)
+ }
+
+ cases := []struct {
+ amount uint64
+ want []uint64
+ err bool
+ }{
+ {
+ amount: 1 * chainTxMergeGas,
+ want: []uint64{1},
+ },
+ {
+ amount: 888888 * chainTxMergeGas,
+ want: []uint64{},
+ err: true,
+ },
+ {
+ amount: 7 * chainTxMergeGas,
+ want: []uint64{4, 3, 1},
+ },
+ {
+ amount: 15 * chainTxMergeGas,
+ want: []uint64{5, 4, 3, 2, 1, 6},
+ },
+ {
+ amount: 163 * chainTxMergeGas,
+ want: []uint64{20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 2, 1, 3},
+ },
+ }
+
+ for i, c := range cases {
+ m.utxoKeeper.expireReservation(time.Unix(999999999, 0))
+ utxos, err := m.reserveBtmUtxoChain(&txbuilder.TemplateBuilder{}, "TestAccountID", c.amount, false)
+
+ if err != nil != c.err {
+ t.Fatalf("case %d got err %v want err = %v", i, err, c.err)
+ }
+
+ got := []uint64{}
+ for _, utxo := range utxos {
+ got = append(got, utxo.Amount/chainTxMergeGas)
+ }
+
+ if !testutil.DeepEqual(got, c.want) {
+ t.Fatalf("case %d got %d want %d", i, got, c.want)
+ }
+ }
+}
package account
import (
- "encoding/json"
-
log "github.com/sirupsen/logrus"
)
Slice: []*ImageSlice{},
}
- accountIter := m.db.IteratorPrefix(accountPrefix)
- defer accountIter.Release()
- for accountIter.Next() {
- a := &Account{}
- if err := json.Unmarshal(accountIter.Value(), a); err != nil {
- return nil, err
- }
+ accounts, err := m.store.ListAccounts("")
+ if err != nil {
+ return nil, err
+ }
+ for _, account := range accounts {
image.Slice = append(image.Slice, &ImageSlice{
- Account: a,
- ContractIndex: m.GetContractIndex(a.ID),
+ Account: account,
+ ContractIndex: m.store.GetContractIndex(account.ID),
})
}
return image, nil
m.accountMu.Lock()
defer m.accountMu.Unlock()
- storeBatch := m.db.NewBatch()
+ newStore := m.store.InitBatch()
+
for _, slice := range image.Slice {
- if existed := m.db.Get(Key(slice.Account.ID)); existed != nil {
+ if _, err := newStore.GetAccountByID(slice.Account.ID); err == nil {
log.WithFields(log.Fields{
"module": logModule,
"alias": slice.Account.Alias,
"id": slice.Account.ID,
}).Warning("skip restore account due to already existed")
continue
+ } else if err != ErrFindAccount {
+ return err
}
- if existed := m.db.Get(aliasKey(slice.Account.Alias)); existed != nil {
+
+ if _, err := newStore.GetAccountByAlias(slice.Account.Alias); err == nil {
return ErrDuplicateAlias
+ } else if err != ErrFindAccount {
+ return err
}
- rawAccount, err := json.Marshal(slice.Account)
- if err != nil {
- return ErrMarshalAccount
+ if err := newStore.SetAccount(slice.Account); err != nil {
+ return err
}
-
- storeBatch.Set(Key(slice.Account.ID), rawAccount)
- storeBatch.Set(aliasKey(slice.Account.Alias), []byte(slice.Account.ID))
}
- storeBatch.Write()
- return nil
+ return newStore.CommitBatch()
}
import (
"github.com/vapor/blockchain/query"
- "github.com/vapor/protocol/bc"
)
-const (
- //UTXOPreFix is StandardUTXOKey prefix
- UTXOPreFix = "ACU:"
- //SUTXOPrefix is ContractUTXOKey prefix
- SUTXOPrefix = "SCU:"
-)
-
-// StandardUTXOKey makes an account unspent outputs key to store
-func StandardUTXOKey(id bc.Hash) []byte {
- name := id.String()
- return []byte(UTXOPreFix + name)
-}
-
-// ContractUTXOKey makes a smart contract unspent outputs key to store
-func ContractUTXOKey(id bc.Hash) []byte {
- name := id.String()
- return []byte(SUTXOPrefix + name)
-}
-
//Annotated init an annotated account object
func Annotated(a *Account) *query.AnnotatedAccount {
return &query.AnnotatedAccount{
--- /dev/null
+package account
+
+import (
+ "github.com/vapor/crypto/ed25519/chainkd"
+ "github.com/vapor/protocol/bc"
+)
+
+// AccountStore interface contains account storage functions.
+type AccountStore interface {
+ InitBatch() AccountStore
+ CommitBatch() error
+ DeleteAccount(*Account) error
+ DeleteStandardUTXO(bc.Hash)
+ GetAccountByAlias(string) (*Account, error)
+ GetAccountByID(string) (*Account, error)
+ GetAccountIndex([]chainkd.XPub) uint64
+ GetBip44ContractIndex(string, bool) uint64
+ GetCoinbaseArbitrary() []byte
+ GetContractIndex(string) uint64
+ GetControlProgram(bc.Hash) (*CtrlProgram, error)
+ GetMiningAddress() (*CtrlProgram, error)
+ GetUTXO(bc.Hash) (*UTXO, error)
+ ListAccounts(string) ([]*Account, error)
+ ListControlPrograms() ([]*CtrlProgram, error)
+ ListUTXOs() ([]*UTXO, error)
+ SetAccount(*Account) error
+ SetAccountIndex(*Account)
+ SetBip44ContractIndex(string, bool, uint64)
+ SetCoinbaseArbitrary([]byte)
+ SetContractIndex(string, uint64)
+ SetControlProgram(bc.Hash, *CtrlProgram) error
+ SetMiningAddress(*CtrlProgram) error
+ SetStandardUTXO(bc.Hash, *UTXO) error
+}
import (
"bytes"
"container/list"
- "encoding/json"
"sort"
"sync"
"sync/atomic"
"time"
log "github.com/sirupsen/logrus"
-
- dbm "github.com/vapor/database/leveldb"
"github.com/vapor/errors"
"github.com/vapor/protocol/bc"
)
// `sync/atomic` expects the first word in an allocated struct to be 64-bit
// aligned on both ARM and x86-32. See https://goo.gl/zW7dgq for more details.
nextIndex uint64
- db dbm.DB
+ store AccountStore
mtx sync.RWMutex
currentHeight func() uint64
reservations map[uint64]*reservation
}
-func newUtxoKeeper(f func() uint64, walletdb dbm.DB) *utxoKeeper {
+func newUtxoKeeper(f func() uint64, store AccountStore) *utxoKeeper {
uk := &utxoKeeper{
- db: walletdb,
+ store: store,
currentHeight: f,
unconfirmed: make(map[bc.Hash]*UTXO),
reserved: make(map[bc.Hash]uint64),
if u.AccountID != accountID || u.AssetID != *assetID || !bytes.Equal(u.Vote, vote) {
return
}
+
if u.ValidHeight > currentHeight {
immatureAmount += u.Amount
} else {
}
}
- utxoIter := uk.db.IteratorPrefix([]byte(UTXOPreFix))
- defer utxoIter.Release()
- for utxoIter.Next() {
- u := &UTXO{}
- if err := json.Unmarshal(utxoIter.Value(), u); err != nil {
- log.WithFields(log.Fields{"module": logModule, "err": err}).Error("utxoKeeper findUtxos fail on unmarshal utxo")
- continue
- }
- appendUtxo(u)
+ UTXOs, err := uk.store.ListUTXOs()
+ if err != nil {
+ log.WithFields(log.Fields{"module": logModule, "err": err}).Error("utxoKeeper findUtxos fail on unmarshal utxo")
}
+
+ for _, UTXO := range UTXOs {
+ appendUtxo(UTXO)
+ }
+
if !useUnconfirmed {
return utxos, immatureAmount
}
if u, ok := uk.unconfirmed[outHash]; useUnconfirmed && ok {
return u, nil
}
-
- u := &UTXO{}
- if data := uk.db.Get(StandardUTXOKey(outHash)); data != nil {
- return u, json.Unmarshal(data, u)
- }
- if data := uk.db.Get(ContractUTXOKey(outHash)); data != nil {
- return u, json.Unmarshal(data, u)
- }
- return nil, ErrMatchUTXO
+ return uk.store.GetUTXO(outHash)
}
func (uk *utxoKeeper) optUTXOs(utxos []*UTXO, amount uint64) ([]*UTXO, uint64, uint64) {
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"
os.RemoveAll("temp")
}()
+ accountStore := newMockAccountStore(testDB)
+
cases := []struct {
before utxoKeeper
after utxoKeeper
}{
{
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{
},
{
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{
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{
os.RemoveAll("temp")
}()
+ accountStore := newMockAccountStore(testDB)
+
cases := []struct {
uk utxoKeeper
dbUtxos []*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{},
},
},
{
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{
},
{
uk: utxoKeeper{
- db: testDB,
+ store: accountStore,
currentHeight: currentHeight,
unconfirmed: map[bc.Hash]*UTXO{},
},
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, c.vote)
}
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),
+ }
+}
--- /dev/null
+package database
+
+import (
+ "encoding/json"
+ "sort"
+ "strings"
+
+ acc "github.com/vapor/account"
+ "github.com/vapor/blockchain/signers"
+ "github.com/vapor/common"
+ "github.com/vapor/crypto/ed25519/chainkd"
+ "github.com/vapor/crypto/sha3pool"
+ dbm "github.com/vapor/database/leveldb"
+ "github.com/vapor/errors"
+ "github.com/vapor/protocol/bc"
+)
+
+const (
+ utxoPrefix byte = iota //UTXOPrefix is StandardUTXOKey prefix
+ contractPrefix
+ contractIndexPrefix
+ accountPrefix // AccountPrefix is account ID prefix
+ accountIndexPrefix
+)
+
+// leveldb key prefix
+var (
+ 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)
+)
+
+func accountIndexKey(xpubs []chainkd.XPub) []byte {
+ var hash [32]byte
+ var xPubs []byte
+ cpy := append([]chainkd.XPub{}, xpubs[:]...)
+ sort.Sort(signers.SortKeys(cpy))
+ for _, xpub := range cpy {
+ xPubs = append(xPubs, xpub[:]...)
+ }
+ sha3pool.Sum256(hash[:], xPubs)
+ return append(AccountIndexPrefix, hash[:]...)
+}
+
+func Bip44ContractIndexKey(accountID string, change bool) []byte {
+ key := append(ContractIndexPrefix, []byte(accountID)...)
+ if change {
+ return append(key, 0x01)
+ }
+ return append(key, 0x00)
+}
+
+// ContractKey account control promgram store prefix
+func ContractKey(hash bc.Hash) []byte {
+ return append(ContractPrefix, hash.Bytes()...)
+}
+
+// AccountIDKey account id store prefix
+func AccountIDKey(accountID string) []byte {
+ return append(AccountPrefix, []byte(accountID)...)
+}
+
+// StandardUTXOKey makes an account unspent outputs key to store
+func StandardUTXOKey(id bc.Hash) []byte {
+ return append(UTXOPrefix, id.Bytes()...)
+}
+
+func accountAliasKey(name string) []byte {
+ return append(AccountAliasPrefix, []byte(name)...)
+}
+
+// AccountStore satisfies AccountStore interface.
+type AccountStore struct {
+ db dbm.DB
+ batch dbm.Batch
+}
+
+// NewAccountStore create new AccountStore.
+func NewAccountStore(db dbm.DB) *AccountStore {
+ return &AccountStore{
+ db: db,
+ batch: nil,
+ }
+}
+
+// InitBatch initial new account store
+func (store *AccountStore) InitBatch() acc.AccountStore {
+ newStore := NewAccountStore(store.db)
+ newStore.batch = newStore.db.NewBatch()
+ return newStore
+}
+
+// CommitBatch commit batch
+func (store *AccountStore) CommitBatch() error {
+ if store.batch == nil {
+ return errors.New("AccountStore commit fail, store batch is nil.")
+ }
+ store.batch.Write()
+ store.batch = nil
+ return nil
+}
+
+// DeleteAccount set account account ID, account alias and raw account.
+func (store *AccountStore) DeleteAccount(account *acc.Account) error {
+ batch := store.db.NewBatch()
+ if store.batch != nil {
+ batch = store.batch
+ }
+
+ // delete account utxos
+ store.deleteAccountUTXOs(account.ID, batch)
+
+ // delete account control program
+ if err := store.deleteAccountControlPrograms(account.ID, batch); err != nil {
+ return err
+ }
+
+ // delete bip44 contract index
+ batch.Delete(Bip44ContractIndexKey(account.ID, false))
+ batch.Delete(Bip44ContractIndexKey(account.ID, true))
+
+ // delete contract index
+ batch.Delete(contractIndexKey(account.ID))
+
+ // delete account id
+ batch.Delete(AccountIDKey(account.ID))
+ batch.Delete(accountAliasKey(account.Alias))
+ if store.batch == nil {
+ batch.Write()
+ }
+ return nil
+}
+
+// deleteAccountUTXOs delete account utxos by accountID
+func (store *AccountStore) deleteAccountUTXOs(accountID string, batch dbm.Batch) error {
+ accountUtxoIter := store.db.IteratorPrefix(UTXOPrefix)
+ defer accountUtxoIter.Release()
+
+ for accountUtxoIter.Next() {
+ accountUtxo := new(acc.UTXO)
+ if err := json.Unmarshal(accountUtxoIter.Value(), accountUtxo); err != nil {
+ return err
+ }
+
+ if accountID == accountUtxo.AccountID {
+ batch.Delete(StandardUTXOKey(accountUtxo.OutputID))
+ }
+ }
+
+ return nil
+}
+
+// deleteAccountControlPrograms deletes account control program
+func (store *AccountStore) deleteAccountControlPrograms(accountID string, batch dbm.Batch) error {
+ cps, err := store.ListControlPrograms()
+ if err != nil {
+ return err
+ }
+
+ var hash [32]byte
+ for _, cp := range cps {
+ if cp.AccountID == accountID {
+ sha3pool.Sum256(hash[:], cp.ControlProgram)
+ batch.Delete(ContractKey(bc.NewHash(hash)))
+ }
+ }
+ return nil
+}
+
+// DeleteStandardUTXO delete utxo by outpu id
+func (store *AccountStore) DeleteStandardUTXO(outputID bc.Hash) {
+ if store.batch == nil {
+ store.db.Delete(StandardUTXOKey(outputID))
+ } else {
+ store.batch.Delete(StandardUTXOKey(outputID))
+ }
+}
+
+// GetAccountByAlias get account by account alias
+func (store *AccountStore) GetAccountByAlias(accountAlias string) (*acc.Account, error) {
+ accountID := store.db.Get(accountAliasKey(accountAlias))
+ if accountID == nil {
+ return nil, acc.ErrFindAccount
+ }
+
+ return store.GetAccountByID(string(accountID))
+}
+
+// GetAccountByID get account by accountID
+func (store *AccountStore) GetAccountByID(accountID string) (*acc.Account, error) {
+ rawAccount := store.db.Get(AccountIDKey(accountID))
+ if rawAccount == nil {
+ return nil, acc.ErrFindAccount
+ }
+
+ account := new(acc.Account)
+ if err := json.Unmarshal(rawAccount, account); err != nil {
+ return nil, err
+ }
+
+ return account, nil
+}
+
+// GetAccountIndex get account index by account xpubs
+func (store *AccountStore) GetAccountIndex(xpubs []chainkd.XPub) uint64 {
+ currentIndex := uint64(0)
+ if rawIndexBytes := store.db.Get(accountIndexKey(xpubs)); rawIndexBytes != nil {
+ currentIndex = common.BytesToUnit64(rawIndexBytes)
+ }
+ return currentIndex
+}
+
+// GetBip44ContractIndex get bip44 contract index
+func (store *AccountStore) GetBip44ContractIndex(accountID string, change bool) uint64 {
+ index := uint64(0)
+ if rawIndexBytes := store.db.Get(Bip44ContractIndexKey(accountID, change)); rawIndexBytes != nil {
+ index = common.BytesToUnit64(rawIndexBytes)
+ }
+ return index
+}
+
+// GetCoinbaseArbitrary get coinbase arbitrary
+func (store *AccountStore) GetCoinbaseArbitrary() []byte {
+ return store.db.Get(CoinbaseAbKey)
+}
+
+// GetContractIndex get contract index
+func (store *AccountStore) GetContractIndex(accountID string) uint64 {
+ index := uint64(0)
+ if rawIndexBytes := store.db.Get(contractIndexKey(accountID)); rawIndexBytes != nil {
+ index = common.BytesToUnit64(rawIndexBytes)
+ }
+ return index
+}
+
+// GetControlProgram get control program
+func (store *AccountStore) GetControlProgram(hash bc.Hash) (*acc.CtrlProgram, error) {
+ rawProgram := store.db.Get(ContractKey(hash))
+ if rawProgram == nil {
+ return nil, acc.ErrFindCtrlProgram
+ }
+
+ cp := new(acc.CtrlProgram)
+ if err := json.Unmarshal(rawProgram, cp); err != nil {
+ return nil, err
+ }
+
+ return cp, nil
+}
+
+// GetMiningAddress get mining address
+func (store *AccountStore) GetMiningAddress() (*acc.CtrlProgram, error) {
+ rawCP := store.db.Get(MiningAddressKey)
+ if rawCP == nil {
+ return nil, acc.ErrFindMiningAddress
+ }
+
+ cp := new(acc.CtrlProgram)
+ if err := json.Unmarshal(rawCP, cp); err != nil {
+ return nil, err
+ }
+
+ return cp, nil
+}
+
+// GetUTXO get standard utxo by id
+func (store *AccountStore) GetUTXO(outid bc.Hash) (*acc.UTXO, error) {
+ u := new(acc.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, acc.ErrMatchUTXO
+}
+
+// ListAccounts get all accounts which name prfix is id.
+func (store *AccountStore) ListAccounts(id string) ([]*acc.Account, error) {
+ accounts := []*acc.Account{}
+ accountIter := store.db.IteratorPrefix(AccountIDKey(strings.TrimSpace(id)))
+ defer accountIter.Release()
+
+ for accountIter.Next() {
+ account := new(acc.Account)
+ if err := json.Unmarshal(accountIter.Value(), account); err != nil {
+ return nil, err
+ }
+
+ accounts = append(accounts, account)
+ }
+ return accounts, nil
+}
+
+// ListControlPrograms get all local control programs
+func (store *AccountStore) ListControlPrograms() ([]*acc.CtrlProgram, error) {
+ cps := []*acc.CtrlProgram{}
+ cpIter := store.db.IteratorPrefix(ContractPrefix)
+ defer cpIter.Release()
+
+ for cpIter.Next() {
+ cp := new(acc.CtrlProgram)
+ if err := json.Unmarshal(cpIter.Value(), cp); err != nil {
+ return nil, err
+ }
+
+ cps = append(cps, cp)
+ }
+ return cps, nil
+}
+
+// ListUTXOs get utxos by accountID
+func (store *AccountStore) ListUTXOs() ([]*acc.UTXO, error) {
+ utxoIter := store.db.IteratorPrefix(UTXOPrefix)
+ defer utxoIter.Release()
+
+ utxos := []*acc.UTXO{}
+ for utxoIter.Next() {
+ utxo := new(acc.UTXO)
+ if err := json.Unmarshal(utxoIter.Value(), utxo); err != nil {
+ return nil, err
+ }
+
+ utxos = append(utxos, utxo)
+ }
+ return utxos, nil
+}
+
+// SetAccount set account account ID, account alias and raw account.
+func (store *AccountStore) SetAccount(account *acc.Account) error {
+ rawAccount, err := json.Marshal(account)
+ if err != nil {
+ return acc.ErrMarshalAccount
+ }
+
+ batch := store.db.NewBatch()
+ if store.batch != nil {
+ batch = store.batch
+ }
+
+ batch.Set(AccountIDKey(account.ID), rawAccount)
+ batch.Set(accountAliasKey(account.Alias), []byte(account.ID))
+
+ if store.batch == nil {
+ batch.Write()
+ }
+ return nil
+}
+
+// SetAccountIndex update account index
+func (store *AccountStore) SetAccountIndex(account *acc.Account) {
+ currentIndex := store.GetAccountIndex(account.XPubs)
+ if account.KeyIndex > currentIndex {
+ if store.batch == nil {
+ store.db.Set(accountIndexKey(account.XPubs), common.Unit64ToBytes(account.KeyIndex))
+ } else {
+ store.batch.Set(accountIndexKey(account.XPubs), common.Unit64ToBytes(account.KeyIndex))
+ }
+ }
+}
+
+// SetBip44ContractIndex set contract index
+func (store *AccountStore) SetBip44ContractIndex(accountID string, change bool, index uint64) {
+ if store.batch == nil {
+ store.db.Set(Bip44ContractIndexKey(accountID, change), common.Unit64ToBytes(index))
+ } else {
+ store.batch.Set(Bip44ContractIndexKey(accountID, change), common.Unit64ToBytes(index))
+ }
+}
+
+// SetCoinbaseArbitrary set coinbase arbitrary
+func (store *AccountStore) SetCoinbaseArbitrary(arbitrary []byte) {
+ if store.batch == nil {
+ store.db.Set(CoinbaseAbKey, arbitrary)
+ } else {
+ store.batch.Set(CoinbaseAbKey, arbitrary)
+ }
+}
+
+// SetContractIndex set contract index
+func (store *AccountStore) SetContractIndex(accountID string, index uint64) {
+ if store.batch == nil {
+ store.db.Set(contractIndexKey(accountID), common.Unit64ToBytes(index))
+ } else {
+ store.batch.Set(contractIndexKey(accountID), common.Unit64ToBytes(index))
+ }
+}
+
+// SetControlProgram set raw program
+func (store *AccountStore) SetControlProgram(hash bc.Hash, program *acc.CtrlProgram) error {
+ accountCP, err := json.Marshal(program)
+ if err != nil {
+ return err
+ }
+ if store.batch == nil {
+ store.db.Set(ContractKey(hash), accountCP)
+ } else {
+ store.batch.Set(ContractKey(hash), accountCP)
+ }
+ return nil
+}
+
+// SetMiningAddress set mining address
+func (store *AccountStore) SetMiningAddress(program *acc.CtrlProgram) error {
+ rawProgram, err := json.Marshal(program)
+ if err != nil {
+ return err
+ }
+
+ if store.batch == nil {
+ store.db.Set(MiningAddressKey, rawProgram)
+ } else {
+ store.batch.Set(MiningAddressKey, rawProgram)
+ }
+ return nil
+}
+
+// SetStandardUTXO set standard utxo
+func (store *AccountStore) SetStandardUTXO(outputID bc.Hash, utxo *acc.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
+}
--- /dev/null
+package database
+
+import (
+ "encoding/binary"
+ "encoding/hex"
+ "encoding/json"
+ "fmt"
+
+ log "github.com/sirupsen/logrus"
+
+ acc "github.com/vapor/account"
+ "github.com/vapor/asset"
+ "github.com/vapor/blockchain/query"
+ dbm "github.com/vapor/database/leveldb"
+ "github.com/vapor/errors"
+ "github.com/vapor/protocol/bc"
+ "github.com/vapor/wallet"
+)
+
+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)
+)
+
+// ContractUTXOKey makes a smart contract unspent outputs key to store
+func ContractUTXOKey(id bc.Hash) []byte {
+ return append(SUTXOPrefix, id.Bytes()...)
+}
+
+func calcDeleteKey(blockHeight uint64) []byte {
+ return []byte(fmt.Sprintf("%s%016x", TxPrefix, blockHeight))
+}
+
+func calcTxIndexKey(txID string) []byte {
+ return append(TxIndexPrefix, []byte(txID)...)
+}
+
+func calcAnnotatedKey(formatKey string) []byte {
+ return append(TxPrefix, []byte(formatKey)...)
+}
+
+func calcUnconfirmedTxKey(formatKey string) []byte {
+ return append(UnconfirmedTxPrefix, []byte(formatKey)...)
+}
+
+func CalcGlobalTxIndexKey(txID string) []byte {
+ return append(GlobalTxIndexPrefix, []byte(txID)...)
+}
+
+func CalcGlobalTxIndex(blockHash *bc.Hash, position uint64) []byte {
+ txIdx := make([]byte, 40)
+ copy(txIdx[:32], blockHash.Bytes())
+ binary.BigEndian.PutUint64(txIdx[32:], position)
+ return txIdx
+}
+
+func formatKey(blockHeight uint64, position uint32) string {
+ return fmt.Sprintf("%016x%08x", blockHeight, position)
+}
+
+func contractIndexKey(accountID string) []byte {
+ return append(ContractIndexPrefix, []byte(accountID)...)
+}
+
+// WalletStore store wallet using leveldb
+type WalletStore struct {
+ db dbm.DB
+ batch dbm.Batch
+}
+
+// NewWalletStore create new WalletStore struct
+func NewWalletStore(db dbm.DB) *WalletStore {
+ return &WalletStore{
+ db: db,
+ batch: nil,
+ }
+}
+
+// InitBatch initial new wallet store
+func (store *WalletStore) InitBatch() wallet.WalletStore {
+ newStore := NewWalletStore(store.db)
+ newStore.batch = newStore.db.NewBatch()
+ return newStore
+}
+
+// CommitBatch commit batch
+func (store *WalletStore) CommitBatch() error {
+ if store.batch == nil {
+ return errors.New("WalletStore commit fail, store batch is nil.")
+ }
+
+ store.batch.Write()
+ store.batch = nil
+ return nil
+}
+
+// DeleteContractUTXO delete contract utxo by outputID
+func (store *WalletStore) DeleteContractUTXO(outputID bc.Hash) {
+ if store.batch == nil {
+ store.db.Delete(ContractUTXOKey(outputID))
+ } else {
+ store.batch.Delete(ContractUTXOKey(outputID))
+ }
+}
+
+// DeleteRecoveryStatus delete recovery status
+func (store *WalletStore) DeleteRecoveryStatus() {
+ if store.batch == nil {
+ store.db.Delete(RecoveryKey)
+ } else {
+ store.batch.Delete(RecoveryKey)
+ }
+}
+
+// DeleteTransactions delete transactions when orphan block rollback
+func (store *WalletStore) DeleteTransactions(height uint64) {
+ batch := store.db.NewBatch()
+ if store.batch != nil {
+ batch = store.batch
+ }
+ txIter := store.db.IteratorPrefix(calcDeleteKey(height))
+ defer txIter.Release()
+
+ for txIter.Next() {
+ tmpTx := new(query.AnnotatedTx)
+ if err := json.Unmarshal(txIter.Value(), tmpTx); err == nil {
+ batch.Delete(calcTxIndexKey(tmpTx.ID.String()))
+ } else {
+ log.WithFields(log.Fields{"module": logModule, "err": err}).Warning("fail on DeleteTransactions.")
+ }
+ batch.Delete(txIter.Key())
+ }
+ if store.batch == nil {
+ batch.Write()
+ }
+}
+
+// DeleteUnconfirmedTransaction delete unconfirmed tx by txID
+func (store *WalletStore) DeleteUnconfirmedTransaction(txID string) {
+ if store.batch == nil {
+ store.db.Delete(calcUnconfirmedTxKey(txID))
+ } else {
+ store.batch.Delete(calcUnconfirmedTxKey(txID))
+ }
+}
+
+// DeleteWalletTransactions delete all txs in wallet
+func (store *WalletStore) DeleteWalletTransactions() {
+ batch := store.db.NewBatch()
+ if store.batch != nil {
+ batch = store.batch
+ }
+ txIter := store.db.IteratorPrefix(TxPrefix)
+ defer txIter.Release()
+
+ for txIter.Next() {
+ batch.Delete(txIter.Key())
+ }
+
+ txIndexIter := store.db.IteratorPrefix(TxIndexPrefix)
+ defer txIndexIter.Release()
+
+ for txIndexIter.Next() {
+ batch.Delete(txIndexIter.Key())
+ }
+ if store.batch == nil {
+ batch.Write()
+ }
+}
+
+// DeleteWalletUTXOs delete all txs in wallet
+func (store *WalletStore) DeleteWalletUTXOs() {
+ batch := store.db.NewBatch()
+ if store.batch != nil {
+ batch = store.batch
+ }
+
+ ruIter := store.db.IteratorPrefix(UTXOPrefix)
+ defer ruIter.Release()
+
+ for ruIter.Next() {
+ batch.Delete(ruIter.Key())
+ }
+
+ suIter := store.db.IteratorPrefix(SUTXOPrefix)
+ defer suIter.Release()
+
+ for suIter.Next() {
+ batch.Delete(suIter.Key())
+ }
+ if store.batch == nil {
+ batch.Write()
+ }
+}
+
+// GetAsset get asset by assetID
+func (store *WalletStore) GetAsset(assetID *bc.AssetID) (*asset.Asset, error) {
+ definitionByte := store.db.Get(asset.ExtAssetKey(assetID))
+ if definitionByte == nil {
+ return nil, wallet.ErrGetAsset
+ }
+
+ definitionMap := make(map[string]interface{})
+ if err := json.Unmarshal(definitionByte, &definitionMap); err != nil {
+ return nil, err
+ }
+
+ alias := assetID.String()
+ externalAsset := &asset.Asset{
+ AssetID: *assetID,
+ Alias: &alias,
+ DefinitionMap: definitionMap,
+ RawDefinitionByte: definitionByte,
+ }
+ return externalAsset, nil
+}
+
+// GetGlobalTransactionIndex get global tx by txID
+func (store *WalletStore) GetGlobalTransactionIndex(txID string) []byte {
+ return store.db.Get(CalcGlobalTxIndexKey(txID))
+}
+
+// GetStandardUTXO get standard utxo by id
+func (store *WalletStore) GetStandardUTXO(outid bc.Hash) (*acc.UTXO, error) {
+ rawUTXO := store.db.Get(StandardUTXOKey(outid))
+ if rawUTXO == nil {
+ return nil, fmt.Errorf("failed get standard UTXO, outputID: %s ", outid.String())
+ }
+
+ UTXO := new(acc.UTXO)
+ if err := json.Unmarshal(rawUTXO, UTXO); err != nil {
+ return nil, err
+ }
+
+ return UTXO, nil
+}
+
+// GetTransaction get tx by txid
+func (store *WalletStore) GetTransaction(txID string) (*query.AnnotatedTx, error) {
+ formatKey := store.db.Get(calcTxIndexKey(txID))
+ if formatKey == nil {
+ return nil, wallet.ErrAccntTxIDNotFound
+ }
+
+ rawTx := store.db.Get(calcAnnotatedKey(string(formatKey)))
+ tx := new(query.AnnotatedTx)
+ if err := json.Unmarshal(rawTx, tx); err != nil {
+ return nil, err
+ }
+
+ return tx, nil
+}
+
+// GetUnconfirmedTransaction get unconfirmed tx by txID
+func (store *WalletStore) GetUnconfirmedTransaction(txID string) (*query.AnnotatedTx, error) {
+ rawUnconfirmedTx := store.db.Get(calcUnconfirmedTxKey(txID))
+ if rawUnconfirmedTx == nil {
+ return nil, fmt.Errorf("failed get unconfirmed tx, txID: %s ", txID)
+ }
+
+ tx := new(query.AnnotatedTx)
+ if err := json.Unmarshal(rawUnconfirmedTx, tx); err != nil {
+ return nil, err
+ }
+
+ return tx, nil
+}
+
+// GetRecoveryStatus delete recovery status
+func (store *WalletStore) GetRecoveryStatus() (*wallet.RecoveryState, error) {
+ rawStatus := store.db.Get(RecoveryKey)
+ if rawStatus == nil {
+ return nil, wallet.ErrGetRecoveryStatus
+ }
+
+ state := new(wallet.RecoveryState)
+ if err := json.Unmarshal(rawStatus, state); err != nil {
+ return nil, err
+ }
+
+ return state, nil
+}
+
+// GetWalletInfo get wallet information
+func (store *WalletStore) GetWalletInfo() (*wallet.StatusInfo, error) {
+ rawStatus := store.db.Get(WalletKey)
+ if rawStatus == nil {
+ return nil, wallet.ErrGetWalletStatusInfo
+ }
+
+ status := new(wallet.StatusInfo)
+ if err := json.Unmarshal(rawStatus, status); err != nil {
+ return nil, err
+ }
+
+ return status, nil
+}
+
+// ListAccountUTXOs get all account unspent outputs
+func (store *WalletStore) ListAccountUTXOs(id string, isSmartContract bool) ([]*acc.UTXO, error) {
+ prefix := UTXOPrefix
+ if isSmartContract {
+ prefix = SUTXOPrefix
+ }
+
+ idBytes, err := hex.DecodeString(id)
+ if err != nil {
+ return nil, err
+ }
+
+ accountUtxoIter := store.db.IteratorPrefix(append(prefix, idBytes...))
+ defer accountUtxoIter.Release()
+
+ confirmedUTXOs := []*acc.UTXO{}
+ for accountUtxoIter.Next() {
+ utxo := new(acc.UTXO)
+ if err := json.Unmarshal(accountUtxoIter.Value(), utxo); err != nil {
+ return nil, err
+ }
+
+ confirmedUTXOs = append(confirmedUTXOs, utxo)
+ }
+ return confirmedUTXOs, nil
+}
+
+func (store *WalletStore) ListTransactions(accountID string, StartTxID string, count uint, unconfirmed bool) ([]*query.AnnotatedTx, error) {
+ annotatedTxs := []*query.AnnotatedTx{}
+ var startKey []byte
+ preFix := TxPrefix
+
+ if StartTxID != "" {
+ if unconfirmed {
+ startKey = calcUnconfirmedTxKey(StartTxID)
+ } else {
+ formatKey := store.db.Get(calcTxIndexKey(StartTxID))
+ if formatKey == nil {
+ return nil, wallet.ErrAccntTxIDNotFound
+ }
+
+ startKey = calcAnnotatedKey(string(formatKey))
+ }
+ }
+
+ if unconfirmed {
+ preFix = UnconfirmedTxPrefix
+ }
+
+ itr := store.db.IteratorPrefixWithStart(preFix, startKey, true)
+ defer itr.Release()
+
+ for txNum := count; itr.Next() && txNum > 0; {
+ annotatedTx := new(query.AnnotatedTx)
+ if err := json.Unmarshal(itr.Value(), annotatedTx); err != nil {
+ return nil, err
+ }
+
+ annotatedTxs = append(annotatedTxs, annotatedTx)
+ txNum--
+ }
+
+ return annotatedTxs, nil
+}
+
+// ListUnconfirmedTransactions get all unconfirmed txs
+func (store *WalletStore) ListUnconfirmedTransactions() ([]*query.AnnotatedTx, error) {
+ annotatedTxs := []*query.AnnotatedTx{}
+ txIter := store.db.IteratorPrefix(UnconfirmedTxPrefix)
+ defer txIter.Release()
+
+ for txIter.Next() {
+ annotatedTx := new(query.AnnotatedTx)
+ if err := json.Unmarshal(txIter.Value(), annotatedTx); err != nil {
+ return nil, err
+ }
+
+ annotatedTxs = append(annotatedTxs, annotatedTx)
+ }
+ return annotatedTxs, nil
+}
+
+// SetAssetDefinition set assetID and definition
+func (store *WalletStore) SetAssetDefinition(assetID *bc.AssetID, definition []byte) {
+ if store.batch == nil {
+ store.db.Set(asset.ExtAssetKey(assetID), definition)
+ } else {
+ store.batch.Set(asset.ExtAssetKey(assetID), definition)
+ }
+}
+
+// SetContractUTXO set standard utxo
+func (store *WalletStore) SetContractUTXO(outputID bc.Hash, utxo *acc.UTXO) error {
+ data, err := json.Marshal(utxo)
+ if err != nil {
+ return err
+ }
+
+ if store.batch == nil {
+ store.db.Set(ContractUTXOKey(outputID), data)
+ } else {
+ store.batch.Set(ContractUTXOKey(outputID), data)
+ }
+ return nil
+}
+
+// SetGlobalTransactionIndex set global tx index by blockhash and position
+func (store *WalletStore) SetGlobalTransactionIndex(globalTxID string, blockHash *bc.Hash, position uint64) {
+ if store.batch == nil {
+ store.db.Set(CalcGlobalTxIndexKey(globalTxID), CalcGlobalTxIndex(blockHash, position))
+ } else {
+ store.batch.Set(CalcGlobalTxIndexKey(globalTxID), CalcGlobalTxIndex(blockHash, position))
+ }
+}
+
+// SetRecoveryStatus set recovery status
+func (store *WalletStore) SetRecoveryStatus(recoveryState *wallet.RecoveryState) error {
+ rawStatus, err := json.Marshal(recoveryState)
+ if err != nil {
+ return err
+ }
+
+ if store.batch == nil {
+ store.db.Set(RecoveryKey, rawStatus)
+ } else {
+ store.batch.Set(RecoveryKey, rawStatus)
+ }
+ return nil
+}
+
+// SetTransaction set raw transaction by block height and tx position
+func (store *WalletStore) SetTransaction(height uint64, tx *query.AnnotatedTx) error {
+ batch := store.db.NewBatch()
+ if store.batch != nil {
+ batch = store.batch
+ }
+
+ rawTx, err := json.Marshal(tx)
+ if err != nil {
+ return err
+ }
+
+ batch.Set(calcAnnotatedKey(formatKey(height, tx.Position)), rawTx)
+ batch.Set(calcTxIndexKey(tx.ID.String()), []byte(formatKey(height, tx.Position)))
+
+ if store.batch == nil {
+ batch.Write()
+ }
+ return nil
+}
+
+// SetUnconfirmedTransaction set unconfirmed tx by txID
+func (store *WalletStore) SetUnconfirmedTransaction(txID string, tx *query.AnnotatedTx) error {
+ rawTx, err := json.Marshal(tx)
+ if err != nil {
+ return err
+ }
+
+ if store.batch == nil {
+ store.db.Set(calcUnconfirmedTxKey(txID), rawTx)
+ } else {
+ store.batch.Set(calcUnconfirmedTxKey(txID), rawTx)
+ }
+ return nil
+}
+
+// SetWalletInfo get wallet information
+func (store *WalletStore) SetWalletInfo(status *wallet.StatusInfo) error {
+ rawWallet, err := json.Marshal(status)
+ if err != nil {
+ return err
+ }
+
+ if store.batch == nil {
+ store.db.Set(WalletKey, rawWallet)
+ } else {
+ store.batch.Set(WalletKey, rawWallet)
+ }
+ return nil
+}
--- /dev/null
+package database
+
+import (
+ "io/ioutil"
+ "os"
+ "reflect"
+ "testing"
+
+ "github.com/vapor/blockchain/pseudohsm"
+ "github.com/vapor/crypto/ed25519/chainkd"
+)
+
+func TestAccountIndexKey(t *testing.T) {
+ dirPath, err := ioutil.TempDir(".", "TestAccount")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(dirPath)
+
+ hsm, err := pseudohsm.New(dirPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ xpub1, _, err := hsm.XCreate("TestAccountIndex1", "password", "en")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ xpub2, _, err := hsm.XCreate("TestAccountIndex2", "password", "en")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ xpubs1 := []chainkd.XPub{xpub1.XPub, xpub2.XPub}
+ xpubs2 := []chainkd.XPub{xpub2.XPub, xpub1.XPub}
+ if !reflect.DeepEqual(accountIndexKey(xpubs1), accountIndexKey(xpubs2)) {
+ t.Fatal("accountIndexKey test err")
+ }
+
+ if reflect.DeepEqual(xpubs1, xpubs2) {
+ t.Fatal("accountIndexKey test err")
+ }
+}
if !config.Wallet.Disable {
walletDB := dbm.NewDB("wallet", config.DBBackend, config.DBDir())
- accounts = account.NewManager(walletDB, chain)
+ walletStore := database.NewWalletStore(walletDB)
+ accountStore := database.NewAccountStore(walletDB)
+ accounts = account.NewManager(accountStore, chain)
assets = asset.NewRegistry(walletDB, chain)
- wallet, err = w.NewWallet(walletDB, accounts, assets, hsm, chain, dispatcher, config.Wallet.TxIndex)
+ wallet, err = w.NewWallet(walletStore, accounts, assets, hsm, chain, dispatcher, config.Wallet.TxIndex)
if err != nil {
log.WithFields(log.Fields{"module": logModule, "error": err}).Error("init NewWallet")
}
-package account
+package test
import (
"io/ioutil"
"os"
- "reflect"
"strings"
"testing"
- "github.com/vapor/blockchain/pseudohsm"
+ acc "github.com/vapor/account"
"github.com/vapor/blockchain/signers"
"github.com/vapor/config"
"github.com/vapor/crypto/ed25519/chainkd"
func TestCreateAccountWithUppercase(t *testing.T) {
m := mockAccountManager(t)
alias := "UPPER"
- account, err := m.Create([]chainkd.XPub{testutil.TestXPub}, 1, alias, signers.BIP0044)
+ account, err := m.Manager.Create([]chainkd.XPub{testutil.TestXPub}, 1, alias, signers.BIP0044)
if err != nil {
t.Fatal(err)
func TestCreateAccountWithSpaceTrimed(t *testing.T) {
m := mockAccountManager(t)
alias := " with space "
- account, err := m.Create([]chainkd.XPub{testutil.TestXPub}, 1, alias, signers.BIP0044)
+ account, err := m.Manager.Create([]chainkd.XPub{testutil.TestXPub}, 1, alias, signers.BIP0044)
if err != nil {
t.Fatal(err)
t.Fatal("created account alias should be lowercase")
}
- nilAccount, err := m.FindByAlias(alias)
+ nilAccount, err := m.Manager.FindByAlias(alias)
if nilAccount != nil {
t.Fatal("expected nil")
}
- target, err := m.FindByAlias(strings.ToLower(strings.TrimSpace(alias)))
+ target, err := m.Manager.FindByAlias(strings.ToLower(strings.TrimSpace(alias)))
if target == nil {
t.Fatal("expected Account, but got nil")
}
func TestCreateAccount(t *testing.T) {
m := mockAccountManager(t)
- account, err := m.Create([]chainkd.XPub{testutil.TestXPub}, 1, "test-alias", signers.BIP0044)
+ account, err := m.Manager.Create([]chainkd.XPub{testutil.TestXPub}, 1, "test-alias", signers.BIP0044)
if err != nil {
t.Fatal(err)
}
- found, err := m.FindByID(account.ID)
+ found, err := m.Manager.FindByID(account.ID)
if err != nil {
t.Errorf("unexpected error %v", err)
}
+
if !testutil.DeepEqual(account, found) {
t.Errorf("expected account %v to be recorded as %v", account, found)
}
m := mockAccountManager(t)
m.createTestAccount(t, "test-alias", nil)
- _, err := m.Create([]chainkd.XPub{testutil.TestXPub}, 1, "test-alias", signers.BIP0044)
- if errors.Root(err) != ErrDuplicateAlias {
- t.Errorf("expected %s when reusing an alias, got %v", ErrDuplicateAlias, err)
+ _, err := m.Manager.Create([]chainkd.XPub{testutil.TestXPub}, 1, "test-alias", signers.BIP0044)
+ if errors.Root(err) != acc.ErrDuplicateAlias {
+ t.Errorf("expected %s when reusing an alias, got %v", acc.ErrDuplicateAlias, err)
}
}
m := mockAccountManager(t)
account := m.createTestAccount(t, oldAlias, nil)
- if err := m.UpdateAccountAlias("testID", newAlias); err == nil {
- t.Fatal("expected error when using an invalid account id")
+ err := m.Manager.UpdateAccountAlias("testID", newAlias)
+ if err == nil {
+ t.Errorf("expected error when using an invalid account id")
}
- err := m.UpdateAccountAlias(account.ID, oldAlias)
- if errors.Root(err) != ErrDuplicateAlias {
- t.Errorf("expected %s when using a duplicate alias, got %v", ErrDuplicateAlias, err)
+ err = m.Manager.UpdateAccountAlias(account.ID, oldAlias)
+ if errors.Root(err) != acc.ErrDuplicateAlias {
+ t.Errorf("expected %s when using a duplicate alias, got %v", acc.ErrDuplicateAlias, err)
}
- if err := m.UpdateAccountAlias(account.ID, newAlias); err != nil {
+ err = m.Manager.UpdateAccountAlias(account.ID, newAlias)
+ if err != nil {
t.Errorf("expected account %v alias should be update", account)
}
- updatedAccount, err := m.FindByID(account.ID)
+ updatedAccount, err := m.Manager.FindByID(account.ID)
if err != nil {
t.Errorf("unexpected error %v", err)
}
if updatedAccount.Alias != newAlias {
- t.Fatalf("alias:\ngot: %v\nwant: %v", updatedAccount.Alias, newAlias)
+ t.Errorf("alias:\ngot: %v\nwant: %v", updatedAccount.Alias, newAlias)
}
- if _, err = m.FindByAlias(oldAlias); errors.Root(err) != ErrFindAccount {
- t.Errorf("expected %s when using a old alias, got %v", ErrFindAccount, err)
+ if _, err = m.Manager.FindByAlias(oldAlias); errors.Root(err) != acc.ErrFindAccount {
+ t.Errorf("expected %s when using a old alias, got %v", acc.ErrFindAccount, err)
}
}
func TestDeleteAccount(t *testing.T) {
m := mockAccountManager(t)
- account1, err := m.Create([]chainkd.XPub{testutil.TestXPub}, 1, "test-alias1", signers.BIP0044)
+ account1, err := m.Manager.Create([]chainkd.XPub{testutil.TestXPub}, 1, "test-alias1", signers.BIP0044)
if err != nil {
t.Fatal(err)
}
- account2, err := m.Create([]chainkd.XPub{testutil.TestXPub}, 1, "test-alias2", signers.BIP0044)
+ account2, err := m.Manager.Create([]chainkd.XPub{testutil.TestXPub}, 1, "test-alias2", signers.BIP0044)
if err != nil {
t.Fatal(err)
}
- found, err := m.FindByID(account1.ID)
+ found, err := m.Manager.FindByID(account1.ID)
if err != nil {
t.Errorf("expected account %v should be deleted", found)
}
- if err = m.DeleteAccount(account2.ID); err != nil {
+ if err = m.Manager.DeleteAccount(account2.ID); err != nil {
t.Fatal(err)
}
- found, err = m.FindByID(account2.ID)
+ found, err = m.Manager.FindByID(account2.ID)
if err != nil {
t.Errorf("expected account %v should be deleted", found)
}
m := mockAccountManager(t)
account := m.createTestAccount(t, "", nil)
- found, err := m.FindByID(account.ID)
+ found, err := m.Manager.FindByID(account.ID)
if err != nil {
t.Fatal(err)
}
m := mockAccountManager(t)
account := m.createTestAccount(t, "some-alias", nil)
- found, err := m.FindByAlias("some-alias")
+ found, err := m.Manager.FindByAlias("some-alias")
if err != nil {
t.Fatal(err)
}
}
}
-func TestGetAccountIndexKey(t *testing.T) {
- dirPath, err := ioutil.TempDir(".", "TestAccount")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(dirPath)
-
- hsm, err := pseudohsm.New(dirPath)
- if err != nil {
- t.Fatal(err)
- }
-
- xpub1, _, err := hsm.XCreate("TestAccountIndex1", "password", "en")
- if err != nil {
- t.Fatal(err)
- }
-
- xpub2, _, err := hsm.XCreate("TestAccountIndex2", "password", "en")
- if err != nil {
- t.Fatal(err)
- }
-
- xpubs1 := []chainkd.XPub{xpub1.XPub, xpub2.XPub}
- xpubs2 := []chainkd.XPub{xpub2.XPub, xpub1.XPub}
- if !reflect.DeepEqual(GetAccountIndexKey(xpubs1), GetAccountIndexKey(xpubs2)) {
- t.Fatal("GetAccountIndexKey test err")
- }
-
- if reflect.DeepEqual(xpubs1, xpubs2) {
- t.Fatal("GetAccountIndexKey test err")
- }
+type mockAccManager struct {
+ Manager *acc.Manager
}
-func mockAccountManager(t *testing.T) *Manager {
+func mockAccountManager(t *testing.T) *mockAccManager {
dirPath, err := ioutil.TempDir(".", "")
if err != nil {
t.Fatal(err)
testDB := dbm.NewDB("testdb", "memdb", dirPath)
dispatcher := event.NewDispatcher()
store := database.NewStore(testDB)
+ accountStore := database.NewAccountStore(testDB)
txPool := protocol.NewTxPool(store, dispatcher)
config.CommonConfig = config.DefaultConfig()
chain, err := protocol.NewChain(store, txPool, dispatcher)
t.Fatal(err)
}
- return NewManager(testDB, chain)
+ return &mockAccManager{acc.NewManager(accountStore, chain)}
}
-func (m *Manager) createTestAccount(t testing.TB, alias string, tags map[string]interface{}) *Account {
- account, err := m.Create([]chainkd.XPub{testutil.TestXPub}, 1, alias, signers.BIP0044)
+func (m *mockAccManager) createTestAccount(t testing.TB, alias string, tags map[string]interface{}) *acc.Account {
+ account, err := m.Manager.Create([]chainkd.XPub{testutil.TestXPub}, 1, alias, signers.BIP0044)
if err != nil {
t.Fatal(err)
}
return account
-
}
}
}
- block, err := proposal.NewBlockTemplate(chain, txPool, nil, uint64(time.Now().UnixNano() / 1e6))
+ block, err := proposal.NewBlockTemplate(chain, txPool, nil, uint64(time.Now().UnixNano()/1e6))
if err != nil {
return err
}
//-------------------------Mock actual transaction----------------------------------
func MockTxsP2PKH(keyDirPath string, testDB dbm.DB, txNumber, otherAssetNum int) ([]*types.Tx, error) {
- accountManager := account.NewManager(testDB, nil)
+ accountStore := database.NewAccountStore(testDB)
+ accountManager := account.NewManager(accountStore, nil)
hsm, err := pseudohsm.New(keyDirPath)
if err != nil {
return nil, err
}
func MockTxsP2SH(keyDirPath string, testDB dbm.DB, txNumber, otherAssetNum int) ([]*types.Tx, error) {
- accountManager := account.NewManager(testDB, nil)
+ accountStore := database.NewAccountStore(testDB)
+ accountManager := account.NewManager(accountStore, nil)
hsm, err := pseudohsm.New(keyDirPath)
if err != nil {
return nil, err
}
func MockTxsMultiSign(keyDirPath string, testDB dbm.DB, txNumber, otherAssetNum int) ([]*types.Tx, error) {
- accountManager := account.NewManager(testDB, nil)
+ accountStore := database.NewAccountStore(testDB)
+ accountManager := account.NewManager(accountStore, nil)
hsm, err := pseudohsm.New(keyDirPath)
if err != nil {
return nil, err
--- /dev/null
+package test
+
+import (
+ "testing"
+
+ acc "github.com/vapor/account"
+ "github.com/vapor/blockchain/signers"
+ "github.com/vapor/consensus"
+ "github.com/vapor/crypto/ed25519/chainkd"
+ "github.com/vapor/testutil"
+)
+
+var (
+ //chainTxUtxoNum maximum utxo quantity in a tx
+ chainTxUtxoNum = 5
+ //chainTxMergeGas chain tx gas
+ chainTxMergeGas = uint64(10000000)
+)
+
+func TestBuildBtmTxChain(t *testing.T) {
+ chainTxUtxoNum = 3
+ m := mockAccountManager(t)
+ cases := []struct {
+ inputUtxo []uint64
+ wantInput [][]uint64
+ wantOutput [][]uint64
+ wantUtxo uint64
+ }{
+ {
+ inputUtxo: []uint64{5},
+ wantInput: [][]uint64{},
+ wantOutput: [][]uint64{},
+ wantUtxo: 5 * chainTxMergeGas,
+ },
+ {
+ inputUtxo: []uint64{5, 4},
+ wantInput: [][]uint64{
+ []uint64{5, 4},
+ },
+ wantOutput: [][]uint64{
+ []uint64{8},
+ },
+ wantUtxo: 8 * chainTxMergeGas,
+ },
+ {
+ inputUtxo: []uint64{5, 4, 1, 1},
+ wantInput: [][]uint64{
+ []uint64{5, 4, 1, 1},
+ []uint64{1, 9},
+ },
+ wantOutput: [][]uint64{
+ []uint64{10},
+ []uint64{9},
+ },
+ wantUtxo: 10 * chainTxMergeGas,
+ },
+ {
+ inputUtxo: []uint64{22, 123, 53, 234, 23, 4, 2423, 24, 23, 43, 34, 234, 234, 24},
+ wantInput: [][]uint64{
+ []uint64{22, 123, 53, 234, 23},
+ []uint64{4, 2423, 24, 23, 43},
+ []uint64{34, 234, 234, 24, 454},
+ []uint64{2516, 979},
+ []uint64{234, 24, 197},
+ []uint64{260, 2469, 310},
+ []uint64{454, 3038},
+ },
+ wantOutput: [][]uint64{
+ []uint64{454},
+ []uint64{2516},
+ []uint64{979},
+ []uint64{3494},
+ []uint64{454},
+ []uint64{3038},
+ []uint64{3491},
+ },
+ wantUtxo: 3494 * chainTxMergeGas,
+ },
+ }
+
+ acct, err := m.Manager.Create([]chainkd.XPub{testutil.TestXPub}, 1, "testAccount", signers.BIP0044)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ acp, err := m.Manager.CreateAddress(acct.ID, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ for caseIndex, c := range cases {
+ utxos := []*acc.UTXO{}
+ for _, amount := range c.inputUtxo {
+ utxos = append(utxos, &acc.UTXO{
+ Amount: amount * chainTxMergeGas,
+ AssetID: *consensus.BTMAssetID,
+ Address: acp.Address,
+ ControlProgram: acp.ControlProgram,
+ })
+ }
+
+ tpls, gotUtxo, err := m.Manager.BuildBtmTxChain(utxos, acct.Signer)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ for i, tpl := range tpls {
+ gotInput := []uint64{}
+ for _, input := range tpl.Transaction.Inputs {
+ gotInput = append(gotInput, input.Amount()/chainTxMergeGas)
+ }
+
+ gotOutput := []uint64{}
+ for _, output := range tpl.Transaction.Outputs {
+ gotOutput = append(gotOutput, output.AssetAmount().Amount/chainTxMergeGas)
+ }
+
+ if !testutil.DeepEqual(c.wantInput[i], gotInput) {
+ t.Errorf("case %d tx %d input got %d want %d", caseIndex, i, gotInput, c.wantInput[i])
+ }
+ if !testutil.DeepEqual(c.wantOutput[i], gotOutput) {
+ t.Errorf("case %d tx %d output got %d want %d", caseIndex, i, gotOutput, c.wantOutput[i])
+ }
+ }
+
+ if c.wantUtxo != gotUtxo.Amount {
+ t.Errorf("case %d got utxo=%d want utxo=%d", caseIndex, gotUtxo.Amount, c.wantUtxo)
+ }
+ }
+}
defer iter.Release()
for iter.Next() {
- utxoEntry := storage.UtxoEntry{}
- if err := proto.Unmarshal(iter.Value(), &utxoEntry); err != nil {
+ utxoEntry := new(storage.UtxoEntry)
+ if err := proto.Unmarshal(iter.Value(), utxoEntry); err != nil {
return nil
}
key := string(iter.Key())
- utxoEntries[key] = &utxoEntry
+ utxoEntries[key] = utxoEntry
}
return utxoEntries
}
"github.com/vapor/blockchain/pseudohsm"
"github.com/vapor/blockchain/signers"
"github.com/vapor/crypto/ed25519/chainkd"
+ "github.com/vapor/database"
dbm "github.com/vapor/database/leveldb"
"github.com/vapor/protocol/bc/types"
"github.com/vapor/protocol/validation"
t.Fatal(err)
}
- accountManager := account.NewManager(testDB, chain)
+ accountStore := database.NewAccountStore(testDB)
+ accountManager := account.NewManager(accountStore, chain)
hsm, err := pseudohsm.New(dirPath)
if err != nil {
t.Fatal(err)
t.Fatal(err)
}
- accountManager := account.NewManager(testDB, chain)
+ accountStore := database.NewAccountStore(testDB)
+ accountManager := account.NewManager(accountStore, chain)
hsm, err := pseudohsm.New(dirPath)
if err != nil {
t.Fatal(err)
t.Fatal(err)
}
- testDB.Set(account.Key(testAccount.ID), rawAccount)
+ testDB.Set(database.AccountIDKey(testAccount.ID), rawAccount)
controlProg, err := accountManager.CreateAddress(testAccount.ID, false)
if err != nil {
t.Fatal(err)
t.Fatal(err)
}
- accountManager := account.NewManager(testDB, chain)
+ accountStore := database.NewAccountStore(testDB)
+ accountManager := account.NewManager(accountStore, chain)
hsm, err := pseudohsm.New(dirPath)
if err != nil {
t.Fatal(err)
t.Fatal(err)
}
- accountManager := account.NewManager(testDB, chain)
+ accountStore := database.NewAccountStore(testDB)
+ accountManager := account.NewManager(accountStore, chain)
hsm, err := pseudohsm.New(dirPath)
if err != nil {
t.Fatal(err)
t.Fatal(err)
}
- testDB.Set(account.Key(testAccount.ID), rawAccount)
+ testDB.Set(database.AccountIDKey(testAccount.ID), rawAccount)
controlProg, err := accountManager.CreateAddress(testAccount.ID, false)
if err != nil {
t.Fatal(err)
}
- accountManager := account.NewManager(testDB, chain)
+ accountStore := database.NewAccountStore(testDB)
+ accountManager := account.NewManager(accountStore, chain)
hsm, err := pseudohsm.New(dirPath)
if err != nil {
t.Fatal(err)
t.Fatal(err)
}
- accountManager := account.NewManager(testDB, chain)
+ accountStore := database.NewAccountStore(testDB)
+ accountManager := account.NewManager(accountStore, chain)
hsm, err := pseudohsm.New(dirPath)
if err != nil {
t.Fatal(err)
t.Fatal(err)
}
- testDB.Set(account.Key(testAccount.ID), rawAccount)
+ testDB.Set(database.AccountIDKey(testAccount.ID), rawAccount)
controlProg, err := accountManager.CreateAddress(testAccount.ID, false)
if err != nil {
"time"
"github.com/vapor/account"
+ "github.com/vapor/database"
dbm "github.com/vapor/database/leveldb"
"github.com/vapor/proposal"
"github.com/vapor/test"
if err != nil {
b.Fatal(err)
}
- accountManager := account.NewManager(testDB, chain)
+ accountStore := database.NewAccountStore(testDB)
+ accountManager := account.NewManager(accountStore, chain)
b.ResetTimer()
for i := 0; i < b.N; i++ {
- proposal.NewBlockTemplate(chain, txPool, accountManager, uint64(time.Now().UnixNano() / 1e6))
+ proposal.NewBlockTemplate(chain, txPool, accountManager, uint64(time.Now().UnixNano()/1e6))
}
}
"github.com/vapor/consensus"
"github.com/vapor/crypto/ed25519/chainkd"
"github.com/vapor/crypto/sha3pool"
+ "github.com/vapor/database"
dbm "github.com/vapor/database/leveldb"
"github.com/vapor/errors"
"github.com/vapor/protocol/bc"
cp := account.CtrlProgram{}
var hash [32]byte
sha3pool.Sum256(hash[:], input.ControlProgram)
- bytes := db.Get(account.ContractKey(hash))
+ bytes := db.Get(database.ContractKey(bc.NewHash(hash)))
if bytes == nil {
return nil, fmt.Errorf("can't find CtrlProgram for the SpendInput")
}
--- /dev/null
+package test
+
+import (
+ "io/ioutil"
+ "os"
+ "reflect"
+ "testing"
+ "time"
+
+ "github.com/vapor/account"
+ "github.com/vapor/asset"
+ "github.com/vapor/blockchain/pseudohsm"
+ "github.com/vapor/blockchain/signers"
+ "github.com/vapor/blockchain/txbuilder"
+ "github.com/vapor/config"
+ "github.com/vapor/consensus"
+ "github.com/vapor/crypto/ed25519/chainkd"
+ "github.com/vapor/database"
+ dbm "github.com/vapor/database/leveldb"
+ "github.com/vapor/event"
+ "github.com/vapor/protocol"
+ "github.com/vapor/protocol/bc"
+ "github.com/vapor/protocol/bc/types"
+ wt "github.com/vapor/wallet"
+)
+
+func TestWalletUpdate(t *testing.T) {
+ dirPath, err := ioutil.TempDir(".", "")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(dirPath)
+
+ config.CommonConfig = config.DefaultConfig()
+ testDB := dbm.NewDB("testdb", "leveldb", "temp")
+ defer func() {
+ testDB.Close()
+ os.RemoveAll("temp")
+ }()
+
+ store := database.NewStore(testDB)
+ walletStore := database.NewWalletStore(testDB)
+ dispatcher := event.NewDispatcher()
+ txPool := protocol.NewTxPool(store, dispatcher)
+
+ chain, err := protocol.NewChain(store, txPool, dispatcher)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ accountStore := database.NewAccountStore(testDB)
+ accountManager := account.NewManager(accountStore, chain)
+ hsm, err := pseudohsm.New(dirPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ xpub1, _, err := hsm.XCreate("test_pub1", "password", "en")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ testAccount, err := accountManager.Create([]chainkd.XPub{xpub1.XPub}, 1, "testAccount", signers.BIP0044)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ controlProg, err := accountManager.CreateAddress(testAccount.ID, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ controlProg.KeyIndex = 1
+
+ reg := asset.NewRegistry(testDB, chain)
+ asset := bc.AssetID{V0: 5}
+
+ utxos := []*account.UTXO{}
+ btmUtxo := mockUTXO(controlProg, consensus.BTMAssetID)
+ utxos = append(utxos, btmUtxo)
+ OtherUtxo := mockUTXO(controlProg, &asset)
+ utxos = append(utxos, OtherUtxo)
+
+ _, txData, err := mockTxData(utxos, testAccount)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ tx := types.NewTx(*txData)
+ block := mockSingleBlock(tx)
+ txStatus := bc.NewTransactionStatus()
+ txStatus.SetStatus(0, false)
+ txStatus.SetStatus(1, false)
+ store.SaveBlock(block, txStatus)
+
+ w := newMockWallet(walletStore, accountManager, reg, chain, dispatcher, true)
+ err = w.AttachBlock(block)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if _, err := w.GetTransactionByTxID(tx.ID.String()); err != nil {
+ t.Fatal(err)
+ }
+
+ wants, err := w.GetTransactions(testAccount.ID, "", 1, false)
+ if len(wants) != 1 {
+ t.Fatal(err)
+ }
+
+ if wants[0].ID != tx.ID {
+ t.Fatal("account txID mismatch")
+ }
+
+ for position, tx := range block.Transactions {
+ get := testDB.Get(database.CalcGlobalTxIndexKey(tx.ID.String()))
+ bh := block.BlockHeader.Hash()
+ expect := database.CalcGlobalTxIndex(&bh, uint64(position))
+ if !reflect.DeepEqual(get, expect) {
+ t.Fatalf("position#%d: compare retrieved globalTxIdx err", position)
+ }
+ }
+}
+
+func TestRescanWallet(t *testing.T) {
+ // prepare wallet & db.
+ dirPath, err := ioutil.TempDir(".", "")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(dirPath)
+
+ config.CommonConfig = config.DefaultConfig()
+ testDB := dbm.NewDB("testdb", "leveldb", "temp")
+ walletStore := database.NewWalletStore(testDB)
+ defer func() {
+ testDB.Close()
+ os.RemoveAll("temp")
+ }()
+
+ store := database.NewStore(testDB)
+ dispatcher := event.NewDispatcher()
+ txPool := protocol.NewTxPool(store, dispatcher)
+ chain, err := protocol.NewChain(store, txPool, dispatcher)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ statusInfo := wt.StatusInfo{
+ Version: uint(1),
+ WorkHash: bc.Hash{V0: 0xff},
+ }
+ if err := walletStore.SetWalletInfo(&statusInfo); err != nil {
+ t.Fatal(err)
+ }
+ walletInfo, err := walletStore.GetWalletInfo()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ accountStore := database.NewAccountStore(testDB)
+ accountManager := account.NewManager(accountStore, chain)
+ w := newMockWallet(walletStore, accountManager, nil, chain, dispatcher, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ w.Status = *walletInfo
+
+ // rescan wallet.
+ if err := w.LoadWalletInfo(); err != nil {
+ t.Fatal(err)
+ }
+
+ block := config.GenesisBlock()
+ if w.Status.WorkHash != block.Hash() {
+ t.Fatal("reattach from genesis block")
+ }
+}
+
+func TestMemPoolTxQueryLoop(t *testing.T) {
+ dirPath, err := ioutil.TempDir(".", "")
+ if err != nil {
+ t.Fatal(err)
+ }
+ config.CommonConfig = config.DefaultConfig()
+ testDB := dbm.NewDB("testdb", "leveldb", dirPath)
+ defer func() {
+ testDB.Close()
+ os.RemoveAll(dirPath)
+ }()
+
+ store := database.NewStore(testDB)
+ dispatcher := event.NewDispatcher()
+ txPool := protocol.NewTxPool(store, dispatcher)
+
+ chain, err := protocol.NewChain(store, txPool, dispatcher)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ accountStore := database.NewAccountStore(testDB)
+ accountManager := account.NewManager(accountStore, chain)
+ hsm, err := pseudohsm.New(dirPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ xpub1, _, err := hsm.XCreate("test_pub1", "password", "en")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ testAccount, err := accountManager.Create([]chainkd.XPub{xpub1.XPub}, 1, "testAccount", signers.BIP0044)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ controlProg, err := accountManager.CreateAddress(testAccount.ID, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ controlProg.KeyIndex = 1
+
+ reg := asset.NewRegistry(testDB, chain)
+ asset := bc.AssetID{V0: 5}
+
+ utxos := []*account.UTXO{}
+ btmUtxo := mockUTXO(controlProg, consensus.BTMAssetID)
+ utxos = append(utxos, btmUtxo)
+ OtherUtxo := mockUTXO(controlProg, &asset)
+ utxos = append(utxos, OtherUtxo)
+
+ _, txData, err := mockTxData(utxos, testAccount)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ tx := types.NewTx(*txData)
+ txStatus := bc.NewTransactionStatus()
+ txStatus.SetStatus(0, false)
+ walletStore := database.NewWalletStore(testDB)
+ w := newMockWallet(walletStore, accountManager, reg, chain, dispatcher, false)
+ go w.MemPoolTxQueryLoop()
+ w.EventDispatcher.Post(protocol.TxMsgEvent{TxMsg: &protocol.TxPoolMsg{TxDesc: &protocol.TxDesc{Tx: tx}, MsgType: protocol.MsgNewTx}})
+ time.Sleep(time.Millisecond * 10)
+ if _, err := w.GetUnconfirmedTxByTxID(tx.ID.String()); err != nil {
+ t.Fatal("dispatch new tx msg error:", err)
+ }
+ w.EventDispatcher.Post(protocol.TxMsgEvent{TxMsg: &protocol.TxPoolMsg{TxDesc: &protocol.TxDesc{Tx: tx}, MsgType: protocol.MsgRemoveTx}})
+ time.Sleep(time.Millisecond * 10)
+ txs, err := w.GetUnconfirmedTxs(testAccount.ID)
+ if err != nil {
+ t.Fatal("get unconfirmed tx error:", err)
+ }
+
+ if len(txs) != 0 {
+ t.Fatal("dispatch remove tx msg error")
+ }
+
+ w.EventDispatcher.Post(protocol.TxMsgEvent{TxMsg: &protocol.TxPoolMsg{TxDesc: &protocol.TxDesc{Tx: tx}, MsgType: 2}})
+}
+
+func mockUTXO(controlProg *account.CtrlProgram, assetID *bc.AssetID) *account.UTXO {
+ utxo := &account.UTXO{}
+ utxo.OutputID = bc.Hash{V0: 1}
+ utxo.SourceID = bc.Hash{V0: 2}
+ utxo.AssetID = *assetID
+ utxo.Amount = 1000000000
+ utxo.SourcePos = 0
+ utxo.ControlProgram = controlProg.ControlProgram
+ utxo.AccountID = controlProg.AccountID
+ utxo.Address = controlProg.Address
+ utxo.ControlProgramIndex = controlProg.KeyIndex
+ return utxo
+}
+
+func mockTxData(utxos []*account.UTXO, testAccount *account.Account) (*txbuilder.Template, *types.TxData, error) {
+ tplBuilder := txbuilder.NewBuilder(time.Now())
+
+ for _, utxo := range utxos {
+ txInput, sigInst, err := account.UtxoToInputs(testAccount.Signer, utxo)
+ if err != nil {
+ return nil, nil, err
+ }
+ tplBuilder.AddInput(txInput, sigInst)
+
+ out := &types.TxOutput{}
+ if utxo.AssetID == *consensus.BTMAssetID {
+ out = types.NewIntraChainOutput(utxo.AssetID, 100, utxo.ControlProgram)
+ } else {
+ out = types.NewIntraChainOutput(utxo.AssetID, utxo.Amount, utxo.ControlProgram)
+ }
+ tplBuilder.AddOutput(out)
+ }
+
+ return tplBuilder.Build()
+}
+
+type mockWallet struct {
+ Wallet *wt.Wallet
+}
+
+func newMockWallet(store wt.WalletStore, account *account.Manager, asset *asset.Registry, chain *protocol.Chain, dispatcher *event.Dispatcher, txIndexFlag bool) *wt.Wallet {
+ wallet := &wt.Wallet{
+ Store: store,
+ AccountMgr: account,
+ AssetReg: asset,
+ Chain: chain,
+ RecoveryMgr: wt.NewRecoveryManager(store, account),
+ EventDispatcher: dispatcher,
+ TxIndexFlag: txIndexFlag,
+ }
+ wallet.TxMsgSub, _ = wallet.EventDispatcher.Subscribe(protocol.TxMsgEvent{})
+ return wallet
+}
+
+func mockSingleBlock(tx *types.Tx) *types.Block {
+ return &types.Block{
+ BlockHeader: types.BlockHeader{
+ Version: 1,
+ Height: 1,
+ },
+ Transactions: []*types.Tx{config.GenesisTx(), tx},
+ }
+}
"github.com/vapor/blockchain/pseudohsm"
"github.com/vapor/blockchain/signers"
"github.com/vapor/crypto/ed25519/chainkd"
+ "github.com/vapor/database"
dbm "github.com/vapor/database/leveldb"
"github.com/vapor/event"
"github.com/vapor/protocol"
return err
}
walletDB := dbm.NewDB("wallet", "leveldb", path.Join(dirPath, "wallet_db"))
- accountManager := account.NewManager(walletDB, chain)
+ walletStore := database.NewWalletStore(walletDB)
+ accountStore := database.NewAccountStore(walletDB)
+ accountManager := account.NewManager(accountStore, chain)
assets := asset.NewRegistry(walletDB, chain)
dispatcher := event.NewDispatcher()
- wallet, err := w.NewWallet(walletDB, accountManager, assets, hsm, chain, dispatcher, false)
+ wallet, err := w.NewWallet(walletStore, accountManager, assets, hsm, chain, dispatcher, false)
if err != nil {
return err
}
import (
"encoding/json"
- "fmt"
log "github.com/sirupsen/logrus"
"github.com/vapor/consensus"
"github.com/vapor/consensus/segwit"
"github.com/vapor/crypto/sha3pool"
- dbm "github.com/vapor/database/leveldb"
"github.com/vapor/protocol/bc"
"github.com/vapor/protocol/bc/types"
)
}
func (w *Wallet) getExternalDefinition(assetID *bc.AssetID) json.RawMessage {
- // definitionByte := w.DB.Get(asset.ExtAssetKey(assetID))
- definitionByte := w.DB.Get(asset.ExtAssetKey(assetID))
- if definitionByte == nil {
- return nil
+ externalAsset, err := w.Store.GetAsset(assetID)
+ if err != nil {
+ log.WithFields(log.Fields{"module": logModule, "err": err, "assetID": assetID.String()}).Info("fail on get asset definition.")
}
-
- definitionMap := make(map[string]interface{})
- if err := json.Unmarshal(definitionByte, &definitionMap); err != nil {
+ if externalAsset == nil {
return nil
}
- alias := assetID.String()
- externalAsset := &asset.Asset{
- AssetID: *assetID,
- Alias: &alias,
- DefinitionMap: definitionMap,
- RawDefinitionByte: definitionByte,
+ if err := w.AssetReg.SaveAsset(externalAsset, *externalAsset.Alias); err != nil {
+ log.WithFields(log.Fields{"module": logModule, "err": err, "assetAlias": *externalAsset.Alias}).Info("fail on save external asset to internal asset DB")
}
-
- if err := w.AssetReg.SaveAsset(externalAsset, alias); err != nil {
- log.WithFields(log.Fields{"module": logModule, "err": err, "assetID": alias}).Warning("fail on save external asset to internal asset DB")
- }
- return definitionByte
+ return json.RawMessage(externalAsset.RawDefinitionByte)
}
func (w *Wallet) getAliasDefinition(assetID bc.AssetID) (string, json.RawMessage) {
}
// annotateTxs adds account data to transactions
-func annotateTxsAccount(txs []*query.AnnotatedTx, walletDB dbm.DB) {
+func (w *Wallet) annotateTxsAccount(txs []*query.AnnotatedTx) {
for i, tx := range txs {
for j, input := range tx.Inputs {
//issue asset tx input SpentOutputID is nil
if input.SpentOutputID == nil {
continue
}
- localAccount, err := getAccountFromACP(input.ControlProgram, walletDB)
+ localAccount, err := w.getAccountFromACP(input.ControlProgram)
if localAccount == nil || err != nil {
continue
}
txs[i].Inputs[j].AccountID = localAccount.ID
}
for j, output := range tx.Outputs {
- localAccount, err := getAccountFromACP(output.ControlProgram, walletDB)
+ localAccount, err := w.getAccountFromACP(output.ControlProgram)
if localAccount == nil || err != nil {
continue
}
}
}
-func getAccountFromACP(program []byte, walletDB dbm.DB) (*account.Account, error) {
- var hash common.Hash
- accountCP := account.CtrlProgram{}
- localAccount := account.Account{}
-
+func (w *Wallet) getAccountFromACP(program []byte) (*account.Account, error) {
+ var hash [32]byte
sha3pool.Sum256(hash[:], program)
-
- rawProgram := walletDB.Get(account.ContractKey(hash))
- if rawProgram == nil {
- return nil, fmt.Errorf("failed get account control program:%x ", hash)
- }
-
- if err := json.Unmarshal(rawProgram, &accountCP); err != nil {
+ accountCP, err := w.AccountMgr.GetControlProgram(bc.NewHash(hash))
+ if err != nil {
return nil, err
}
- accountValue := walletDB.Get(account.Key(accountCP.AccountID))
- if accountValue == nil {
- return nil, fmt.Errorf("failed get account:%s ", accountCP.AccountID)
- }
-
- if err := json.Unmarshal(accountValue, &localAccount); err != nil {
+ account, err := w.AccountMgr.FindByID(accountCP.AccountID)
+ if err != nil {
return nil, err
}
- return &localAccount, nil
+ return account, nil
}
var emptyJSONObject = json.RawMessage(`{}`)
if orig.InputType() != types.CoinbaseInputType {
in.AssetID = orig.AssetID()
in.Amount = orig.Amount()
+ } else {
+ in.AssetID = *consensus.BTMAssetID
}
id := tx.Tx.InputIDs[i]
import (
"encoding/binary"
"encoding/hex"
- "encoding/json"
"fmt"
"sort"
log "github.com/sirupsen/logrus"
"github.com/vapor/account"
- "github.com/vapor/asset"
"github.com/vapor/blockchain/query"
"github.com/vapor/consensus"
"github.com/vapor/crypto/sha3pool"
- dbm "github.com/vapor/database/leveldb"
chainjson "github.com/vapor/encoding/json"
- "github.com/vapor/errors"
"github.com/vapor/protocol/bc"
"github.com/vapor/protocol/bc/types"
)
-const (
- //TxPrefix is wallet database transactions prefix
- TxPrefix = "TXS:"
- //TxIndexPrefix is wallet database tx index prefix
- TxIndexPrefix = "TID:"
- //TxIndexPrefix is wallet database global tx index prefix
- GlobalTxIndexPrefix = "GTID:"
-)
-
-var ErrAccntTxIDNotFound = errors.New("account TXID not found")
-
-func formatKey(blockHeight uint64, position uint32) string {
- return fmt.Sprintf("%016x%08x", blockHeight, position)
-}
-
-func calcAnnotatedKey(formatKey string) []byte {
- return []byte(TxPrefix + formatKey)
-}
-
-func calcDeleteKey(blockHeight uint64) []byte {
- return []byte(fmt.Sprintf("%s%016x", TxPrefix, blockHeight))
-}
-
-func calcTxIndexKey(txID string) []byte {
- return []byte(TxIndexPrefix + txID)
-}
-
-func calcGlobalTxIndexKey(txID string) []byte {
- return []byte(GlobalTxIndexPrefix + txID)
-}
-
-func calcGlobalTxIndex(blockHash *bc.Hash, position uint64) []byte {
- txIdx := make([]byte, 40)
- copy(txIdx[:32], blockHash.Bytes())
- binary.BigEndian.PutUint64(txIdx[32:], position)
- return txIdx
-}
-
func parseGlobalTxIdx(globalTxIdx []byte) (*bc.Hash, uint64) {
var hashBytes [32]byte
copy(hashBytes[:], globalTxIdx[:32])
return &hash, position
}
-// deleteTransaction delete transactions when orphan block rollback
-func (w *Wallet) deleteTransactions(batch dbm.Batch, height uint64) {
- tmpTx := query.AnnotatedTx{}
- txIter := w.DB.IteratorPrefix(calcDeleteKey(height))
- defer txIter.Release()
-
- for txIter.Next() {
- if err := json.Unmarshal(txIter.Value(), &tmpTx); err == nil {
- batch.Delete(calcTxIndexKey(tmpTx.ID.String()))
- }
- batch.Delete(txIter.Key())
- }
-}
-
// saveExternalAssetDefinition save external and local assets definition,
// when query ,query local first and if have no then query external
// details see getAliasDefinition
-func saveExternalAssetDefinition(b *types.Block, walletDB dbm.DB) {
- storeBatch := walletDB.NewBatch()
- defer storeBatch.Write()
+func saveExternalAssetDefinition(b *types.Block, store WalletStore) error {
+ newStore := store.InitBatch()
for _, tx := range b.Transactions {
for _, orig := range tx.Inputs {
if cci, ok := orig.TypedInput.(*types.CrossChainInput); ok {
assetID := cci.AssetId
- if assetExist := walletDB.Get(asset.ExtAssetKey(assetID)); assetExist == nil {
- storeBatch.Set(asset.ExtAssetKey(assetID), cci.AssetDefinition)
+ if _, err := newStore.GetAsset(assetID); err == nil {
+ continue
+ } else if err != ErrGetAsset {
+ return err
}
+
+ newStore.SetAssetDefinition(assetID, cci.AssetDefinition)
}
}
}
+ if err := newStore.CommitBatch(); err != nil {
+ return err
+ }
+
+ return nil
}
// Summary is the struct of transaction's input and output summary
}
// indexTransactions saves all annotated transactions to the database.
-func (w *Wallet) indexTransactions(batch dbm.Batch, b *types.Block, txStatus *bc.TransactionStatus) error {
- annotatedTxs := w.filterAccountTxs(b, txStatus)
- saveExternalAssetDefinition(b, w.DB)
- annotateTxsAccount(annotatedTxs, w.DB)
-
+func (w *Wallet) indexTransactions(b *types.Block, txStatus *bc.TransactionStatus, annotatedTxs []*query.AnnotatedTx, store WalletStore) error {
for _, tx := range annotatedTxs {
- rawTx, err := json.Marshal(tx)
- if err != nil {
- log.WithFields(log.Fields{"module": logModule, "err": err}).Error("inserting annotated_txs to db")
+ if err := w.Store.SetTransaction(b.Height, tx); err != nil {
return err
}
- batch.Set(calcAnnotatedKey(formatKey(b.Height, uint32(tx.Position))), rawTx)
- batch.Set(calcTxIndexKey(tx.ID.String()), []byte(formatKey(b.Height, uint32(tx.Position))))
-
- // delete unconfirmed transaction
- batch.Delete(calcUnconfirmedTxKey(tx.ID.String()))
+ store.DeleteUnconfirmedTransaction(tx.ID.String())
}
if !w.TxIndexFlag {
for position, globalTx := range b.Transactions {
blockHash := b.BlockHeader.Hash()
- batch.Set(calcGlobalTxIndexKey(globalTx.ID.String()), calcGlobalTxIndex(&blockHash, uint64(position)))
+ store.SetGlobalTransactionIndex(globalTx.ID.String(), &blockHash, uint64(position))
}
return nil
for _, v := range tx.Outputs {
var hash [32]byte
sha3pool.Sum256(hash[:], v.ControlProgram())
-
- if bytes := w.DB.Get(account.ContractKey(hash)); bytes != nil {
+ if _, err := w.AccountMgr.GetControlProgram(bc.NewHash(hash)); err == nil {
annotatedTxs = append(annotatedTxs, w.buildAnnotatedTransaction(tx, b, statusFail, pos))
continue transactionLoop
+ } else {
+ log.WithFields(log.Fields{"module": logModule, "err": err, "hash": hex.EncodeToString(hash[:])}).Info("filterAccountTxs fail.")
}
}
for _, v := range tx.Inputs {
outid, err := v.SpentOutputID()
if err != nil {
+ log.WithFields(log.Fields{"module": logModule, "err": err, "outputID": hex.EncodeToString(outid.Bytes())}).Info("filterAccountTxs fail.")
continue
}
- if bytes := w.DB.Get(account.StandardUTXOKey(outid)); bytes != nil {
+ if _, err = w.Store.GetStandardUTXO(outid); err == nil {
annotatedTxs = append(annotatedTxs, w.buildAnnotatedTransaction(tx, b, statusFail, pos))
continue transactionLoop
+ } else {
+ log.WithFields(log.Fields{"module": logModule, "err": err, "outputID": hex.EncodeToString(outid.Bytes())}).Info("filterAccountTxs fail.")
}
}
}
}
func (w *Wallet) getAccountTxByTxID(txID string) (*query.AnnotatedTx, error) {
- annotatedTx := &query.AnnotatedTx{}
- formatKey := w.DB.Get(calcTxIndexKey(txID))
- if formatKey == nil {
- return nil, ErrAccntTxIDNotFound
- }
-
- txInfo := w.DB.Get(calcAnnotatedKey(string(formatKey)))
- if err := json.Unmarshal(txInfo, annotatedTx); err != nil {
+ annotatedTx, err := w.Store.GetTransaction(txID)
+ if err != nil {
return nil, err
}
}
func (w *Wallet) getGlobalTxByTxID(txID string) (*query.AnnotatedTx, error) {
- globalTxIdx := w.DB.Get(calcGlobalTxIndexKey(txID))
+ globalTxIdx := w.Store.GetGlobalTransactionIndex(txID)
if globalTxIdx == nil {
return nil, fmt.Errorf("No transaction(tx_id=%s) ", txID)
}
blockHash, pos := parseGlobalTxIdx(globalTxIdx)
- block, err := w.chain.GetBlockByHash(blockHash)
+ block, err := w.Chain.GetBlockByHash(blockHash)
if err != nil {
return nil, err
}
- txStatus, err := w.chain.GetTransactionStatus(blockHash)
+ txStatus, err := w.Chain.GetTransactionStatus(blockHash)
if err != nil {
return nil, err
}
// GetTransactions get all walletDB transactions or unconfirmed transactions, and filter transactions by accountID and StartTxID optional
func (w *Wallet) GetTransactions(accountID string, StartTxID string, count uint, unconfirmed bool) ([]*query.AnnotatedTx, error) {
annotatedTxs := []*query.AnnotatedTx{}
- var startKey []byte
- preFix := TxPrefix
-
- if StartTxID != "" {
- if unconfirmed {
- startKey = calcUnconfirmedTxKey(StartTxID)
- } else {
- formatKey := w.DB.Get(calcTxIndexKey(StartTxID))
- if formatKey == nil {
- return nil, ErrAccntTxIDNotFound
- }
- startKey = calcAnnotatedKey(string(formatKey))
- }
- }
-
- if unconfirmed {
- preFix = UnconfirmedTxPrefix
+ annotatedTxs, err := w.Store.ListTransactions(accountID, StartTxID, count, unconfirmed)
+ if err != nil {
+ return nil, err
}
- itr := w.DB.IteratorPrefixWithStart([]byte(preFix), startKey, true)
- defer itr.Release()
-
- for txNum := count; itr.Next() && txNum > 0; {
- annotatedTx := &query.AnnotatedTx{}
- if err := json.Unmarshal(itr.Value(), &annotatedTx); err != nil {
- return nil, err
- }
-
+ newAnnotatedTxs := []*query.AnnotatedTx{}
+ for _, annotatedTx := range annotatedTxs {
if accountID == "" || findTransactionsByAccount(annotatedTx, accountID) {
annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
- annotatedTxs = append([]*query.AnnotatedTx{annotatedTx}, annotatedTxs...)
- txNum--
+ newAnnotatedTxs = append([]*query.AnnotatedTx{annotatedTx}, newAnnotatedTxs...)
}
}
sort.Sort(SortByHeight(annotatedTxs))
}
- return annotatedTxs, nil
+ return newAnnotatedTxs, nil
}
// GetAccountBalances return all account balances
package wallet
import (
- "encoding/json"
"fmt"
"reflect"
"sync"
"github.com/vapor/blockchain/signers"
"github.com/vapor/crypto/ed25519/chainkd"
"github.com/vapor/crypto/sha3pool"
- dbm "github.com/vapor/database/leveldb"
"github.com/vapor/errors"
"github.com/vapor/protocol/bc"
"github.com/vapor/protocol/bc/types"
addrRecoveryWindow = uint64(128)
)
-//recoveryKey key for db store recovery info.
var (
- recoveryKey = []byte("RecoveryInfo")
-
// ErrRecoveryBusy another recovery in progress, can not get recovery manager lock
ErrRecoveryBusy = errors.New("another recovery in progress")
// ErrInvalidAcctID can not find account by account id
- ErrInvalidAcctID = errors.New("invalid account id")
+ ErrInvalidAcctID = errors.New("invalid account id")
+ ErrGetRecoveryStatus = errors.New("failed to get recovery status.")
+ ErrRecoveryStatus = errors.New("recovery status is nil.")
)
// branchRecoveryState maintains the required state in-order to properly
}
}
-// recoveryState used to record the status of a recovery process.
-type recoveryState struct {
+// RecoveryState used to record the status of a recovery process.
+type RecoveryState struct {
// XPubs recovery account xPubs
XPubs []chainkd.XPub
AccountsStatus map[string]*addressRecoveryState
}
-func newRecoveryState() *recoveryState {
- return &recoveryState{
+func newRecoveryState() *RecoveryState {
+ return &RecoveryState{
AccountsStatus: make(map[string]*addressRecoveryState),
StartTime: time.Now(),
}
// stateForScope returns a ScopeRecoveryState for the provided key scope. If one
// does not already exist, a new one will be generated with the RecoveryState's
// recoveryWindow.
-func (rs *recoveryState) stateForScope(account *account.Account) {
+func (rs *RecoveryState) stateForScope(account *account.Account) {
// If the account recovery state already exists, return it.
if _, ok := rs.AccountsStatus[account.ID]; ok {
return
type recoveryManager struct {
mu sync.Mutex
- db dbm.DB
+ store WalletStore
accountMgr *account.Manager
locked int32
// state encapsulates and allocates the necessary recovery state for all
// key scopes and subsidiary derivation paths.
- state *recoveryState
+ state *RecoveryState
//addresses all addresses derivation lookahead used when
// attempting to recover the set of used addresses.
addresses map[bc.Hash]*account.CtrlProgram
}
-// newRecoveryManager create recovery manger.
-func newRecoveryManager(db dbm.DB, accountMgr *account.Manager) *recoveryManager {
+// NewRecoveryManager create recovery manger.
+func NewRecoveryManager(store WalletStore, accountMgr *account.Manager) *recoveryManager {
return &recoveryManager{
- db: db,
+ store: store,
accountMgr: accountMgr,
addresses: make(map[bc.Hash]*account.CtrlProgram),
state: newRecoveryState(),
}
func (m *recoveryManager) commitStatusInfo() error {
- rawStatus, err := json.Marshal(m.state)
- if err != nil {
- return err
- }
-
- m.db.Set(recoveryKey, rawStatus)
- return nil
+ return m.store.SetRecoveryStatus(m.state)
}
func genAcctAlias(xPubs []chainkd.XPub, index uint64) string {
}
func (m *recoveryManager) finished() {
- m.db.Delete(recoveryKey)
+ m.store.DeleteRecoveryStatus()
m.started = false
m.addresses = make(map[bc.Hash]*account.CtrlProgram)
m.state = newRecoveryState()
m.mu.Lock()
defer m.mu.Unlock()
- rawStatus := m.db.Get(recoveryKey)
- if rawStatus == nil {
+ if m.state == nil {
+ return ErrRecoveryStatus
+ }
+ status, err := m.store.GetRecoveryStatus()
+ if err == ErrGetRecoveryStatus {
return nil
}
-
- if err := json.Unmarshal(rawStatus, m.state); err != nil {
+ if err != nil {
return err
}
+ m.state = status
if m.state.XPubs != nil && !m.tryStartXPubsRec() {
return ErrRecoveryBusy
"time"
"github.com/vapor/account"
+ acc "github.com/vapor/account"
"github.com/vapor/blockchain/pseudohsm"
"github.com/vapor/blockchain/signers"
"github.com/vapor/blockchain/txbuilder"
defer os.RemoveAll(dirPath)
testDB := dbm.NewDB("testdb", "leveldb", dirPath)
+ walletStore := NewMockWalletStore(testDB)
hsm, err := pseudohsm.New(dirPath)
if err != nil {
t.Fatal(err)
t.Fatal(err)
}
- acctMgr := account.NewManager(testDB, nil)
- recoveryMgr := newRecoveryManager(testDB, acctMgr)
+ acctStore := NewMockAccountStore(testDB)
+ acctMgr := account.NewManager(acctStore, nil)
+ recoveryMgr := NewRecoveryManager(walletStore, acctMgr)
recoveryMgr.state = newRecoveryState()
recoveryMgr.state.XPubs = []chainkd.XPub{xpub.XPub}
recoveryMgr.state.XPubsStatus = newBranchRecoveryState(acctRecoveryWindow)
defer os.RemoveAll(dirPath)
testDB := dbm.NewDB("testdb", "leveldb", dirPath)
+ walletStore := NewMockWalletStore(testDB)
hsm, err := pseudohsm.New(dirPath)
if err != nil {
t.Fatal(err)
t.Fatal(err)
}
- acctMgr := account.NewManager(testDB, nil)
- recoveryMgr := newRecoveryManager(testDB, acctMgr)
+ acctStore := NewMockAccountStore(testDB)
+ acctMgr := account.NewManager(acctStore, nil)
+ recoveryMgr := NewRecoveryManager(walletStore, acctMgr)
acc1 := &account.Account{ID: "testA", Alias: "test1", Signer: &signers.Signer{XPubs: []chainkd.XPub{xpub.XPub}, KeyIndex: 1, DeriveRule: signers.BIP0044}}
acc2 := &account.Account{ID: "testB", Alias: "test2"}
acc3 := &account.Account{ID: "testC", Alias: "test3", Signer: &signers.Signer{XPubs: []chainkd.XPub{xpub.XPub}, KeyIndex: 2, DeriveRule: 3}}
testDB := dbm.NewDB("testdb", "leveldb", dirPath)
recoveryDB := dbm.NewDB("recdb", "leveldb", dirPath)
+ recoveryStore := NewMockWalletStore(recoveryDB)
hsm, err := pseudohsm.New(dirPath)
if err != nil {
t.Fatal(err)
t.Fatal(err)
}
- acctMgr := account.NewManager(testDB, nil)
+ acctStore := NewMockAccountStore(testDB)
+ acctMgr := account.NewManager(acctStore, nil)
txs, err := MockTxsP2PKH(acctMgr, xpub.XPub, false)
- recAcctMgr := account.NewManager(recoveryDB, nil)
- recoveryMgr := newRecoveryManager(recoveryDB, recAcctMgr)
+ recActStore := NewMockAccountStore(recoveryDB)
+ recAcctMgr := account.NewManager(recActStore, nil)
+ recoveryMgr := NewRecoveryManager(recoveryStore, recAcctMgr)
cases := []struct {
xPubs []chainkd.XPub
for _, acct := range Accounts {
tmp, err := recAcctMgr.GetAccountByXPubsIndex(acct.XPubs, acct.KeyIndex)
- if err != nil {
+ if err != nil && err != acc.ErrFindAccount {
t.Fatal("recovery from XPubs err:", err)
}
testDB := dbm.NewDB("testdb", "leveldb", dirPath)
recoveryDB := dbm.NewDB("recdb", "leveldb", dirPath)
+ recoveryStore := NewMockWalletStore(recoveryDB)
hsm, err := pseudohsm.New(dirPath)
if err != nil {
t.Fatal(err)
t.Fatal(err)
}
- acctMgr := account.NewManager(testDB, nil)
+ acctStore := NewMockAccountStore(testDB)
+ acctMgr := account.NewManager(acctStore, nil)
txs, err := MockTxsP2PKH(acctMgr, xpub.XPub, true)
if err != nil {
t.Fatal("recovery by rescan account err:", err)
t.Fatal("recovery by rescan account err:", err)
}
- recAcctMgr := account.NewManager(recoveryDB, nil)
+ recActStore := NewMockAccountStore(recoveryDB)
+ recAcctMgr := account.NewManager(recActStore, nil)
for _, acct := range allAccounts {
if err := recAcctMgr.SaveAccount(acct); err != nil {
t.Fatal("recovery by rescan account err:", err)
}
}
- recoveryMgr := newRecoveryManager(recoveryDB, recAcctMgr)
+ recoveryMgr := NewRecoveryManager(recoveryStore, recAcctMgr)
acct := &account.Account{ID: "testA", Alias: "test1", Signer: &signers.Signer{XPubs: []chainkd.XPub{xpub.XPub}, KeyIndex: 1, DeriveRule: 3}}
for _, acct := range accounts {
tmp, err := recAcctMgr.GetAccountByXPubsIndex(acct.XPubs, acct.KeyIndex)
- if err != nil {
+ if err != nil && err != acc.ErrFindAccount {
t.Fatal("recovery from XPubs err:", err)
}
defer os.RemoveAll(dirPath)
testDB := dbm.NewDB("testdb", "leveldb", dirPath)
+ testStore := NewMockWalletStore(testDB)
hsm, err := pseudohsm.New(dirPath)
if err != nil {
t.Fatal(err)
t.Fatal(err)
}
- acctMgr := account.NewManager(testDB, nil)
- recoveryMgr := newRecoveryManager(testDB, acctMgr)
+ acctStore := NewMockAccountStore(testDB)
+ acctMgr := account.NewManager(acctStore, nil)
+ recoveryMgr := NewRecoveryManager(testStore, acctMgr)
acc1 := &account.Account{ID: "testA", Alias: "test1", Signer: &signers.Signer{XPubs: []chainkd.XPub{xpub1.XPub}, KeyIndex: 1, DeriveRule: signers.BIP0044}}
acc2 := &account.Account{ID: "testB", Alias: "test2", Signer: &signers.Signer{XPubs: []chainkd.XPub{xpub2.XPub}, KeyIndex: 1, DeriveRule: signers.BIP0032}}
acc3 := &account.Account{ID: "testC", Alias: "test3", Signer: &signers.Signer{XPubs: []chainkd.XPub{xpub2.XPub}, KeyIndex: 2, DeriveRule: signers.BIP0044}}
defer os.RemoveAll(dirPath)
testDB := dbm.NewDB("testdb", "leveldb", "temp")
+ testStore := NewMockWalletStore(testDB)
defer os.RemoveAll("temp")
hsm, err := pseudohsm.New(dirPath)
t.Fatal(err)
}
- acctMgr := account.NewManager(testDB, nil)
- recoveryMgr := newRecoveryManager(testDB, acctMgr)
+ acctStore := NewMockAccountStore(testDB)
+ acctMgr := account.NewManager(acctStore, nil)
+ recoveryMgr := NewRecoveryManager(testStore, acctMgr)
// StatusInit init recovery status manager.
recoveryMgr.state = newRecoveryState()
recoveryMgr.state.XPubs = []chainkd.XPub{xpub.XPub}
recoveryMgr.commitStatusInfo()
- recoveryMgrRestore := newRecoveryManager(testDB, acctMgr)
+ recoveryMgrRestore := NewRecoveryManager(testStore, acctMgr)
if err := recoveryMgrRestore.LoadStatusInfo(); err != nil {
t.Fatal("TestLoadStatusInfo err:", err)
}
defer os.RemoveAll(dirPath)
testDB := dbm.NewDB("testdb", "leveldb", "temp")
+ testStore := NewMockWalletStore(testDB)
defer os.RemoveAll("temp")
- acctMgr := account.NewManager(testDB, nil)
- recoveryMgr := newRecoveryManager(testDB, acctMgr)
+ acctStore := NewMockAccountStore(testDB)
+ acctMgr := account.NewManager(acctStore, nil)
+ recoveryMgr := NewRecoveryManager(testStore, acctMgr)
if !recoveryMgr.tryStartXPubsRec() {
t.Fatal("recovery manager try lock test err")
}
}
}
-func bip44ContractIndexKey(accountID string, change bool) []byte {
- contractIndexPrefix := []byte("ContractIndex")
- key := append(contractIndexPrefix, accountID...)
- if change {
- return append(key, []byte{1}...)
- }
- return append(key, []byte{0}...)
-}
-
func TestContractIndexResidue(t *testing.T) {
dirPath, err := ioutil.TempDir(".", "")
if err != nil {
defer os.RemoveAll(dirPath)
testDB := dbm.NewDB("testdb", "leveldb", dirPath)
+ testStore := NewMockWalletStore(testDB)
hsm, err := pseudohsm.New(dirPath)
if err != nil {
t.Fatal(err)
}
contractIndexResidue := uint64(5)
- acctMgr := account.NewManager(testDB, nil)
- recoveryMgr := newRecoveryManager(testDB, acctMgr)
+ acctStore := NewMockAccountStore(testDB)
+ acctMgr := account.NewManager(acctStore, nil)
+ recoveryMgr := NewRecoveryManager(testStore, acctMgr)
acct := &account.Account{ID: "testA", Alias: "test1", Signer: &signers.Signer{XPubs: []chainkd.XPub{xpub1.XPub}, KeyIndex: 1, DeriveRule: signers.BIP0044}}
cp1 := &account.CtrlProgram{AccountID: acct.ID, Address: "address1", KeyIndex: 10, Change: false}
setContractIndexKey := func(acctMgr *account.Manager, accountID string, change bool) {
- testDB.Set(bip44ContractIndexKey(accountID, change), common.Unit64ToBytes(contractIndexResidue))
+ testDB.Set(Bip44ContractIndexKey(accountID, change), common.Unit64ToBytes(contractIndexResidue))
}
delAccount := func(acctMgr *account.Manager, accountID string, change bool) {
--- /dev/null
+package wallet
+
+import (
+ acc "github.com/vapor/account"
+ "github.com/vapor/asset"
+ "github.com/vapor/blockchain/query"
+ "github.com/vapor/protocol/bc"
+)
+
+// WalletStore interface contains wallet storage functions.
+type WalletStore interface {
+ InitBatch() WalletStore
+ CommitBatch() error
+ DeleteContractUTXO(bc.Hash)
+ DeleteRecoveryStatus()
+ DeleteTransactions(uint64)
+ DeleteUnconfirmedTransaction(string)
+ DeleteWalletTransactions()
+ DeleteWalletUTXOs()
+ GetAsset(*bc.AssetID) (*asset.Asset, error)
+ GetGlobalTransactionIndex(string) []byte
+ GetStandardUTXO(bc.Hash) (*acc.UTXO, error)
+ GetTransaction(string) (*query.AnnotatedTx, error)
+ GetUnconfirmedTransaction(string) (*query.AnnotatedTx, error)
+ GetRecoveryStatus() (*RecoveryState, error)
+ GetWalletInfo() (*StatusInfo, error)
+ ListAccountUTXOs(string, bool) ([]*acc.UTXO, error)
+ ListTransactions(string, string, uint, bool) ([]*query.AnnotatedTx, error)
+ ListUnconfirmedTransactions() ([]*query.AnnotatedTx, error)
+ SetAssetDefinition(*bc.AssetID, []byte)
+ SetContractUTXO(bc.Hash, *acc.UTXO) error
+ SetGlobalTransactionIndex(string, *bc.Hash, uint64)
+ SetRecoveryStatus(*RecoveryState) error
+ SetTransaction(uint64, *query.AnnotatedTx) error
+ SetUnconfirmedTransaction(string, *query.AnnotatedTx) error
+ SetWalletInfo(*StatusInfo) error
+}
package wallet
import (
- "encoding/json"
"fmt"
"sort"
"time"
+ "github.com/vapor/protocol/bc"
+
log "github.com/sirupsen/logrus"
- "github.com/vapor/account"
"github.com/vapor/blockchain/query"
"github.com/vapor/crypto/sha3pool"
"github.com/vapor/protocol"
)
const (
- //UnconfirmedTxPrefix is txpool unconfirmed transactions prefix
- UnconfirmedTxPrefix = "UTXS:"
UnconfirmedTxCheckPeriod = 30 * time.Minute
MaxUnconfirmedTxDuration = 24 * time.Hour
)
-func calcUnconfirmedTxKey(formatKey string) []byte {
- return []byte(UnconfirmedTxPrefix + formatKey)
-}
-
// SortByTimestamp implements sort.Interface for AnnotatedTx slices
type SortByTimestamp []*query.AnnotatedTx
// GetUnconfirmedTxs get account unconfirmed transactions, filter transactions by accountID when accountID is not empty
func (w *Wallet) GetUnconfirmedTxs(accountID string) ([]*query.AnnotatedTx, error) {
annotatedTxs := []*query.AnnotatedTx{}
- txIter := w.DB.IteratorPrefix([]byte(UnconfirmedTxPrefix))
- defer txIter.Release()
-
- for txIter.Next() {
- annotatedTx := &query.AnnotatedTx{}
- if err := json.Unmarshal(txIter.Value(), &annotatedTx); err != nil {
- return nil, err
- }
+ annotatedTxs, err := w.Store.ListUnconfirmedTransactions()
+ if err != nil {
+ return nil, err
+ }
+ newAnnotatedTxs := []*query.AnnotatedTx{}
+ for _, annotatedTx := range annotatedTxs {
if accountID == "" || findTransactionsByAccount(annotatedTx, accountID) {
annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
- annotatedTxs = append([]*query.AnnotatedTx{annotatedTx}, annotatedTxs...)
+ newAnnotatedTxs = append([]*query.AnnotatedTx{annotatedTx}, newAnnotatedTxs...)
}
}
- sort.Sort(SortByTimestamp(annotatedTxs))
- return annotatedTxs, nil
+ sort.Sort(SortByTimestamp(newAnnotatedTxs))
+ return newAnnotatedTxs, nil
}
// GetUnconfirmedTxByTxID get unconfirmed transaction by txID
func (w *Wallet) GetUnconfirmedTxByTxID(txID string) (*query.AnnotatedTx, error) {
- annotatedTx := &query.AnnotatedTx{}
- txInfo := w.DB.Get(calcUnconfirmedTxKey(txID))
- if txInfo == nil {
- return nil, fmt.Errorf("No transaction(tx_id=%s) from txpool", txID)
- }
-
- if err := json.Unmarshal(txInfo, annotatedTx); err != nil {
+ annotatedTx, err := w.Store.GetUnconfirmedTransaction(txID)
+ if err != nil {
return nil, err
}
+ if annotatedTx == nil {
+ return nil, fmt.Errorf("No transaction(tx_id=%s) from txpool", txID)
+ }
annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
return annotatedTx, nil
}
if !w.checkRelatedTransaction(txD.Tx) {
return
}
- w.DB.Delete(calcUnconfirmedTxKey(txD.Tx.ID.String()))
+
+ w.Store.DeleteUnconfirmedTransaction(txD.Tx.ID.String())
w.AccountMgr.RemoveUnconfirmedUtxo(txD.Tx.ResultIds)
}
for _, v := range tx.Outputs {
var hash [32]byte
sha3pool.Sum256(hash[:], v.ControlProgram())
- if bytes := w.DB.Get(account.ContractKey(hash)); bytes != nil {
+ cp, err := w.AccountMgr.GetControlProgram(bc.NewHash(hash))
+ if err != nil {
+ log.WithFields(log.Fields{"module": logModule, "err": err, "hash": string(hash[:])}).Error("checkRelatedTransaction fail.")
+ continue
+ }
+ if cp != nil {
return true
}
}
if err != nil {
continue
}
- if bytes := w.DB.Get(account.StandardUTXOKey(outid)); bytes != nil {
+ utxo, err := w.Store.GetStandardUTXO(outid)
+ if err != nil {
+ log.WithFields(log.Fields{"module": logModule, "err": err, "outputID": outid.String()}).Error("checkRelatedTransaction fail.")
+ continue
+ }
+ if utxo != nil {
return true
}
}
annotatedTx := w.buildAnnotatedUnconfirmedTx(tx)
annotatedTxs := []*query.AnnotatedTx{}
annotatedTxs = append(annotatedTxs, annotatedTx)
- annotateTxsAccount(annotatedTxs, w.DB)
+ w.annotateTxsAccount(annotatedTxs)
- rawTx, err := json.Marshal(annotatedTxs[0])
- if err != nil {
+ if err := w.Store.SetUnconfirmedTransaction(tx.ID.String(), annotatedTxs[0]); err != nil {
return err
}
-
- w.DB.Set(calcUnconfirmedTxKey(tx.ID.String()), rawTx)
return nil
}
if err != nil {
return err
}
+
for _, tx := range AnnotatedTx {
if time.Now().After(time.Unix(int64(tx.Timestamp), 0).Add(MaxUnconfirmedTxDuration)) {
- w.DB.Delete(calcUnconfirmedTxKey(tx.ID.String()))
+ w.Store.DeleteUnconfirmedTransaction(tx.ID.String())
}
}
return nil
log.WithFields(log.Fields{"module": logModule, "err": err}).Error("wallet fail on delUnconfirmedTx")
return
}
+
ticker := time.NewTicker(UnconfirmedTxCheckPeriod)
defer ticker.Stop()
+
for {
<-ticker.C
if err := w.delExpiredTxs(); err != nil {
defer os.RemoveAll(dirPath)
testDB := dbm.NewDB("testdb", "leveldb", "temp")
+ testStore := NewMockWalletStore(testDB)
defer os.RemoveAll("temp")
- accountManager := account.NewManager(testDB, nil)
+ accountStore := NewMockAccountStore(testDB)
+ accountManager := account.NewManager(accountStore, nil)
hsm, err := pseudohsm.New(dirPath)
if err != nil {
t.Fatal(err)
asset := bc.AssetID{V0: 5}
dispatcher := event.NewDispatcher()
- w := mockWallet(testDB, accountManager, reg, nil, dispatcher, false)
+ w := mockWallet(testStore, accountManager, reg, nil, dispatcher, false)
utxos := []*account.UTXO{}
btmUtxo := mockUTXO(controlProg, consensus.BTMAssetID)
utxos = append(utxos, btmUtxo)
annotatedTxs = append(annotatedTxs, annotatedTx)
}
- annotateTxsAccount(annotatedTxs, w.DB)
+ w.annotateTxsAccount(annotatedTxs)
annotateTxsAsset(w, annotatedTxs)
return annotatedTxs
package wallet
import (
- "encoding/json"
-
log "github.com/sirupsen/logrus"
"github.com/vapor/account"
"github.com/vapor/consensus"
"github.com/vapor/consensus/segwit"
"github.com/vapor/crypto/sha3pool"
- dbm "github.com/vapor/database/leveldb"
- "github.com/vapor/errors"
"github.com/vapor/protocol/bc"
"github.com/vapor/protocol/bc/types"
)
// GetAccountUtxos return all account unspent outputs
func (w *Wallet) GetAccountUtxos(accountID string, id string, unconfirmed, isSmartContract bool, vote bool) []*account.UTXO {
- prefix := account.UTXOPreFix
- if isSmartContract {
- prefix = account.SUTXOPrefix
- }
-
accountUtxos := []*account.UTXO{}
if unconfirmed {
accountUtxos = w.AccountMgr.ListUnconfirmedUtxo(accountID, isSmartContract)
}
- accountUtxoIter := w.DB.IteratorPrefix([]byte(prefix + id))
- defer accountUtxoIter.Release()
-
- for accountUtxoIter.Next() {
- accountUtxo := &account.UTXO{}
- if err := json.Unmarshal(accountUtxoIter.Value(), accountUtxo); err != nil {
- log.WithFields(log.Fields{"module": logModule, "err": err}).Warn("GetAccountUtxos fail on unmarshal utxo")
- continue
- }
+ confirmedUTXOs, err := w.Store.ListAccountUTXOs(id, isSmartContract)
+ if err != nil {
+ log.WithFields(log.Fields{"module": logModule, "err": err}).Error("GetAccountUtxos fail.")
+ }
+ accountUtxos = append(accountUtxos, confirmedUTXOs...)
+ newAccountUtxos := []*account.UTXO{}
+ for _, accountUtxo := range accountUtxos {
if vote && accountUtxo.Vote == nil {
continue
}
-
if accountID == accountUtxo.AccountID || accountID == "" {
- accountUtxos = append(accountUtxos, accountUtxo)
+ newAccountUtxos = append(newAccountUtxos, accountUtxo)
}
}
- return accountUtxos
+ return newAccountUtxos
}
-func (w *Wallet) attachUtxos(batch dbm.Batch, b *types.Block, txStatus *bc.TransactionStatus) {
+func (w *Wallet) attachUtxos(b *types.Block, txStatus *bc.TransactionStatus, store WalletStore) {
for txIndex, tx := range b.Transactions {
statusFail, err := txStatus.GetStatus(txIndex)
if err != nil {
inputUtxos := txInToUtxos(tx, statusFail)
for _, inputUtxo := range inputUtxos {
if segwit.IsP2WScript(inputUtxo.ControlProgram) {
- batch.Delete(account.StandardUTXOKey(inputUtxo.OutputID))
+ w.AccountMgr.DeleteStandardUTXO(inputUtxo.OutputID)
} else {
- batch.Delete(account.ContractUTXOKey(inputUtxo.OutputID))
+ store.DeleteContractUTXO(inputUtxo.OutputID)
}
}
//hand update the transaction output utxos
outputUtxos := txOutToUtxos(tx, statusFail, b.Height)
utxos := w.filterAccountUtxo(outputUtxos)
- if err := batchSaveUtxos(utxos, batch); err != nil {
- log.WithFields(log.Fields{"module": logModule, "err": err}).Error("attachUtxos fail on batchSaveUtxos")
+ if err := w.saveUtxos(utxos, store); err != nil {
+ log.WithFields(log.Fields{"module": logModule, "err": err}).Error("attachUtxos fail on saveUtxos")
}
}
}
-func (w *Wallet) detachUtxos(batch dbm.Batch, b *types.Block, txStatus *bc.TransactionStatus) {
+func (w *Wallet) detachUtxos(b *types.Block, txStatus *bc.TransactionStatus, store WalletStore) {
for txIndex := len(b.Transactions) - 1; txIndex >= 0; txIndex-- {
tx := b.Transactions[txIndex]
for j := range tx.Outputs {
}
if segwit.IsP2WScript(code) {
- batch.Delete(account.StandardUTXOKey(*tx.ResultIds[j]))
+ w.AccountMgr.DeleteStandardUTXO(*tx.ResultIds[j])
} else {
- batch.Delete(account.ContractUTXOKey(*tx.ResultIds[j]))
+ store.DeleteContractUTXO(*tx.ResultIds[j])
}
}
inputUtxos := txInToUtxos(tx, statusFail)
utxos := w.filterAccountUtxo(inputUtxos)
- if err := batchSaveUtxos(utxos, batch); err != nil {
+ if err := w.saveUtxos(utxos, store); err != nil {
log.WithFields(log.Fields{"module": logModule, "err": err}).Error("detachUtxos fail on batchSaveUtxos")
return
}
var hash [32]byte
sha3pool.Sum256(hash[:], []byte(s))
- data := w.DB.Get(account.ContractKey(hash))
- if data == nil {
+ cp, err := w.AccountMgr.GetControlProgram(bc.NewHash(hash))
+ if err != nil {
+ log.WithFields(log.Fields{"module": logModule, "err": err}).Error("filterAccountUtxo fail.")
continue
}
-
- cp := &account.CtrlProgram{}
- if err := json.Unmarshal(data, cp); err != nil {
- log.WithFields(log.Fields{"module": logModule, "err": err}).Error("filterAccountUtxo fail on unmarshal control program")
+ if cp == nil {
continue
}
return result
}
-func batchSaveUtxos(utxos []*account.UTXO, batch dbm.Batch) error {
+func (w *Wallet) saveUtxos(utxos []*account.UTXO, store WalletStore) error {
for _, utxo := range utxos {
- data, err := json.Marshal(utxo)
- if err != nil {
- return errors.Wrap(err, "failed marshal accountutxo")
- }
-
if segwit.IsP2WScript(utxo.ControlProgram) {
- batch.Set(account.StandardUTXOKey(utxo.OutputID), data)
+ if err := w.AccountMgr.SetStandardUTXO(utxo.OutputID, utxo); err != nil {
+ return err
+ }
} else {
- batch.Set(account.ContractUTXOKey(utxo.OutputID), data)
+ if err := store.SetContractUTXO(utxo.OutputID, utxo); err != nil {
+ return err
+ }
}
}
return nil
func TestGetAccountUtxos(t *testing.T) {
testDB := dbm.NewDB("testdb", "leveldb", "temp")
+ testStore := NewMockWalletStore(testDB)
defer func() {
testDB.Close()
os.RemoveAll("temp")
},
{
dbUtxos: map[string]*account.UTXO{
- string(account.StandardUTXOKey(bc.Hash{V0: 1})): &account.UTXO{
+ string(StandardUTXOKey(bc.Hash{V0: 1})): &account.UTXO{
OutputID: bc.Hash{V0: 1},
},
- string(account.StandardUTXOKey(bc.Hash{V0: 2})): &account.UTXO{
+ string(StandardUTXOKey(bc.Hash{V0: 2})): &account.UTXO{
OutputID: bc.Hash{V0: 2},
},
- string(account.StandardUTXOKey(bc.Hash{V0: 3})): &account.UTXO{
+ string(StandardUTXOKey(bc.Hash{V0: 3})): &account.UTXO{
OutputID: bc.Hash{V0: 3},
},
- string(account.ContractUTXOKey(bc.Hash{V0: 4})): &account.UTXO{
+ string(ContractUTXOKey(bc.Hash{V0: 4})): &account.UTXO{
OutputID: bc.Hash{V0: 4},
},
},
},
{
dbUtxos: map[string]*account.UTXO{
- string(account.StandardUTXOKey(bc.Hash{V0: 1})): &account.UTXO{
+ string(StandardUTXOKey(bc.Hash{V0: 1})): &account.UTXO{
OutputID: bc.Hash{V0: 1},
},
- string(account.StandardUTXOKey(bc.Hash{V0: 2})): &account.UTXO{
+ string(StandardUTXOKey(bc.Hash{V0: 2})): &account.UTXO{
OutputID: bc.Hash{V0: 2},
},
- string(account.StandardUTXOKey(bc.Hash{V0: 3})): &account.UTXO{
+ string(StandardUTXOKey(bc.Hash{V0: 3})): &account.UTXO{
OutputID: bc.Hash{V0: 3},
},
- string(account.ContractUTXOKey(bc.Hash{V0: 4})): &account.UTXO{
+ string(ContractUTXOKey(bc.Hash{V0: 4})): &account.UTXO{
OutputID: bc.Hash{V0: 4},
},
},
},
{
dbUtxos: map[string]*account.UTXO{
- string(account.StandardUTXOKey(bc.Hash{V0: 1})): &account.UTXO{
+ string(StandardUTXOKey(bc.Hash{V0: 1})): &account.UTXO{
OutputID: bc.Hash{V0: 1},
},
- string(account.StandardUTXOKey(bc.Hash{V0: 1, V1: 2})): &account.UTXO{
+ string(StandardUTXOKey(bc.Hash{V0: 1, V1: 2})): &account.UTXO{
OutputID: bc.Hash{V0: 1, V1: 2},
},
- string(account.StandardUTXOKey(bc.Hash{V0: 2})): &account.UTXO{
+ string(StandardUTXOKey(bc.Hash{V0: 2})): &account.UTXO{
OutputID: bc.Hash{V0: 2},
},
- string(account.StandardUTXOKey(bc.Hash{V0: 2, V1: 2})): &account.UTXO{
+ string(StandardUTXOKey(bc.Hash{V0: 2, V1: 2})): &account.UTXO{
OutputID: bc.Hash{V0: 2, V1: 2},
},
},
},
{
dbUtxos: map[string]*account.UTXO{
- string(account.StandardUTXOKey(bc.Hash{V0: 3})): &account.UTXO{
+ string(StandardUTXOKey(bc.Hash{V0: 3})): &account.UTXO{
OutputID: bc.Hash{V0: 3},
},
- string(account.ContractUTXOKey(bc.Hash{V0: 4})): &account.UTXO{
+ string(ContractUTXOKey(bc.Hash{V0: 4})): &account.UTXO{
OutputID: bc.Hash{V0: 4},
},
},
},
{
dbUtxos: map[string]*account.UTXO{
- string(account.StandardUTXOKey(bc.Hash{V0: 3})): &account.UTXO{
+ string(StandardUTXOKey(bc.Hash{V0: 3})): &account.UTXO{
OutputID: bc.Hash{V0: 3},
},
- string(account.ContractUTXOKey(bc.Hash{V0: 4})): &account.UTXO{
+ string(ContractUTXOKey(bc.Hash{V0: 4})): &account.UTXO{
OutputID: bc.Hash{V0: 4},
},
},
},
}
- w := &Wallet{DB: testDB}
+ w := &Wallet{Store: testStore}
for i, c := range cases {
for k, u := range c.dbUtxos {
data, err := json.Marshal(u)
testDB.Set([]byte(k), data)
}
- w.AccountMgr = account.NewManager(testDB, nil)
+ acccountStore := NewMockAccountStore(testDB)
+ w.AccountMgr = account.NewManager(acccountStore, nil)
w.AccountMgr.AddUnconfirmedUtxo(c.unconfirmedUtxos)
gotUtxos := w.GetAccountUtxos("", c.id, c.unconfirmed, c.isSmartContract, false)
if !testutil.DeepEqual(gotUtxos, c.wantUtxos) {
func TestFilterAccountUtxo(t *testing.T) {
testDB := dbm.NewDB("testdb", "leveldb", "temp")
+ testStore := NewMockWalletStore(testDB)
defer func() {
testDB.Close()
os.RemoveAll("temp")
},
{
dbPrograms: map[string]*account.CtrlProgram{
- "436f6e74726163743a2a37a64a4e15a772ab43bf3f5956d0d1f353946496788e7f40d0ff1796286a6f": &account.CtrlProgram{
+ "41533a013a2a37a64a4e15a772ab43bf3f5956d0d1f353946496788e7f40d0ff1796286a6f": &account.CtrlProgram{
AccountID: "testAccount",
Address: "testAddress",
KeyIndex: 53,
},
{
dbPrograms: map[string]*account.CtrlProgram{
- "436f6e74726163743a2a37a64a4e15a772ab43bf3f5956d0d1f353946496788e7f40d0ff1796286a6f": &account.CtrlProgram{
+ "41533a013a2a37a64a4e15a772ab43bf3f5956d0d1f353946496788e7f40d0ff1796286a6f": &account.CtrlProgram{
AccountID: "testAccount",
Address: "testAddress",
KeyIndex: 53,
Change: true,
},
- "436f6e74726163743adb4d86262c12ba70d50b3ca3ae102d5682436243bd1e8c79569603f75675036a": &account.CtrlProgram{
+ "41533a013adb4d86262c12ba70d50b3ca3ae102d5682436243bd1e8c79569603f75675036a": &account.CtrlProgram{
AccountID: "testAccount2",
Address: "testAddress2",
KeyIndex: 72,
},
}
- w := &Wallet{DB: testDB}
+ accountStore := NewMockAccountStore(testDB)
+ accountManager := account.NewManager(accountStore, nil)
+ w := &Wallet{
+ Store: testStore,
+ AccountMgr: accountManager,
+ }
for i, c := range cases {
for s, p := range c.dbPrograms {
data, err := json.Marshal(p)
package wallet
import (
- "encoding/json"
"sync"
log "github.com/sirupsen/logrus"
"github.com/vapor/account"
"github.com/vapor/asset"
"github.com/vapor/blockchain/pseudohsm"
- dbm "github.com/vapor/database/leveldb"
"github.com/vapor/errors"
"github.com/vapor/event"
"github.com/vapor/protocol"
var (
currentVersion = uint(1)
- walletKey = []byte("walletInfo")
errBestBlockNotFoundInCore = errors.New("best block not found in core")
errWalletVersionMismatch = errors.New("wallet version mismatch")
+ ErrGetWalletStatusInfo = errors.New("failed get wallet info")
+ ErrGetAsset = errors.New("Failed to find asset definition")
+ ErrAccntTxIDNotFound = errors.New("account TXID not found")
)
//StatusInfo is base valid block info to handle orphan block rollback
//Wallet is related to storing account unspent outputs
type Wallet struct {
- DB dbm.DB
+ Store WalletStore
rw sync.RWMutex
- status StatusInfo
+ Status StatusInfo
TxIndexFlag bool
AccountMgr *account.Manager
AssetReg *asset.Registry
Hsm *pseudohsm.HSM
- chain *protocol.Chain
+ Chain *protocol.Chain
RecoveryMgr *recoveryManager
- eventDispatcher *event.Dispatcher
- txMsgSub *event.Subscription
+ EventDispatcher *event.Dispatcher
+ TxMsgSub *event.Subscription
rescanCh chan struct{}
}
//NewWallet return a new wallet instance
-func NewWallet(walletDB dbm.DB, account *account.Manager, asset *asset.Registry, hsm *pseudohsm.HSM, chain *protocol.Chain, dispatcher *event.Dispatcher, txIndexFlag bool) (*Wallet, error) {
+func NewWallet(store WalletStore, account *account.Manager, asset *asset.Registry, hsm *pseudohsm.HSM, chain *protocol.Chain, dispatcher *event.Dispatcher, txIndexFlag bool) (*Wallet, error) {
w := &Wallet{
- DB: walletDB,
+ Store: store,
AccountMgr: account,
AssetReg: asset,
- chain: chain,
+ Chain: chain,
Hsm: hsm,
- RecoveryMgr: newRecoveryManager(walletDB, account),
- eventDispatcher: dispatcher,
+ RecoveryMgr: NewRecoveryManager(store, account),
+ EventDispatcher: dispatcher,
rescanCh: make(chan struct{}, 1),
TxIndexFlag: txIndexFlag,
}
- if err := w.loadWalletInfo(); err != nil {
+ if err := w.LoadWalletInfo(); err != nil {
return nil, err
}
}
var err error
- w.txMsgSub, err = w.eventDispatcher.Subscribe(protocol.TxMsgEvent{})
+ w.TxMsgSub, err = w.EventDispatcher.Subscribe(protocol.TxMsgEvent{})
if err != nil {
return nil, err
}
go w.walletUpdater()
go w.delUnconfirmedTx()
- go w.memPoolTxQueryLoop()
+ go w.MemPoolTxQueryLoop()
return w, nil
}
-// memPoolTxQueryLoop constantly pass a transaction accepted by mempool to the wallet.
-func (w *Wallet) memPoolTxQueryLoop() {
+// MemPoolTxQueryLoop constantly pass a transaction accepted by mempool to the wallet.
+func (w *Wallet) MemPoolTxQueryLoop() {
for {
select {
- case obj, ok := <-w.txMsgSub.Chan():
+ case obj, ok := <-w.TxMsgSub.Chan():
if !ok {
log.WithFields(log.Fields{"module": logModule}).Warning("tx pool tx msg subscription channel closed")
return
}
func (w *Wallet) checkWalletInfo() error {
- if w.status.Version != currentVersion {
+ if w.Status.Version != currentVersion {
return errWalletVersionMismatch
- } else if !w.chain.BlockExist(&w.status.BestHash) {
+ } else if !w.Chain.BlockExist(&w.Status.BestHash) {
return errBestBlockNotFoundInCore
}
return nil
}
-//loadWalletInfo return stored wallet info and nil,
+//LoadWalletInfo return stored wallet info and nil,
//if error, return initial wallet info and err
-func (w *Wallet) loadWalletInfo() error {
- if rawWallet := w.DB.Get(walletKey); rawWallet != nil {
- if err := json.Unmarshal(rawWallet, &w.status); err != nil {
- return err
- }
+func (w *Wallet) LoadWalletInfo() error {
+ walletStatus, err := w.Store.GetWalletInfo()
+ if walletStatus == nil && err != ErrGetWalletStatusInfo {
+ return err
+ }
- err := w.checkWalletInfo()
+ if walletStatus != nil {
+ w.Status = *walletStatus
+ err = w.checkWalletInfo()
if err == nil {
return nil
}
log.WithFields(log.Fields{"module": logModule}).Warn(err.Error())
- w.deleteAccountTxs()
- w.deleteUtxos()
+ w.Store.DeleteWalletTransactions()
+ w.Store.DeleteWalletUTXOs()
}
- w.status.Version = currentVersion
- w.status.WorkHash = bc.Hash{}
- block, err := w.chain.GetBlockByHeight(0)
+ w.Status.Version = currentVersion
+ w.Status.WorkHash = bc.Hash{}
+ block, err := w.Chain.GetBlockByHeight(0)
if err != nil {
return err
}
+
return w.AttachBlock(block)
}
-func (w *Wallet) commitWalletInfo(batch dbm.Batch) error {
- rawWallet, err := json.Marshal(w.status)
- if err != nil {
+func (w *Wallet) commitWalletInfo(store WalletStore) error {
+ if err := store.SetWalletInfo(&w.Status); err != nil {
log.WithFields(log.Fields{"module": logModule, "err": err}).Error("save wallet info")
return err
}
-
- batch.Set(walletKey, rawWallet)
- batch.Write()
return nil
}
w.rw.Lock()
defer w.rw.Unlock()
- if block.PreviousBlockHash != w.status.WorkHash {
+ if block.PreviousBlockHash != w.Status.WorkHash {
log.Warn("wallet skip attachBlock due to status hash not equal to previous hash")
return nil
}
blockHash := block.Hash()
- txStatus, err := w.chain.GetTransactionStatus(&blockHash)
+ txStatus, err := w.Chain.GetTransactionStatus(&blockHash)
if err != nil {
return err
}
w.RecoveryMgr.finished()
}
- storeBatch := w.DB.NewBatch()
- if err := w.indexTransactions(storeBatch, block, txStatus); err != nil {
+ annotatedTxs := w.filterAccountTxs(block, txStatus)
+ if err := saveExternalAssetDefinition(block, w.Store); err != nil {
+ return err
+ }
+
+ w.annotateTxsAccount(annotatedTxs)
+
+ newStore := w.Store.InitBatch()
+ if err := w.indexTransactions(block, txStatus, annotatedTxs, newStore); err != nil {
+ return err
+ }
+
+ w.attachUtxos(block, txStatus, newStore)
+ w.Status.WorkHeight = block.Height
+ w.Status.WorkHash = block.Hash()
+ if w.Status.WorkHeight >= w.Status.BestHeight {
+ w.Status.BestHeight = w.Status.WorkHeight
+ w.Status.BestHash = w.Status.WorkHash
+ }
+
+ if err := w.commitWalletInfo(newStore); err != nil {
return err
}
- w.attachUtxos(storeBatch, block, txStatus)
- w.status.WorkHeight = block.Height
- w.status.WorkHash = block.Hash()
- if w.status.WorkHeight >= w.status.BestHeight {
- w.status.BestHeight = w.status.WorkHeight
- w.status.BestHash = w.status.WorkHash
+ if err := newStore.CommitBatch(); err != nil {
+ return err
}
- return w.commitWalletInfo(storeBatch)
+
+ return nil
}
// DetachBlock detach a block and rollback state
defer w.rw.Unlock()
blockHash := block.Hash()
- txStatus, err := w.chain.GetTransactionStatus(&blockHash)
+ txStatus, err := w.Chain.GetTransactionStatus(&blockHash)
if err != nil {
return err
}
- storeBatch := w.DB.NewBatch()
- w.detachUtxos(storeBatch, block, txStatus)
- w.deleteTransactions(storeBatch, w.status.BestHeight)
+ newStore := w.Store.InitBatch()
- w.status.BestHeight = block.Height - 1
- w.status.BestHash = block.PreviousBlockHash
+ w.detachUtxos(block, txStatus, newStore)
+ newStore.DeleteTransactions(w.Status.BestHeight)
- if w.status.WorkHeight > w.status.BestHeight {
- w.status.WorkHeight = w.status.BestHeight
- w.status.WorkHash = w.status.BestHash
+ w.Status.BestHeight = block.Height - 1
+ w.Status.BestHash = block.PreviousBlockHash
+
+ if w.Status.WorkHeight > w.Status.BestHeight {
+ w.Status.WorkHeight = w.Status.BestHeight
+ w.Status.WorkHash = w.Status.BestHash
+ }
+ if err := w.commitWalletInfo(newStore); err != nil {
+ return err
}
- return w.commitWalletInfo(storeBatch)
+ if err := newStore.CommitBatch(); err != nil {
+ return err
+ }
+
+ return nil
}
//WalletUpdate process every valid block and reverse every invalid block which need to rollback
func (w *Wallet) walletUpdater() {
for {
w.getRescanNotification()
- for !w.chain.InMainChain(w.status.BestHash) {
- block, err := w.chain.GetBlockByHash(&w.status.BestHash)
+ for !w.Chain.InMainChain(w.Status.BestHash) {
+ block, err := w.Chain.GetBlockByHash(&w.Status.BestHash)
if err != nil {
log.WithFields(log.Fields{"module": logModule, "err": err}).Error("walletUpdater GetBlockByHash")
return
}
}
- block, _ := w.chain.GetBlockByHeight(w.status.WorkHeight + 1)
+ block, _ := w.Chain.GetBlockByHeight(w.Status.WorkHeight + 1)
if block == nil {
w.walletBlockWaiter()
continue
}
}
-// deleteAccountTxs deletes all txs in wallet
-func (w *Wallet) deleteAccountTxs() {
- storeBatch := w.DB.NewBatch()
-
- txIter := w.DB.IteratorPrefix([]byte(TxPrefix))
- defer txIter.Release()
-
- for txIter.Next() {
- storeBatch.Delete(txIter.Key())
- }
-
- txIndexIter := w.DB.IteratorPrefix([]byte(TxIndexPrefix))
- defer txIndexIter.Release()
-
- for txIndexIter.Next() {
- storeBatch.Delete(txIndexIter.Key())
- }
-
- storeBatch.Write()
-}
-
-func (w *Wallet) deleteUtxos() {
- storeBatch := w.DB.NewBatch()
- ruIter := w.DB.IteratorPrefix([]byte(account.UTXOPreFix))
- defer ruIter.Release()
- for ruIter.Next() {
- storeBatch.Delete(ruIter.Key())
- }
-
- suIter := w.DB.IteratorPrefix([]byte(account.SUTXOPrefix))
- defer suIter.Release()
- for suIter.Next() {
- storeBatch.Delete(suIter.Key())
- }
- storeBatch.Write()
-}
-
// DeleteAccount deletes account matching accountID, then rescan wallet
func (w *Wallet) DeleteAccount(accountID string) (err error) {
w.rw.Lock()
return err
}
- w.deleteAccountTxs()
+ w.Store.DeleteWalletTransactions()
w.RescanBlocks()
return nil
}
return err
}
- w.deleteAccountTxs()
+ w.Store.DeleteWalletTransactions()
w.RescanBlocks()
return nil
}
}
func (w *Wallet) setRescanStatus() {
- block, _ := w.chain.GetBlockByHeight(0)
- w.status.WorkHash = bc.Hash{}
+ block, _ := w.Chain.GetBlockByHeight(0)
+ w.Status.WorkHash = bc.Hash{}
w.AttachBlock(block)
}
func (w *Wallet) walletBlockWaiter() {
select {
- case <-w.chain.BlockWaiter(w.status.WorkHeight + 1):
+ case <-w.Chain.BlockWaiter(w.Status.WorkHeight + 1):
case <-w.rescanCh:
w.setRescanStatus()
}
w.rw.RLock()
defer w.rw.RUnlock()
- return w.status
+ return w.Status
}
package wallet
import (
+ "encoding/binary"
+ "encoding/hex"
"encoding/json"
+ "fmt"
"io/ioutil"
"os"
- "reflect"
+ "sort"
+ "strings"
"testing"
"time"
"github.com/vapor/account"
+ acc "github.com/vapor/account"
"github.com/vapor/asset"
- "github.com/vapor/blockchain/pseudohsm"
+ "github.com/vapor/blockchain/query"
"github.com/vapor/blockchain/signers"
"github.com/vapor/blockchain/txbuilder"
+ "github.com/vapor/common"
"github.com/vapor/config"
"github.com/vapor/consensus"
"github.com/vapor/crypto/ed25519/chainkd"
- "github.com/vapor/database"
+ "github.com/vapor/crypto/sha3pool"
dbm "github.com/vapor/database/leveldb"
+ "github.com/vapor/errors"
"github.com/vapor/event"
"github.com/vapor/protocol"
"github.com/vapor/protocol/bc"
Position: 1,
}
- globalTxIdx := calcGlobalTxIndex(&want.BlockHash, want.Position)
+ globalTxIdx := CalcGlobalTxIndex(&want.BlockHash, want.Position)
blockHashGot, positionGot := parseGlobalTxIdx(globalTxIdx)
if *blockHashGot != want.BlockHash {
t.Errorf("blockHash mismatch. Get: %v. Expect: %v", *blockHashGot, want.BlockHash)
defer os.RemoveAll(dirPath)
testDB := dbm.NewDB("testdb", "leveldb", "temp")
+ walletStore := NewMockWalletStore(testDB)
defer func() {
testDB.Close()
os.RemoveAll("temp")
}()
dispatcher := event.NewDispatcher()
- w := mockWallet(testDB, nil, nil, nil, dispatcher, false)
+ w := mockWallet(walletStore, nil, nil, nil, dispatcher, false)
- // legacy status test case
- type legacyStatusInfo struct {
- WorkHeight uint64
- WorkHash bc.Hash
- BestHeight uint64
- BestHash bc.Hash
- }
- rawWallet, err := json.Marshal(legacyStatusInfo{})
- if err != nil {
- t.Fatal("Marshal legacyStatusInfo")
- }
-
- w.DB.Set(walletKey, rawWallet)
- rawWallet = w.DB.Get(walletKey)
- if rawWallet == nil {
- t.Fatal("fail to load wallet StatusInfo")
+ walletStatus := new(StatusInfo)
+ if err := w.Store.SetWalletInfo(walletStatus); err != nil {
+ t.Fatal(err)
}
- if err := json.Unmarshal(rawWallet, &w.status); err != nil {
+ status, err := w.Store.GetWalletInfo()
+ if err != nil {
t.Fatal(err)
}
+ w.Status = *status
if err := w.checkWalletInfo(); err != errWalletVersionMismatch {
t.Fatal("fail to detect legacy wallet version")
// lower wallet version test case
lowerVersion := StatusInfo{Version: currentVersion - 1}
- rawWallet, err = json.Marshal(lowerVersion)
- if err != nil {
- t.Fatal("save wallet info")
- }
-
- w.DB.Set(walletKey, rawWallet)
- rawWallet = w.DB.Get(walletKey)
- if rawWallet == nil {
- t.Fatal("fail to load wallet StatusInfo")
+ if err := w.Store.SetWalletInfo(&lowerVersion); err != nil {
+ t.Fatal(err)
}
- if err := json.Unmarshal(rawWallet, &w.status); err != nil {
+ status, err = w.Store.GetWalletInfo()
+ if err != nil {
t.Fatal(err)
}
+ w.Status = *status
if err := w.checkWalletInfo(); err != errWalletVersionMismatch {
t.Fatal("fail to detect expired wallet version")
}
}
-func TestWalletUpdate(t *testing.T) {
- dirPath, err := ioutil.TempDir(".", "")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(dirPath)
+func mockUTXO(controlProg *account.CtrlProgram, assetID *bc.AssetID) *account.UTXO {
+ utxo := &account.UTXO{}
+ utxo.OutputID = bc.Hash{V0: 1}
+ utxo.SourceID = bc.Hash{V0: 2}
+ utxo.AssetID = *assetID
+ utxo.Amount = 1000000000
+ utxo.SourcePos = 0
+ utxo.ControlProgram = controlProg.ControlProgram
+ utxo.AccountID = controlProg.AccountID
+ utxo.Address = controlProg.Address
+ utxo.ControlProgramIndex = controlProg.KeyIndex
+ return utxo
+}
- config.CommonConfig = config.DefaultConfig()
- testDB := dbm.NewDB("testdb", "leveldb", "temp")
- defer func() {
- testDB.Close()
- os.RemoveAll("temp")
- }()
+func mockTxData(utxos []*account.UTXO, testAccount *account.Account) (*txbuilder.Template, *types.TxData, error) {
+ tplBuilder := txbuilder.NewBuilder(time.Now())
- store := database.NewStore(testDB)
- dispatcher := event.NewDispatcher()
- txPool := protocol.NewTxPool(store, dispatcher)
+ for _, utxo := range utxos {
+ txInput, sigInst, err := account.UtxoToInputs(testAccount.Signer, utxo)
+ if err != nil {
+ return nil, nil, err
+ }
+ tplBuilder.AddInput(txInput, sigInst)
- chain, err := protocol.NewChain(store, txPool, dispatcher)
- if err != nil {
- t.Fatal(err)
+ out := &types.TxOutput{}
+ if utxo.AssetID == *consensus.BTMAssetID {
+ out = types.NewIntraChainOutput(utxo.AssetID, 100, utxo.ControlProgram)
+ } else {
+ out = types.NewIntraChainOutput(utxo.AssetID, utxo.Amount, utxo.ControlProgram)
+ }
+ tplBuilder.AddOutput(out)
}
- accountManager := account.NewManager(testDB, chain)
- hsm, err := pseudohsm.New(dirPath)
- if err != nil {
- t.Fatal(err)
+ return tplBuilder.Build()
+}
+
+func mockWallet(store WalletStore, account *account.Manager, asset *asset.Registry, chain *protocol.Chain, dispatcher *event.Dispatcher, txIndexFlag bool) *Wallet {
+ wallet := &Wallet{
+ Store: store,
+ AccountMgr: account,
+ AssetReg: asset,
+ Chain: chain,
+ RecoveryMgr: NewRecoveryManager(store, account),
+ EventDispatcher: dispatcher,
+ TxIndexFlag: txIndexFlag,
}
+ wallet.TxMsgSub, _ = wallet.EventDispatcher.Subscribe(protocol.TxMsgEvent{})
+ return wallet
+}
- xpub1, _, err := hsm.XCreate("test_pub1", "password", "en")
- if err != nil {
- t.Fatal(err)
+func mockSingleBlock(tx *types.Tx) *types.Block {
+ return &types.Block{
+ BlockHeader: types.BlockHeader{
+ Version: 1,
+ Height: 1,
+ },
+ Transactions: []*types.Tx{config.GenesisTx(), tx},
}
+}
- testAccount, err := accountManager.Create([]chainkd.XPub{xpub1.XPub}, 1, "testAccount", signers.BIP0044)
- if err != nil {
- t.Fatal(err)
+// errors
+var (
+ errAccntTxIDNotFound = errors.New("account TXID not found")
+ errGetAsset = errors.New("Failed to find asset definition")
+)
+
+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)
+)
+
+func accountIndexKey(xpubs []chainkd.XPub) []byte {
+ var hash [32]byte
+ var xPubs []byte
+ cpy := append([]chainkd.XPub{}, xpubs[:]...)
+ sort.Sort(signers.SortKeys(cpy))
+ for _, xpub := range cpy {
+ xPubs = append(xPubs, xpub[:]...)
}
+ sha3pool.Sum256(hash[:], xPubs)
+ return append([]byte(AccountIndexPrefix), hash[:]...)
+}
- controlProg, err := accountManager.CreateAddress(testAccount.ID, false)
- if err != nil {
- t.Fatal(err)
+func Bip44ContractIndexKey(accountID string, change bool) []byte {
+ key := append([]byte(ContractIndexPrefix), accountID...)
+ if change {
+ return append(key, []byte{1}...)
}
+ return append(key, []byte{0}...)
+}
- controlProg.KeyIndex = 1
+// ContractKey account control promgram store prefix
+func ContractKey(hash bc.Hash) []byte {
+ return append([]byte(ContractPrefix), hash.Bytes()...)
+}
- reg := asset.NewRegistry(testDB, chain)
- asset := bc.AssetID{V0: 5}
+// AccountIDKey account id store prefix
+func AccountIDKey(accountID string) []byte {
+ return append([]byte(AccountPrefix), []byte(accountID)...)
+}
- utxos := []*account.UTXO{}
- btmUtxo := mockUTXO(controlProg, consensus.BTMAssetID)
- utxos = append(utxos, btmUtxo)
- OtherUtxo := mockUTXO(controlProg, &asset)
- utxos = append(utxos, OtherUtxo)
+// StandardUTXOKey makes an account unspent outputs key to store
+func StandardUTXOKey(id bc.Hash) []byte {
+ return append(UTXOPrefix, id.Bytes()...)
+}
- _, txData, err := mockTxData(utxos, testAccount)
- if err != nil {
- t.Fatal(err)
+// ContractUTXOKey makes a smart contract unspent outputs key to store
+func ContractUTXOKey(id bc.Hash) []byte {
+ return append(SUTXOPrefix, id.Bytes()...)
+}
+
+func calcDeleteKey(blockHeight uint64) []byte {
+ return []byte(fmt.Sprintf("%s%016x", TxPrefix, blockHeight))
+}
+
+func calcTxIndexKey(txID string) []byte {
+ return append(TxIndexPrefix, []byte(txID)...)
+}
+
+func calcAnnotatedKey(formatKey string) []byte {
+ return append(TxPrefix, []byte(formatKey)...)
+}
+
+func calcUnconfirmedTxKey(formatKey string) []byte {
+ return append(UnconfirmedTxPrefix, []byte(formatKey)...)
+}
+
+func CalcGlobalTxIndexKey(txID string) []byte {
+ return append(GlobalTxIndexPrefix, []byte(txID)...)
+}
+
+func CalcGlobalTxIndex(blockHash *bc.Hash, position uint64) []byte {
+ txIdx := make([]byte, 40)
+ copy(txIdx[:32], blockHash.Bytes())
+ binary.BigEndian.PutUint64(txIdx[32:], position)
+ return txIdx
+}
+
+func formatKey(blockHeight uint64, position uint32) string {
+ return fmt.Sprintf("%016x%08x", blockHeight, position)
+}
+
+func contractIndexKey(accountID string) []byte {
+ return append([]byte(ContractIndexPrefix), []byte(accountID)...)
+}
+
+func accountAliasKey(name string) []byte {
+ return append([]byte(AccountAliasPrefix), []byte(name)...)
+}
+
+// MockWalletStore store wallet using leveldb
+type MockWalletStore struct {
+ db dbm.DB
+ batch dbm.Batch
+}
+
+// NewMockWalletStore create new MockWalletStore struct
+func NewMockWalletStore(db dbm.DB) *MockWalletStore {
+ return &MockWalletStore{
+ db: db,
+ batch: nil,
}
+}
- tx := types.NewTx(*txData)
- block := mockSingleBlock(tx)
- txStatus := bc.NewTransactionStatus()
- txStatus.SetStatus(0, false)
- txStatus.SetStatus(1, false)
- store.SaveBlock(block, txStatus)
+// InitBatch initial new wallet store
+func (store *MockWalletStore) InitBatch() WalletStore {
+ newStore := NewMockWalletStore(store.db)
+ newStore.batch = newStore.db.NewBatch()
+ return newStore
+}
- w := mockWallet(testDB, accountManager, reg, chain, dispatcher, true)
- err = w.AttachBlock(block)
- if err != nil {
- t.Fatal(err)
+// CommitBatch commit batch
+func (store *MockWalletStore) CommitBatch() error {
+ if store.batch == nil {
+ return errors.New("MockWalletStore commit fail, store batch is nil.")
}
+ store.batch.Write()
+ store.batch = nil
+ return nil
+}
- if _, err := w.GetTransactionByTxID(tx.ID.String()); err != nil {
- t.Fatal(err)
+// DeleteContractUTXO delete contract utxo by outputID
+func (store *MockWalletStore) DeleteContractUTXO(outputID bc.Hash) {
+ if store.batch == nil {
+ store.db.Delete(ContractUTXOKey(outputID))
+ } else {
+ store.batch.Delete(ContractUTXOKey(outputID))
}
+}
- wants, err := w.GetTransactions(testAccount.ID, "", 1, false)
- if len(wants) != 1 {
- t.Fatal(err)
+// DeleteRecoveryStatus delete recovery status
+func (store *MockWalletStore) DeleteRecoveryStatus() {
+ if store.batch == nil {
+ store.db.Delete(RecoveryKey)
+ } else {
+ store.batch.Delete(RecoveryKey)
}
+}
- if wants[0].ID != tx.ID {
- t.Fatal("account txID mismatch")
+// DeleteTransactions delete transactions when orphan block rollback
+func (store *MockWalletStore) DeleteTransactions(height uint64) {
+ batch := store.db.NewBatch()
+ if store.batch != nil {
+ batch = store.batch
}
+ txIter := store.db.IteratorPrefix(calcDeleteKey(height))
+ defer txIter.Release()
- for position, tx := range block.Transactions {
- get := w.DB.Get(calcGlobalTxIndexKey(tx.ID.String()))
- bh := block.BlockHeader.Hash()
- expect := calcGlobalTxIndex(&bh, uint64(position))
- if !reflect.DeepEqual(get, expect) {
- t.Fatalf("position#%d: compare retrieved globalTxIdx err", position)
+ tmpTx := query.AnnotatedTx{}
+ for txIter.Next() {
+ if err := json.Unmarshal(txIter.Value(), &tmpTx); err == nil {
+ batch.Delete(calcTxIndexKey(tmpTx.ID.String()))
}
+ batch.Delete(txIter.Key())
+ }
+ if store.batch == nil {
+ batch.Write()
}
}
-func TestRescanWallet(t *testing.T) {
- // prepare wallet & db
- dirPath, err := ioutil.TempDir(".", "")
- if err != nil {
- t.Fatal(err)
+// DeleteUnconfirmedTransaction delete unconfirmed tx by txID
+func (store *MockWalletStore) DeleteUnconfirmedTransaction(txID string) {
+ if store.batch == nil {
+ store.db.Delete(calcUnconfirmedTxKey(txID))
+ } else {
+ store.batch.Delete(calcUnconfirmedTxKey(txID))
}
- defer os.RemoveAll(dirPath)
+}
- config.CommonConfig = config.DefaultConfig()
- testDB := dbm.NewDB("testdb", "leveldb", "temp")
- defer func() {
- testDB.Close()
- os.RemoveAll("temp")
- }()
+// DeleteWalletTransactions delete all txs in wallet
+func (store *MockWalletStore) DeleteWalletTransactions() {
+ batch := store.db.NewBatch()
+ if store.batch != nil {
+ batch = store.batch
+ }
+ txIter := store.db.IteratorPrefix([]byte(TxPrefix))
+ defer txIter.Release()
- store := database.NewStore(testDB)
- dispatcher := event.NewDispatcher()
- txPool := protocol.NewTxPool(store, dispatcher)
- chain, err := protocol.NewChain(store, txPool, dispatcher)
- if err != nil {
- t.Fatal(err)
+ for txIter.Next() {
+ batch.Delete(txIter.Key())
}
- statusInfo := StatusInfo{
- Version: currentVersion,
- WorkHash: bc.Hash{V0: 0xff},
+ txIndexIter := store.db.IteratorPrefix([]byte(TxIndexPrefix))
+ defer txIndexIter.Release()
+
+ for txIndexIter.Next() {
+ batch.Delete(txIndexIter.Key())
}
- rawWallet, err := json.Marshal(statusInfo)
- if err != nil {
- t.Fatal("save wallet info")
+ if store.batch == nil {
+ batch.Write()
}
+}
- w := mockWallet(testDB, nil, nil, chain, dispatcher, false)
- w.DB.Set(walletKey, rawWallet)
- rawWallet = w.DB.Get(walletKey)
- if rawWallet == nil {
- t.Fatal("fail to load wallet StatusInfo")
+// DeleteWalletUTXOs delete all txs in wallet
+func (store *MockWalletStore) DeleteWalletUTXOs() {
+ batch := store.db.NewBatch()
+ if store.batch != nil {
+ batch = store.batch
+ }
+ ruIter := store.db.IteratorPrefix([]byte(UTXOPrefix))
+ defer ruIter.Release()
+ for ruIter.Next() {
+ batch.Delete(ruIter.Key())
}
- if err := json.Unmarshal(rawWallet, &w.status); err != nil {
- t.Fatal(err)
+ suIter := store.db.IteratorPrefix([]byte(SUTXOPrefix))
+ defer suIter.Release()
+ for suIter.Next() {
+ batch.Delete(suIter.Key())
+ }
+ if store.batch == nil {
+ batch.Write()
}
+}
- // rescan wallet
- if err := w.loadWalletInfo(); err != nil {
- t.Fatal(err)
+// GetAsset get asset by assetID
+func (store *MockWalletStore) GetAsset(assetID *bc.AssetID) (*asset.Asset, error) {
+ definitionByte := store.db.Get(asset.ExtAssetKey(assetID))
+ if definitionByte == nil {
+ return nil, errGetAsset
+ }
+ definitionMap := make(map[string]interface{})
+ if err := json.Unmarshal(definitionByte, &definitionMap); err != nil {
+ return nil, err
}
+ alias := assetID.String()
+ externalAsset := &asset.Asset{
+ AssetID: *assetID,
+ Alias: &alias,
+ DefinitionMap: definitionMap,
+ RawDefinitionByte: definitionByte,
+ }
+ return externalAsset, nil
+}
- block := config.GenesisBlock()
- if w.status.WorkHash != block.Hash() {
- t.Fatal("reattach from genesis block")
+// GetGlobalTransactionIndex get global tx by txID
+func (store *MockWalletStore) GetGlobalTransactionIndex(txID string) []byte {
+ return store.db.Get(CalcGlobalTxIndexKey(txID))
+}
+
+// GetStandardUTXO get standard utxo by id
+func (store *MockWalletStore) GetStandardUTXO(outid bc.Hash) (*acc.UTXO, error) {
+ rawUTXO := store.db.Get(StandardUTXOKey(outid))
+ if rawUTXO == nil {
+ return nil, fmt.Errorf("failed get standard UTXO, outputID: %s ", outid.String())
+ }
+ UTXO := new(acc.UTXO)
+ if err := json.Unmarshal(rawUTXO, UTXO); err != nil {
+ return nil, err
}
+ return UTXO, nil
}
-func TestMemPoolTxQueryLoop(t *testing.T) {
- dirPath, err := ioutil.TempDir(".", "")
+// GetTransaction get tx by txid
+func (store *MockWalletStore) GetTransaction(txID string) (*query.AnnotatedTx, error) {
+ formatKey := store.db.Get(calcTxIndexKey(txID))
+ if formatKey == nil {
+ return nil, errAccntTxIDNotFound
+ }
+ rawTx := store.db.Get(calcAnnotatedKey(string(formatKey)))
+ tx := new(query.AnnotatedTx)
+ if err := json.Unmarshal(rawTx, tx); err != nil {
+ return nil, err
+ }
+ return tx, nil
+}
+
+// GetUnconfirmedTransaction get unconfirmed tx by txID
+func (store *MockWalletStore) GetUnconfirmedTransaction(txID string) (*query.AnnotatedTx, error) {
+ rawUnconfirmedTx := store.db.Get(calcUnconfirmedTxKey(txID))
+ if rawUnconfirmedTx == nil {
+ return nil, fmt.Errorf("failed get unconfirmed tx, txID: %s ", txID)
+ }
+ tx := new(query.AnnotatedTx)
+ if err := json.Unmarshal(rawUnconfirmedTx, tx); err != nil {
+ return nil, err
+ }
+ return tx, nil
+}
+
+// GetRecoveryStatus delete recovery status
+func (store *MockWalletStore) GetRecoveryStatus() (*RecoveryState, error) {
+ rawStatus := store.db.Get(RecoveryKey)
+ if rawStatus == nil {
+ return nil, ErrGetRecoveryStatus
+ }
+ state := new(RecoveryState)
+ if err := json.Unmarshal(rawStatus, state); err != nil {
+ return nil, err
+ }
+ return state, nil
+}
+
+// GetWalletInfo get wallet information
+func (store *MockWalletStore) GetWalletInfo() (*StatusInfo, error) {
+ rawStatus := store.db.Get([]byte(WalletKey))
+ if rawStatus == nil {
+ return nil, fmt.Errorf("failed get wallet info")
+ }
+ status := new(StatusInfo)
+ if err := json.Unmarshal(rawStatus, status); err != nil {
+ return nil, err
+ }
+ return status, nil
+}
+
+// ListAccountUTXOs get all account unspent outputs
+func (store *MockWalletStore) ListAccountUTXOs(id string, isSmartContract bool) ([]*acc.UTXO, error) {
+ prefix := UTXOPrefix
+ if isSmartContract {
+ prefix = SUTXOPrefix
+ }
+
+ idBytes, err := hex.DecodeString(id)
if err != nil {
- t.Fatal(err)
+ return nil, err
}
- config.CommonConfig = config.DefaultConfig()
- testDB := dbm.NewDB("testdb", "leveldb", dirPath)
- defer func() {
- testDB.Close()
- os.RemoveAll(dirPath)
- }()
- store := database.NewStore(testDB)
- dispatcher := event.NewDispatcher()
- txPool := protocol.NewTxPool(store, dispatcher)
+ accountUtxoIter := store.db.IteratorPrefix(append(prefix, idBytes...))
+ defer accountUtxoIter.Release()
+
+ confirmedUTXOs := []*acc.UTXO{}
+ for accountUtxoIter.Next() {
+ utxo := new(acc.UTXO)
+ if err := json.Unmarshal(accountUtxoIter.Value(), utxo); err != nil {
+ return nil, err
+ }
+
+ confirmedUTXOs = append(confirmedUTXOs, utxo)
+ }
+ return confirmedUTXOs, nil
+}
+
+func (store *MockWalletStore) ListTransactions(accountID string, StartTxID string, count uint, unconfirmed bool) ([]*query.AnnotatedTx, error) {
+ annotatedTxs := []*query.AnnotatedTx{}
+ var startKey []byte
+ preFix := TxPrefix
+
+ if StartTxID != "" {
+ if unconfirmed {
+ startKey = calcUnconfirmedTxKey(StartTxID)
+ } else {
+ formatKey := store.db.Get(calcTxIndexKey(StartTxID))
+ if formatKey == nil {
+ return nil, errAccntTxIDNotFound
+ }
+ startKey = calcAnnotatedKey(string(formatKey))
+ }
+ }
+
+ if unconfirmed {
+ preFix = UnconfirmedTxPrefix
+ }
+
+ itr := store.db.IteratorPrefixWithStart([]byte(preFix), startKey, true)
+ defer itr.Release()
- chain, err := protocol.NewChain(store, txPool, dispatcher)
+ for txNum := count; itr.Next() && txNum > 0; txNum-- {
+ annotatedTx := new(query.AnnotatedTx)
+ if err := json.Unmarshal(itr.Value(), &annotatedTx); err != nil {
+ return nil, err
+ }
+ annotatedTxs = append(annotatedTxs, annotatedTx)
+ }
+
+ return annotatedTxs, nil
+}
+
+// ListUnconfirmedTransactions get all unconfirmed txs
+func (store *MockWalletStore) ListUnconfirmedTransactions() ([]*query.AnnotatedTx, error) {
+ annotatedTxs := []*query.AnnotatedTx{}
+ txIter := store.db.IteratorPrefix([]byte(UnconfirmedTxPrefix))
+ defer txIter.Release()
+
+ for txIter.Next() {
+ annotatedTx := &query.AnnotatedTx{}
+ if err := json.Unmarshal(txIter.Value(), &annotatedTx); err != nil {
+ return nil, err
+ }
+ annotatedTxs = append(annotatedTxs, annotatedTx)
+ }
+ return annotatedTxs, nil
+}
+
+// SetAssetDefinition set assetID and definition
+func (store *MockWalletStore) SetAssetDefinition(assetID *bc.AssetID, definition []byte) {
+ if store.batch == nil {
+ store.db.Set(asset.ExtAssetKey(assetID), definition)
+ } else {
+ store.batch.Set(asset.ExtAssetKey(assetID), definition)
+ }
+}
+
+// SetContractUTXO set standard utxo
+func (store *MockWalletStore) SetContractUTXO(outputID bc.Hash, utxo *acc.UTXO) error {
+ data, err := json.Marshal(utxo)
if err != nil {
- t.Fatal(err)
+ return err
}
+ if store.batch == nil {
+ store.db.Set(ContractUTXOKey(outputID), data)
+ } else {
+ store.batch.Set(ContractUTXOKey(outputID), data)
+ }
+ return nil
+}
- accountManager := account.NewManager(testDB, chain)
- hsm, err := pseudohsm.New(dirPath)
+// SetGlobalTransactionIndex set global tx index by blockhash and position
+func (store *MockWalletStore) SetGlobalTransactionIndex(globalTxID string, blockHash *bc.Hash, position uint64) {
+ if store.batch == nil {
+ store.db.Set(CalcGlobalTxIndexKey(globalTxID), CalcGlobalTxIndex(blockHash, position))
+ } else {
+ store.batch.Set(CalcGlobalTxIndexKey(globalTxID), CalcGlobalTxIndex(blockHash, position))
+ }
+}
+
+// SetRecoveryStatus set recovery status
+func (store *MockWalletStore) SetRecoveryStatus(recoveryState *RecoveryState) error {
+ rawStatus, err := json.Marshal(recoveryState)
if err != nil {
- t.Fatal(err)
+ return err
+ }
+ if store.batch == nil {
+ store.db.Set(RecoveryKey, rawStatus)
+ } else {
+ store.batch.Set(RecoveryKey, rawStatus)
+ }
+ return nil
+}
+
+// SetTransaction set raw transaction by block height and tx position
+func (store *MockWalletStore) SetTransaction(height uint64, tx *query.AnnotatedTx) error {
+ batch := store.db.NewBatch()
+ if store.batch != nil {
+ batch = store.batch
}
- xpub1, _, err := hsm.XCreate("test_pub1", "password", "en")
+ rawTx, err := json.Marshal(tx)
if err != nil {
- t.Fatal(err)
+ return err
}
+ batch.Set(calcAnnotatedKey(formatKey(height, tx.Position)), rawTx)
+ batch.Set(calcTxIndexKey(tx.ID.String()), []byte(formatKey(height, tx.Position)))
+
+ if store.batch == nil {
+ batch.Write()
+ }
+ return nil
+}
- testAccount, err := accountManager.Create([]chainkd.XPub{xpub1.XPub}, 1, "testAccount", signers.BIP0044)
+// SetUnconfirmedTransaction set unconfirmed tx by txID
+func (store *MockWalletStore) SetUnconfirmedTransaction(txID string, tx *query.AnnotatedTx) error {
+ rawTx, err := json.Marshal(tx)
if err != nil {
- t.Fatal(err)
+ return err
+ }
+ if store.batch == nil {
+ store.db.Set(calcUnconfirmedTxKey(txID), rawTx)
+ } else {
+ store.batch.Set(calcUnconfirmedTxKey(txID), rawTx)
}
+ return nil
+}
- controlProg, err := accountManager.CreateAddress(testAccount.ID, false)
+// SetWalletInfo get wallet information
+func (store *MockWalletStore) SetWalletInfo(status *StatusInfo) error {
+ rawWallet, err := json.Marshal(status)
if err != nil {
- t.Fatal(err)
+ return err
+ }
+
+ if store.batch == nil {
+ store.db.Set([]byte(WalletKey), rawWallet)
+ } else {
+ store.batch.Set([]byte(WalletKey), rawWallet)
}
+ return nil
+}
+
+type MockAccountStore struct {
+ db dbm.DB
+ batch dbm.Batch
+}
- controlProg.KeyIndex = 1
+// NewAccountStore create new MockAccountStore.
+func NewMockAccountStore(db dbm.DB) *MockAccountStore {
+ return &MockAccountStore{
+ db: db,
+ batch: nil,
+ }
+}
- reg := asset.NewRegistry(testDB, chain)
- asset := bc.AssetID{V0: 5}
+// InitBatch initial new account store
+func (store *MockAccountStore) InitBatch() acc.AccountStore {
+ newStore := NewMockAccountStore(store.db)
+ newStore.batch = newStore.db.NewBatch()
+ return newStore
+}
- utxos := []*account.UTXO{}
- btmUtxo := mockUTXO(controlProg, consensus.BTMAssetID)
- utxos = append(utxos, btmUtxo)
- OtherUtxo := mockUTXO(controlProg, &asset)
- utxos = append(utxos, OtherUtxo)
+// CommitBatch commit batch
+func (store *MockAccountStore) CommitBatch() error {
+ if store.batch == nil {
+ return errors.New("MockAccountStore commit fail, store batch is nil.")
+ }
+ store.batch.Write()
+ store.batch = nil
+ return nil
+}
- _, txData, err := mockTxData(utxos, testAccount)
- if err != nil {
- t.Fatal(err)
+// DeleteAccount set account account ID, account alias and raw account.
+func (store *MockAccountStore) DeleteAccount(account *acc.Account) error {
+ batch := store.db.NewBatch()
+ if store.batch != nil {
+ batch = store.batch
}
- tx := types.NewTx(*txData)
- //block := mockSingleBlock(tx)
- txStatus := bc.NewTransactionStatus()
- txStatus.SetStatus(0, false)
- w, err := NewWallet(testDB, accountManager, reg, hsm, chain, dispatcher, false)
- go w.memPoolTxQueryLoop()
- w.eventDispatcher.Post(protocol.TxMsgEvent{TxMsg: &protocol.TxPoolMsg{TxDesc: &protocol.TxDesc{Tx: tx}, MsgType: protocol.MsgNewTx}})
- time.Sleep(time.Millisecond * 10)
- if _, err = w.GetUnconfirmedTxByTxID(tx.ID.String()); err != nil {
- t.Fatal("disaptch new tx msg error:", err)
- }
- w.eventDispatcher.Post(protocol.TxMsgEvent{TxMsg: &protocol.TxPoolMsg{TxDesc: &protocol.TxDesc{Tx: tx}, MsgType: protocol.MsgRemoveTx}})
- time.Sleep(time.Millisecond * 10)
- txs, err := w.GetUnconfirmedTxs(testAccount.ID)
+ // delete account utxos
+ store.deleteAccountUTXOs(account.ID, batch)
+
+ // delete account control program
+ if err := store.deleteAccountControlPrograms(account.ID, batch); err != nil {
+ return err
+ }
+
+ // delete bip44 contract index
+ batch.Delete(Bip44ContractIndexKey(account.ID, false))
+ batch.Delete(Bip44ContractIndexKey(account.ID, true))
+
+ // delete contract index
+ batch.Delete(contractIndexKey(account.ID))
+
+ // delete account id
+ batch.Delete(AccountIDKey(account.ID))
+ batch.Delete(accountAliasKey(account.Alias))
+ if store.batch == nil {
+ batch.Write()
+ }
+ return nil
+}
+
+// deleteAccountUTXOs delete account utxos by accountID
+func (store *MockAccountStore) deleteAccountUTXOs(accountID string, batch dbm.Batch) error {
+ accountUtxoIter := store.db.IteratorPrefix([]byte(UTXOPrefix))
+ defer accountUtxoIter.Release()
+
+ for accountUtxoIter.Next() {
+ accountUtxo := new(acc.UTXO)
+ if err := json.Unmarshal(accountUtxoIter.Value(), accountUtxo); err != nil {
+ return err
+ }
+
+ if accountID == accountUtxo.AccountID {
+ batch.Delete(StandardUTXOKey(accountUtxo.OutputID))
+ }
+ }
+
+ return nil
+}
+
+// deleteAccountControlPrograms deletes account control program
+func (store *MockAccountStore) deleteAccountControlPrograms(accountID string, batch dbm.Batch) error {
+ cps, err := store.ListControlPrograms()
if err != nil {
- t.Fatal("get unconfirmed tx error:", err)
+ return err
}
- if len(txs) != 0 {
- t.Fatal("disaptch remove tx msg error")
+ var hash [32]byte
+ for _, cp := range cps {
+ if cp.AccountID == accountID {
+ sha3pool.Sum256(hash[:], cp.ControlProgram)
+ batch.Delete(ContractKey(bc.NewHash(hash)))
+ }
}
+ return nil
+}
- w.eventDispatcher.Post(protocol.TxMsgEvent{TxMsg: &protocol.TxPoolMsg{TxDesc: &protocol.TxDesc{Tx: tx}, MsgType: 2}})
+// 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))
+ }
}
-func mockUTXO(controlProg *account.CtrlProgram, assetID *bc.AssetID) *account.UTXO {
- utxo := &account.UTXO{}
- utxo.OutputID = bc.Hash{V0: 1}
- utxo.SourceID = bc.Hash{V0: 2}
- utxo.AssetID = *assetID
- utxo.Amount = 1000000000
- utxo.SourcePos = 0
- utxo.ControlProgram = controlProg.ControlProgram
- utxo.AccountID = controlProg.AccountID
- utxo.Address = controlProg.Address
- utxo.ControlProgramIndex = controlProg.KeyIndex
- return utxo
+// GetAccountByAlias get account by account alias
+func (store *MockAccountStore) GetAccountByAlias(accountAlias string) (*acc.Account, error) {
+ accountID := store.db.Get(accountAliasKey(accountAlias))
+ if accountID == nil {
+ return nil, acc.ErrFindAccount
+ }
+ return store.GetAccountByID(string(accountID))
}
-func mockTxData(utxos []*account.UTXO, testAccount *account.Account) (*txbuilder.Template, *types.TxData, error) {
- tplBuilder := txbuilder.NewBuilder(time.Now())
+// GetAccountByID get account by accountID
+func (store *MockAccountStore) GetAccountByID(accountID string) (*acc.Account, error) {
+ rawAccount := store.db.Get(AccountIDKey(accountID))
+ if rawAccount == nil {
+ return nil, acc.ErrFindAccount
+ }
+ account := new(acc.Account)
+ if err := json.Unmarshal(rawAccount, account); err != nil {
+ return nil, err
+ }
+ return account, nil
+}
- for _, utxo := range utxos {
- txInput, sigInst, err := account.UtxoToInputs(testAccount.Signer, utxo)
- if err != nil {
- return nil, nil, err
+// GetAccountIndex get account index by account xpubs
+func (store *MockAccountStore) GetAccountIndex(xpubs []chainkd.XPub) uint64 {
+ currentIndex := uint64(0)
+ if rawIndexBytes := store.db.Get(accountIndexKey(xpubs)); rawIndexBytes != nil {
+ currentIndex = common.BytesToUnit64(rawIndexBytes)
+ }
+ return currentIndex
+}
+
+// GetBip44ContractIndex get bip44 contract index
+func (store *MockAccountStore) GetBip44ContractIndex(accountID string, change bool) uint64 {
+ index := uint64(0)
+ if rawIndexBytes := store.db.Get(Bip44ContractIndexKey(accountID, change)); rawIndexBytes != nil {
+ index = common.BytesToUnit64(rawIndexBytes)
+ }
+ return index
+}
+
+// GetCoinbaseArbitrary get coinbase arbitrary
+func (store *MockAccountStore) GetCoinbaseArbitrary() []byte {
+ return store.db.Get([]byte(CoinbaseAbKey))
+}
+
+// GetContractIndex get contract index
+func (store *MockAccountStore) GetContractIndex(accountID string) uint64 {
+ index := uint64(0)
+ if rawIndexBytes := store.db.Get(contractIndexKey(accountID)); rawIndexBytes != nil {
+ index = common.BytesToUnit64(rawIndexBytes)
+ }
+ return index
+}
+
+// GetControlProgram get control program
+func (store *MockAccountStore) GetControlProgram(hash bc.Hash) (*acc.CtrlProgram, error) {
+ rawProgram := store.db.Get(ContractKey(hash))
+ if rawProgram == nil {
+ return nil, acc.ErrFindCtrlProgram
+ }
+ cp := new(acc.CtrlProgram)
+ if err := json.Unmarshal(rawProgram, cp); err != nil {
+ return nil, err
+ }
+ return cp, nil
+}
+
+// GetMiningAddress get mining address
+func (store *MockAccountStore) GetMiningAddress() (*acc.CtrlProgram, error) {
+ rawCP := store.db.Get([]byte(MiningAddressKey))
+ if rawCP == nil {
+ return nil, acc.ErrFindMiningAddress
+ }
+ cp := new(acc.CtrlProgram)
+ if err := json.Unmarshal(rawCP, cp); err != nil {
+ return nil, err
+ }
+ return cp, nil
+}
+
+// GetUTXO get standard utxo by id
+func (store *MockAccountStore) GetUTXO(outid bc.Hash) (*acc.UTXO, error) {
+ u := new(acc.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, acc.ErrMatchUTXO
+}
+
+// ListAccounts get all accounts which name prfix is id.
+func (store *MockAccountStore) ListAccounts(id string) ([]*acc.Account, error) {
+ accounts := []*acc.Account{}
+ accountIter := store.db.IteratorPrefix(AccountIDKey(strings.TrimSpace(id)))
+ defer accountIter.Release()
+
+ for accountIter.Next() {
+ account := new(acc.Account)
+ if err := json.Unmarshal(accountIter.Value(), &account); err != nil {
+ return nil, err
}
- tplBuilder.AddInput(txInput, sigInst)
+ accounts = append(accounts, account)
+ }
+ return accounts, nil
+}
- out := &types.TxOutput{}
- if utxo.AssetID == *consensus.BTMAssetID {
- out = types.NewIntraChainOutput(utxo.AssetID, 100, utxo.ControlProgram)
+// ListControlPrograms get all local control programs
+func (store *MockAccountStore) ListControlPrograms() ([]*acc.CtrlProgram, error) {
+ cps := []*acc.CtrlProgram{}
+ cpIter := store.db.IteratorPrefix([]byte(ContractPrefix))
+ defer cpIter.Release()
+
+ for cpIter.Next() {
+ cp := new(acc.CtrlProgram)
+ if err := json.Unmarshal(cpIter.Value(), cp); err != nil {
+ return nil, err
+ }
+ cps = append(cps, cp)
+ }
+ return cps, nil
+}
+
+// ListUTXOs get utxos by accountID
+func (store *MockAccountStore) ListUTXOs() ([]*acc.UTXO, error) {
+ utxoIter := store.db.IteratorPrefix([]byte(UTXOPrefix))
+ defer utxoIter.Release()
+
+ utxos := []*acc.UTXO{}
+ for utxoIter.Next() {
+ utxo := new(acc.UTXO)
+ if err := json.Unmarshal(utxoIter.Value(), utxo); err != nil {
+ return nil, err
+ }
+ utxos = append(utxos, utxo)
+ }
+ return utxos, nil
+}
+
+// SetAccount set account account ID, account alias and raw account.
+func (store *MockAccountStore) SetAccount(account *acc.Account) error {
+ rawAccount, err := json.Marshal(account)
+ if err != nil {
+ return acc.ErrMarshalAccount
+ }
+
+ batch := store.db.NewBatch()
+ if store.batch != nil {
+ batch = store.batch
+ }
+
+ batch.Set(AccountIDKey(account.ID), rawAccount)
+ batch.Set(accountAliasKey(account.Alias), []byte(account.ID))
+
+ if store.batch == nil {
+ batch.Write()
+ }
+ return nil
+}
+
+// SetAccountIndex update account index
+func (store *MockAccountStore) SetAccountIndex(account *acc.Account) {
+ currentIndex := store.GetAccountIndex(account.XPubs)
+ if account.KeyIndex > currentIndex {
+ if store.batch == nil {
+ store.db.Set(accountIndexKey(account.XPubs), common.Unit64ToBytes(account.KeyIndex))
} else {
- out = types.NewIntraChainOutput(utxo.AssetID, utxo.Amount, utxo.ControlProgram)
+ store.batch.Set(accountIndexKey(account.XPubs), common.Unit64ToBytes(account.KeyIndex))
}
- tplBuilder.AddOutput(out)
}
+}
- return tplBuilder.Build()
+// SetBip44ContractIndex set contract index
+func (store *MockAccountStore) SetBip44ContractIndex(accountID string, change bool, index uint64) {
+ if store.batch == nil {
+ store.db.Set(Bip44ContractIndexKey(accountID, change), common.Unit64ToBytes(index))
+ } else {
+ store.batch.Set(Bip44ContractIndexKey(accountID, change), common.Unit64ToBytes(index))
+ }
}
-func mockWallet(walletDB dbm.DB, account *account.Manager, asset *asset.Registry, chain *protocol.Chain, dispatcher *event.Dispatcher, txIndexFlag bool) *Wallet {
- wallet := &Wallet{
- DB: walletDB,
- AccountMgr: account,
- AssetReg: asset,
- chain: chain,
- RecoveryMgr: newRecoveryManager(walletDB, account),
- eventDispatcher: dispatcher,
- TxIndexFlag: txIndexFlag,
+// SetCoinbaseArbitrary set coinbase arbitrary
+func (store *MockAccountStore) SetCoinbaseArbitrary(arbitrary []byte) {
+ if store.batch == nil {
+ store.db.Set([]byte(CoinbaseAbKey), arbitrary)
+ } else {
+ store.batch.Set([]byte(CoinbaseAbKey), arbitrary)
}
- wallet.txMsgSub, _ = wallet.eventDispatcher.Subscribe(protocol.TxMsgEvent{})
- return wallet
}
-func mockSingleBlock(tx *types.Tx) *types.Block {
- return &types.Block{
- BlockHeader: types.BlockHeader{
- Version: 1,
- Height: 1,
- },
- Transactions: []*types.Tx{config.GenesisTx(), tx},
+// SetContractIndex set contract index
+func (store *MockAccountStore) SetContractIndex(accountID string, index uint64) {
+ if store.batch == nil {
+ store.db.Set(contractIndexKey(accountID), common.Unit64ToBytes(index))
+ } else {
+ store.batch.Set(contractIndexKey(accountID), common.Unit64ToBytes(index))
+ }
+}
+
+// SetControlProgram set raw program
+func (store *MockAccountStore) SetControlProgram(hash bc.Hash, program *acc.CtrlProgram) error {
+ accountCP, err := json.Marshal(program)
+ if err != nil {
+ return err
+ }
+ if store.batch == nil {
+ store.db.Set(ContractKey(hash), accountCP)
+ } else {
+ store.batch.Set(ContractKey(hash), accountCP)
+ }
+ return nil
+}
+
+// SetMiningAddress set mining address
+func (store *MockAccountStore) SetMiningAddress(program *acc.CtrlProgram) error {
+ rawProgram, err := json.Marshal(program)
+ if err != nil {
+ return err
+ }
+
+ if store.batch == nil {
+ store.db.Set([]byte(MiningAddressKey), rawProgram)
+ } else {
+ store.batch.Set([]byte(MiningAddressKey), rawProgram)
+ }
+ return nil
+}
+
+// SetStandardUTXO set standard utxo
+func (store *MockAccountStore) SetStandardUTXO(outputID bc.Hash, utxo *acc.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
}