OSDN Git Service

update
[bytom/vapor.git] / database / account_store.go
index cb010a8..a02f17a 100644 (file)
 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
 }