OSDN Git Service

try to fix ban peer bug (#273)
[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         "github.com/vapor/database"
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) mockUtxo(accountAlias, assetAlias string, amount uint64) (*account.UTXO, error) {
78         ctrlProg, err := g.createControlProgram(accountAlias, false)
79         if err != nil {
80                 return nil, err
81         }
82
83         assetAmount, err := g.assetAmount(assetAlias, amount)
84         if err != nil {
85                 return nil, err
86         }
87         utxo := &account.UTXO{
88                 OutputID:            bc.Hash{V0: 1},
89                 SourceID:            bc.Hash{V0: 1},
90                 AssetID:             *assetAmount.AssetId,
91                 Amount:              assetAmount.Amount,
92                 SourcePos:           0,
93                 ControlProgram:      ctrlProg.ControlProgram,
94                 ControlProgramIndex: ctrlProg.KeyIndex,
95                 AccountID:           ctrlProg.AccountID,
96                 Address:             ctrlProg.Address,
97                 ValidHeight:         0,
98                 Change:              ctrlProg.Change,
99         }
100         return utxo, nil
101 }
102
103 func (g *TxGenerator) assetAmount(assetAlias string, amount uint64) (*bc.AssetAmount, error) {
104         if assetAlias == "BTM" {
105                 a := &bc.AssetAmount{
106                         Amount:  amount,
107                         AssetId: consensus.BTMAssetID,
108                 }
109                 return a, nil
110         }
111
112         asset, err := g.Assets.FindByAlias(assetAlias)
113         if err != nil {
114                 return nil, err
115         }
116         return &bc.AssetAmount{
117                 Amount:  amount,
118                 AssetId: &asset.AssetID,
119         }, nil
120 }
121
122 func (g *TxGenerator) createControlProgram(accountAlias string, change bool) (*account.CtrlProgram, error) {
123         acc, err := g.AccountManager.FindByAlias(accountAlias)
124         if err != nil {
125                 return nil, err
126         }
127         return g.AccountManager.CreateAddress(acc.ID, change)
128 }
129
130 // AddSpendInput add a spend input
131 func (g *TxGenerator) AddSpendInput(accountAlias, assetAlias string, amount uint64) error {
132         assetAmount, err := g.assetAmount(assetAlias, amount)
133         if err != nil {
134                 return err
135         }
136
137         acc, err := g.AccountManager.FindByAlias(accountAlias)
138         if err != nil {
139                 return err
140         }
141
142         reqAction := make(map[string]interface{})
143         reqAction["account_id"] = acc.ID
144         reqAction["amount"] = amount
145         reqAction["asset_id"] = assetAmount.AssetId.String()
146         data, err := json.Marshal(reqAction)
147         if err != nil {
148                 return err
149         }
150
151         spendAction, err := g.AccountManager.DecodeSpendAction(data)
152         if err != nil {
153                 return err
154         }
155         return spendAction.Build(nil, g.Builder)
156 }
157
158 // AddTxInput add a tx input and signing instruction
159 func (g *TxGenerator) AddTxInput(txInput *types.TxInput, signInstruction *txbuilder.SigningInstruction) error {
160         return g.Builder.AddInput(txInput, signInstruction)
161 }
162
163 // AddTxInputFromUtxo add a tx input which spent the utxo
164 func (g *TxGenerator) AddTxInputFromUtxo(utxo *account.UTXO, accountAlias string) error {
165         acc, err := g.AccountManager.FindByAlias(accountAlias)
166         if err != nil {
167                 return err
168         }
169
170         txInput, signInst, err := account.UtxoToInputs(acc.Signer, utxo)
171         if err != nil {
172                 return err
173         }
174         return g.AddTxInput(txInput, signInst)
175 }
176
177 // AddTxOutput add a tx output
178 func (g *TxGenerator) AddTxOutput(accountAlias, assetAlias string, amount uint64) error {
179         assetAmount, err := g.assetAmount(assetAlias, uint64(amount))
180         if err != nil {
181                 return err
182         }
183         controlProgram, err := g.createControlProgram(accountAlias, false)
184         if err != nil {
185                 return err
186         }
187         out := types.NewIntraChainOutput(*assetAmount.AssetId, assetAmount.Amount, controlProgram.ControlProgram)
188         return g.Builder.AddOutput(out)
189 }
190
191 // AddRetirement add a retirement output
192 func (g *TxGenerator) AddRetirement(assetAlias string, amount uint64) error {
193         assetAmount, err := g.assetAmount(assetAlias, uint64(amount))
194         if err != nil {
195                 return err
196         }
197         retirementProgram := []byte{byte(vm.OP_FAIL)}
198         out := types.NewIntraChainOutput(*assetAmount.AssetId, assetAmount.Amount, retirementProgram)
199         return g.Builder.AddOutput(out)
200 }
201
202 // Sign used to sign tx
203 func (g *TxGenerator) Sign(passwords []string) (*types.Tx, error) {
204         tpl, _, err := g.Builder.Build()
205         if err != nil {
206                 return nil, err
207         }
208
209         txSerialized, err := tpl.Transaction.MarshalText()
210         if err != nil {
211                 return nil, err
212         }
213
214         tpl.Transaction.Tx.SerializedSize = uint64(len(txSerialized))
215         tpl.Transaction.TxData.SerializedSize = uint64(len(txSerialized))
216         for _, password := range passwords {
217                 _, err = MockSign(tpl, g.Hsm, password)
218                 if err != nil {
219                         return nil, err
220                 }
221         }
222         return tpl.Transaction, nil
223 }
224
225 func txFee(tx *types.Tx) uint64 {
226         if len(tx.Inputs) == 1 && tx.Inputs[0].InputType() == types.CoinbaseInputType {
227                 return 0
228         }
229
230         inputSum := uint64(0)
231         outputSum := uint64(0)
232         for _, input := range tx.Inputs {
233                 if input.AssetID() == *consensus.BTMAssetID {
234                         inputSum += input.Amount()
235                 }
236         }
237
238         for _, output := range tx.Outputs {
239                 if *output.AssetAmount().AssetId == *consensus.BTMAssetID {
240                         outputSum += output.AssetAmount().Amount
241                 }
242         }
243         return inputSum - outputSum
244 }
245
246 // CreateSpendInput create SpendInput which spent the output from tx
247 func CreateSpendInput(tx *types.Tx, outputIndex uint64) (*types.SpendInput, error) {
248         outputID := tx.ResultIds[outputIndex]
249         output, ok := tx.Entries[*outputID].(*bc.IntraChainOutput)
250         if !ok {
251                 return nil, fmt.Errorf("retirement can't be spent")
252         }
253
254         sc := types.SpendCommitment{
255                 AssetAmount:    *output.Source.Value,
256                 SourceID:       *output.Source.Ref,
257                 SourcePosition: output.Ordinal,
258                 VMVersion:      vmVersion,
259                 ControlProgram: output.ControlProgram.Code,
260         }
261         return &types.SpendInput{
262                 SpendCommitment: sc,
263         }, nil
264 }
265
266 // SignInstructionFor read CtrlProgram from db, construct SignInstruction for SpendInput
267 func SignInstructionFor(input *types.SpendInput, db dbm.DB, signer *signers.Signer) (*txbuilder.SigningInstruction, error) {
268         cp := account.CtrlProgram{}
269         var hash [32]byte
270         sha3pool.Sum256(hash[:], input.ControlProgram)
271         bytes := db.Get(database.ContractKey(bc.NewHash(hash)))
272         if bytes == nil {
273                 return nil, fmt.Errorf("can't find CtrlProgram for the SpendInput")
274         }
275
276         err := json.Unmarshal(bytes, &cp)
277         if err != nil {
278                 return nil, err
279         }
280
281         sigInst := &txbuilder.SigningInstruction{}
282         if signer == nil {
283                 return sigInst, nil
284         }
285
286         // FIXME: code duplicate with account/builder.go
287         path, err := signers.Path(signer, signers.AccountKeySpace, cp.Change, cp.KeyIndex)
288         if err != nil {
289                 return nil, err
290         }
291
292         if cp.Address == "" {
293                 sigInst.AddWitnessKeys(signer.XPubs, path, signer.Quorum)
294                 return sigInst, nil
295         }
296
297         address, err := common.DecodeAddress(cp.Address, &consensus.MainNetParams)
298         if err != nil {
299                 return nil, err
300         }
301
302         switch address.(type) {
303         case *common.AddressWitnessPubKeyHash:
304                 sigInst.AddRawWitnessKeys(signer.XPubs, path, signer.Quorum)
305                 derivedXPubs := chainkd.DeriveXPubs(signer.XPubs, path)
306                 derivedPK := derivedXPubs[0].PublicKey()
307                 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness([]byte(derivedPK)))
308
309         case *common.AddressWitnessScriptHash:
310                 sigInst.AddRawWitnessKeys(signer.XPubs, path, signer.Quorum)
311                 path, err := signers.Path(signer, signers.AccountKeySpace, cp.Change, cp.KeyIndex)
312                 if err != nil {
313                         return nil, err
314                 }
315                 derivedXPubs := chainkd.DeriveXPubs(signer.XPubs, path)
316                 derivedPKs := chainkd.XPubKeys(derivedXPubs)
317                 script, err := vmutil.P2SPMultiSigProgram(derivedPKs, signer.Quorum)
318                 if err != nil {
319                         return nil, err
320                 }
321                 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness(script))
322
323         default:
324                 return nil, errors.New("unsupport address type")
325         }
326
327         return sigInst, nil
328 }
329
330 // CreateCoinbaseTx create coinbase tx at block height
331 func CreateCoinbaseTx(controlProgram []byte, height, txsFee uint64) (*types.Tx, error) {
332         coinbaseValue := consensus.BlockSubsidy(height) + txsFee
333         builder := txbuilder.NewBuilder(time.Now())
334         if err := builder.AddInput(types.NewCoinbaseInput([]byte(string(height))), &txbuilder.SigningInstruction{}); err != nil {
335                 return nil, err
336         }
337         if err := builder.AddOutput(types.NewIntraChainOutput(*consensus.BTMAssetID, coinbaseValue, controlProgram)); err != nil {
338                 return nil, err
339         }
340
341         tpl, _, err := builder.Build()
342         if err != nil {
343                 return nil, err
344         }
345
346         txSerialized, err := tpl.Transaction.MarshalText()
347         if err != nil {
348                 return nil, err
349         }
350
351         tpl.Transaction.Tx.SerializedSize = uint64(len(txSerialized))
352         tpl.Transaction.TxData.SerializedSize = uint64(len(txSerialized))
353         return tpl.Transaction, nil
354 }
355
356 // CreateTxFromTx create a tx spent the output in outputIndex at baseTx
357 func CreateTxFromTx(baseTx *types.Tx, outputIndex uint64, outputAmount uint64, ctrlProgram []byte) (*types.Tx, error) {
358         spendInput, err := CreateSpendInput(baseTx, outputIndex)
359         if err != nil {
360                 return nil, err
361         }
362
363         txInput := &types.TxInput{
364                 AssetVersion: assetVersion,
365                 TypedInput:   spendInput,
366         }
367         output := types.NewIntraChainOutput(*consensus.BTMAssetID, outputAmount, ctrlProgram)
368         builder := txbuilder.NewBuilder(time.Now())
369         if err := builder.AddInput(txInput, &txbuilder.SigningInstruction{}); err != nil {
370                 return nil, err
371         }
372         if err := builder.AddOutput(output); err != nil {
373                 return nil, err
374         }
375
376         tpl, _, err := builder.Build()
377         if err != nil {
378                 return nil, err
379         }
380
381         txSerialized, err := tpl.Transaction.MarshalText()
382         if err != nil {
383                 return nil, err
384         }
385
386         tpl.Transaction.Tx.SerializedSize = uint64(len(txSerialized))
387         tpl.Transaction.TxData.SerializedSize = uint64(len(txSerialized))
388         return tpl.Transaction, nil
389 }