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 }