OSDN Git Service

Merge pull request #41 from Bytom/dev
[bytom/vapor.git] / claim / rpc / bytom / bytom_claim_tx.go
1 package bytom
2
3 import (
4         "bytes"
5         "context"
6         "encoding/json"
7         "math"
8         "strconv"
9         "time"
10
11         log "github.com/sirupsen/logrus"
12
13         "github.com/vapor/account"
14         "github.com/vapor/blockchain/txbuilder"
15         "github.com/vapor/claim"
16         bytomtypes "github.com/vapor/claim/bytom/protocolbc/types"
17         "github.com/vapor/claim/rpc"
18         "github.com/vapor/consensus"
19         "github.com/vapor/consensus/segwit"
20         "github.com/vapor/crypto/ed25519/chainkd"
21         "github.com/vapor/crypto/sha3pool"
22         chainjson "github.com/vapor/encoding/json"
23         "github.com/vapor/errors"
24         "github.com/vapor/math/checked"
25         "github.com/vapor/protocol"
26         "github.com/vapor/protocol/bc"
27         "github.com/vapor/protocol/bc/types"
28         "github.com/vapor/util"
29         "github.com/vapor/wallet"
30 )
31
32 func getPeginTxnOutputIndex(rawTx bytomtypes.Tx, controlProg []byte) int {
33         for index, output := range rawTx.Outputs {
34                 if bytes.Equal(output.ControlProgram, controlProg) {
35                         return index
36                 }
37         }
38         return -1
39 }
40
41 func toHash(hexBytes []chainjson.HexBytes) (hashs []*bc.Hash) {
42         for _, data := range hexBytes {
43                 b32 := [32]byte{}
44                 copy(b32[:], data)
45                 res := bc.NewHash(b32)
46                 hashs = append(hashs, &res)
47         }
48         return
49 }
50
51 type BytomClaimTx struct {
52         rpc.ClaimTxParam
53         Wallet *wallet.Wallet
54         Chain  *protocol.Chain
55 }
56
57 func (b *BytomClaimTx) pseudohsmSignTemplate(ctx context.Context, xpub chainkd.XPub, path [][]byte, data [32]byte, password string) ([]byte, error) {
58         return b.Wallet.Hsm.XSign(xpub, path, data[:], password)
59 }
60
61 func (b *BytomClaimTx) ClaimPeginTx(ctx context.Context) (interface{}, error) {
62         tmpl, err := b.createRawPegin(b.ClaimTxParam)
63         if err != nil {
64                 log.WithField("build err", err).Error("fail on createrawpegin.")
65                 return nil, err
66         }
67
68         // 交易签名
69         if err := txbuilder.Sign(ctx, tmpl, b.ClaimTxParam.Password, b.pseudohsmSignTemplate); err != nil {
70                 log.WithField("build err", err).Error("fail on sign transaction.")
71                 return nil, err
72         }
73
74         // submit
75         if err := txbuilder.FinalizeTx(ctx, b.Chain, tmpl.Transaction); err != nil {
76                 return nil, err
77         }
78
79         log.WithField("tx_id", tmpl.Transaction.ID.String()).Info("claim script tx")
80         return &struct {
81                 TxID *bc.Hash `json:"tx_id"`
82         }{TxID: &tmpl.Transaction.ID}, nil
83 }
84
85 func (b *BytomClaimTx) createRawPegin(ins rpc.ClaimTxParam) (*txbuilder.Template, error) {
86         // proof验证
87         var flags []uint8
88         for flag := range ins.Flags {
89                 flags = append(flags, uint8(flag))
90         }
91         txHashes := toHash(ins.TxHashes)
92         matchedTxIDs := toHash(ins.MatchedTxIDs)
93         statusHashes := toHash(ins.StatusHashes)
94         blockHeader := &bytomtypes.BlockHeader{}
95         if err := blockHeader.UnmarshalText([]byte(ins.BlockHeader)); err != nil {
96                 return nil, err
97         }
98         if !types.ValidateTxMerkleTreeProof(txHashes, flags, matchedTxIDs, blockHeader.BlockCommitment.TransactionsMerkleRoot) {
99                 return nil, errors.New("Merkleblock validation failed")
100         }
101         // CheckBytomProof
102         //difficulty.CheckBytomProofOfWork(ins.BlockHeader.Hash(), ins.BlockHeader)
103         // 增加spv验证以及连接主链api查询交易的确认数
104         if util.ValidatePegin {
105                 if err := util.IsConfirmedBytomBlock(blockHeader.Height, consensus.ActiveNetParams.PeginMinDepth); err != nil {
106                         return nil, err
107                 }
108         }
109         // 找出与claim script有关联的交易的输出
110         var claimScript []byte
111         rawTx := &bytomtypes.Tx{}
112         if err := rawTx.UnmarshalText([]byte(ins.RawTx)); err != nil {
113                 return nil, err
114         }
115         nOut := len(rawTx.Outputs)
116         if ins.ClaimScript == nil {
117                 // 遍历寻找与交易输出有关的claim script
118                 cps, err := b.Wallet.AccountMgr.ListControlProgram()
119                 if err != nil {
120                         return nil, err
121                 }
122
123                 for _, cp := range cps {
124                         _, controlProg := b.Wallet.AccountMgr.GetPeginControlPrograms(cp.ControlProgram)
125                         if controlProg == nil {
126                                 continue
127                         }
128                         // 获取交易的输出
129                         nOut = getPeginTxnOutputIndex(*rawTx, controlProg)
130                         if nOut != len(rawTx.Outputs) {
131                                 claimScript = cp.ControlProgram
132                         }
133                 }
134         } else {
135                 claimScript = ins.ClaimScript
136                 _, controlProg := b.Wallet.AccountMgr.GetPeginControlPrograms(claimScript)
137                 // 获取交易的输出
138                 nOut = getPeginTxnOutputIndex(*rawTx, controlProg)
139         }
140         if nOut == len(rawTx.Outputs) || nOut == -1 {
141                 return nil, errors.New("Failed to find output in bytom to the mainchain_address from getpeginaddress")
142         }
143
144         // 根据ClaimScript 获取account id
145         var hash [32]byte
146         sha3pool.Sum256(hash[:], claimScript)
147         data := b.Wallet.DB.Get(account.ContractKey(hash))
148         if data == nil {
149                 return nil, errors.New("Failed to find control program through claim script")
150         }
151
152         cp := &account.CtrlProgram{}
153         if err := json.Unmarshal(data, cp); err != nil {
154                 return nil, errors.New("Failed on unmarshal control program")
155         }
156
157         // 构造交易
158         // 用输出作为交易输入 生成新的交易
159         builder := txbuilder.NewBuilder(time.Now())
160         // TODO 根据raw tx生成一个utxo
161         sourceID := *rawTx.OutputID(nOut)
162         outputAccount := rawTx.Outputs[nOut].Amount
163         assetID := *rawTx.Outputs[nOut].AssetId
164
165         txInput := types.NewClaimInput(nil, sourceID, assetID, outputAccount, uint64(nOut), cp.ControlProgram)
166         if err := builder.AddInput(txInput, &txbuilder.SigningInstruction{}); err != nil {
167                 return nil, err
168         }
169         program, err := b.Wallet.AccountMgr.CreateAddress(cp.AccountID, false)
170         if err != nil {
171                 return nil, err
172         }
173
174         if err = builder.AddOutput(types.NewTxOutput(assetID, outputAccount, program.ControlProgram)); err != nil {
175                 return nil, err
176         }
177
178         tmpl, txData, err := builder.Build()
179         if err != nil {
180                 return nil, err
181         }
182
183         // todo把一些主链的信息加到交易的stack中
184         var stack [][]byte
185
186         //amount
187         amount := strconv.FormatUint(rawTx.Outputs[nOut].Amount, 10)
188         stack = append(stack, []byte(amount))
189         // 主链的gennesisBlockHash
190         stack = append(stack, []byte(consensus.ActiveNetParams.ParentGenesisBlockHash))
191         // claim script
192         stack = append(stack, claimScript)
193         // raw tx
194         tx, _ := json.Marshal(rawTx)
195         stack = append(stack, tx)
196         // proof
197         blockHeaderByte, err := blockHeader.MarshalText()
198         if err != nil {
199                 return nil, err
200         }
201         merkleBlock := claim.MerkleBlock{
202                 BlockHeader:  blockHeaderByte,
203                 TxHashes:     txHashes,
204                 StatusHashes: statusHashes,
205                 Flags:        ins.Flags,
206                 MatchedTxIDs: matchedTxIDs,
207         }
208
209         txOutProof, _ := json.Marshal(merkleBlock)
210
211         stack = append(stack, txOutProof)
212
213         //      tmpl.Transaction.Inputs[0].Peginwitness = stack
214         txData.Inputs[0].Peginwitness = stack
215
216         //交易费估算
217         txGasResp, err := EstimateTxGas(*tmpl)
218         if err != nil {
219                 return nil, err
220         }
221         txData.Outputs[0].Amount = txData.Outputs[0].Amount - uint64(txGasResp.TotalNeu)
222         //重设置Transaction
223         tmpl.Transaction = types.NewTx(*txData)
224         return tmpl, nil
225 }
226
227 func (b *BytomClaimTx) ClaimContractPeginTx(ctx context.Context) (interface{}, error) {
228         tmpl, err := b.createContractRawPegin(b.ClaimTxParam)
229         if err != nil {
230                 log.WithField("build err", err).Error("fail on claimContractPeginTx.")
231                 return nil, err
232         }
233         // 交易签名
234         if err := txbuilder.Sign(ctx, tmpl, b.Password, b.pseudohsmSignTemplate); err != nil {
235                 log.WithField("build err", err).Error("fail on sign transaction.")
236                 return nil, err
237         }
238
239         // submit
240         if err := txbuilder.FinalizeTx(ctx, b.Chain, tmpl.Transaction); err != nil {
241                 return nil, err
242         }
243
244         log.WithField("tx_id", tmpl.Transaction.ID.String()).Info("claim script tx")
245         return &struct {
246                 TxID *bc.Hash `json:"tx_id"`
247         }{TxID: &tmpl.Transaction.ID}, nil
248 }
249
250 func (b *BytomClaimTx) createContractRawPegin(ins rpc.ClaimTxParam) (*txbuilder.Template, error) {
251         // proof验证
252         var flags []uint8
253         for flag := range ins.Flags {
254                 flags = append(flags, uint8(flag))
255         }
256         txHashes := toHash(ins.TxHashes)
257         matchedTxIDs := toHash(ins.MatchedTxIDs)
258         statusHashes := toHash(ins.StatusHashes)
259         blockHeader := &bytomtypes.BlockHeader{}
260         if err := blockHeader.UnmarshalText([]byte(ins.BlockHeader)); err != nil {
261                 return nil, err
262         }
263
264         if !types.ValidateTxMerkleTreeProof(txHashes, flags, matchedTxIDs, blockHeader.BlockCommitment.TransactionsMerkleRoot) {
265                 return nil, errors.New("Merkleblock validation failed")
266         }
267         // CheckBytomProof
268         //difficulty.CheckBytomProofOfWork(ins.BlockHeader.Hash(), ins.BlockHeader)
269         // 增加spv验证以及连接主链api查询交易的确认数
270         if util.ValidatePegin {
271                 if err := util.IsConfirmedBytomBlock(blockHeader.Height, consensus.ActiveNetParams.PeginMinDepth); err != nil {
272                         return nil, err
273                 }
274         }
275         // 找出与claim script有关联的交易的输出
276         var claimScript []byte
277         rawTx := &bytomtypes.Tx{}
278         if err := rawTx.UnmarshalText([]byte(ins.RawTx)); err != nil {
279                 return nil, err
280         }
281
282         nOut := len(rawTx.Outputs)
283         if ins.ClaimScript == nil {
284                 // 遍历寻找与交易输出有关的claim script
285                 cps, err := b.Wallet.AccountMgr.ListControlProgram()
286                 if err != nil {
287                         return nil, err
288                 }
289
290                 for _, cp := range cps {
291                         _, controlProg := b.Wallet.AccountMgr.GetPeginContractControlPrograms(claimScript)
292                         // 获取交易的输出
293                         nOut = getPeginTxnOutputIndex(*rawTx, controlProg)
294                         if nOut != len(rawTx.Outputs) {
295                                 claimScript = cp.ControlProgram
296                         }
297                 }
298         } else {
299                 claimScript = ins.ClaimScript
300                 _, controlProg := b.Wallet.AccountMgr.GetPeginContractControlPrograms(claimScript)
301                 // 获取交易的输出
302                 nOut = getPeginTxnOutputIndex(*rawTx, controlProg)
303         }
304         if nOut == len(rawTx.Outputs) || nOut == -1 {
305                 return nil, errors.New("Failed to find output in bytom to the mainchain_address from createContractRawPegin")
306         }
307
308         // 根据ClaimScript 获取account id
309         var hash [32]byte
310         sha3pool.Sum256(hash[:], claimScript)
311         data := b.Wallet.DB.Get(account.ContractKey(hash))
312         if data == nil {
313                 return nil, errors.New("Failed to find control program through claim script")
314         }
315
316         cp := &account.CtrlProgram{}
317         if err := json.Unmarshal(data, cp); err != nil {
318                 return nil, errors.New("Failed on unmarshal control program")
319         }
320
321         // 构造交易
322         // 用输出作为交易输入 生成新的交易
323         builder := txbuilder.NewBuilder(time.Now())
324         // TODO 根据raw tx生成一个utxo
325
326         sourceID := *rawTx.OutputID(nOut)
327         outputAccount := rawTx.Outputs[nOut].Amount
328         assetID := *rawTx.Outputs[nOut].AssetId
329
330         txInput := types.NewClaimInput(nil, sourceID, assetID, outputAccount, uint64(nOut), cp.ControlProgram)
331         if err := builder.AddInput(txInput, &txbuilder.SigningInstruction{}); err != nil {
332                 return nil, err
333         }
334         program, err := b.Wallet.AccountMgr.CreateAddress(cp.AccountID, false)
335         if err != nil {
336                 return nil, err
337         }
338
339         if err = builder.AddOutput(types.NewTxOutput(assetID, outputAccount, program.ControlProgram)); err != nil {
340                 return nil, err
341         }
342
343         tmpl, txData, err := builder.Build()
344         if err != nil {
345                 return nil, err
346         }
347
348         // todo把一些主链的信息加到交易的stack中
349         var stack [][]byte
350
351         //amount
352         amount := strconv.FormatUint(rawTx.Outputs[nOut].Amount, 10)
353         stack = append(stack, []byte(amount))
354         // 主链的gennesisBlockHash
355         stack = append(stack, []byte(consensus.ActiveNetParams.ParentGenesisBlockHash))
356         // claim script
357         stack = append(stack, claimScript)
358         // raw tx
359         tx, _ := rawTx.MarshalText()
360         stack = append(stack, tx)
361         // proof
362         blockHeaderByte, err := blockHeader.MarshalText()
363         if err != nil {
364                 return nil, err
365         }
366         merkleBlock := claim.MerkleBlock{
367                 BlockHeader:  blockHeaderByte,
368                 TxHashes:     txHashes,
369                 StatusHashes: statusHashes,
370                 Flags:        ins.Flags,
371                 MatchedTxIDs: matchedTxIDs,
372         }
373         txOutProof, _ := json.Marshal(merkleBlock)
374         stack = append(stack, txOutProof)
375         //      tmpl.Transaction.Inputs[0].Peginwitness = stack
376         txData.Inputs[0].Peginwitness = stack
377
378         //交易费估算
379         txGasResp, err := EstimateTxGas(*tmpl)
380         if err != nil {
381                 return nil, err
382         }
383         txData.Outputs[0].Amount = txData.Outputs[0].Amount - uint64(txGasResp.TotalNeu)
384         //重设置Transaction
385         tmpl.Transaction = types.NewTx(*txData)
386         return tmpl, nil
387 }
388
389 // EstimateTxGasResp estimate transaction consumed gas
390 type EstimateTxGasResp struct {
391         TotalNeu   int64 `json:"total_neu"`
392         StorageNeu int64 `json:"storage_neu"`
393         VMNeu      int64 `json:"vm_neu"`
394 }
395
396 var (
397         defaultBaseRate = float64(100000)
398         flexibleGas     = int64(1800)
399 )
400
401 // EstimateTxGas estimate consumed neu for transaction
402 func EstimateTxGas(template txbuilder.Template) (*EstimateTxGasResp, error) {
403         // base tx size and not include sign
404         data, err := template.Transaction.TxData.MarshalText()
405         if err != nil {
406                 return nil, err
407         }
408         baseTxSize := int64(len(data))
409
410         // extra tx size for sign witness parts
411         signSize := estimateSignSize(template.SigningInstructions)
412
413         // total gas for tx storage
414         totalTxSizeGas, ok := checked.MulInt64(baseTxSize+signSize, consensus.StorageGasRate)
415         if !ok {
416                 return nil, errors.New("calculate txsize gas got a math error")
417         }
418
419         // consume gas for run VM
420         totalP2WPKHGas := int64(0)
421         totalP2WSHGas := int64(0)
422         baseP2WPKHGas := int64(1419)
423         // flexible Gas is used for handle need extra utxo situation
424
425         for pos, inpID := range template.Transaction.Tx.InputIDs {
426                 sp, err := template.Transaction.Spend(inpID)
427                 if err != nil {
428                         continue
429                 }
430
431                 resOut, err := template.Transaction.Output(*sp.SpentOutputId)
432                 if err != nil {
433                         continue
434                 }
435
436                 if segwit.IsP2WPKHScript(resOut.ControlProgram.Code) {
437                         totalP2WPKHGas += baseP2WPKHGas
438                 } else if segwit.IsP2WSHScript(resOut.ControlProgram.Code) {
439                         sigInst := template.SigningInstructions[pos]
440                         totalP2WSHGas += estimateP2WSHGas(sigInst)
441                 }
442         }
443
444         // total estimate gas
445         totalGas := totalTxSizeGas + totalP2WPKHGas + totalP2WSHGas + flexibleGas
446
447         // rounding totalNeu with base rate 100000
448         totalNeu := float64(totalGas*consensus.VMGasRate) / defaultBaseRate
449         roundingNeu := math.Ceil(totalNeu)
450         estimateNeu := int64(roundingNeu) * int64(defaultBaseRate)
451
452         // TODO add priority
453
454         return &EstimateTxGasResp{
455                 TotalNeu:   estimateNeu,
456                 StorageNeu: totalTxSizeGas * consensus.VMGasRate,
457                 VMNeu:      (totalP2WPKHGas + totalP2WSHGas) * consensus.VMGasRate,
458         }, nil
459 }
460
461 // estimate p2wsh gas.
462 // OP_CHECKMULTISIG consume (984 * a - 72 * b - 63) gas,
463 // where a represent the num of public keys, and b represent the num of quorum.
464 func estimateP2WSHGas(sigInst *txbuilder.SigningInstruction) int64 {
465         P2WSHGas := int64(0)
466         baseP2WSHGas := int64(738)
467
468         for _, witness := range sigInst.WitnessComponents {
469                 switch t := witness.(type) {
470                 case *txbuilder.SignatureWitness:
471                         P2WSHGas += baseP2WSHGas + (984*int64(len(t.Keys)) - 72*int64(t.Quorum) - 63)
472                 case *txbuilder.RawTxSigWitness:
473                         P2WSHGas += baseP2WSHGas + (984*int64(len(t.Keys)) - 72*int64(t.Quorum) - 63)
474                 }
475         }
476         return P2WSHGas
477 }
478
479 // estimate signature part size.
480 // if need multi-sign, calculate the size according to the length of keys.
481 func estimateSignSize(signingInstructions []*txbuilder.SigningInstruction) int64 {
482         signSize := int64(0)
483         baseWitnessSize := int64(300)
484
485         for _, sigInst := range signingInstructions {
486                 for _, witness := range sigInst.WitnessComponents {
487                         switch t := witness.(type) {
488                         case *txbuilder.SignatureWitness:
489                                 signSize += int64(t.Quorum) * baseWitnessSize
490                         case *txbuilder.RawTxSigWitness:
491                                 signSize += int64(t.Quorum) * baseWitnessSize
492                         }
493                 }
494         }
495         return signSize
496 }