package api
import (
- "bytes"
"context"
- "crypto/hmac"
- "crypto/sha256"
"encoding/json"
"math"
- "strconv"
"strings"
"time"
log "github.com/sirupsen/logrus"
"github.com/vapor/account"
- "github.com/vapor/blockchain/signers"
"github.com/vapor/blockchain/txbuilder"
- "github.com/vapor/blockchain/txbuilder/mainchain"
- "github.com/vapor/common"
"github.com/vapor/consensus"
"github.com/vapor/consensus/segwit"
- "github.com/vapor/crypto/ed25519/chainkd"
- "github.com/vapor/crypto/sha3pool"
- chainjson "github.com/vapor/encoding/json"
"github.com/vapor/errors"
"github.com/vapor/math/checked"
"github.com/vapor/net/http/reqid"
"github.com/vapor/protocol/bc"
"github.com/vapor/protocol/bc/types"
- "github.com/vapor/protocol/bc/types/bytom"
- bytomtypes "github.com/vapor/protocol/bc/types/bytom/types"
- "github.com/vapor/protocol/vm/vmutil"
- "github.com/vapor/util"
)
var (
"retire": txbuilder.DecodeRetireAction,
"spend_account": a.wallet.AccountMgr.DecodeSpendAction,
"spend_account_unspent_output": a.wallet.AccountMgr.DecodeSpendUTXOAction,
+ "dpos": a.wallet.AccountMgr.DecodeDposAction,
+ "ipfs_data": txbuilder.DecodeIpfsDataAction,
}
decoder, ok := decoders[action]
return decoder, ok
func onlyHaveInputActions(req *BuildRequest) (bool, error) {
count := 0
+ dpos := false
for i, act := range req.Actions {
actionType, ok := act["type"].(string)
if !ok {
return false, errors.WithDetailf(ErrBadActionType, "no action type provided on action %d", i)
}
-
+ if strings.HasPrefix(actionType, "dpos") {
+ dpos = true
+ }
if strings.HasPrefix(actionType, "spend") || actionType == "issue" {
count++
}
}
+ if dpos == true && count == 0 {
+ return false, nil
+ }
+
return count == len(req.Actions), nil
}
}
return NewSuccessResponse(txGasResp)
}
-
-func getPeginTxnOutputIndex(rawTx bytomtypes.Tx, controlProg []byte) int {
- for index, output := range rawTx.Outputs {
- if bytes.Equal(output.ControlProgram, controlProg) {
- return index
- }
- }
- return 0
-}
-
-func toHash(hexBytes []chainjson.HexBytes) (hashs []*bytom.Hash) {
- for _, data := range hexBytes {
- b32 := [32]byte{}
- copy(b32[:], data)
- res := bytom.NewHash(b32)
- hashs = append(hashs, &res)
- }
- return
-}
-
-func (a *API) claimPeginTx(ctx context.Context, ins struct {
- Password string `json:"password"`
- RawTx bytomtypes.Tx `json:"raw_transaction"`
- BlockHeader bytomtypes.BlockHeader `json:"block_header"`
- TxHashes []chainjson.HexBytes `json:"tx_hashes"`
- StatusHashes []chainjson.HexBytes `json:"status_hashes"`
- Flags []uint32 `json:"flags"`
- MatchedTxIDs []chainjson.HexBytes `json:"matched_tx_ids"`
- ClaimScript chainjson.HexBytes `json:"claim_script"`
-}) Response {
- tmpl, err := a.createrawpegin(ctx, ins)
- if err != nil {
- log.WithField("build err", err).Error("fail on createrawpegin.")
- return NewErrorResponse(err)
- }
- // 交易签名
- if err := txbuilder.Sign(ctx, tmpl, ins.Password, a.PseudohsmSignTemplate); err != nil {
- log.WithField("build err", err).Error("fail on sign transaction.")
- return NewErrorResponse(err)
- }
-
- // submit
- if err := txbuilder.FinalizeTx(ctx, a.chain, tmpl.Transaction); err != nil {
- return NewErrorResponse(err)
- }
-
- log.WithField("tx_id", tmpl.Transaction.ID.String()).Info("claim script tx")
- return NewSuccessResponse(&submitTxResp{TxID: &tmpl.Transaction.ID})
-}
-
-// GetMerkleBlockResp is resp struct for GetTxOutProof API
-type GetMerkleBlock struct {
- BlockHeader bytomtypes.BlockHeader `json:"block_header"`
- TxHashes []chainjson.HexBytes `json:"tx_hashes"`
- StatusHashes []chainjson.HexBytes `json:"status_hashes"`
- Flags []uint32 `json:"flags"`
- MatchedTxIDs []chainjson.HexBytes `json:"matched_tx_ids"`
-}
-
-func (a *API) createrawpegin(ctx context.Context, ins struct {
- Password string `json:"password"`
- RawTx bytomtypes.Tx `json:"raw_transaction"`
- BlockHeader bytomtypes.BlockHeader `json:"block_header"`
- TxHashes []chainjson.HexBytes `json:"tx_hashes"`
- StatusHashes []chainjson.HexBytes `json:"status_hashes"`
- Flags []uint32 `json:"flags"`
- MatchedTxIDs []chainjson.HexBytes `json:"matched_tx_ids"`
- ClaimScript chainjson.HexBytes `json:"claim_script"`
-}) (*txbuilder.Template, error) {
- // proof验证
- var flags []uint8
- for flag := range ins.Flags {
- flags = append(flags, uint8(flag))
- }
- txHashes := toHash(ins.TxHashes)
- matchedTxIDs := toHash(ins.MatchedTxIDs)
- if !bytomtypes.ValidateTxMerkleTreeProof(txHashes, flags, matchedTxIDs, ins.BlockHeader.BlockCommitment.TransactionsMerkleRoot) {
- return nil, errors.New("Merkleblock validation failed")
- }
- // CheckBytomProof
- //difficulty.CheckBytomProofOfWork(ins.BlockHeader.Hash(), ins.BlockHeader)
- // 增加spv验证以及连接主链api查询交易的确认数
- if util.ValidatePegin {
- if err := util.IsConfirmedBytomBlock(ins.BlockHeader.Height, consensus.ActiveNetParams.PeginMinDepth); err != nil {
- return nil, err
- }
- }
- // 找出与claim script有关联的交易的输出
- var claimScript []byte
- nOut := len(ins.RawTx.Outputs)
- if ins.ClaimScript == nil {
- // 遍历寻找与交易输出有关的claim script
- cps, err := a.wallet.AccountMgr.ListControlProgram()
- if err != nil {
- return nil, err
- }
-
- for _, cp := range cps {
- _, controlProg := a.wallet.AccountMgr.GetPeginControlPrograms(cp.ControlProgram)
- if controlProg == nil {
- continue
- }
- // 获取交易的输出
- nOut = getPeginTxnOutputIndex(ins.RawTx, controlProg)
- if nOut != len(ins.RawTx.Outputs) {
- claimScript = cp.ControlProgram
- }
- }
- } else {
- claimScript = ins.ClaimScript
- _, controlProg := a.wallet.AccountMgr.GetPeginControlPrograms(claimScript)
- // 获取交易的输出
- nOut = getPeginTxnOutputIndex(ins.RawTx, controlProg)
- }
- if nOut == len(ins.RawTx.Outputs) {
- return nil, errors.New("Failed to find output in bytom to the mainchain_address from getpeginaddress")
- }
-
- // 根据ClaimScript 获取account id
- var hash [32]byte
- sha3pool.Sum256(hash[:], claimScript)
- data := a.wallet.DB.Get(account.ContractKey(hash))
- if data == nil {
- return nil, errors.New("Failed to find control program through claim script")
- }
-
- cp := &account.CtrlProgram{}
- if err := json.Unmarshal(data, cp); err != nil {
- return nil, errors.New("Failed on unmarshal control program")
- }
-
- // 构造交易
- // 用输出作为交易输入 生成新的交易
- builder := txbuilder.NewBuilder(time.Now())
- // TODO 根据raw tx生成一个utxo
- //txInput := types.NewClaimInputInput(nil, *ins.RawTx.Outputs[nOut].AssetId, ins.RawTx.Outputs[nOut].Amount, cp.ControlProgram)
- assetId := bc.AssetID{}
- assetId.V0 = ins.RawTx.Outputs[nOut].AssetId.GetV0()
- assetId.V1 = ins.RawTx.Outputs[nOut].AssetId.GetV1()
- assetId.V2 = ins.RawTx.Outputs[nOut].AssetId.GetV2()
- assetId.V3 = ins.RawTx.Outputs[nOut].AssetId.GetV3()
-
- sourceID := bc.Hash{}
- sourceID.V0 = ins.RawTx.OutputID(nOut).GetV0()
- sourceID.V1 = ins.RawTx.OutputID(nOut).GetV1()
- sourceID.V2 = ins.RawTx.OutputID(nOut).GetV2()
- sourceID.V3 = ins.RawTx.OutputID(nOut).GetV3()
- outputAccount := ins.RawTx.Outputs[nOut].Amount
-
- txInput := types.NewClaimInputInput(nil, sourceID, assetId, outputAccount, uint64(nOut), cp.ControlProgram)
- if err := builder.AddInput(txInput, &txbuilder.SigningInstruction{}); err != nil {
- return nil, err
- }
- program, err := a.wallet.AccountMgr.CreateAddress(cp.AccountID, false)
- if err != nil {
- return nil, err
- }
-
- if err = builder.AddOutput(types.NewTxOutput(assetId, outputAccount, program.ControlProgram)); err != nil {
- return nil, err
- }
-
- tmpl, txData, err := builder.Build()
- if err != nil {
- return nil, err
- }
-
- // todo把一些主链的信息加到交易的stack中
- var stack [][]byte
-
- //amount
- amount := strconv.FormatUint(ins.RawTx.Outputs[nOut].Amount, 10)
- stack = append(stack, []byte(amount))
- // 主链的gennesisBlockHash
- stack = append(stack, []byte(consensus.ActiveNetParams.ParentGenesisBlockHash))
- // claim script
- stack = append(stack, claimScript)
- // raw tx
- tx, _ := json.Marshal(ins.RawTx)
- stack = append(stack, tx)
- // proof
- MerkleBLock := GetMerkleBlock{
- BlockHeader: ins.BlockHeader,
- TxHashes: ins.TxHashes,
- StatusHashes: ins.StatusHashes,
- Flags: ins.Flags,
- MatchedTxIDs: ins.MatchedTxIDs,
- }
- txOutProof, _ := json.Marshal(MerkleBLock)
- stack = append(stack, txOutProof)
-
- // tmpl.Transaction.Inputs[0].Peginwitness = stack
- txData.Inputs[0].Peginwitness = stack
-
- //交易费估算
- txGasResp, err := EstimateTxGas(*tmpl)
- if err != nil {
- return nil, err
- }
- txData.Outputs[0].Amount = txData.Outputs[0].Amount - uint64(txGasResp.TotalNeu)
- //重设置Transaction
- tmpl.Transaction = types.NewTx(*txData)
- return tmpl, nil
-}
-
-func (a *API) buildMainChainTx(ins struct {
- Utxo account.UTXO `json:"utxo"`
- Tx types.Tx `json:"raw_transaction"`
- RootXPubs []chainkd.XPub `json:"root_xpubs"`
- Alias string `json:"alias"`
- ControlProgram string `json:"control_program"`
- ClaimScript chainjson.HexBytes `json:"claim_script"`
-}) Response {
-
- var xpubs []chainkd.XPub
- for _, xpub := range ins.RootXPubs {
- // pub + scriptPubKey 生成一个随机数A
- var tmp [32]byte
- h := hmac.New(sha256.New, xpub[:])
- h.Write(ins.ClaimScript)
- tweak := h.Sum(tmp[:])
- // pub + A 生成一个新的公钥pub_new
- chaildXPub := xpub.Child(tweak)
- xpubs = append(xpubs, chaildXPub)
- }
- acc := &account.Account{}
- var err error
- if acc, err = a.wallet.AccountMgr.FindByAlias(ins.Alias); err != nil {
- acc, err = a.wallet.AccountMgr.Create(xpubs, len(xpubs), ins.Alias, signers.BIP0044)
- if err != nil {
- return NewErrorResponse(err)
- }
- }
- ins.Utxo.ControlProgramIndex = acc.Signer.KeyIndex
-
- txInput, sigInst, err := utxoToInputs(acc.Signer, &ins.Utxo)
- if err != nil {
- return NewErrorResponse(err)
- }
-
- builder := mainchain.NewBuilder(time.Now())
- builder.AddInput(txInput, sigInst)
- changeAmount := uint64(0)
- retire := false
- for _, key := range ins.Tx.GetResultIds() {
- output, err := ins.Tx.Retire(*key)
- if err != nil {
- log.WithFields(log.Fields{"moudle": "transact", "err": err}).Warn("buildMainChainTx error")
- continue
- }
- retire = true
- var controlProgram []byte
- retBool := true
- if controlProgram, retBool = getInput(ins.Tx.Entries, *key, ins.ControlProgram); !retBool {
- return NewErrorResponse(errors.New("The corresponding input cannot be found"))
- }
-
- assetID := bytom.AssetID{
- V0: output.Source.Value.AssetId.GetV0(),
- V1: output.Source.Value.AssetId.GetV1(),
- V2: output.Source.Value.AssetId.GetV2(),
- V3: output.Source.Value.AssetId.GetV3(),
- }
- out := bytomtypes.NewTxOutput(assetID, output.Source.Value.Amount, controlProgram)
- builder.AddOutput(out)
- changeAmount = ins.Utxo.Amount - output.Source.Value.Amount
-
- }
-
- if !retire {
- return NewErrorResponse(errors.New("It's not a transaction to retire assets"))
- }
-
- if changeAmount > 0 {
- u := ins.Utxo
- assetID := bytom.AssetID{
- V0: u.AssetID.GetV0(),
- V1: u.AssetID.GetV1(),
- V2: u.AssetID.GetV2(),
- V3: u.AssetID.GetV3(),
- }
- out := bytomtypes.NewTxOutput(assetID, changeAmount, ins.Utxo.ControlProgram)
- builder.AddOutput(out)
- }
-
- tmpl, tx, err := builder.Build()
- if err != nil {
- return NewErrorResponse(err)
- }
- //交易费估算
- txGasResp, err := EstimateTxGasForMainchain(*tmpl)
- if err != nil {
- return NewErrorResponse(err)
- }
- for i, out := range tmpl.Transaction.Outputs {
- if bytes.Equal(out.ControlProgram, ins.Utxo.ControlProgram) {
- tx.Outputs[i].Amount = changeAmount - uint64(txGasResp.TotalNeu)
- }
- }
- tmpl.Transaction = bytomtypes.NewTx(*tx)
- return NewSuccessResponse(tmpl)
-}
-
-//
-func getInput(entry map[bc.Hash]bc.Entry, outputID bc.Hash, controlProgram string) ([]byte, bool) {
- output := entry[outputID].(*bc.Retirement)
- mux := entry[*output.Source.Ref].(*bc.Mux)
-
- for _, valueSource := range mux.GetSources() {
- spend := entry[*valueSource.Ref].(*bc.Spend)
- prevout := entry[*spend.SpentOutputId].(*bc.Output)
-
- var ctrlProgram chainjson.HexBytes
- ctrlProgram = prevout.ControlProgram.Code
- tmp, _ := ctrlProgram.MarshalText()
- if string(tmp) == controlProgram {
- return ctrlProgram, true
- }
- }
- return nil, false
-}
-
-// UtxoToInputs convert an utxo to the txinput
-func utxoToInputs(signer *signers.Signer, u *account.UTXO) (*bytomtypes.TxInput, *mainchain.SigningInstruction, error) {
- sourceID := bytom.Hash{
- V0: u.SourceID.GetV0(),
- V1: u.SourceID.GetV1(),
- V2: u.SourceID.GetV2(),
- V3: u.SourceID.GetV3(),
- }
-
- assetID := bytom.AssetID{
- V0: u.AssetID.GetV0(),
- V1: u.AssetID.GetV1(),
- V2: u.AssetID.GetV2(),
- V3: u.AssetID.GetV3(),
- }
-
- txInput := bytomtypes.NewSpendInput(nil, sourceID, assetID, u.Amount, u.SourcePos, u.ControlProgram)
- sigInst := &mainchain.SigningInstruction{}
- if signer == nil {
- return txInput, sigInst, nil
- }
-
- // TODO u.Change看怎么填写
- path, _ := signers.Path(signer, signers.AccountKeySpace, u.Change, u.ControlProgramIndex)
- if u.Address == "" {
- sigInst.AddWitnessKeys(signer.XPubs, path, signer.Quorum)
- return txInput, sigInst, nil
- }
-
- address, err := common.DecodeBytomAddress(u.Address, &consensus.ActiveNetParams)
- if err != nil {
- return nil, nil, err
- }
-
- switch address.(type) {
- case *common.AddressWitnessPubKeyHash:
- sigInst.AddRawWitnessKeys(signer.XPubs, path, signer.Quorum)
- derivedXPubs := chainkd.DeriveXPubs(signer.XPubs, path)
- derivedPK := derivedXPubs[0].PublicKey()
- sigInst.WitnessComponents = append(sigInst.WitnessComponents, mainchain.DataWitness([]byte(derivedPK)))
-
- case *common.AddressWitnessScriptHash:
- sigInst.AddRawWitnessKeys(signer.XPubs, path, signer.Quorum)
- //path := signers.Path(signer, signers.AccountKeySpace, u.ControlProgramIndex)
- //derivedXPubs := chainkd.DeriveXPubs(signer.XPubs, path)
- derivedXPubs := signer.XPubs
- derivedPKs := chainkd.XPubKeys(derivedXPubs)
- script, err := vmutil.P2SPMultiSigProgram(derivedPKs, signer.Quorum)
- if err != nil {
- return nil, nil, err
- }
- sigInst.WitnessComponents = append(sigInst.WitnessComponents, mainchain.DataWitness(script))
-
- default:
- return nil, nil, errors.New("unsupport address type")
- }
-
- return txInput, sigInst, nil
-}
-
-type signRespForMainchain struct {
- Tx *mainchain.Template `json:"transaction"`
- SignComplete bool `json:"sign_complete"`
-}
-
-func (a *API) signWithKey(ins struct {
- Xprv string `json:"xprv"`
- XPub chainkd.XPub `json:"xpub"`
- Txs mainchain.Template `json:"transaction"`
- ClaimScript chainjson.HexBytes `json:"claim_script"`
-}) Response {
- xprv := &chainkd.XPrv{}
- if err := xprv.UnmarshalText([]byte(ins.Xprv)); err != nil {
- return NewErrorResponse(err)
- }
- // pub + scriptPubKey 生成一个随机数A
- var tmp [32]byte
- h := hmac.New(sha256.New, ins.XPub[:])
- h.Write(ins.ClaimScript)
- tweak := h.Sum(tmp[:])
- // pub + A 生成一个新的公钥pub_new
- privateKey := xprv.Child(tweak, false)
-
- if err := sign(&ins.Txs, privateKey); err != nil {
- return NewErrorResponse(err)
- }
- log.Info("Sign Transaction complete.")
- log.Info(mainchain.SignProgress(&ins.Txs))
- return NewSuccessResponse(&signRespForMainchain{Tx: &ins.Txs, SignComplete: mainchain.SignProgress(&ins.Txs)})
-}
-
-func sign(tmpl *mainchain.Template, xprv chainkd.XPrv) error {
- for i, sigInst := range tmpl.SigningInstructions {
- for j, wc := range sigInst.WitnessComponents {
- switch sw := wc.(type) {
- case *mainchain.SignatureWitness:
- err := sw.Sign(tmpl, uint32(i), xprv)
- if err != nil {
- return errors.WithDetailf(err, "adding signature(s) to signature witness component %d of input %d", j, i)
- }
- case *mainchain.RawTxSigWitness:
- err := sw.Sign(tmpl, uint32(i), xprv)
- if err != nil {
- return errors.WithDetailf(err, "adding signature(s) to raw-signature witness component %d of input %d", j, i)
- }
- }
- }
- }
- return materializeWitnesses(tmpl)
-}
-
-func materializeWitnesses(txTemplate *mainchain.Template) error {
- msg := txTemplate.Transaction
-
- if msg == nil {
- return errors.Wrap(txbuilder.ErrMissingRawTx)
- }
-
- if len(txTemplate.SigningInstructions) > len(msg.Inputs) {
- return errors.Wrap(txbuilder.ErrBadInstructionCount)
- }
-
- for i, sigInst := range txTemplate.SigningInstructions {
- if msg.Inputs[sigInst.Position] == nil {
- return errors.WithDetailf(txbuilder.ErrBadTxInputIdx, "signing instruction %d references missing tx input %d", i, sigInst.Position)
- }
-
- var witness [][]byte
- for j, wc := range sigInst.WitnessComponents {
- err := wc.Materialize(&witness)
- if err != nil {
- return errors.WithDetailf(err, "error in witness component %d of input %d", j, i)
- }
- }
- msg.SetInputArguments(sigInst.Position, witness)
- }
-
- return nil
-}