github.com/Bytom/bytom@v1.1.2-0.20210127130405-ae40204c0b09/account/builder.go (about)

     1  package account
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  
     7  	"github.com/bytom/bytom/blockchain/signers"
     8  	"github.com/bytom/bytom/blockchain/txbuilder"
     9  	"github.com/bytom/bytom/common"
    10  	"github.com/bytom/bytom/consensus"
    11  	"github.com/bytom/bytom/crypto/ed25519/chainkd"
    12  	"github.com/bytom/bytom/errors"
    13  	"github.com/bytom/bytom/protocol/bc"
    14  	"github.com/bytom/bytom/protocol/bc/types"
    15  	"github.com/bytom/bytom/protocol/vm/vmutil"
    16  )
    17  
    18  //DecodeSpendAction unmarshal JSON-encoded data of spend action
    19  func (m *Manager) DecodeSpendAction(data []byte) (txbuilder.Action, error) {
    20  	a := &spendAction{accounts: m}
    21  	return a, json.Unmarshal(data, a)
    22  }
    23  
    24  type spendAction struct {
    25  	accounts *Manager
    26  	bc.AssetAmount
    27  	AccountID      string `json:"account_id"`
    28  	UseUnconfirmed bool   `json:"use_unconfirmed"`
    29  }
    30  
    31  func (a *spendAction) ActionType() string {
    32  	return "spend_account"
    33  }
    34  
    35  // MergeSpendAction merge common assetID and accountID spend action
    36  func MergeSpendAction(actions []txbuilder.Action) []txbuilder.Action {
    37  	resultActions := []txbuilder.Action{}
    38  	spendActionMap := make(map[string]*spendAction)
    39  
    40  	for _, act := range actions {
    41  		switch act := act.(type) {
    42  		case *spendAction:
    43  			actionKey := act.AssetId.String() + act.AccountID
    44  			if tmpAct, ok := spendActionMap[actionKey]; ok {
    45  				tmpAct.Amount += act.Amount
    46  				tmpAct.UseUnconfirmed = tmpAct.UseUnconfirmed || act.UseUnconfirmed
    47  			} else {
    48  				spendActionMap[actionKey] = act
    49  				resultActions = append(resultActions, act)
    50  			}
    51  		default:
    52  			resultActions = append(resultActions, act)
    53  		}
    54  	}
    55  	return resultActions
    56  }
    57  
    58  //calcMergeGas calculate the gas required that n utxos are merged into one
    59  func calcMergeGas(num int) uint64 {
    60  	gas := uint64(0)
    61  	for num > 1 {
    62  		gas += txbuilder.ChainTxMergeGas
    63  		num -= txbuilder.ChainTxUtxoNum - 1
    64  	}
    65  	return gas
    66  }
    67  
    68  func (m *Manager) reserveBtmUtxoChain(builder *txbuilder.TemplateBuilder, accountID string, amount uint64, useUnconfirmed bool) ([]*UTXO, error) {
    69  	reservedAmount := uint64(0)
    70  	utxos := []*UTXO{}
    71  	for gasAmount := uint64(0); reservedAmount < gasAmount+amount; gasAmount = calcMergeGas(len(utxos)) {
    72  		reserveAmount := amount + gasAmount - reservedAmount
    73  		res, err := m.utxoKeeper.Reserve(accountID, consensus.BTMAssetID, reserveAmount, useUnconfirmed, builder.MaxTime())
    74  		if err != nil {
    75  			return nil, err
    76  		}
    77  
    78  		builder.OnRollback(func() { m.utxoKeeper.Cancel(res.id) })
    79  		reservedAmount += reserveAmount + res.change
    80  		utxos = append(utxos, res.utxos[:]...)
    81  	}
    82  	return utxos, nil
    83  }
    84  
    85  func (m *Manager) buildBtmTxChain(utxos []*UTXO, signer *signers.Signer) ([]*txbuilder.Template, *UTXO, error) {
    86  	if len(utxos) == 0 {
    87  		return nil, nil, errors.New("mergeSpendActionUTXO utxos num 0")
    88  	}
    89  
    90  	tpls := []*txbuilder.Template{}
    91  	if len(utxos) == 1 {
    92  		return tpls, utxos[len(utxos)-1], nil
    93  	}
    94  
    95  	acp, err := m.GetLocalCtrlProgramByAddress(utxos[0].Address)
    96  	if err != nil {
    97  		return nil, nil, err
    98  	}
    99  
   100  	buildAmount := uint64(0)
   101  	builder := &txbuilder.TemplateBuilder{}
   102  	for index := 0; index < len(utxos); index++ {
   103  		input, sigInst, err := UtxoToInputs(signer, utxos[index])
   104  		if err != nil {
   105  			return nil, nil, err
   106  		}
   107  
   108  		if err = builder.AddInput(input, sigInst); err != nil {
   109  			return nil, nil, err
   110  		}
   111  
   112  		buildAmount += input.Amount()
   113  		if builder.InputCount() != txbuilder.ChainTxUtxoNum && index != len(utxos)-1 {
   114  			continue
   115  		}
   116  
   117  		outAmount := buildAmount - txbuilder.ChainTxMergeGas
   118  		output := types.NewTxOutput(*consensus.BTMAssetID, outAmount, acp.ControlProgram)
   119  		if err := builder.AddOutput(output); err != nil {
   120  			return nil, nil, err
   121  		}
   122  
   123  		tpl, _, err := builder.Build()
   124  		if err != nil {
   125  			return nil, nil, err
   126  		}
   127  
   128  		bcOut, err := tpl.Transaction.Output(*tpl.Transaction.ResultIds[0])
   129  		if err != nil {
   130  			return nil, nil, err
   131  		}
   132  
   133  		utxos = append(utxos, &UTXO{
   134  			OutputID:            *tpl.Transaction.ResultIds[0],
   135  			AssetID:             *consensus.BTMAssetID,
   136  			Amount:              outAmount,
   137  			ControlProgram:      acp.ControlProgram,
   138  			SourceID:            *bcOut.Source.Ref,
   139  			SourcePos:           bcOut.Source.Position,
   140  			ControlProgramIndex: acp.KeyIndex,
   141  			Address:             acp.Address,
   142  			Change:              acp.Change,
   143  		})
   144  
   145  		tpls = append(tpls, tpl)
   146  		buildAmount = 0
   147  		builder = &txbuilder.TemplateBuilder{}
   148  		if index == len(utxos)-2 {
   149  			break
   150  		}
   151  	}
   152  	return tpls, utxos[len(utxos)-1], nil
   153  }
   154  
   155  // SpendAccountChain build the spend action with auto merge utxo function
   156  func SpendAccountChain(ctx context.Context, builder *txbuilder.TemplateBuilder, action txbuilder.Action) ([]*txbuilder.Template, error) {
   157  	act, ok := action.(*spendAction)
   158  	if !ok {
   159  		return nil, errors.New("fail to convert the spend action")
   160  	}
   161  	if *act.AssetId != *consensus.BTMAssetID {
   162  		return nil, errors.New("spend chain action only support BTM")
   163  	}
   164  
   165  	utxos, err := act.accounts.reserveBtmUtxoChain(builder, act.AccountID, act.Amount, act.UseUnconfirmed)
   166  	if err != nil {
   167  		return nil, err
   168  	}
   169  
   170  	acct, err := act.accounts.FindByID(act.AccountID)
   171  	if err != nil {
   172  		return nil, err
   173  	}
   174  
   175  	tpls, utxo, err := act.accounts.buildBtmTxChain(utxos, acct.Signer)
   176  	if err != nil {
   177  		return nil, err
   178  	}
   179  
   180  	input, sigInst, err := UtxoToInputs(acct.Signer, utxo)
   181  	if err != nil {
   182  		return nil, err
   183  	}
   184  
   185  	if err := builder.AddInput(input, sigInst); err != nil {
   186  		return nil, err
   187  	}
   188  
   189  	if utxo.Amount > act.Amount {
   190  		if err = builder.AddOutput(types.NewTxOutput(*consensus.BTMAssetID, utxo.Amount-act.Amount, utxo.ControlProgram)); err != nil {
   191  			return nil, errors.Wrap(err, "adding change output")
   192  		}
   193  	}
   194  	return tpls, nil
   195  }
   196  
   197  func (a *spendAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
   198  	var missing []string
   199  	if a.AccountID == "" {
   200  		missing = append(missing, "account_id")
   201  	}
   202  	if a.AssetId.IsZero() {
   203  		missing = append(missing, "asset_id")
   204  	}
   205  	if a.AssetAmount.Amount == 0 {
   206  		missing = append(missing, "amount")
   207  	}
   208  	if len(missing) > 0 {
   209  		return txbuilder.MissingFieldsError(missing...)
   210  	}
   211  
   212  	acct, err := a.accounts.FindByID(a.AccountID)
   213  	if err != nil {
   214  		return errors.Wrap(err, "get account info")
   215  	}
   216  
   217  	res, err := a.accounts.utxoKeeper.Reserve(a.AccountID, a.AssetId, a.Amount, a.UseUnconfirmed, b.MaxTime())
   218  	if err != nil {
   219  		return errors.Wrap(err, "reserving utxos")
   220  	}
   221  
   222  	// Cancel the reservation if the build gets rolled back.
   223  	b.OnRollback(func() { a.accounts.utxoKeeper.Cancel(res.id) })
   224  	for _, r := range res.utxos {
   225  		txInput, sigInst, err := UtxoToInputs(acct.Signer, r)
   226  		if err != nil {
   227  			return errors.Wrap(err, "creating inputs")
   228  		}
   229  
   230  		if err = b.AddInput(txInput, sigInst); err != nil {
   231  			return errors.Wrap(err, "adding inputs")
   232  		}
   233  	}
   234  
   235  	if res.change > 0 {
   236  		acp, err := a.accounts.CreateAddress(a.AccountID, true)
   237  		if err != nil {
   238  			return errors.Wrap(err, "creating control program")
   239  		}
   240  
   241  		// Don't insert the control program until callbacks are executed.
   242  		a.accounts.insertControlProgramDelayed(b, acp)
   243  		if err = b.AddOutput(types.NewTxOutput(*a.AssetId, res.change, acp.ControlProgram)); err != nil {
   244  			return errors.Wrap(err, "adding change output")
   245  		}
   246  	}
   247  	return nil
   248  }
   249  
   250  //DecodeSpendUTXOAction unmarshal JSON-encoded data of spend utxo action
   251  func (m *Manager) DecodeSpendUTXOAction(data []byte) (txbuilder.Action, error) {
   252  	a := &spendUTXOAction{accounts: m}
   253  	return a, json.Unmarshal(data, a)
   254  }
   255  
   256  type spendUTXOAction struct {
   257  	accounts       *Manager
   258  	OutputID       *bc.Hash                     `json:"output_id"`
   259  	UseUnconfirmed bool                         `json:"use_unconfirmed"`
   260  	Arguments      []txbuilder.ContractArgument `json:"arguments"`
   261  }
   262  
   263  func (a *spendUTXOAction) ActionType() string {
   264  	return "spend_account_unspent_output"
   265  }
   266  
   267  func (a *spendUTXOAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
   268  	if a.OutputID == nil {
   269  		return txbuilder.MissingFieldsError("output_id")
   270  	}
   271  
   272  	res, err := a.accounts.utxoKeeper.ReserveParticular(*a.OutputID, a.UseUnconfirmed, b.MaxTime())
   273  	if err != nil {
   274  		return err
   275  	}
   276  
   277  	b.OnRollback(func() { a.accounts.utxoKeeper.Cancel(res.id) })
   278  	var accountSigner *signers.Signer
   279  	if len(res.utxos[0].AccountID) != 0 {
   280  		account, err := a.accounts.FindByID(res.utxos[0].AccountID)
   281  		if err != nil {
   282  			return err
   283  		}
   284  
   285  		accountSigner = account.Signer
   286  	}
   287  
   288  	txInput, sigInst, err := UtxoToInputs(accountSigner, res.utxos[0])
   289  	if err != nil {
   290  		return err
   291  	}
   292  
   293  	if a.Arguments == nil {
   294  		return b.AddInput(txInput, sigInst)
   295  	}
   296  
   297  	sigInst = &txbuilder.SigningInstruction{}
   298  	if err := txbuilder.AddContractArgs(sigInst, a.Arguments); err != nil {
   299  		return err
   300  	}
   301  
   302  	return b.AddInput(txInput, sigInst)
   303  }
   304  
   305  // UtxoToInputs convert an utxo to the txinput
   306  func UtxoToInputs(signer *signers.Signer, u *UTXO) (*types.TxInput, *txbuilder.SigningInstruction, error) {
   307  	txInput := types.NewSpendInput(nil, u.SourceID, u.AssetID, u.Amount, u.SourcePos, u.ControlProgram)
   308  	sigInst := &txbuilder.SigningInstruction{}
   309  	if signer == nil {
   310  		return txInput, sigInst, nil
   311  	}
   312  
   313  	path, err := signers.Path(signer, signers.AccountKeySpace, u.Change, u.ControlProgramIndex)
   314  	if err != nil {
   315  		return nil, nil, err
   316  	}
   317  	if u.Address == "" {
   318  		sigInst.AddWitnessKeys(signer.XPubs, path, signer.Quorum)
   319  		return txInput, sigInst, nil
   320  	}
   321  
   322  	address, err := common.DecodeAddress(u.Address, &consensus.ActiveNetParams)
   323  	if err != nil {
   324  		return nil, nil, err
   325  	}
   326  
   327  	sigInst.AddRawWitnessKeys(signer.XPubs, path, signer.Quorum)
   328  	derivedXPubs := chainkd.DeriveXPubs(signer.XPubs, path)
   329  
   330  	switch address.(type) {
   331  	case *common.AddressWitnessPubKeyHash:
   332  		derivedPK := derivedXPubs[0].PublicKey()
   333  		sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness([]byte(derivedPK)))
   334  
   335  	case *common.AddressWitnessScriptHash:
   336  		derivedPKs := chainkd.XPubKeys(derivedXPubs)
   337  		script, err := vmutil.P2SPMultiSigProgram(derivedPKs, signer.Quorum)
   338  		if err != nil {
   339  			return nil, nil, err
   340  		}
   341  		sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness(script))
   342  
   343  	default:
   344  		return nil, nil, errors.New("unsupport address type")
   345  	}
   346  
   347  	return txInput, sigInst, nil
   348  }
   349  
   350  // insertControlProgramDelayed takes a template builder and an account
   351  // control program that hasn't been inserted to the database yet. It
   352  // registers callbacks on the TemplateBuilder so that all of the template's
   353  // account control programs are batch inserted if building the rest of
   354  // the template is successful.
   355  func (m *Manager) insertControlProgramDelayed(b *txbuilder.TemplateBuilder, acp *CtrlProgram) {
   356  	m.delayedACPsMu.Lock()
   357  	m.delayedACPs[b] = append(m.delayedACPs[b], acp)
   358  	m.delayedACPsMu.Unlock()
   359  
   360  	b.OnRollback(func() {
   361  		m.delayedACPsMu.Lock()
   362  		delete(m.delayedACPs, b)
   363  		m.delayedACPsMu.Unlock()
   364  	})
   365  	b.OnBuild(func() error {
   366  		m.delayedACPsMu.Lock()
   367  		acps := m.delayedACPs[b]
   368  		delete(m.delayedACPs, b)
   369  		m.delayedACPsMu.Unlock()
   370  
   371  		// Insert all of the account control programs at once.
   372  		if len(acps) == 0 {
   373  			return nil
   374  		}
   375  		return m.SaveControlPrograms(acps...)
   376  	})
   377  }