OSDN Git Service

init for remove issue (#63)
[bytom/vapor.git] / test / tx_test_util.go
1 package test
2
3 import (
4         "encoding/json"
5         "fmt"
6         "time"
7
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"
23 )
24
25 // TxGenerator used to generate new tx
26 type TxGenerator struct {
27         Builder        *txbuilder.TemplateBuilder
28         AccountManager *account.Manager
29         Assets         *asset.Registry
30         Hsm            *pseudohsm.HSM
31 }
32
33 // NewTxGenerator create a TxGenerator
34 func NewTxGenerator(accountManager *account.Manager, assets *asset.Registry, hsm *pseudohsm.HSM) *TxGenerator {
35         return &TxGenerator{
36                 Builder:        txbuilder.NewBuilder(time.Now()),
37                 AccountManager: accountManager,
38                 Assets:         assets,
39                 Hsm:            hsm,
40         }
41 }
42
43 // Reset reset transaction builder, used to create a new tx
44 func (g *TxGenerator) Reset() {
45         g.Builder = txbuilder.NewBuilder(time.Now())
46 }
47
48 func (g *TxGenerator) createKey(alias string, auth string) error {
49         _, _, err := g.Hsm.XCreate(alias, auth, "en")
50         return err
51 }
52
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
58                 }
59         }
60         return nil
61 }
62
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)
67                 if xpub == nil {
68                         return fmt.Errorf("can't find pubkey for %s", alias)
69                 }
70                 xpubs = append(xpubs, *xpub)
71         }
72         _, err := g.AccountManager.Create(xpubs, quorum, name, signers.BIP0044)
73         return err
74 }
75
76 func (g *TxGenerator) mockUtxo(accountAlias, assetAlias string, amount uint64) (*account.UTXO, error) {
77         ctrlProg, err := g.createControlProgram(accountAlias, false)
78         if err != nil {
79                 return nil, err
80         }
81
82         assetAmount, err := g.assetAmount(assetAlias, amount)
83         if err != nil {
84                 return nil, err
85         }
86         utxo := &account.UTXO{
87                 OutputID:            bc.Hash{V0: 1},
88                 SourceID:            bc.Hash{V0: 1},
89                 AssetID:             *assetAmount.AssetId,
90                 Amount:              assetAmount.Amount,
91                 SourcePos:           0,
92                 ControlProgram:      ctrlProg.ControlProgram,
93                 ControlProgramIndex: ctrlProg.KeyIndex,
94                 AccountID:           ctrlProg.AccountID,
95                 Address:             ctrlProg.Address,
96                 ValidHeight:         0,
97                 Change:              ctrlProg.Change,
98         }
99         return utxo, nil
100 }
101
102 func (g *TxGenerator) assetAmount(assetAlias string, amount uint64) (*bc.AssetAmount, error) {
103         if assetAlias == "BTM" {
104                 a := &bc.AssetAmount{
105                         Amount:  amount,
106                         AssetId: consensus.BTMAssetID,
107                 }
108                 return a, nil
109         }
110
111         asset, err := g.Assets.FindByAlias(assetAlias)
112         if err != nil {
113                 return nil, err
114         }
115         return &bc.AssetAmount{
116                 Amount:  amount,
117                 AssetId: &asset.AssetID,
118         }, nil
119 }
120
121 func (g *TxGenerator) createControlProgram(accountAlias string, change bool) (*account.CtrlProgram, error) {
122         acc, err := g.AccountManager.FindByAlias(accountAlias)
123         if err != nil {
124                 return nil, err
125         }
126         return g.AccountManager.CreateAddress(acc.ID, change)
127 }
128
129 // AddSpendInput add a spend input
130 func (g *TxGenerator) AddSpendInput(accountAlias, assetAlias string, amount uint64) error {
131         assetAmount, err := g.assetAmount(assetAlias, amount)
132         if err != nil {
133                 return err
134         }
135
136         acc, err := g.AccountManager.FindByAlias(accountAlias)
137         if err != nil {
138                 return err
139         }
140
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)
146         if err != nil {
147                 return err
148         }
149
150         spendAction, err := g.AccountManager.DecodeSpendAction(data)
151         if err != nil {
152                 return err
153         }
154         return spendAction.Build(nil, g.Builder)
155 }
156
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)
160 }
161
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)
165         if err != nil {
166                 return err
167         }
168
169         txInput, signInst, err := account.UtxoToInputs(acc.Signer, utxo)
170         if err != nil {
171                 return err
172         }
173         return g.AddTxInput(txInput, signInst)
174 }
175
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))
179         if err != nil {
180                 return err
181         }
182         controlProgram, err := g.createControlProgram(accountAlias, false)
183         if err != nil {
184                 return err
185         }
186         out := types.NewIntraChainOutput(*assetAmount.AssetId, assetAmount.Amount, controlProgram.ControlProgram)
187         return g.Builder.AddOutput(out)
188 }
189
190 // AddRetirement add a retirement output
191 func (g *TxGenerator) AddRetirement(assetAlias string, amount uint64) error {
192         assetAmount, err := g.assetAmount(assetAlias, uint64(amount))
193         if err != nil {
194                 return err
195         }
196         retirementProgram := []byte{byte(vm.OP_FAIL)}
197         out := types.NewIntraChainOutput(*assetAmount.AssetId, assetAmount.Amount, retirementProgram)
198         return g.Builder.AddOutput(out)
199 }
200
201 // Sign used to sign tx
202 func (g *TxGenerator) Sign(passwords []string) (*types.Tx, error) {
203         tpl, _, err := g.Builder.Build()
204         if err != nil {
205                 return nil, err
206         }
207
208         txSerialized, err := tpl.Transaction.MarshalText()
209         if err != nil {
210                 return nil, err
211         }
212
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)
217                 if err != nil {
218                         return nil, err
219                 }
220         }
221         return tpl.Transaction, nil
222 }
223
224 func txFee(tx *types.Tx) uint64 {
225         if len(tx.Inputs) == 1 && tx.Inputs[0].InputType() == types.CoinbaseInputType {
226                 return 0
227         }
228
229         inputSum := uint64(0)
230         outputSum := uint64(0)
231         for _, input := range tx.Inputs {
232                 if input.AssetID() == *consensus.BTMAssetID {
233                         inputSum += input.Amount()
234                 }
235         }
236
237         for _, output := range tx.Outputs {
238                 if *output.AssetAmount().AssetId == *consensus.BTMAssetID {
239                         outputSum += output.AssetAmount().Amount
240                 }
241         }
242         return inputSum - outputSum
243 }
244
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)
249         if !ok {
250                 return nil, fmt.Errorf("retirement can't be spent")
251         }
252
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,
259         }
260         return &types.SpendInput{
261                 SpendCommitment: sc,
262         }, nil
263 }
264
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{}
268         var hash [32]byte
269         sha3pool.Sum256(hash[:], input.ControlProgram)
270         bytes := db.Get(account.ContractKey(hash))
271         if bytes == nil {
272                 return nil, fmt.Errorf("can't find CtrlProgram for the SpendInput")
273         }
274
275         err := json.Unmarshal(bytes, &cp)
276         if err != nil {
277                 return nil, err
278         }
279
280         sigInst := &txbuilder.SigningInstruction{}
281         if signer == nil {
282                 return sigInst, nil
283         }
284
285         // FIXME: code duplicate with account/builder.go
286         path, err := signers.Path(signer, signers.AccountKeySpace, cp.Change, cp.KeyIndex)
287         if err != nil {
288                 return nil, err
289         }
290
291         if cp.Address == "" {
292                 sigInst.AddWitnessKeys(signer.XPubs, path, signer.Quorum)
293                 return sigInst, nil
294         }
295
296         address, err := common.DecodeAddress(cp.Address, &consensus.MainNetParams)
297         if err != nil {
298                 return nil, err
299         }
300
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)))
307
308         case *common.AddressWitnessScriptHash:
309                 sigInst.AddRawWitnessKeys(signer.XPubs, path, signer.Quorum)
310                 path, err := signers.Path(signer, signers.AccountKeySpace, cp.Change, cp.KeyIndex)
311                 if err != nil {
312                         return nil, err
313                 }
314                 derivedXPubs := chainkd.DeriveXPubs(signer.XPubs, path)
315                 derivedPKs := chainkd.XPubKeys(derivedXPubs)
316                 script, err := vmutil.P2SPMultiSigProgram(derivedPKs, signer.Quorum)
317                 if err != nil {
318                         return nil, err
319                 }
320                 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness(script))
321
322         default:
323                 return nil, errors.New("unsupport address type")
324         }
325
326         return sigInst, nil
327 }
328
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 {
334                 return nil, err
335         }
336         if err := builder.AddOutput(types.NewIntraChainOutput(*consensus.BTMAssetID, coinbaseValue, controlProgram)); err != nil {
337                 return nil, err
338         }
339
340         tpl, _, err := builder.Build()
341         if err != nil {
342                 return nil, err
343         }
344
345         txSerialized, err := tpl.Transaction.MarshalText()
346         if err != nil {
347                 return nil, err
348         }
349
350         tpl.Transaction.Tx.SerializedSize = uint64(len(txSerialized))
351         tpl.Transaction.TxData.SerializedSize = uint64(len(txSerialized))
352         return tpl.Transaction, nil
353 }
354
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)
358         if err != nil {
359                 return nil, err
360         }
361
362         txInput := &types.TxInput{
363                 AssetVersion: assetVersion,
364                 TypedInput:   spendInput,
365         }
366         output := types.NewIntraChainOutput(*consensus.BTMAssetID, outputAmount, ctrlProgram)
367         builder := txbuilder.NewBuilder(time.Now())
368         if err := builder.AddInput(txInput, &txbuilder.SigningInstruction{}); err != nil {
369                 return nil, err
370         }
371         if err := builder.AddOutput(output); err != nil {
372                 return nil, err
373         }
374
375         tpl, _, err := builder.Build()
376         if err != nil {
377                 return nil, err
378         }
379
380         txSerialized, err := tpl.Transaction.MarshalText()
381         if err != nil {
382                 return nil, err
383         }
384
385         tpl.Transaction.Tx.SerializedSize = uint64(len(txSerialized))
386         tpl.Transaction.TxData.SerializedSize = uint64(len(txSerialized))
387         return tpl.Transaction, nil
388 }