OSDN Git Service

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