github.com/bytom/bytom@v1.1.2-0.20221014091027-bbcba3df6075/account/builder.go (about)

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