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"
)
-// AccountStorer interface contains account storage functions.
-type AccountStorer interface {
- InitBatch()
- CommitBatch()
- SetAccount(string, string, []byte)
- SetAccountIndex([]chainkd.XPub, uint64)
- GetAccountByAccountAlias(string) []byte
- GetAccountByAccountID(string) []byte
- GetAccountIndex([]chainkd.XPub) []byte
- DeleteAccountByAccountAlias(string)
- DeleteAccountByAccountID(string)
- DeleteRawProgram(common.Hash)
- DeleteBip44ContractIndex(string)
- DeleteContractIndex(string)
- GetContractIndex(string) []byte
- GetAccountUTXOs(string) [][]byte
- DeleteStandardUTXO(bc.Hash)
- GetCoinbaseArbitrary() []byte
- SetCoinbaseArbitrary([]byte)
- GetMiningAddress() []byte
- GetFirstAccount() ([]byte, error)
- SetMiningAddress([]byte)
- GetBip44ContractIndex(string, bool) []byte
- GetRawProgram(common.Hash) []byte
- GetAccounts(string) [][]byte
- GetControlPrograms() ([][]byte, error)
- SetRawProgram(common.Hash, []byte)
- SetContractIndex(string, uint64)
- SetBip44ContractIndex(string, bool, uint64)
- GetUTXOs() [][]byte
- GetStandardUTXO(bc.Hash) []byte
- GetContractUTXO(bc.Hash) []byte
- SetStandardUTXO(bc.Hash, []byte)
-}
-
-// AccountStore satisfies AccountStorer interface.
+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()...)
+}
+
+// AccountStore satisfies AccountStore interface.
type AccountStore struct {
- accountDB dbm.DB
- batch dbm.Batch
+ db dbm.DB
+ batch dbm.Batch
}
// NewAccountStore create new AccountStore.
func NewAccountStore(db dbm.DB) *AccountStore {
return &AccountStore{
- accountDB: db,
- batch: nil,
+ db: db,
+ batch: nil,
}
}
-// InitBatch initial batch
-func (store *AccountStore) InitBatch() {
- if store.batch == nil {
- store.batch = store.accountDB.NewBatch()
- }
+// InitStore initial new account store
+func (store *AccountStore) InitStore() acc.AccountStore {
+ newStore := NewAccountStore(store.db)
+ newStore.batch = newStore.db.NewBatch()
+ return newStore
}
-// CommitBatch commit batch
-func (store *AccountStore) CommitBatch() {
- if store.batch != nil {
- store.batch.Write()
- store.batch = nil
- }
-}
-
-// SetAccount set account account ID, account alias and raw account.
-func (store *AccountStore) SetAccount(accountID, accountAlias string, rawAccount []byte) {
- batch := store.accountDB.NewBatch()
- if store.batch != nil {
- batch = store.batch
- }
- batch.Set(AccountIDKey(accountID), rawAccount)
- batch.Set(AccountAliasKey(accountAlias), []byte(accountID))
+// CommitStore commit batch
+func (store *AccountStore) CommitStore() error {
if store.batch == nil {
- batch.Write()
+ 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(accountID, accountAlias string) {
- batch := store.accountDB.NewBatch()
+func (store *AccountStore) DeleteAccount(account *acc.Account) error {
+ batch := store.db.NewBatch()
if store.batch != nil {
batch = store.batch
}
- batch.Delete(AccountIDKey(accountID))
- batch.Delete(AccountAliasKey(accountAlias))
- if store.batch == nil {
- batch.Write()
+
+ // delete account utxos
+ store.deleteAccountUTXOs(account.ID, batch)
+
+ // delete account control program
+ if err := store.deleteAccountControlPrograms(account.ID, batch); err != nil {
+ return err
}
-}
-// SetAccountIndex set account index
-func (store *AccountStore) SetAccountIndex(xpubs []chainkd.XPub, keyIndex uint64) {
+ // 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 {
- store.accountDB.Set(AccountIndexKey(xpubs), common.Unit64ToBytes(keyIndex))
- } else {
- store.batch.Set(AccountIndexKey(xpubs), common.Unit64ToBytes(keyIndex))
+ batch.Write()
}
+ return nil
}
-// GetAccountByAccountAlias get account by account alias
-func (store *AccountStore) GetAccountByAccountAlias(accountAlias string) []byte {
- return store.accountDB.Get(AccountAliasKey(accountAlias))
-}
+// deleteAccountUTXOs delete account utxos by accountID
+func (store *AccountStore) deleteAccountUTXOs(accountID string, batch dbm.Batch) error {
+ accountUtxoIter := store.db.IteratorPrefix(UTXOPrefix)
+ defer accountUtxoIter.Release()
-// GetAccountByAccountID get account by accountID
-func (store *AccountStore) GetAccountByAccountID(accountID string) []byte {
- return store.accountDB.Get(AccountIDKey(accountID))
-}
+ 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))
+ }
+ }
-// GetAccountIndex get account index by account xpubs
-func (store *AccountStore) GetAccountIndex(xpubs []chainkd.XPub) []byte {
- return store.accountDB.Get(AccountIndexKey(xpubs))
+ return nil
}
-// DeleteAccountByAccountAlias delete account by account alias
-func (store *AccountStore) DeleteAccountByAccountAlias(accountAlias string) {
- if store.batch == nil {
- store.accountDB.Delete(AccountAliasKey(accountAlias))
- } else {
- store.batch.Delete(AccountAliasKey(accountAlias))
+// deleteAccountControlPrograms deletes account control program
+func (store *AccountStore) deleteAccountControlPrograms(accountID string, batch dbm.Batch) error {
+ cps, err := store.ListControlPrograms()
+ if err != nil {
+ return err
}
-}
-// DeleteAccountByAccountID delete account by accountID
-func (store *AccountStore) DeleteAccountByAccountID(accountID string) {
- if store.batch == nil {
- store.accountDB.Delete(AccountIDKey(accountID))
- } else {
- store.batch.Delete(AccountIDKey(accountID))
+ 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
}
-// DeleteRawProgram delete raw control program by hash
-func (store *AccountStore) DeleteRawProgram(hash common.Hash) {
+// DeleteStandardUTXO delete utxo by outpu id
+func (store *AccountStore) DeleteStandardUTXO(outputID bc.Hash) {
if store.batch == nil {
- store.accountDB.Delete(ContractKey(hash))
+ store.db.Delete(StandardUTXOKey(outputID))
} else {
- store.batch.Delete(ContractKey(hash))
+ store.batch.Delete(StandardUTXOKey(outputID))
}
}
-// DeleteBip44ContractIndex delete bip44 contract index by accountID
-func (store *AccountStore) DeleteBip44ContractIndex(accountID string) {
- batch := store.accountDB.NewBatch()
- if store.batch != nil {
- batch = store.batch
- }
- batch.Delete(Bip44ContractIndexKey(accountID, false))
- batch.Delete(Bip44ContractIndexKey(accountID, true))
- if store.batch == nil {
- batch.Write()
+// 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))
}
-// DeleteContractIndex delete contract index by accountID
-func (store *AccountStore) DeleteContractIndex(accountID string) {
- if store.batch == nil {
- store.accountDB.Delete(ContractIndexKey(accountID))
- } else {
- store.batch.Delete(ContractIndexKey(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
}
-}
-// GetContractIndex get contract index
-func (store *AccountStore) GetContractIndex(accountID string) []byte {
- return store.accountDB.Get(ContractIndexKey(accountID))
-}
+ account := new(acc.Account)
+ if err := json.Unmarshal(rawAccount, account); err != nil {
+ return nil, err
+ }
-// GetAccountUTXOs get account utxos by account id
-func (store *AccountStore) GetAccountUTXOs(accountID string) [][]byte {
- accountUtxoIter := store.accountDB.IteratorPrefix([]byte(UTXOPrefix))
- defer accountUtxoIter.Release()
+ return account, nil
+}
- utxos := make([][]byte, 0)
- for accountUtxoIter.Next() {
- utxos = append(utxos, accountUtxoIter.Value())
+// 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 utxos
+ return currentIndex
}
-// DeleteStandardUTXO delete utxo by outpu id
-func (store *AccountStore) DeleteStandardUTXO(outputID bc.Hash) {
- if store.batch == nil {
- store.accountDB.Delete(StandardUTXOKey(outputID))
- } else {
- store.batch.Delete(StandardUTXOKey(outputID))
+// 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.accountDB.Get([]byte(CoinbaseAbKey))
+ return store.db.Get(CoinbaseAbKey)
}
-// SetCoinbaseArbitrary set coinbase arbitrary
-func (store *AccountStore) SetCoinbaseArbitrary(arbitrary []byte) {
- if store.batch == nil {
- store.accountDB.Set([]byte(CoinbaseAbKey), arbitrary)
- } else {
- store.batch.Set([]byte(CoinbaseAbKey), arbitrary)
+// 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
}
-// GetMiningAddress get mining address
-func (store *AccountStore) GetMiningAddress() []byte {
- return store.accountDB.Get([]byte(MiningAddressKey))
+// 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
}
-// GetFirstAccount get first account
-func (store *AccountStore) GetFirstAccount() ([]byte, error) {
- accountIter := store.accountDB.IteratorPrefix([]byte(AccountPrefix))
- defer accountIter.Release()
+// GetMiningAddress get mining address
+func (store *AccountStore) GetMiningAddress() (*acc.CtrlProgram, error) {
+ rawCP := store.db.Get(MiningAddressKey)
+ if rawCP == nil {
+ return nil, acc.ErrFindMiningAddress
+ }
- if !accountIter.Next() {
- return nil, ErrFindAccount
+ cp := new(acc.CtrlProgram)
+ if err := json.Unmarshal(rawCP, cp); err != nil {
+ return nil, err
}
- return accountIter.Value(), nil
+
+ return cp, nil
}
-// SetMiningAddress set mining address
-func (store *AccountStore) SetMiningAddress(rawProgram []byte) {
- if store.batch == nil {
- store.accountDB.Set([]byte(MiningAddressKey), rawProgram)
- } else {
- store.batch.Set([]byte(MiningAddressKey), rawProgram)
+// 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)
}
-}
-// GetBip44ContractIndex get bip44 contract index
-func (store *AccountStore) GetBip44ContractIndex(accountID string, change bool) []byte {
- return store.accountDB.Get(Bip44ContractIndexKey(accountID, change))
-}
+ if data := store.db.Get(ContractUTXOKey(outid)); data != nil {
+ return u, json.Unmarshal(data, u)
+ }
-// GetRawProgram get raw control program
-func (store *AccountStore) GetRawProgram(hash common.Hash) []byte {
- return store.accountDB.Get(ContractKey(hash))
+ return nil, acc.ErrMatchUTXO
}
-// GetAccounts get all accounts which name prfix is id.
-func (store *AccountStore) GetAccounts(id string) [][]byte {
- accountIter := store.accountDB.IteratorPrefix(AccountIDKey(strings.TrimSpace(id)))
+// 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()
- accounts := make([][]byte, 0)
for accountIter.Next() {
- accounts = append(accounts, accountIter.Value())
+ account := new(acc.Account)
+ if err := json.Unmarshal(accountIter.Value(), account); err != nil {
+ return nil, err
+ }
+
+ accounts = append(accounts, account)
}
- return accounts
+ return accounts, nil
}
-// GetControlPrograms get all local control programs
-func (store *AccountStore) GetControlPrograms() ([][]byte, error) {
- cpIter := store.accountDB.IteratorPrefix([]byte(ContractPrefix))
+// ListControlPrograms get all local control programs
+func (store *AccountStore) ListControlPrograms() ([]*acc.CtrlProgram, error) {
+ cps := []*acc.CtrlProgram{}
+ cpIter := store.db.IteratorPrefix(ContractPrefix)
defer cpIter.Release()
- cps := make([][]byte, 0)
for cpIter.Next() {
- cps = append(cps, cpIter.Value())
+ cp := new(acc.CtrlProgram)
+ if err := json.Unmarshal(cpIter.Value(), cp); err != nil {
+ return nil, err
+ }
+
+ cps = append(cps, cp)
}
return cps, nil
}
-// SetRawProgram set raw program
-func (store *AccountStore) SetRawProgram(hash common.Hash, program []byte) {
- if store.batch == nil {
- store.accountDB.Set(ContractKey(hash), program)
- } else {
- store.batch.Set(ContractKey(hash), program)
+// 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
}
-// SetContractIndex set contract index
-func (store *AccountStore) SetContractIndex(accountID string, index uint64) {
+// 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 {
- store.accountDB.Set(ContractIndexKey(accountID), common.Unit64ToBytes(index))
- } else {
- store.batch.Set(ContractIndexKey(accountID), common.Unit64ToBytes(index))
+ 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.accountDB.Set(Bip44ContractIndexKey(accountID, change), common.Unit64ToBytes(index))
+ store.db.Set(Bip44ContractIndexKey(accountID, change), common.Unit64ToBytes(index))
} else {
store.batch.Set(Bip44ContractIndexKey(accountID, change), common.Unit64ToBytes(index))
}
}
-// GetUTXOs get utxos by accountID
-func (store *AccountStore) GetUTXOs() [][]byte {
- utxoIter := store.accountDB.IteratorPrefix([]byte(UTXOPrefix))
- defer utxoIter.Release()
+// 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)
+ }
+}
- utxos := make([][]byte, 0)
- for utxoIter.Next() {
- utxos = append(utxos, utxoIter.Value())
+// 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))
}
- return utxos
}
-// GetStandardUTXO get standard utxo by id
-func (store *AccountStore) GetStandardUTXO(outid bc.Hash) []byte {
- return store.accountDB.Get(StandardUTXOKey(outid))
+// 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
}
-// GetContractUTXO get contract utxo
-func (store *AccountStore) GetContractUTXO(outid bc.Hash) []byte {
- return store.accountDB.Get(ContractUTXOKey(outid))
+// 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, data []byte) {
+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.accountDB.Set(StandardUTXOKey(outputID), data)
+ store.db.Set(StandardUTXOKey(outputID), data)
} else {
store.batch.Set(StandardUTXOKey(outputID), data)
}
+ return nil
}