8 "github.com/vapor/account"
9 "github.com/vapor/asset"
10 "github.com/vapor/blockchain/pseudohsm"
11 "github.com/vapor/blockchain/signers"
12 "github.com/vapor/blockchain/txbuilder"
13 "github.com/vapor/common"
14 "github.com/vapor/consensus"
15 "github.com/vapor/crypto/ed25519/chainkd"
16 "github.com/vapor/crypto/sha3pool"
17 dbm "github.com/vapor/database/leveldb"
18 "github.com/vapor/errors"
19 "github.com/vapor/protocol/bc"
20 "github.com/vapor/protocol/bc/types"
21 "github.com/vapor/protocol/vm"
22 "github.com/vapor/protocol/vm/vmutil"
25 // TxGenerator used to generate new tx
26 type TxGenerator struct {
27 Builder *txbuilder.TemplateBuilder
28 AccountManager *account.Manager
29 Assets *asset.Registry
33 // NewTxGenerator create a TxGenerator
34 func NewTxGenerator(accountManager *account.Manager, assets *asset.Registry, hsm *pseudohsm.HSM) *TxGenerator {
36 Builder: txbuilder.NewBuilder(time.Now()),
37 AccountManager: accountManager,
43 // Reset reset transaction builder, used to create a new tx
44 func (g *TxGenerator) Reset() {
45 g.Builder = txbuilder.NewBuilder(time.Now())
48 func (g *TxGenerator) createKey(alias string, auth string) error {
49 _, _, err := g.Hsm.XCreate(alias, auth, "en")
53 func (g *TxGenerator) getPubkey(keyAlias string) *chainkd.XPub {
54 pubKeys := g.Hsm.ListKeys()
55 for i, key := range pubKeys {
56 if key.Alias == keyAlias {
57 return &pubKeys[i].XPub
63 func (g *TxGenerator) createAccount(name string, keys []string, quorum int) error {
64 xpubs := []chainkd.XPub{}
65 for _, alias := range keys {
66 xpub := g.getPubkey(alias)
68 return fmt.Errorf("can't find pubkey for %s", alias)
70 xpubs = append(xpubs, *xpub)
72 _, err := g.AccountManager.Create(xpubs, quorum, name, signers.BIP0044)
76 func (g *TxGenerator) mockUtxo(accountAlias, assetAlias string, amount uint64) (*account.UTXO, error) {
77 ctrlProg, err := g.createControlProgram(accountAlias, false)
82 assetAmount, err := g.assetAmount(assetAlias, amount)
86 utxo := &account.UTXO{
87 OutputID: bc.Hash{V0: 1},
88 SourceID: bc.Hash{V0: 1},
89 AssetID: *assetAmount.AssetId,
90 Amount: assetAmount.Amount,
92 ControlProgram: ctrlProg.ControlProgram,
93 ControlProgramIndex: ctrlProg.KeyIndex,
94 AccountID: ctrlProg.AccountID,
95 Address: ctrlProg.Address,
97 Change: ctrlProg.Change,
102 func (g *TxGenerator) assetAmount(assetAlias string, amount uint64) (*bc.AssetAmount, error) {
103 if assetAlias == "BTM" {
104 a := &bc.AssetAmount{
106 AssetId: consensus.BTMAssetID,
111 asset, err := g.Assets.FindByAlias(assetAlias)
115 return &bc.AssetAmount{
117 AssetId: &asset.AssetID,
121 func (g *TxGenerator) createControlProgram(accountAlias string, change bool) (*account.CtrlProgram, error) {
122 acc, err := g.AccountManager.FindByAlias(accountAlias)
126 return g.AccountManager.CreateAddress(acc.ID, change)
129 // AddSpendInput add a spend input
130 func (g *TxGenerator) AddSpendInput(accountAlias, assetAlias string, amount uint64) error {
131 assetAmount, err := g.assetAmount(assetAlias, amount)
136 acc, err := g.AccountManager.FindByAlias(accountAlias)
141 reqAction := make(map[string]interface{})
142 reqAction["account_id"] = acc.ID
143 reqAction["amount"] = amount
144 reqAction["asset_id"] = assetAmount.AssetId.String()
145 data, err := json.Marshal(reqAction)
150 spendAction, err := g.AccountManager.DecodeSpendAction(data)
154 return spendAction.Build(nil, g.Builder)
157 // AddTxInput add a tx input and signing instruction
158 func (g *TxGenerator) AddTxInput(txInput *types.TxInput, signInstruction *txbuilder.SigningInstruction) error {
159 return g.Builder.AddInput(txInput, signInstruction)
162 // AddTxInputFromUtxo add a tx input which spent the utxo
163 func (g *TxGenerator) AddTxInputFromUtxo(utxo *account.UTXO, accountAlias string) error {
164 acc, err := g.AccountManager.FindByAlias(accountAlias)
169 txInput, signInst, err := account.UtxoToInputs(acc.Signer, utxo)
173 return g.AddTxInput(txInput, signInst)
176 // AddTxOutput add a tx output
177 func (g *TxGenerator) AddTxOutput(accountAlias, assetAlias string, amount uint64) error {
178 assetAmount, err := g.assetAmount(assetAlias, uint64(amount))
182 controlProgram, err := g.createControlProgram(accountAlias, false)
186 out := types.NewIntraChainOutput(*assetAmount.AssetId, assetAmount.Amount, controlProgram.ControlProgram)
187 return g.Builder.AddOutput(out)
190 // AddRetirement add a retirement output
191 func (g *TxGenerator) AddRetirement(assetAlias string, amount uint64) error {
192 assetAmount, err := g.assetAmount(assetAlias, uint64(amount))
196 retirementProgram := []byte{byte(vm.OP_FAIL)}
197 out := types.NewIntraChainOutput(*assetAmount.AssetId, assetAmount.Amount, retirementProgram)
198 return g.Builder.AddOutput(out)
201 // Sign used to sign tx
202 func (g *TxGenerator) Sign(passwords []string) (*types.Tx, error) {
203 tpl, _, err := g.Builder.Build()
208 txSerialized, err := tpl.Transaction.MarshalText()
213 tpl.Transaction.Tx.SerializedSize = uint64(len(txSerialized))
214 tpl.Transaction.TxData.SerializedSize = uint64(len(txSerialized))
215 for _, password := range passwords {
216 _, err = MockSign(tpl, g.Hsm, password)
221 return tpl.Transaction, nil
224 func txFee(tx *types.Tx) uint64 {
225 if len(tx.Inputs) == 1 && tx.Inputs[0].InputType() == types.CoinbaseInputType {
229 inputSum := uint64(0)
230 outputSum := uint64(0)
231 for _, input := range tx.Inputs {
232 if input.AssetID() == *consensus.BTMAssetID {
233 inputSum += input.Amount()
237 for _, output := range tx.Outputs {
238 if *output.AssetAmount().AssetId == *consensus.BTMAssetID {
239 outputSum += output.AssetAmount().Amount
242 return inputSum - outputSum
245 // CreateSpendInput create SpendInput which spent the output from tx
246 func CreateSpendInput(tx *types.Tx, outputIndex uint64) (*types.SpendInput, error) {
247 outputID := tx.ResultIds[outputIndex]
248 output, ok := tx.Entries[*outputID].(*bc.IntraChainOutput)
250 return nil, fmt.Errorf("retirement can't be spent")
253 sc := types.SpendCommitment{
254 AssetAmount: *output.Source.Value,
255 SourceID: *output.Source.Ref,
256 SourcePosition: output.Ordinal,
257 VMVersion: vmVersion,
258 ControlProgram: output.ControlProgram.Code,
260 return &types.SpendInput{
265 // SignInstructionFor read CtrlProgram from db, construct SignInstruction for SpendInput
266 func SignInstructionFor(input *types.SpendInput, db dbm.DB, signer *signers.Signer) (*txbuilder.SigningInstruction, error) {
267 cp := account.CtrlProgram{}
269 sha3pool.Sum256(hash[:], input.ControlProgram)
270 bytes := db.Get(account.ContractKey(hash))
272 return nil, fmt.Errorf("can't find CtrlProgram for the SpendInput")
275 err := json.Unmarshal(bytes, &cp)
280 sigInst := &txbuilder.SigningInstruction{}
285 // FIXME: code duplicate with account/builder.go
286 path, err := signers.Path(signer, signers.AccountKeySpace, cp.Change, cp.KeyIndex)
291 if cp.Address == "" {
292 sigInst.AddWitnessKeys(signer.XPubs, path, signer.Quorum)
296 address, err := common.DecodeAddress(cp.Address, &consensus.MainNetParams)
301 switch address.(type) {
302 case *common.AddressWitnessPubKeyHash:
303 sigInst.AddRawWitnessKeys(signer.XPubs, path, signer.Quorum)
304 derivedXPubs := chainkd.DeriveXPubs(signer.XPubs, path)
305 derivedPK := derivedXPubs[0].PublicKey()
306 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness([]byte(derivedPK)))
308 case *common.AddressWitnessScriptHash:
309 sigInst.AddRawWitnessKeys(signer.XPubs, path, signer.Quorum)
310 path, err := signers.Path(signer, signers.AccountKeySpace, cp.Change, cp.KeyIndex)
314 derivedXPubs := chainkd.DeriveXPubs(signer.XPubs, path)
315 derivedPKs := chainkd.XPubKeys(derivedXPubs)
316 script, err := vmutil.P2SPMultiSigProgram(derivedPKs, signer.Quorum)
320 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness(script))
323 return nil, errors.New("unsupport address type")
329 // CreateCoinbaseTx create coinbase tx at block height
330 func CreateCoinbaseTx(controlProgram []byte, height, txsFee uint64) (*types.Tx, error) {
331 coinbaseValue := consensus.BlockSubsidy(height) + txsFee
332 builder := txbuilder.NewBuilder(time.Now())
333 if err := builder.AddInput(types.NewCoinbaseInput([]byte(string(height))), &txbuilder.SigningInstruction{}); err != nil {
336 if err := builder.AddOutput(types.NewIntraChainOutput(*consensus.BTMAssetID, coinbaseValue, controlProgram)); err != nil {
340 tpl, _, err := builder.Build()
345 txSerialized, err := tpl.Transaction.MarshalText()
350 tpl.Transaction.Tx.SerializedSize = uint64(len(txSerialized))
351 tpl.Transaction.TxData.SerializedSize = uint64(len(txSerialized))
352 return tpl.Transaction, nil
355 // CreateTxFromTx create a tx spent the output in outputIndex at baseTx
356 func CreateTxFromTx(baseTx *types.Tx, outputIndex uint64, outputAmount uint64, ctrlProgram []byte) (*types.Tx, error) {
357 spendInput, err := CreateSpendInput(baseTx, outputIndex)
362 txInput := &types.TxInput{
363 AssetVersion: assetVersion,
364 TypedInput: spendInput,
366 output := types.NewIntraChainOutput(*consensus.BTMAssetID, outputAmount, ctrlProgram)
367 builder := txbuilder.NewBuilder(time.Now())
368 if err := builder.AddInput(txInput, &txbuilder.SigningInstruction{}); err != nil {
371 if err := builder.AddOutput(output); err != nil {
375 tpl, _, err := builder.Build()
380 txSerialized, err := tpl.Transaction.MarshalText()
385 tpl.Transaction.Tx.SerializedSize = uint64(len(txSerialized))
386 tpl.Transaction.TxData.SerializedSize = uint64(len(txSerialized))
387 return tpl.Transaction, nil