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

     1  package test
     2  
     3  import (
     4  	"crypto/rand"
     5  	"encoding/json"
     6  	"fmt"
     7  	"time"
     8  
     9  	"github.com/bytom/bytom/account"
    10  	"github.com/bytom/bytom/asset"
    11  	"github.com/bytom/bytom/blockchain/pseudohsm"
    12  	"github.com/bytom/bytom/blockchain/signers"
    13  	"github.com/bytom/bytom/blockchain/txbuilder"
    14  	"github.com/bytom/bytom/common"
    15  	"github.com/bytom/bytom/consensus"
    16  	"github.com/bytom/bytom/crypto/ed25519/chainkd"
    17  	"github.com/bytom/bytom/crypto/sha3pool"
    18  	dbm "github.com/bytom/bytom/database/leveldb"
    19  	"github.com/bytom/bytom/errors"
    20  	"github.com/bytom/bytom/protocol/bc"
    21  	"github.com/bytom/bytom/protocol/bc/types"
    22  	"github.com/bytom/bytom/protocol/vm"
    23  	"github.com/bytom/bytom/protocol/vm/vmutil"
    24  )
    25  
    26  // TxGenerator used to generate new tx
    27  type TxGenerator struct {
    28  	Builder        *txbuilder.TemplateBuilder
    29  	AccountManager *account.Manager
    30  	Assets         *asset.Registry
    31  	Hsm            *pseudohsm.HSM
    32  }
    33  
    34  // NewTxGenerator create a TxGenerator
    35  func NewTxGenerator(accountManager *account.Manager, assets *asset.Registry, hsm *pseudohsm.HSM) *TxGenerator {
    36  	return &TxGenerator{
    37  		Builder:        txbuilder.NewBuilder(time.Now()),
    38  		AccountManager: accountManager,
    39  		Assets:         assets,
    40  		Hsm:            hsm,
    41  	}
    42  }
    43  
    44  // Reset reset transaction builder, used to create a new tx
    45  func (g *TxGenerator) Reset() {
    46  	g.Builder = txbuilder.NewBuilder(time.Now())
    47  }
    48  
    49  func (g *TxGenerator) createKey(alias string, auth string) error {
    50  	_, _, err := g.Hsm.XCreate(alias, auth, "en")
    51  	return err
    52  }
    53  
    54  func (g *TxGenerator) getPubkey(keyAlias string) *chainkd.XPub {
    55  	pubKeys := g.Hsm.ListKeys()
    56  	for i, key := range pubKeys {
    57  		if key.Alias == keyAlias {
    58  			return &pubKeys[i].XPub
    59  		}
    60  	}
    61  	return nil
    62  }
    63  
    64  func (g *TxGenerator) createAccount(name string, keys []string, quorum int) error {
    65  	xpubs := []chainkd.XPub{}
    66  	for _, alias := range keys {
    67  		xpub := g.getPubkey(alias)
    68  		if xpub == nil {
    69  			return fmt.Errorf("can't find pubkey for %s", alias)
    70  		}
    71  		xpubs = append(xpubs, *xpub)
    72  	}
    73  	_, err := g.AccountManager.Create(xpubs, quorum, name, signers.BIP0044)
    74  	return err
    75  }
    76  
    77  func (g *TxGenerator) createAsset(accountAlias string, assetAlias string) (*asset.Asset, error) {
    78  	acc, err := g.AccountManager.FindByAlias(accountAlias)
    79  	if err != nil {
    80  		return nil, err
    81  	}
    82  	return g.Assets.Define(acc.XPubs, len(acc.XPubs), nil, 0, assetAlias, nil)
    83  }
    84  
    85  func (g *TxGenerator) mockUtxo(accountAlias, assetAlias string, amount uint64) (*account.UTXO, error) {
    86  	ctrlProg, err := g.createControlProgram(accountAlias, false)
    87  	if err != nil {
    88  		return nil, err
    89  	}
    90  
    91  	assetAmount, err := g.assetAmount(assetAlias, amount)
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  	utxo := &account.UTXO{
    96  		OutputID:            bc.Hash{V0: 1},
    97  		SourceID:            bc.Hash{V0: 1},
    98  		AssetID:             *assetAmount.AssetId,
    99  		Amount:              assetAmount.Amount,
   100  		SourcePos:           0,
   101  		ControlProgram:      ctrlProg.ControlProgram,
   102  		ControlProgramIndex: ctrlProg.KeyIndex,
   103  		AccountID:           ctrlProg.AccountID,
   104  		Address:             ctrlProg.Address,
   105  		ValidHeight:         0,
   106  		Change:              ctrlProg.Change,
   107  	}
   108  	return utxo, nil
   109  }
   110  
   111  func (g *TxGenerator) assetAmount(assetAlias string, amount uint64) (*bc.AssetAmount, error) {
   112  	if assetAlias == "BTM" {
   113  		a := &bc.AssetAmount{
   114  			Amount:  amount,
   115  			AssetId: consensus.BTMAssetID,
   116  		}
   117  		return a, nil
   118  	}
   119  
   120  	asset, err := g.Assets.FindByAlias(assetAlias)
   121  	if err != nil {
   122  		return nil, err
   123  	}
   124  	return &bc.AssetAmount{
   125  		Amount:  amount,
   126  		AssetId: &asset.AssetID,
   127  	}, nil
   128  }
   129  
   130  func (g *TxGenerator) createControlProgram(accountAlias string, change bool) (*account.CtrlProgram, error) {
   131  	acc, err := g.AccountManager.FindByAlias(accountAlias)
   132  	if err != nil {
   133  		return nil, err
   134  	}
   135  	return g.AccountManager.CreateAddress(acc.ID, change)
   136  }
   137  
   138  // AddSpendInput add a spend input
   139  func (g *TxGenerator) AddSpendInput(accountAlias, assetAlias string, amount uint64) error {
   140  	assetAmount, err := g.assetAmount(assetAlias, amount)
   141  	if err != nil {
   142  		return err
   143  	}
   144  
   145  	acc, err := g.AccountManager.FindByAlias(accountAlias)
   146  	if err != nil {
   147  		return err
   148  	}
   149  
   150  	reqAction := make(map[string]interface{})
   151  	reqAction["account_id"] = acc.ID
   152  	reqAction["amount"] = amount
   153  	reqAction["asset_id"] = assetAmount.AssetId.String()
   154  	data, err := json.Marshal(reqAction)
   155  	if err != nil {
   156  		return err
   157  	}
   158  
   159  	spendAction, err := g.AccountManager.DecodeSpendAction(data)
   160  	if err != nil {
   161  		return err
   162  	}
   163  	return spendAction.Build(nil, g.Builder)
   164  }
   165  
   166  // AddTxInput add a tx input and signing instruction
   167  func (g *TxGenerator) AddTxInput(txInput *types.TxInput, signInstruction *txbuilder.SigningInstruction) error {
   168  	return g.Builder.AddInput(txInput, signInstruction)
   169  }
   170  
   171  // AddTxInputFromUtxo add a tx input which spent the utxo
   172  func (g *TxGenerator) AddTxInputFromUtxo(utxo *account.UTXO, accountAlias string) error {
   173  	acc, err := g.AccountManager.FindByAlias(accountAlias)
   174  	if err != nil {
   175  		return err
   176  	}
   177  
   178  	txInput, signInst, err := account.UtxoToInputs(acc.Signer, utxo)
   179  	if err != nil {
   180  		return err
   181  	}
   182  	return g.AddTxInput(txInput, signInst)
   183  }
   184  
   185  // AddIssuanceInput add a issue input
   186  func (g *TxGenerator) AddIssuanceInput(assetAlias string, amount uint64) error {
   187  	asset, err := g.Assets.FindByAlias(assetAlias)
   188  	if err != nil {
   189  		return err
   190  	}
   191  
   192  	var nonce [8]byte
   193  	_, err = rand.Read(nonce[:])
   194  	if err != nil {
   195  		return err
   196  	}
   197  	issuanceInput := types.NewIssuanceInput(nonce[:], amount, asset.IssuanceProgram, nil, asset.RawDefinitionByte)
   198  	signInstruction := &txbuilder.SigningInstruction{}
   199  	path := signers.GetBip0032Path(asset.Signer, signers.AssetKeySpace)
   200  	signInstruction.AddRawWitnessKeys(asset.Signer.XPubs, path, asset.Signer.Quorum)
   201  	g.Builder.RestrictMinTime(time.Now())
   202  	return g.Builder.AddInput(issuanceInput, signInstruction)
   203  }
   204  
   205  // AddTxOutput add a tx output
   206  func (g *TxGenerator) AddTxOutput(accountAlias, assetAlias string, amount uint64) error {
   207  	assetAmount, err := g.assetAmount(assetAlias, uint64(amount))
   208  	if err != nil {
   209  		return err
   210  	}
   211  	controlProgram, err := g.createControlProgram(accountAlias, false)
   212  	if err != nil {
   213  		return err
   214  	}
   215  	out := types.NewTxOutput(*assetAmount.AssetId, assetAmount.Amount, controlProgram.ControlProgram)
   216  	return g.Builder.AddOutput(out)
   217  }
   218  
   219  // AddRetirement add a retirement output
   220  func (g *TxGenerator) AddRetirement(assetAlias string, amount uint64) error {
   221  	assetAmount, err := g.assetAmount(assetAlias, uint64(amount))
   222  	if err != nil {
   223  		return err
   224  	}
   225  	retirementProgram := []byte{byte(vm.OP_FAIL)}
   226  	out := types.NewTxOutput(*assetAmount.AssetId, assetAmount.Amount, retirementProgram)
   227  	return g.Builder.AddOutput(out)
   228  }
   229  
   230  // Sign used to sign tx
   231  func (g *TxGenerator) Sign(passwords []string) (*types.Tx, error) {
   232  	tpl, _, err := g.Builder.Build()
   233  	if err != nil {
   234  		return nil, err
   235  	}
   236  
   237  	txSerialized, err := tpl.Transaction.MarshalText()
   238  	if err != nil {
   239  		return nil, err
   240  	}
   241  
   242  	tpl.Transaction.Tx.SerializedSize = uint64(len(txSerialized))
   243  	tpl.Transaction.TxData.SerializedSize = uint64(len(txSerialized))
   244  	for _, password := range passwords {
   245  		_, err = MockSign(tpl, g.Hsm, password)
   246  		if err != nil {
   247  			return nil, err
   248  		}
   249  	}
   250  	return tpl.Transaction, nil
   251  }
   252  
   253  func txFee(tx *types.Tx) uint64 {
   254  	if len(tx.Inputs) == 1 && tx.Inputs[0].InputType() == types.CoinbaseInputType {
   255  		return 0
   256  	}
   257  
   258  	inputSum := uint64(0)
   259  	outputSum := uint64(0)
   260  	for _, input := range tx.Inputs {
   261  		if input.AssetID() == *consensus.BTMAssetID {
   262  			inputSum += input.Amount()
   263  		}
   264  	}
   265  
   266  	for _, output := range tx.Outputs {
   267  		if *output.AssetId == *consensus.BTMAssetID {
   268  			outputSum += output.Amount
   269  		}
   270  	}
   271  	return inputSum - outputSum
   272  }
   273  
   274  // CreateSpendInput create SpendInput which spent the output from tx
   275  func CreateSpendInput(tx *types.Tx, outputIndex uint64) (*types.SpendInput, error) {
   276  	outputID := tx.ResultIds[outputIndex]
   277  	output, ok := tx.Entries[*outputID].(*bc.Output)
   278  	if !ok {
   279  		return nil, fmt.Errorf("retirement can't be spent")
   280  	}
   281  
   282  	sc := types.SpendCommitment{
   283  		AssetAmount:    *output.Source.Value,
   284  		SourceID:       *output.Source.Ref,
   285  		SourcePosition: output.Ordinal,
   286  		VMVersion:      vmVersion,
   287  		ControlProgram: output.ControlProgram.Code,
   288  	}
   289  	return &types.SpendInput{
   290  		SpendCommitment: sc,
   291  	}, nil
   292  }
   293  
   294  // SignInstructionFor read CtrlProgram from db, construct SignInstruction for SpendInput
   295  func SignInstructionFor(input *types.SpendInput, db dbm.DB, signer *signers.Signer) (*txbuilder.SigningInstruction, error) {
   296  	cp := account.CtrlProgram{}
   297  	var hash [32]byte
   298  	sha3pool.Sum256(hash[:], input.ControlProgram)
   299  	bytes := db.Get(account.ContractKey(hash))
   300  	if bytes == nil {
   301  		return nil, fmt.Errorf("can't find CtrlProgram for the SpendInput")
   302  	}
   303  
   304  	err := json.Unmarshal(bytes, &cp)
   305  	if err != nil {
   306  		return nil, err
   307  	}
   308  
   309  	sigInst := &txbuilder.SigningInstruction{}
   310  	if signer == nil {
   311  		return sigInst, nil
   312  	}
   313  
   314  	// FIXME: code duplicate with account/builder.go
   315  	path, err := signers.Path(signer, signers.AccountKeySpace, cp.Change, cp.KeyIndex)
   316  	if err != nil {
   317  		return nil, err
   318  	}
   319  
   320  	if cp.Address == "" {
   321  		sigInst.AddWitnessKeys(signer.XPubs, path, signer.Quorum)
   322  		return sigInst, nil
   323  	}
   324  
   325  	address, err := common.DecodeAddress(cp.Address, &consensus.MainNetParams)
   326  	if err != nil {
   327  		return nil, err
   328  	}
   329  
   330  	switch address.(type) {
   331  	case *common.AddressWitnessPubKeyHash:
   332  		sigInst.AddRawWitnessKeys(signer.XPubs, path, signer.Quorum)
   333  		derivedXPubs := chainkd.DeriveXPubs(signer.XPubs, path)
   334  		derivedPK := derivedXPubs[0].PublicKey()
   335  		sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness([]byte(derivedPK)))
   336  
   337  	case *common.AddressWitnessScriptHash:
   338  		sigInst.AddRawWitnessKeys(signer.XPubs, path, signer.Quorum)
   339  		path, err := signers.Path(signer, signers.AccountKeySpace, cp.Change, cp.KeyIndex)
   340  		if err != nil {
   341  			return nil, err
   342  		}
   343  		derivedXPubs := chainkd.DeriveXPubs(signer.XPubs, path)
   344  		derivedPKs := chainkd.XPubKeys(derivedXPubs)
   345  		script, err := vmutil.P2SPMultiSigProgram(derivedPKs, signer.Quorum)
   346  		if err != nil {
   347  			return nil, err
   348  		}
   349  		sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness(script))
   350  
   351  	default:
   352  		return nil, errors.New("unsupport address type")
   353  	}
   354  
   355  	return sigInst, nil
   356  }
   357  
   358  // CreateCoinbaseTx create coinbase tx at block height
   359  func CreateCoinbaseTx(controlProgram []byte, height, txsFee uint64) (*types.Tx, error) {
   360  	coinbaseValue := consensus.BlockSubsidy(height) + txsFee
   361  	builder := txbuilder.NewBuilder(time.Now())
   362  	if err := builder.AddInput(types.NewCoinbaseInput([]byte(string(height))), &txbuilder.SigningInstruction{}); err != nil {
   363  		return nil, err
   364  	}
   365  	if err := builder.AddOutput(types.NewTxOutput(*consensus.BTMAssetID, coinbaseValue, controlProgram)); err != nil {
   366  		return nil, err
   367  	}
   368  
   369  	tpl, _, err := builder.Build()
   370  	if err != nil {
   371  		return nil, err
   372  	}
   373  
   374  	txSerialized, err := tpl.Transaction.MarshalText()
   375  	if err != nil {
   376  		return nil, err
   377  	}
   378  
   379  	tpl.Transaction.Tx.SerializedSize = uint64(len(txSerialized))
   380  	tpl.Transaction.TxData.SerializedSize = uint64(len(txSerialized))
   381  	return tpl.Transaction, nil
   382  }
   383  
   384  // CreateTxFromTx create a tx spent the output in outputIndex at baseTx
   385  func CreateTxFromTx(baseTx *types.Tx, outputIndex uint64, outputAmount uint64, ctrlProgram []byte) (*types.Tx, error) {
   386  	spendInput, err := CreateSpendInput(baseTx, outputIndex)
   387  	if err != nil {
   388  		return nil, err
   389  	}
   390  
   391  	txInput := &types.TxInput{
   392  		AssetVersion: assetVersion,
   393  		TypedInput:   spendInput,
   394  	}
   395  	output := types.NewTxOutput(*consensus.BTMAssetID, outputAmount, ctrlProgram)
   396  	builder := txbuilder.NewBuilder(time.Now())
   397  	if err := builder.AddInput(txInput, &txbuilder.SigningInstruction{}); err != nil {
   398  		return nil, err
   399  	}
   400  	if err := builder.AddOutput(output); err != nil {
   401  		return nil, err
   402  	}
   403  
   404  	tpl, _, err := builder.Build()
   405  	if err != nil {
   406  		return nil, err
   407  	}
   408  
   409  	txSerialized, err := tpl.Transaction.MarshalText()
   410  	if err != nil {
   411  		return nil, err
   412  	}
   413  
   414  	tpl.Transaction.Tx.SerializedSize = uint64(len(txSerialized))
   415  	tpl.Transaction.TxData.SerializedSize = uint64(len(txSerialized))
   416  	return tpl.Transaction, nil
   417  }