9 "github.com/tendermint/tmlibs/db"
11 "github.com/bytom/account"
12 "github.com/bytom/asset"
13 "github.com/bytom/blockchain/pseudohsm"
14 "github.com/bytom/blockchain/signers"
15 "github.com/bytom/blockchain/txbuilder"
16 "github.com/bytom/common"
17 "github.com/bytom/consensus"
18 "github.com/bytom/crypto/ed25519/chainkd"
19 "github.com/bytom/crypto/sha3pool"
20 "github.com/bytom/errors"
21 "github.com/bytom/protocol/bc"
22 "github.com/bytom/protocol/bc/types"
23 "github.com/bytom/protocol/vm"
24 "github.com/bytom/protocol/vm/vmutil"
27 // TxGenerator used to generate new tx
28 type TxGenerator struct {
29 Builder *txbuilder.TemplateBuilder
30 AccountManager *account.Manager
31 Assets *asset.Registry
35 // NewTxGenerator create a TxGenerator
36 func NewTxGenerator(accountManager *account.Manager, assets *asset.Registry, hsm *pseudohsm.HSM) *TxGenerator {
38 Builder: txbuilder.NewBuilder(time.Now()),
39 AccountManager: accountManager,
45 // Reset reset transaction builder, used to create a new tx
46 func (g *TxGenerator) Reset() {
47 g.Builder = txbuilder.NewBuilder(time.Now())
50 func (g *TxGenerator) createKey(alias string, auth string) error {
51 _, _, err := g.Hsm.XCreate(alias, auth, "en")
55 func (g *TxGenerator) getPubkey(keyAlias string) *chainkd.XPub {
56 pubKeys := g.Hsm.ListKeys()
57 for i, key := range pubKeys {
58 if key.Alias == keyAlias {
59 return &pubKeys[i].XPub
65 func (g *TxGenerator) createAccount(name string, keys []string, quorum int) error {
66 xpubs := []chainkd.XPub{}
67 for _, alias := range keys {
68 xpub := g.getPubkey(alias)
70 return fmt.Errorf("can't find pubkey for %s", alias)
72 xpubs = append(xpubs, *xpub)
74 _, err := g.AccountManager.Create(xpubs, quorum, name, signers.BIP0044)
78 func (g *TxGenerator) createAsset(accountAlias string, assetAlias string) (*asset.Asset, error) {
79 acc, err := g.AccountManager.FindByAlias(accountAlias)
83 return g.Assets.Define(acc.XPubs, len(acc.XPubs), nil, assetAlias, nil)
86 func (g *TxGenerator) mockUtxo(accountAlias, assetAlias string, amount uint64) (*account.UTXO, error) {
87 ctrlProg, err := g.createControlProgram(accountAlias, false)
92 assetAmount, err := g.assetAmount(assetAlias, amount)
96 utxo := &account.UTXO{
97 OutputID: bc.Hash{V0: 1},
98 SourceID: bc.Hash{V0: 1},
99 AssetID: *assetAmount.AssetId,
100 Amount: assetAmount.Amount,
102 ControlProgram: ctrlProg.ControlProgram,
103 ControlProgramIndex: ctrlProg.KeyIndex,
104 AccountID: ctrlProg.AccountID,
105 Address: ctrlProg.Address,
107 Change: ctrlProg.Change,
112 func (g *TxGenerator) assetAmount(assetAlias string, amount uint64) (*bc.AssetAmount, error) {
113 if assetAlias == "BTM" {
114 a := &bc.AssetAmount{
116 AssetId: consensus.BTMAssetID,
121 asset, err := g.Assets.FindByAlias(assetAlias)
125 return &bc.AssetAmount{
127 AssetId: &asset.AssetID,
131 func (g *TxGenerator) createControlProgram(accountAlias string, change bool) (*account.CtrlProgram, error) {
132 acc, err := g.AccountManager.FindByAlias(accountAlias)
136 return g.AccountManager.CreateAddress(acc.ID, change)
139 // AddSpendInput add a spend input
140 func (g *TxGenerator) AddSpendInput(accountAlias, assetAlias string, amount uint64) error {
141 assetAmount, err := g.assetAmount(assetAlias, amount)
146 acc, err := g.AccountManager.FindByAlias(accountAlias)
151 reqAction := make(map[string]interface{})
152 reqAction["account_id"] = acc.ID
153 reqAction["amount"] = amount
154 reqAction["asset_id"] = assetAmount.AssetId.String()
155 data, err := json.Marshal(reqAction)
160 spendAction, err := g.AccountManager.DecodeSpendAction(data)
164 return spendAction.Build(nil, g.Builder)
167 // AddTxInput add a tx input and signing instruction
168 func (g *TxGenerator) AddTxInput(txInput *types.TxInput, signInstruction *txbuilder.SigningInstruction) error {
169 return g.Builder.AddInput(txInput, signInstruction)
172 // AddTxInputFromUtxo add a tx input which spent the utxo
173 func (g *TxGenerator) AddTxInputFromUtxo(utxo *account.UTXO, accountAlias string) error {
174 acc, err := g.AccountManager.FindByAlias(accountAlias)
179 txInput, signInst, err := account.UtxoToInputs(acc.Signer, utxo)
183 return g.AddTxInput(txInput, signInst)
186 // AddIssuanceInput add a issue input
187 func (g *TxGenerator) AddIssuanceInput(assetAlias string, amount uint64) error {
188 asset, err := g.Assets.FindByAlias(assetAlias)
194 _, err = rand.Read(nonce[:])
198 issuanceInput := types.NewIssuanceInput(nonce[:], amount, asset.IssuanceProgram, nil, asset.RawDefinitionByte)
199 signInstruction := &txbuilder.SigningInstruction{}
200 path := signers.GetBip0032Path(asset.Signer, signers.AssetKeySpace)
201 signInstruction.AddRawWitnessKeys(asset.Signer.XPubs, path, asset.Signer.Quorum)
202 g.Builder.RestrictMinTime(time.Now())
203 return g.Builder.AddInput(issuanceInput, signInstruction)
206 // AddTxOutput add a tx output
207 func (g *TxGenerator) AddTxOutput(accountAlias, assetAlias string, amount uint64) error {
208 assetAmount, err := g.assetAmount(assetAlias, uint64(amount))
212 controlProgram, err := g.createControlProgram(accountAlias, false)
216 out := types.NewTxOutput(*assetAmount.AssetId, assetAmount.Amount, controlProgram.ControlProgram)
217 return g.Builder.AddOutput(out)
220 // AddRetirement add a retirement output
221 func (g *TxGenerator) AddRetirement(assetAlias string, amount uint64) error {
222 assetAmount, err := g.assetAmount(assetAlias, uint64(amount))
226 retirementProgram := []byte{byte(vm.OP_FAIL)}
227 out := types.NewTxOutput(*assetAmount.AssetId, assetAmount.Amount, retirementProgram)
228 return g.Builder.AddOutput(out)
231 // Sign used to sign tx
232 func (g *TxGenerator) Sign(passwords []string) (*types.Tx, error) {
233 tpl, _, err := g.Builder.Build()
238 txSerialized, err := tpl.Transaction.MarshalText()
243 tpl.Transaction.Tx.SerializedSize = uint64(len(txSerialized))
244 tpl.Transaction.TxData.SerializedSize = uint64(len(txSerialized))
245 for _, password := range passwords {
246 _, err = MockSign(tpl, g.Hsm, password)
251 return tpl.Transaction, nil
254 func txFee(tx *types.Tx) uint64 {
255 if len(tx.Inputs) == 1 && tx.Inputs[0].InputType() == types.CoinbaseInputType {
259 inputSum := uint64(0)
260 outputSum := uint64(0)
261 for _, input := range tx.Inputs {
262 if input.AssetID() == *consensus.BTMAssetID {
263 inputSum += input.Amount()
267 for _, output := range tx.Outputs {
268 if *output.AssetId == *consensus.BTMAssetID {
269 outputSum += output.Amount
272 return inputSum - outputSum
275 // CreateSpendInput create SpendInput which spent the output from tx
276 func CreateSpendInput(tx *types.Tx, outputIndex uint64) (*types.SpendInput, error) {
277 outputID := tx.ResultIds[outputIndex]
278 output, ok := tx.Entries[*outputID].(*bc.Output)
280 return nil, fmt.Errorf("retirement can't be spent")
283 sc := types.SpendCommitment{
284 AssetAmount: *output.Source.Value,
285 SourceID: *output.Source.Ref,
286 SourcePosition: output.Ordinal,
287 VMVersion: vmVersion,
288 ControlProgram: output.ControlProgram.Code,
290 return &types.SpendInput{
295 // SignInstructionFor read CtrlProgram from db, construct SignInstruction for SpendInput
296 func SignInstructionFor(input *types.SpendInput, db db.DB, signer *signers.Signer) (*txbuilder.SigningInstruction, error) {
297 cp := account.CtrlProgram{}
299 sha3pool.Sum256(hash[:], input.ControlProgram)
300 bytes := db.Get(account.ContractKey(hash))
302 return nil, fmt.Errorf("can't find CtrlProgram for the SpendInput")
305 err := json.Unmarshal(bytes, &cp)
310 sigInst := &txbuilder.SigningInstruction{}
315 // FIXME: code duplicate with account/builder.go
316 path, err := signers.Path(signer, signers.AccountKeySpace, cp.Change, cp.KeyIndex)
321 if cp.Address == "" {
322 sigInst.AddWitnessKeys(signer.XPubs, path, signer.Quorum)
326 address, err := common.DecodeAddress(cp.Address, &consensus.MainNetParams)
331 switch address.(type) {
332 case *common.AddressWitnessPubKeyHash:
333 sigInst.AddRawWitnessKeys(signer.XPubs, path, signer.Quorum)
334 derivedXPubs := chainkd.DeriveXPubs(signer.XPubs, path)
335 derivedPK := derivedXPubs[0].PublicKey()
336 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness([]byte(derivedPK)))
338 case *common.AddressWitnessScriptHash:
339 sigInst.AddRawWitnessKeys(signer.XPubs, path, signer.Quorum)
340 path, err := signers.Path(signer, signers.AccountKeySpace, cp.Change, cp.KeyIndex)
344 derivedXPubs := chainkd.DeriveXPubs(signer.XPubs, path)
345 derivedPKs := chainkd.XPubKeys(derivedXPubs)
346 script, err := vmutil.P2SPMultiSigProgram(derivedPKs, signer.Quorum)
350 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness(script))
353 return nil, errors.New("unsupport address type")
359 // CreateCoinbaseTx create coinbase tx at block height
360 func CreateCoinbaseTx(controlProgram []byte, height, txsFee uint64) (*types.Tx, error) {
361 coinbaseValue := consensus.BlockSubsidy(height) + txsFee
362 builder := txbuilder.NewBuilder(time.Now())
363 if err := builder.AddInput(types.NewCoinbaseInput([]byte(string(height))), &txbuilder.SigningInstruction{}); err != nil {
366 if err := builder.AddOutput(types.NewTxOutput(*consensus.BTMAssetID, coinbaseValue, controlProgram)); err != nil {
370 tpl, _, err := builder.Build()
375 txSerialized, err := tpl.Transaction.MarshalText()
380 tpl.Transaction.Tx.SerializedSize = uint64(len(txSerialized))
381 tpl.Transaction.TxData.SerializedSize = uint64(len(txSerialized))
382 return tpl.Transaction, nil
385 // CreateTxFromTx create a tx spent the output in outputIndex at baseTx
386 func CreateTxFromTx(baseTx *types.Tx, outputIndex uint64, outputAmount uint64, ctrlProgram []byte) (*types.Tx, error) {
387 spendInput, err := CreateSpendInput(baseTx, outputIndex)
392 txInput := &types.TxInput{
393 AssetVersion: assetVersion,
394 TypedInput: spendInput,
396 output := types.NewTxOutput(*consensus.BTMAssetID, outputAmount, ctrlProgram)
397 builder := txbuilder.NewBuilder(time.Now())
398 if err := builder.AddInput(txInput, &txbuilder.SigningInstruction{}); err != nil {
401 if err := builder.AddOutput(output); err != nil {
405 tpl, _, err := builder.Build()
410 txSerialized, err := tpl.Transaction.MarshalText()
415 tpl.Transaction.Tx.SerializedSize = uint64(len(txSerialized))
416 tpl.Transaction.TxData.SerializedSize = uint64(len(txSerialized))
417 return tpl.Transaction, nil