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)
+}