OSDN Git Service

Hulk did something
[bytom/vapor.git] / account / builder.go
diff --git a/account/builder.go b/account/builder.go
new file mode 100644 (file)
index 0000000..ca3c2da
--- /dev/null
@@ -0,0 +1,381 @@
+package account
+
+import (
+       "context"
+       "encoding/json"
+
+       "github.com/vapor/blockchain/signers"
+       "github.com/vapor/blockchain/txbuilder"
+       "github.com/vapor/common"
+       "github.com/vapor/consensus"
+       "github.com/vapor/crypto/ed25519/chainkd"
+       "github.com/vapor/errors"
+       "github.com/vapor/protocol/bc"
+       "github.com/vapor/protocol/bc/types"
+       "github.com/vapor/protocol/vm/vmutil"
+)
+
+var (
+       //chainTxUtxoNum maximum utxo quantity in a tx
+       chainTxUtxoNum = 5
+       //chainTxMergeGas chain tx gas
+       chainTxMergeGas = uint64(10000000)
+)
+
+//DecodeSpendAction unmarshal JSON-encoded data of spend action
+func (m *Manager) DecodeSpendAction(data []byte) (txbuilder.Action, error) {
+       a := &spendAction{accounts: m}
+       return a, json.Unmarshal(data, a)
+}
+
+type spendAction struct {
+       accounts *Manager
+       bc.AssetAmount
+       AccountID      string `json:"account_id"`
+       UseUnconfirmed bool   `json:"use_unconfirmed"`
+}
+
+func (a *spendAction) ActionType() string {
+       return "spend_account"
+}
+
+// MergeSpendAction merge common assetID and accountID spend action
+func MergeSpendAction(actions []txbuilder.Action) []txbuilder.Action {
+       resultActions := []txbuilder.Action{}
+       spendActionMap := make(map[string]*spendAction)
+
+       for _, act := range actions {
+               switch act := act.(type) {
+               case *spendAction:
+                       actionKey := act.AssetId.String() + act.AccountID
+                       if tmpAct, ok := spendActionMap[actionKey]; ok {
+                               tmpAct.Amount += act.Amount
+                               tmpAct.UseUnconfirmed = tmpAct.UseUnconfirmed || act.UseUnconfirmed
+                       } else {
+                               spendActionMap[actionKey] = act
+                               resultActions = append(resultActions, act)
+                       }
+               default:
+                       resultActions = append(resultActions, act)
+               }
+       }
+       return resultActions
+}
+
+//calcMergeGas calculate the gas required that n utxos are merged into one
+func calcMergeGas(num int) uint64 {
+       gas := uint64(0)
+       for num > 1 {
+               gas += chainTxMergeGas
+               num -= chainTxUtxoNum - 1
+       }
+       return gas
+}
+
+func (m *Manager) reserveBtmUtxoChain(builder *txbuilder.TemplateBuilder, accountID string, amount uint64, useUnconfirmed bool) ([]*UTXO, error) {
+       reservedAmount := uint64(0)
+       utxos := []*UTXO{}
+       for gasAmount := uint64(0); reservedAmount < gasAmount+amount; gasAmount = calcMergeGas(len(utxos)) {
+               reserveAmount := amount + gasAmount - reservedAmount
+               res, err := m.utxoKeeper.Reserve(accountID, consensus.BTMAssetID, reserveAmount, useUnconfirmed, builder.MaxTime())
+               if err != nil {
+                       return nil, err
+               }
+
+               builder.OnRollback(func() { m.utxoKeeper.Cancel(res.id) })
+               reservedAmount += reserveAmount + res.change
+               utxos = append(utxos, res.utxos[:]...)
+       }
+       return utxos, nil
+}
+
+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")
+       }
+
+       tpls := []*txbuilder.Template{}
+       if len(utxos) == 1 {
+               return tpls, utxos[len(utxos)-1], nil
+       }
+
+       acp, err := m.GetLocalCtrlProgramByAddress(utxos[0].Address)
+       if err != nil {
+               return nil, nil, err
+       }
+
+       buildAmount := uint64(0)
+       builder := &txbuilder.TemplateBuilder{}
+       for index := 0; index < len(utxos); index++ {
+               input, sigInst, err := UtxoToInputs(signer, utxos[index])
+               if err != nil {
+                       return nil, nil, err
+               }
+
+               if err = builder.AddInput(input, sigInst); err != nil {
+                       return nil, nil, err
+               }
+
+               buildAmount += input.Amount()
+               if builder.InputCount() != chainTxUtxoNum && index != len(utxos)-1 {
+                       continue
+               }
+
+               outAmount := buildAmount - chainTxMergeGas
+               output := types.NewTxOutput(*consensus.BTMAssetID, outAmount, acp.ControlProgram)
+               if err := builder.AddOutput(output); err != nil {
+                       return nil, nil, err
+               }
+
+               tpl, _, err := builder.Build()
+               if err != nil {
+                       return nil, nil, err
+               }
+
+               bcOut, err := tpl.Transaction.Output(*tpl.Transaction.ResultIds[0])
+               if err != nil {
+                       return nil, nil, err
+               }
+
+               utxos = append(utxos, &UTXO{
+                       OutputID:            *tpl.Transaction.ResultIds[0],
+                       AssetID:             *consensus.BTMAssetID,
+                       Amount:              outAmount,
+                       ControlProgram:      acp.ControlProgram,
+                       SourceID:            *bcOut.Source.Ref,
+                       SourcePos:           bcOut.Source.Position,
+                       ControlProgramIndex: acp.KeyIndex,
+                       Address:             acp.Address,
+                       Change:              acp.Change,
+               })
+
+               tpls = append(tpls, tpl)
+               buildAmount = 0
+               builder = &txbuilder.TemplateBuilder{}
+               if index == len(utxos)-2 {
+                       break
+               }
+       }
+       return tpls, utxos[len(utxos)-1], nil
+}
+
+// SpendAccountChain build the spend action with auto merge utxo function
+func SpendAccountChain(ctx context.Context, builder *txbuilder.TemplateBuilder, action txbuilder.Action) ([]*txbuilder.Template, error) {
+       act, ok := action.(*spendAction)
+       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")
+       }
+
+       utxos, err := act.accounts.reserveBtmUtxoChain(builder, act.AccountID, act.Amount, act.UseUnconfirmed)
+       if err != nil {
+               return nil, err
+       }
+
+       acct, err := act.accounts.FindByID(act.AccountID)
+       if err != nil {
+               return nil, err
+       }
+
+       tpls, utxo, err := act.accounts.buildBtmTxChain(utxos, acct.Signer)
+       if err != nil {
+               return nil, err
+       }
+
+       input, sigInst, err := UtxoToInputs(acct.Signer, utxo)
+       if err != nil {
+               return nil, err
+       }
+
+       if err := builder.AddInput(input, sigInst); err != nil {
+               return nil, err
+       }
+
+       if utxo.Amount > act.Amount {
+               if err = builder.AddOutput(types.NewTxOutput(*consensus.BTMAssetID, utxo.Amount-act.Amount, utxo.ControlProgram)); err != nil {
+                       return nil, errors.Wrap(err, "adding change output")
+               }
+       }
+       return tpls, nil
+}
+
+func (a *spendAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
+       var missing []string
+       if a.AccountID == "" {
+               missing = append(missing, "account_id")
+       }
+       if a.AssetId.IsZero() {
+               missing = append(missing, "asset_id")
+       }
+       if len(missing) > 0 {
+               return txbuilder.MissingFieldsError(missing...)
+       }
+
+       acct, err := a.accounts.FindByID(a.AccountID)
+       if err != nil {
+               return errors.Wrap(err, "get account info")
+       }
+
+       res, err := a.accounts.utxoKeeper.Reserve(a.AccountID, a.AssetId, a.Amount, a.UseUnconfirmed, b.MaxTime())
+       if err != nil {
+               return errors.Wrap(err, "reserving utxos")
+       }
+
+       // Cancel the reservation if the build gets rolled back.
+       b.OnRollback(func() { a.accounts.utxoKeeper.Cancel(res.id) })
+       for _, r := range res.utxos {
+               txInput, sigInst, err := UtxoToInputs(acct.Signer, r)
+               if err != nil {
+                       return errors.Wrap(err, "creating inputs")
+               }
+
+               if err = b.AddInput(txInput, sigInst); err != nil {
+                       return errors.Wrap(err, "adding inputs")
+               }
+       }
+
+       if res.change > 0 {
+               acp, err := a.accounts.CreateAddress(a.AccountID, true)
+               if err != nil {
+                       return errors.Wrap(err, "creating control program")
+               }
+
+               // Don't insert the control program until callbacks are executed.
+               a.accounts.insertControlProgramDelayed(b, acp)
+               if err = b.AddOutput(types.NewTxOutput(*a.AssetId, res.change, acp.ControlProgram)); err != nil {
+                       return errors.Wrap(err, "adding change output")
+               }
+       }
+       return nil
+}
+
+//DecodeSpendUTXOAction unmarshal JSON-encoded data of spend utxo action
+func (m *Manager) DecodeSpendUTXOAction(data []byte) (txbuilder.Action, error) {
+       a := &spendUTXOAction{accounts: m}
+       return a, json.Unmarshal(data, a)
+}
+
+type spendUTXOAction struct {
+       accounts       *Manager
+       OutputID       *bc.Hash                     `json:"output_id"`
+       UseUnconfirmed bool                         `json:"use_unconfirmed"`
+       Arguments      []txbuilder.ContractArgument `json:"arguments"`
+}
+
+func (a *spendUTXOAction) ActionType() string {
+       return "spend_account_unspent_output"
+}
+
+func (a *spendUTXOAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
+       if a.OutputID == nil {
+               return txbuilder.MissingFieldsError("output_id")
+       }
+
+       res, err := a.accounts.utxoKeeper.ReserveParticular(*a.OutputID, a.UseUnconfirmed, b.MaxTime())
+       if err != nil {
+               return err
+       }
+
+       b.OnRollback(func() { a.accounts.utxoKeeper.Cancel(res.id) })
+       var accountSigner *signers.Signer
+       if len(res.utxos[0].AccountID) != 0 {
+               account, err := a.accounts.FindByID(res.utxos[0].AccountID)
+               if err != nil {
+                       return err
+               }
+
+               accountSigner = account.Signer
+       }
+
+       txInput, sigInst, err := UtxoToInputs(accountSigner, res.utxos[0])
+       if err != nil {
+               return err
+       }
+
+       if a.Arguments == nil {
+               return b.AddInput(txInput, sigInst)
+       }
+
+       sigInst = &txbuilder.SigningInstruction{}
+       if err := txbuilder.AddContractArgs(sigInst, a.Arguments); err != nil {
+               return err
+       }
+
+       return b.AddInput(txInput, sigInst)
+}
+
+// UtxoToInputs convert an utxo to the txinput
+func UtxoToInputs(signer *signers.Signer, u *UTXO) (*types.TxInput, *txbuilder.SigningInstruction, error) {
+       txInput := types.NewSpendInput(nil, u.SourceID, u.AssetID, u.Amount, u.SourcePos, u.ControlProgram)
+       sigInst := &txbuilder.SigningInstruction{}
+       if signer == nil {
+               return txInput, sigInst, nil
+       }
+
+       path, err := signers.Path(signer, signers.AccountKeySpace, u.Change, u.ControlProgramIndex)
+       if err != nil {
+               return nil, nil, err
+       }
+       if u.Address == "" {
+               sigInst.AddWitnessKeys(signer.XPubs, path, signer.Quorum)
+               return txInput, sigInst, nil
+       }
+
+       address, err := common.DecodeAddress(u.Address, &consensus.ActiveNetParams)
+       if err != nil {
+               return nil, nil, err
+       }
+
+       sigInst.AddRawWitnessKeys(signer.XPubs, path, signer.Quorum)
+       derivedXPubs := chainkd.DeriveXPubs(signer.XPubs, path)
+
+       switch address.(type) {
+       case *common.AddressWitnessPubKeyHash:
+               derivedPK := derivedXPubs[0].PublicKey()
+               sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness([]byte(derivedPK)))
+
+       case *common.AddressWitnessScriptHash:
+               derivedPKs := chainkd.XPubKeys(derivedXPubs)
+               script, err := vmutil.P2SPMultiSigProgram(derivedPKs, signer.Quorum)
+               if err != nil {
+                       return nil, nil, err
+               }
+               sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness(script))
+
+       default:
+               return nil, nil, errors.New("unsupport address type")
+       }
+
+       return txInput, sigInst, nil
+}
+
+// insertControlProgramDelayed takes a template builder and an account
+// control program that hasn't been inserted to the database yet. It
+// registers callbacks on the TemplateBuilder so that all of the template's
+// account control programs are batch inserted if building the rest of
+// the template is successful.
+func (m *Manager) insertControlProgramDelayed(b *txbuilder.TemplateBuilder, acp *CtrlProgram) {
+       m.delayedACPsMu.Lock()
+       m.delayedACPs[b] = append(m.delayedACPs[b], acp)
+       m.delayedACPsMu.Unlock()
+
+       b.OnRollback(func() {
+               m.delayedACPsMu.Lock()
+               delete(m.delayedACPs, b)
+               m.delayedACPsMu.Unlock()
+       })
+       b.OnBuild(func() error {
+               m.delayedACPsMu.Lock()
+               acps := m.delayedACPs[b]
+               delete(m.delayedACPs, b)
+               m.delayedACPsMu.Unlock()
+
+               // Insert all of the account control programs at once.
+               if len(acps) == 0 {
+                       return nil
+               }
+               return m.SaveControlPrograms(acps...)
+       })
+}