11 log "github.com/sirupsen/logrus"
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"
32 func getPeginTxnOutputIndex(rawTx bytomtypes.Tx, controlProg []byte) int {
33 for index, output := range rawTx.Outputs {
34 if bytes.Equal(output.ControlProgram, controlProg) {
41 func toHash(hexBytes []chainjson.HexBytes) (hashs []*bc.Hash) {
42 for _, data := range hexBytes {
45 res := bc.NewHash(b32)
46 hashs = append(hashs, &res)
51 type BytomClaimTx struct {
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)
61 func (b *BytomClaimTx) ClaimPeginTx(ctx context.Context) (interface{}, error) {
62 tmpl, err := b.createRawPegin(b.ClaimTxParam)
64 log.WithField("build err", err).Error("fail on createrawpegin.")
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.")
75 if err := txbuilder.FinalizeTx(ctx, b.Chain, tmpl.Transaction); err != nil {
79 log.WithField("tx_id", tmpl.Transaction.ID.String()).Info("claim script tx")
81 TxID *bc.Hash `json:"tx_id"`
82 }{TxID: &tmpl.Transaction.ID}, nil
85 func (b *BytomClaimTx) createRawPegin(ins rpc.ClaimTxParam) (*txbuilder.Template, error) {
88 for flag := range ins.Flags {
89 flags = append(flags, uint8(flag))
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 {
98 if !types.ValidateTxMerkleTreeProof(txHashes, flags, matchedTxIDs, blockHeader.BlockCommitment.TransactionsMerkleRoot) {
99 return nil, errors.New("Merkleblock validation failed")
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 {
109 // 找出与claim script有关联的交易的输出
110 var claimScript []byte
111 rawTx := &bytomtypes.Tx{}
112 if err := rawTx.UnmarshalText([]byte(ins.RawTx)); err != nil {
115 nOut := len(rawTx.Outputs)
116 if ins.ClaimScript == nil {
117 // 遍历寻找与交易输出有关的claim script
118 cps, err := b.Wallet.AccountMgr.ListControlProgram()
123 for _, cp := range cps {
124 _, controlProg := b.Wallet.AccountMgr.GetPeginControlPrograms(cp.ControlProgram)
125 if controlProg == nil {
129 nOut = getPeginTxnOutputIndex(*rawTx, controlProg)
130 if nOut != len(rawTx.Outputs) {
131 claimScript = cp.ControlProgram
135 claimScript = ins.ClaimScript
136 _, controlProg := b.Wallet.AccountMgr.GetPeginControlPrograms(claimScript)
138 nOut = getPeginTxnOutputIndex(*rawTx, controlProg)
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")
144 // 根据ClaimScript 获取account id
146 sha3pool.Sum256(hash[:], claimScript)
147 data := b.Wallet.DB.Get(account.ContractKey(hash))
149 return nil, errors.New("Failed to find control program through claim script")
152 cp := &account.CtrlProgram{}
153 if err := json.Unmarshal(data, cp); err != nil {
154 return nil, errors.New("Failed on unmarshal control program")
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
165 txInput := types.NewClaimInput(nil, sourceID, assetID, outputAccount, uint64(nOut), cp.ControlProgram)
166 if err := builder.AddInput(txInput, &txbuilder.SigningInstruction{}); err != nil {
169 program, err := b.Wallet.AccountMgr.CreateAddress(cp.AccountID, false)
174 if err = builder.AddOutput(types.NewTxOutput(assetID, outputAccount, program.ControlProgram)); err != nil {
178 tmpl, txData, err := builder.Build()
183 // todo把一些主链的信息加到交易的stack中
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))
192 stack = append(stack, claimScript)
194 tx, _ := json.Marshal(rawTx)
195 stack = append(stack, tx)
197 blockHeaderByte, err := blockHeader.MarshalText()
201 merkleBlock := claim.MerkleBlock{
202 BlockHeader: blockHeaderByte,
204 StatusHashes: statusHashes,
206 MatchedTxIDs: matchedTxIDs,
209 txOutProof, _ := json.Marshal(merkleBlock)
211 stack = append(stack, txOutProof)
213 // tmpl.Transaction.Inputs[0].Peginwitness = stack
214 txData.Inputs[0].Peginwitness = stack
217 txGasResp, err := EstimateTxGas(*tmpl)
221 txData.Outputs[0].Amount = txData.Outputs[0].Amount - uint64(txGasResp.TotalNeu)
223 tmpl.Transaction = types.NewTx(*txData)
227 func (b *BytomClaimTx) ClaimContractPeginTx(ctx context.Context) (interface{}, error) {
228 tmpl, err := b.createContractRawPegin(b.ClaimTxParam)
230 log.WithField("build err", err).Error("fail on claimContractPeginTx.")
234 if err := txbuilder.Sign(ctx, tmpl, b.Password, b.pseudohsmSignTemplate); err != nil {
235 log.WithField("build err", err).Error("fail on sign transaction.")
240 if err := txbuilder.FinalizeTx(ctx, b.Chain, tmpl.Transaction); err != nil {
244 log.WithField("tx_id", tmpl.Transaction.ID.String()).Info("claim script tx")
246 TxID *bc.Hash `json:"tx_id"`
247 }{TxID: &tmpl.Transaction.ID}, nil
250 func (b *BytomClaimTx) createContractRawPegin(ins rpc.ClaimTxParam) (*txbuilder.Template, error) {
253 for flag := range ins.Flags {
254 flags = append(flags, uint8(flag))
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 {
264 if !types.ValidateTxMerkleTreeProof(txHashes, flags, matchedTxIDs, blockHeader.BlockCommitment.TransactionsMerkleRoot) {
265 return nil, errors.New("Merkleblock validation failed")
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 {
275 // 找出与claim script有关联的交易的输出
276 var claimScript []byte
277 rawTx := &bytomtypes.Tx{}
278 if err := rawTx.UnmarshalText([]byte(ins.RawTx)); err != nil {
282 nOut := len(rawTx.Outputs)
283 if ins.ClaimScript == nil {
284 // 遍历寻找与交易输出有关的claim script
285 cps, err := b.Wallet.AccountMgr.ListControlProgram()
290 for _, cp := range cps {
291 _, controlProg := b.Wallet.AccountMgr.GetPeginContractControlPrograms(claimScript)
293 nOut = getPeginTxnOutputIndex(*rawTx, controlProg)
294 if nOut != len(rawTx.Outputs) {
295 claimScript = cp.ControlProgram
299 claimScript = ins.ClaimScript
300 _, controlProg := b.Wallet.AccountMgr.GetPeginContractControlPrograms(claimScript)
302 nOut = getPeginTxnOutputIndex(*rawTx, controlProg)
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")
308 // 根据ClaimScript 获取account id
310 sha3pool.Sum256(hash[:], claimScript)
311 data := b.Wallet.DB.Get(account.ContractKey(hash))
313 return nil, errors.New("Failed to find control program through claim script")
316 cp := &account.CtrlProgram{}
317 if err := json.Unmarshal(data, cp); err != nil {
318 return nil, errors.New("Failed on unmarshal control program")
323 builder := txbuilder.NewBuilder(time.Now())
324 // TODO 根据raw tx生成一个utxo
326 sourceID := *rawTx.OutputID(nOut)
327 outputAccount := rawTx.Outputs[nOut].Amount
328 assetID := *rawTx.Outputs[nOut].AssetId
330 txInput := types.NewClaimInput(nil, sourceID, assetID, outputAccount, uint64(nOut), cp.ControlProgram)
331 if err := builder.AddInput(txInput, &txbuilder.SigningInstruction{}); err != nil {
334 program, err := b.Wallet.AccountMgr.CreateAddress(cp.AccountID, false)
339 if err = builder.AddOutput(types.NewTxOutput(assetID, outputAccount, program.ControlProgram)); err != nil {
343 tmpl, txData, err := builder.Build()
348 // todo把一些主链的信息加到交易的stack中
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))
357 stack = append(stack, claimScript)
359 tx, _ := rawTx.MarshalText()
360 stack = append(stack, tx)
362 blockHeaderByte, err := blockHeader.MarshalText()
366 merkleBlock := claim.MerkleBlock{
367 BlockHeader: blockHeaderByte,
369 StatusHashes: statusHashes,
371 MatchedTxIDs: matchedTxIDs,
373 txOutProof, _ := json.Marshal(merkleBlock)
374 stack = append(stack, txOutProof)
375 // tmpl.Transaction.Inputs[0].Peginwitness = stack
376 txData.Inputs[0].Peginwitness = stack
379 txGasResp, err := EstimateTxGas(*tmpl)
383 txData.Outputs[0].Amount = txData.Outputs[0].Amount - uint64(txGasResp.TotalNeu)
385 tmpl.Transaction = types.NewTx(*txData)
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"`
397 defaultBaseRate = float64(100000)
398 flexibleGas = int64(1800)
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()
408 baseTxSize := int64(len(data))
410 // extra tx size for sign witness parts
411 signSize := estimateSignSize(template.SigningInstructions)
413 // total gas for tx storage
414 totalTxSizeGas, ok := checked.MulInt64(baseTxSize+signSize, consensus.StorageGasRate)
416 return nil, errors.New("calculate txsize gas got a math error")
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
425 for pos, inpID := range template.Transaction.Tx.InputIDs {
426 sp, err := template.Transaction.Spend(inpID)
431 resOut, err := template.Transaction.Output(*sp.SpentOutputId)
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)
444 // total estimate gas
445 totalGas := totalTxSizeGas + totalP2WPKHGas + totalP2WSHGas + flexibleGas
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)
454 return &EstimateTxGasResp{
455 TotalNeu: estimateNeu,
456 StorageNeu: totalTxSizeGas * consensus.VMGasRate,
457 VMNeu: (totalP2WPKHGas + totalP2WSHGas) * consensus.VMGasRate,
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 {
466 baseP2WSHGas := int64(738)
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)
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 {
483 baseWitnessSize := int64(300)
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