OSDN Git Service

feat: add cross-chain output (#56)
[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/vapor/account"
10         "github.com/vapor/asset"
11         "github.com/vapor/blockchain/pseudohsm"
12         "github.com/vapor/blockchain/signers"
13         "github.com/vapor/blockchain/txbuilder"
14         "github.com/vapor/common"
15         "github.com/vapor/consensus"
16         "github.com/vapor/crypto/ed25519/chainkd"
17         "github.com/vapor/crypto/sha3pool"
18         dbm "github.com/vapor/database/leveldb"
19         "github.com/vapor/errors"
20         "github.com/vapor/protocol/bc"
21         "github.com/vapor/protocol/bc/types"
22         "github.com/vapor/protocol/vm"
23         "github.com/vapor/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.NewIntraChainOutput(*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.NewIntraChainOutput(*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.AssetAmount().AssetId == *consensus.BTMAssetID {
268                         outputSum += output.AssetAmount().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.IntraChainOutput)
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.NewIntraChainOutput(*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.NewIntraChainOutput(*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 }