package account import ( "context" "encoding/json" "fmt" "github.com/vapor/config" "github.com/vapor/blockchain/txbuilder" "github.com/vapor/common" "github.com/vapor/consensus" dpos "github.com/vapor/consensus/consensus/dpos" "github.com/vapor/crypto/ed25519/chainkd" "github.com/vapor/errors" "github.com/vapor/protocol/bc" "github.com/vapor/protocol/bc/types" "github.com/vapor/protocol/vm" "github.com/vapor/protocol/vm/vmutil" ) func (m *Manager) DecodeDposAction(data []byte) (txbuilder.Action, error) { a := &DopsAction{Accounts: m} err := json.Unmarshal(data, a) return a, err } type DopsAction struct { Accounts *Manager bc.AssetAmount DposType uint32 `json:"dpos_type"` Address string `json:"address"` Name string `json:"name"` Forgers []string `json:"forgers"` UseUnconfirmed bool `json:"use_unconfirmed"` } func (a *DopsAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error { var missing []string if a.AssetId.IsZero() { missing = append(missing, "asset_id") } if a.Address == "" { missing = append(missing, "address") } if len(missing) > 0 { return txbuilder.MissingFieldsError(missing...) } if types.TxType(a.DposType) < types.Binary || types.TxType(a.DposType) > types.CancelVote { return errors.New("tx type of dpos is error") } var ( referenceData []byte data []byte op vm.Op err error ) switch types.TxType(a.DposType) { case types.Binary: case types.Registe: if a.Name == "" { return errors.New("name is null for dpos Registe") } if a.Amount < consensus.RegisrerForgerFee { return errors.New("The transaction fee is 100000000 for dpos Registe") } if dpos.GDpos.HaveDelegate(a.Name, a.Address) { return errors.New("Forger name has registe") } data, err = json.Marshal(&dpos.RegisterForgerData{Name: a.Name}) if err != nil { return err } op = vm.OP_REGISTE case types.Vote: if len(a.Forgers) == 0 { return errors.New("Forgers is null for dpos Vote") } if a.Amount < consensus.VoteForgerFee { return errors.New("The transaction fee is 10000000 for dpos Registe") } for _, forger := range a.Forgers { if dpos.GDpos.HaveVote(a.Address, forger) { return fmt.Errorf("delegate name: %s is voted", forger) } } data, err = json.Marshal(&dpos.VoteForgerData{Forgers: a.Forgers}) if err != nil { return err } op = vm.OP_VOTE case types.CancelVote: if len(a.Forgers) == 0 { return errors.New("Forgers is null for dpos CancelVote") } if a.Amount < consensus.CancelVoteForgerFee { return errors.New("The transaction fee is 10000000 for dpos Registe") } for _, forger := range a.Forgers { if !dpos.GDpos.HaveVote(a.Address, forger) { return fmt.Errorf("delegate name: %s is not voted", forger) } } data, err = json.Marshal(&dpos.CancelVoteForgerData{Forgers: a.Forgers}) if err != nil { return err } op = vm.OP_REVOKE } msg := dpos.DposMsg{ Type: op, Data: data, } referenceData, err = json.Marshal(&msg) if err != nil { return err } b.SetReferenceData(referenceData) res, err := a.Accounts.utxoKeeper.ReserveByAddress(a.Address, a.AssetId, a.Amount, a.UseUnconfirmed, false) if err != nil { return errors.Wrap(err, "reserving utxos") } // Cancel the reservation if the build gets rolled back. b.OnRollback(func() { a.Accounts.utxoKeeper.Cancel(res.id) }) for _, r := range res.utxos { txSpendInput, sigInst, err := spendInput(r) if err != nil { return errors.Wrap(err, "creating inputs") } if err = b.AddInput(txSpendInput, sigInst); err != nil { return errors.Wrap(err, "adding inputs") } } if res.change >= 0 { address, err := common.DecodeAddress(a.Address, &consensus.ActiveNetParams) if err != nil { return err } redeemContract := address.ScriptAddress() program, err := vmutil.P2WPKHProgram(redeemContract) if err != nil { return err } if err = b.AddOutput(types.NewTxOutput(*consensus.BTMAssetID, res.change, program)); err != nil { return errors.Wrap(err, "adding change output") } } return nil } func (a *DopsAction) ActionType() string { return "dpos" } // spendInput convert an utxo to the txinput func spendInput(u *UTXO) (*types.TxInput, *txbuilder.SigningInstruction, error) { txSpendInput := types.NewSpendInput(nil, u.SourceID, u.AssetID, u.Amount, u.SourcePos, u.ControlProgram) sigInst := &txbuilder.SigningInstruction{} var xpubs []chainkd.XPub var xprv chainkd.XPrv xprv.UnmarshalText([]byte(config.CommonConfig.Consensus.XPrv)) xpubs = append(xpubs, xprv.XPub()) quorum := len(xpubs) if u.Address == "" { sigInst.AddWitnessKeysWithOutPath(xpubs, quorum) return txSpendInput, sigInst, nil } address, err := common.DecodeAddress(u.Address, &consensus.ActiveNetParams) if err != nil { return nil, nil, err } sigInst.AddRawWitnessKeysWithoutPath(xpubs, quorum) switch address.(type) { case *common.AddressWitnessPubKeyHash: derivedPK := xpubs[0].PublicKey() sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness([]byte(derivedPK))) case *common.AddressWitnessScriptHash: derivedXPubs := xpubs derivedPKs := chainkd.XPubKeys(derivedXPubs) script, err := vmutil.P2SPMultiSigProgram(derivedPKs, quorum) if err != nil { return nil, nil, err } sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness(script)) default: return nil, nil, errors.New("unsupport address type") } return txSpendInput, sigInst, nil }