OSDN Git Service

Try (#1165)
[bytom/bytom.git] / account / accounts.go
index 8e90848..5990868 100644 (file)
@@ -2,11 +2,9 @@
 package account
 
 import (
-       "context"
        "encoding/json"
        "strings"
        "sync"
-       "time"
 
        "github.com/golang/groupcache/lru"
        log "github.com/sirupsen/logrus"
@@ -16,11 +14,13 @@ import (
        "github.com/bytom/blockchain/txbuilder"
        "github.com/bytom/common"
        "github.com/bytom/consensus"
+       "github.com/bytom/consensus/segwit"
        "github.com/bytom/crypto"
        "github.com/bytom/crypto/ed25519/chainkd"
        "github.com/bytom/crypto/sha3pool"
        "github.com/bytom/errors"
        "github.com/bytom/protocol"
+       "github.com/bytom/protocol/bc"
        "github.com/bytom/protocol/vm/vmutil"
 )
 
@@ -39,9 +39,11 @@ var (
 
 // pre-define errors for supporting bytom errorFormatter
 var (
-       ErrDuplicateAlias = errors.New("duplicate account alias")
-       ErrFindAccount    = errors.New("fail to find account")
-       ErrMarshalAccount = errors.New("failed marshal account")
+       ErrDuplicateAlias  = errors.New("duplicate account alias")
+       ErrFindAccount     = errors.New("fail to find account")
+       ErrMarshalAccount  = errors.New("failed marshal account")
+       ErrInvalidAddress  = errors.New("invalid address")
+       ErrFindCtrlProgram = errors.New("fail to find account control program")
 )
 
 func aliasKey(name string) []byte {
@@ -62,23 +64,27 @@ func contractIndexKey(accountID string) []byte {
        return append(contractIndexPrefix, []byte(accountID)...)
 }
 
-// NewManager creates a new account manager
-func NewManager(walletDB dbm.DB, chain *protocol.Chain) *Manager {
-       return &Manager{
-               db:          walletDB,
-               chain:       chain,
-               utxoDB:      newReserver(chain, walletDB),
-               cache:       lru.New(maxAccountCache),
-               aliasCache:  lru.New(maxAccountCache),
-               delayedACPs: make(map[*txbuilder.TemplateBuilder][]*CtrlProgram),
-       }
+// Account is structure of Bytom account
+type Account struct {
+       *signers.Signer
+       ID    string `json:"id"`
+       Alias string `json:"alias"`
+}
+
+//CtrlProgram is structure of account control program
+type CtrlProgram struct {
+       AccountID      string
+       Address        string
+       KeyIndex       uint64
+       ControlProgram []byte
+       Change         bool // Mark whether this control program is for UTXO change
 }
 
 // Manager stores accounts and their associated control programs.
 type Manager struct {
-       db     dbm.DB
-       chain  *protocol.Chain
-       utxoDB *reserver
+       db         dbm.DB
+       chain      *protocol.Chain
+       utxoKeeper *utxoKeeper
 
        cacheMu    sync.Mutex
        cache      *lru.Cache
@@ -91,59 +97,25 @@ type Manager struct {
        accountMu  sync.Mutex
 }
 
-// ExpireReservations removes reservations that have expired periodically.
-// It blocks until the context is canceled.
-func (m *Manager) ExpireReservations(ctx context.Context, period time.Duration) {
-       ticks := time.Tick(period)
-       for {
-               select {
-               case <-ctx.Done():
-                       log.Info("Deposed, ExpireReservations exiting")
-                       return
-               case <-ticks:
-                       err := m.utxoDB.ExpireReservations(ctx)
-                       if err != nil {
-                               log.WithField("error", err).Error("Expire reservations")
-                       }
-               }
-       }
-}
-
-// Account is structure of Bytom account
-type Account struct {
-       *signers.Signer
-       ID    string `json:"id"`
-       Alias string `json:"alias"`
-}
-
-func (m *Manager) getNextAccountIndex() uint64 {
-       m.accIndexMu.Lock()
-       defer m.accIndexMu.Unlock()
-
-       var nextIndex uint64 = 1
-       if rawIndexBytes := m.db.Get(accountIndexKey); rawIndexBytes != nil {
-               nextIndex = common.BytesToUnit64(rawIndexBytes) + 1
+// NewManager creates a new account manager
+func NewManager(walletDB dbm.DB, chain *protocol.Chain) *Manager {
+       return &Manager{
+               db:          walletDB,
+               chain:       chain,
+               utxoKeeper:  newUtxoKeeper(chain.BestBlockHeight, walletDB),
+               cache:       lru.New(maxAccountCache),
+               aliasCache:  lru.New(maxAccountCache),
+               delayedACPs: make(map[*txbuilder.TemplateBuilder][]*CtrlProgram),
        }
-
-       m.db.Set(accountIndexKey, common.Unit64ToBytes(nextIndex))
-       return nextIndex
 }
 
-func (m *Manager) getNextContractIndex(accountID string) uint64 {
-       m.accIndexMu.Lock()
-       defer m.accIndexMu.Unlock()
-
-       nextIndex := uint64(1)
-       if rawIndexBytes := m.db.Get(contractIndexKey(accountID)); rawIndexBytes != nil {
-               nextIndex = common.BytesToUnit64(rawIndexBytes) + 1
-       }
-
-       m.db.Set(contractIndexKey(accountID), common.Unit64ToBytes(nextIndex))
-       return nextIndex
+// AddUnconfirmedUtxo add untxo list to utxoKeeper
+func (m *Manager) AddUnconfirmedUtxo(utxos []*UTXO) {
+       m.utxoKeeper.AddUnconfirmedUtxo(utxos)
 }
 
 // Create creates a new Account.
-func (m *Manager) Create(ctx context.Context, xpubs []chainkd.XPub, quorum int, alias string) (*Account, error) {
+func (m *Manager) Create(xpubs []chainkd.XPub, quorum int, alias string) (*Account, error) {
        m.accountMu.Lock()
        defer m.accountMu.Unlock()
 
@@ -163,23 +135,51 @@ func (m *Manager) Create(ctx context.Context, xpubs []chainkd.XPub, quorum int,
        if err != nil {
                return nil, ErrMarshalAccount
        }
-       storeBatch := m.db.NewBatch()
 
        accountID := Key(id)
+       storeBatch := m.db.NewBatch()
        storeBatch.Set(accountID, rawAccount)
-       storeBatch.Set(aliasKey(alias), []byte(id))
+       storeBatch.Set(aliasKey(normalizedAlias), []byte(id))
        storeBatch.Write()
-
        return account, nil
 }
 
+// CreateAddress generate an address for the select account
+func (m *Manager) CreateAddress(accountID string, change bool) (cp *CtrlProgram, err error) {
+       account, err := m.FindByID(accountID)
+       if err != nil {
+               return nil, err
+       }
+       return m.createAddress(account, change)
+}
+
+// DeleteAccount deletes the account's ID or alias matching accountInfo.
+func (m *Manager) DeleteAccount(aliasOrID string) (err error) {
+       account := &Account{}
+       if account, err = m.FindByAlias(aliasOrID); err != nil {
+               if account, err = m.FindByID(aliasOrID); 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
+}
+
 // FindByAlias retrieves an account's Signer record by its alias
-func (m *Manager) FindByAlias(ctx context.Context, alias string) (*Account, error) {
+func (m *Manager) FindByAlias(alias string) (*Account, error) {
        m.cacheMu.Lock()
        cachedID, ok := m.aliasCache.Get(alias)
        m.cacheMu.Unlock()
        if ok {
-               return m.FindByID(ctx, cachedID.(string))
+               return m.FindByID(cachedID.(string))
        }
 
        rawID := m.db.Get(aliasKey(alias))
@@ -191,11 +191,11 @@ func (m *Manager) FindByAlias(ctx context.Context, alias string) (*Account, erro
        m.cacheMu.Lock()
        m.aliasCache.Add(alias, accountID)
        m.cacheMu.Unlock()
-       return m.FindByID(ctx, accountID)
+       return m.FindByID(accountID)
 }
 
 // FindByID returns an account's Signer record by its ID.
-func (m *Manager) FindByID(ctx context.Context, id string) (*Account, error) {
+func (m *Manager) FindByID(id string) (*Account, error) {
        m.cacheMu.Lock()
        cachedAccount, ok := m.cache.Get(id)
        m.cacheMu.Unlock()
@@ -219,57 +219,185 @@ func (m *Manager) FindByID(ctx context.Context, id string) (*Account, error) {
        return account, nil
 }
 
-// GetAliasByID return the account alias by given ID
-func (m *Manager) GetAliasByID(id string) string {
+// 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)
+}
 
+// GetAliasByID return the account alias by given ID
+func (m *Manager) GetAliasByID(id string) string {
        rawAccount := m.db.Get(Key(id))
        if rawAccount == nil {
-               log.Warn("fail to find account")
+               log.Warn("GetAliasByID fail to find account")
                return ""
        }
 
+       account := &Account{}
        if err := json.Unmarshal(rawAccount, account); err != nil {
                log.Warn(err)
-               return ""
        }
        return account.Alias
 }
 
-// CreateAddress generate an address for the select account
-func (m *Manager) CreateAddress(ctx context.Context, accountID string, change bool) (cp *CtrlProgram, err error) {
-       account, err := m.FindByID(ctx, accountID)
+// GetCoinbaseControlProgram will return a coinbase script
+func (m *Manager) GetCoinbaseControlProgram() ([]byte, error) {
+       if data := m.db.Get(miningAddressKey); data != nil {
+               cp := &CtrlProgram{}
+               return cp.ControlProgram, json.Unmarshal(data, cp)
+       }
+
+       accountIter := m.db.IteratorPrefix([]byte(accountPrefix))
+       defer accountIter.Release()
+       if !accountIter.Next() {
+               log.Warningf("GetCoinbaseControlProgram: can't find any account in db")
+               return vmutil.DefaultCoinbaseProgram()
+       }
+
+       account := &Account{}
+       if err := json.Unmarshal(accountIter.Value(), account); err != nil {
+               return nil, err
+       }
+
+       program, err := m.createAddress(account, false)
+       if err != nil {
+               return nil, err
+       }
+
+       rawCP, err := json.Marshal(program)
        if err != nil {
                return nil, err
        }
-       return m.createAddress(ctx, account, change)
+
+       m.db.Set(miningAddressKey, rawCP)
+       return program.ControlProgram, nil
 }
 
-// CreateAddress generate an address for the select account
-func (m *Manager) createAddress(ctx context.Context, account *Account, change bool) (cp *CtrlProgram, err error) {
-       if len(account.XPubs) == 1 {
-               cp, err = m.createP2PKH(ctx, account, change)
-       } else {
-               cp, err = m.createP2SH(ctx, account, change)
+// GetContractIndex return the current index
+func (m *Manager) GetContractIndex(accountID string) uint64 {
+       m.accIndexMu.Lock()
+       defer m.accIndexMu.Unlock()
+
+       index := uint64(1)
+       if rawIndexBytes := m.db.Get(contractIndexKey(accountID)); rawIndexBytes != nil {
+               index = common.BytesToUnit64(rawIndexBytes)
        }
+       return index
+}
+
+// GetProgramByAddress return CtrlProgram by given address
+func (m *Manager) GetProgramByAddress(address string) (*CtrlProgram, error) {
+       addr, err := common.DecodeAddress(address, &consensus.ActiveNetParams)
        if err != nil {
                return nil, err
        }
 
-       if err = m.insertAccountControlProgram(ctx, cp); err != nil {
+       redeemContract := addr.ScriptAddress()
+       program := []byte{}
+       switch addr.(type) {
+       case *common.AddressWitnessPubKeyHash:
+               program, err = vmutil.P2WPKHProgram(redeemContract)
+       case *common.AddressWitnessScriptHash:
+               program, err = vmutil.P2WSHProgram(redeemContract)
+       default:
+               return nil, ErrInvalidAddress
+       }
+       if err != nil {
                return nil, err
        }
-       return cp, nil
+
+       var hash [32]byte
+       sha3pool.Sum256(hash[:], program)
+       rawProgram := m.db.Get(ContractKey(hash))
+       if rawProgram == nil {
+               return nil, ErrFindCtrlProgram
+       }
+
+       cp := &CtrlProgram{}
+       return cp, json.Unmarshal(rawProgram, cp)
+}
+
+// IsLocalControlProgram check is the input control program belong to local
+func (m *Manager) IsLocalControlProgram(prog []byte) bool {
+       var hash common.Hash
+       sha3pool.Sum256(hash[:], prog)
+       bytes := m.db.Get(ContractKey(hash))
+       return bytes != nil
 }
 
-func (m *Manager) createP2PKH(ctx context.Context, account *Account, change bool) (*CtrlProgram, error) {
+// 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
+}
+
+// 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
+}
+
+func (m *Manager) ListUnconfirmedUtxo(isSmartContract bool) []*UTXO {
+       utxos := m.utxoKeeper.ListUnconfirmed()
+       result := []*UTXO{}
+       for _, utxo := range utxos {
+               if segwit.IsP2WScript(utxo.ControlProgram) != isSmartContract {
+                       result = append(result, utxo)
+               }
+       }
+       return result
+}
+
+// RemoveUnconfirmedUtxo remove utxos from the utxoKeeper
+func (m *Manager) RemoveUnconfirmedUtxo(hashes []*bc.Hash) {
+       m.utxoKeeper.RemoveUnconfirmedUtxo(hashes)
+}
+
+// CreateAddress generate an address for the select account
+func (m *Manager) createAddress(account *Account, change bool) (cp *CtrlProgram, err error) {
+       if len(account.XPubs) == 1 {
+               cp, err = m.createP2PKH(account, change)
+       } else {
+               cp, err = m.createP2SH(account, change)
+       }
+       if err != nil {
+               return nil, err
+       }
+       return cp, m.insertControlPrograms(cp)
+}
+
+func (m *Manager) createP2PKH(account *Account, change bool) (*CtrlProgram, error) {
        idx := m.getNextContractIndex(account.ID)
        path := signers.Path(account.Signer, signers.AccountKeySpace, idx)
        derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
        derivedPK := derivedXPubs[0].PublicKey()
        pubHash := crypto.Ripemd160(derivedPK)
 
-       // TODO: pass different params due to config
        address, err := common.NewAddressWitnessPubKeyHash(pubHash, &consensus.ActiveNetParams)
        if err != nil {
                return nil, err
@@ -289,7 +417,7 @@ func (m *Manager) createP2PKH(ctx context.Context, account *Account, change bool
        }, nil
 }
 
-func (m *Manager) createP2SH(ctx context.Context, account *Account, change bool) (*CtrlProgram, error) {
+func (m *Manager) createP2SH(account *Account, change bool) (*CtrlProgram, error) {
        idx := m.getNextContractIndex(account.ID)
        path := signers.Path(account.Signer, signers.AccountKeySpace, idx)
        derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
@@ -300,7 +428,6 @@ func (m *Manager) createP2SH(ctx context.Context, account *Account, change bool)
        }
        scriptHash := crypto.Sha256(signScript)
 
-       // TODO: pass different params due to config
        address, err := common.NewAddressWitnessScriptHash(scriptHash, &consensus.ActiveNetParams)
        if err != nil {
                return nil, err
@@ -320,16 +447,31 @@ func (m *Manager) createP2SH(ctx context.Context, account *Account, change bool)
        }, nil
 }
 
-//CtrlProgram is structure of account control program
-type CtrlProgram struct {
-       AccountID      string
-       Address        string
-       KeyIndex       uint64
-       ControlProgram []byte
-       Change         bool // Mark whether this control program is for UTXO change
+func (m *Manager) getNextAccountIndex() uint64 {
+       m.accIndexMu.Lock()
+       defer m.accIndexMu.Unlock()
+
+       var nextIndex uint64 = 1
+       if rawIndexBytes := m.db.Get(accountIndexKey); rawIndexBytes != nil {
+               nextIndex = common.BytesToUnit64(rawIndexBytes) + 1
+       }
+       m.db.Set(accountIndexKey, common.Unit64ToBytes(nextIndex))
+       return nextIndex
+}
+
+func (m *Manager) getNextContractIndex(accountID string) uint64 {
+       m.accIndexMu.Lock()
+       defer m.accIndexMu.Unlock()
+
+       nextIndex := uint64(1)
+       if rawIndexBytes := m.db.Get(contractIndexKey(accountID)); rawIndexBytes != nil {
+               nextIndex = common.BytesToUnit64(rawIndexBytes) + 1
+       }
+       m.db.Set(contractIndexKey(accountID), common.Unit64ToBytes(nextIndex))
+       return nextIndex
 }
 
-func (m *Manager) insertAccountControlProgram(ctx context.Context, progs ...*CtrlProgram) error {
+func (m *Manager) insertControlPrograms(progs ...*CtrlProgram) error {
        var hash common.Hash
        for _, prog := range progs {
                accountCP, err := json.Marshal(prog)
@@ -342,100 +484,3 @@ func (m *Manager) insertAccountControlProgram(ctx context.Context, progs ...*Ctr
        }
        return nil
 }
-
-// IsLocalControlProgram check is the input control program belong to local
-func (m *Manager) IsLocalControlProgram(prog []byte) bool {
-       var hash common.Hash
-       sha3pool.Sum256(hash[:], prog)
-       bytes := m.db.Get(ContractKey(hash))
-       return bytes != nil
-}
-
-// GetCoinbaseControlProgram will return a coinbase script
-func (m *Manager) GetCoinbaseControlProgram() ([]byte, error) {
-       if data := m.db.Get(miningAddressKey); data != nil {
-               cp := &CtrlProgram{}
-               return cp.ControlProgram, json.Unmarshal(data, cp)
-       }
-
-       accountIter := m.db.IteratorPrefix([]byte(accountPrefix))
-       defer accountIter.Release()
-       if !accountIter.Next() {
-               log.Warningf("GetCoinbaseControlProgram: can't find any account in db")
-               return vmutil.DefaultCoinbaseProgram()
-       }
-
-       account := &Account{}
-       if err := json.Unmarshal(accountIter.Value(), account); err != nil {
-               return nil, err
-       }
-
-       program, err := m.createAddress(nil, account, false)
-       if err != nil {
-               return nil, err
-       }
-
-       rawCP, err := json.Marshal(program)
-       if err != nil {
-               return nil, err
-       }
-
-       m.db.Set(miningAddressKey, rawCP)
-       return program.ControlProgram, nil
-}
-
-// DeleteAccount deletes the account's ID or alias matching accountInfo.
-func (m *Manager) DeleteAccount(aliasOrID string) (err error) {
-       account := &Account{}
-       if account, err = m.FindByAlias(nil, aliasOrID); err != nil {
-               if account, err = m.FindByID(nil, aliasOrID); err != nil {
-                       return err
-               }
-       }
-
-       storeBatch := m.db.NewBatch()
-
-       m.cacheMu.Lock()
-       m.aliasCache.Remove(account.Alias)
-       m.cacheMu.Unlock()
-
-       storeBatch.Delete(aliasKey(account.Alias))
-       storeBatch.Delete(Key(account.ID))
-       storeBatch.Write()
-
-       return nil
-}
-
-// 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
-}
-
-// ListControlProgram return all the local control program
-func (m *Manager) ListControlProgram() ([]*CtrlProgram, error) {
-       var 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
-}