5 stdjson "encoding/json"
7 "github.com/bytom/bytom/blockchain/signers"
8 "github.com/bytom/bytom/blockchain/txbuilder"
9 "github.com/bytom/bytom/common"
10 "github.com/bytom/bytom/consensus"
11 "github.com/bytom/bytom/crypto/ed25519/chainkd"
12 "github.com/bytom/bytom/encoding/json"
13 "github.com/bytom/bytom/errors"
14 "github.com/bytom/bytom/protocol/bc"
15 "github.com/bytom/bytom/protocol/bc/types"
16 "github.com/bytom/bytom/protocol/vm/vmutil"
19 //DecodeSpendAction unmarshal JSON-encoded data of spend action
20 func (m *Manager) DecodeSpendAction(data []byte) (txbuilder.Action, error) {
21 a := &spendAction{accounts: m}
22 return a, stdjson.Unmarshal(data, a)
25 type spendAction struct {
28 AccountID string `json:"account_id"`
29 UseUnconfirmed bool `json:"use_unconfirmed"`
32 func (a *spendAction) ActionType() string {
33 return "spend_account"
36 // MergeSpendAction merge common assetID and accountID spend action
37 func MergeSpendAction(actions []txbuilder.Action) []txbuilder.Action {
38 resultActions := []txbuilder.Action{}
39 spendActionMap := make(map[string]*spendAction)
41 for _, act := range actions {
42 switch act := act.(type) {
44 actionKey := act.AssetId.String() + act.AccountID
45 if tmpAct, ok := spendActionMap[actionKey]; ok {
46 tmpAct.Amount += act.Amount
47 tmpAct.UseUnconfirmed = tmpAct.UseUnconfirmed || act.UseUnconfirmed
49 spendActionMap[actionKey] = act
50 resultActions = append(resultActions, act)
53 resultActions = append(resultActions, act)
59 //calcMergeGas calculate the gas required that n utxos are merged into one
60 func calcMergeGas(num int) uint64 {
63 gas += txbuilder.ChainTxMergeGas
64 num -= txbuilder.ChainTxUtxoNum - 1
69 func (m *Manager) reserveBtmUtxoChain(builder *txbuilder.TemplateBuilder, accountID string, amount uint64, useUnconfirmed bool) ([]*UTXO, error) {
70 reservedAmount := uint64(0)
72 for gasAmount := uint64(0); reservedAmount < gasAmount+amount; gasAmount = calcMergeGas(len(utxos)) {
73 reserveAmount := amount + gasAmount - reservedAmount
74 res, err := m.utxoKeeper.Reserve(accountID, consensus.BTMAssetID, reserveAmount, useUnconfirmed, nil, builder.MaxTime())
79 builder.OnRollback(func() { m.utxoKeeper.Cancel(res.id) })
80 reservedAmount += reserveAmount + res.change
81 utxos = append(utxos, res.utxos[:]...)
86 func (m *Manager) buildBtmTxChain(utxos []*UTXO, signer *signers.Signer) ([]*txbuilder.Template, *UTXO, error) {
88 return nil, nil, errors.New("mergeSpendActionUTXO utxos num 0")
91 tpls := []*txbuilder.Template{}
93 return tpls, utxos[len(utxos)-1], nil
96 acp, err := m.GetLocalCtrlProgramByAddress(utxos[0].Address)
101 buildAmount := uint64(0)
102 builder := &txbuilder.TemplateBuilder{}
103 for index := 0; index < len(utxos); index++ {
104 input, sigInst, err := UtxoToInputs(signer, utxos[index])
109 if err = builder.AddInput(input, sigInst); err != nil {
113 buildAmount += input.Amount()
114 if builder.InputCount() != txbuilder.ChainTxUtxoNum && index != len(utxos)-1 {
118 outAmount := buildAmount - txbuilder.ChainTxMergeGas
119 output := types.NewOriginalTxOutput(*consensus.BTMAssetID, outAmount, acp.ControlProgram, utxos[index].StateData)
120 if err := builder.AddOutput(output); err != nil {
124 tpl, _, err := builder.Build()
129 bcOut, err := tpl.Transaction.OriginalOutput(*tpl.Transaction.ResultIds[0])
134 utxos = append(utxos, &UTXO{
135 OutputID: *tpl.Transaction.ResultIds[0],
136 AssetID: *consensus.BTMAssetID,
138 ControlProgram: acp.ControlProgram,
139 StateData: utxos[index].StateData,
140 SourceID: *bcOut.Source.Ref,
141 SourcePos: bcOut.Source.Position,
142 ControlProgramIndex: acp.KeyIndex,
143 Address: acp.Address,
147 tpls = append(tpls, tpl)
149 builder = &txbuilder.TemplateBuilder{}
150 if index == len(utxos)-2 {
154 return tpls, utxos[len(utxos)-1], nil
157 // SpendAccountChain build the spend action with auto merge utxo function
158 func SpendAccountChain(ctx context.Context, builder *txbuilder.TemplateBuilder, action txbuilder.Action) ([]*txbuilder.Template, error) {
159 act, ok := action.(*spendAction)
161 return nil, errors.New("fail to convert the spend action")
163 if *act.AssetId != *consensus.BTMAssetID {
164 return nil, errors.New("spend chain action only support BTM")
167 utxos, err := act.accounts.reserveBtmUtxoChain(builder, act.AccountID, act.Amount, act.UseUnconfirmed)
172 acct, err := act.accounts.FindByID(act.AccountID)
177 tpls, utxo, err := act.accounts.buildBtmTxChain(utxos, acct.Signer)
182 input, sigInst, err := UtxoToInputs(acct.Signer, utxo)
187 if err := builder.AddInput(input, sigInst); err != nil {
191 if utxo.Amount > act.Amount {
192 if err = builder.AddOutput(types.NewOriginalTxOutput(*consensus.BTMAssetID, utxo.Amount-act.Amount, utxo.ControlProgram, utxo.StateData)); err != nil {
193 return nil, errors.Wrap(err, "adding change output")
199 func (a *spendAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
201 if a.AccountID == "" {
202 missing = append(missing, "account_id")
204 if a.AssetId.IsZero() {
205 missing = append(missing, "asset_id")
207 if a.AssetAmount.Amount == 0 {
208 missing = append(missing, "amount")
210 if len(missing) > 0 {
211 return txbuilder.MissingFieldsError(missing...)
214 acct, err := a.accounts.FindByID(a.AccountID)
216 return errors.Wrap(err, "get account info")
219 res, err := a.accounts.utxoKeeper.Reserve(a.AccountID, a.AssetId, a.Amount, a.UseUnconfirmed, nil, b.MaxTime())
221 return errors.Wrap(err, "reserving utxos")
224 // Cancel the reservation if the build gets rolled back.
225 b.OnRollback(func() { a.accounts.utxoKeeper.Cancel(res.id) })
226 for _, r := range res.utxos {
227 txInput, sigInst, err := UtxoToInputs(acct.Signer, r)
229 return errors.Wrap(err, "creating inputs")
232 if err = b.AddInput(txInput, sigInst); err != nil {
233 return errors.Wrap(err, "adding inputs")
238 if err = b.AddOutput(types.NewOriginalTxOutput(*a.AssetId, res.change, res.utxos[0].ControlProgram, nil)); err != nil {
239 return errors.Wrap(err, "adding change output")
245 //DecodeSpendUTXOAction unmarshal JSON-encoded data of spend utxo action
246 func (m *Manager) DecodeSpendUTXOAction(data []byte) (txbuilder.Action, error) {
247 a := &spendUTXOAction{accounts: m}
248 return a, stdjson.Unmarshal(data, a)
251 type spendUTXOAction struct {
253 OutputID *bc.Hash `json:"output_id"`
254 UseUnconfirmed bool `json:"use_unconfirmed"`
255 Arguments []txbuilder.ContractArgument `json:"arguments"`
258 func (a *spendUTXOAction) ActionType() string {
259 return "spend_account_unspent_output"
262 func (a *spendUTXOAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
263 if a.OutputID == nil {
264 return txbuilder.MissingFieldsError("output_id")
267 res, err := a.accounts.utxoKeeper.ReserveParticular(*a.OutputID, a.UseUnconfirmed, b.MaxTime())
272 b.OnRollback(func() { a.accounts.utxoKeeper.Cancel(res.id) })
273 var accountSigner *signers.Signer
274 if len(res.utxos[0].AccountID) != 0 {
275 account, err := a.accounts.FindByID(res.utxos[0].AccountID)
280 accountSigner = account.Signer
283 txInput, sigInst, err := UtxoToInputs(accountSigner, res.utxos[0])
288 if a.Arguments == nil {
289 return b.AddInput(txInput, sigInst)
292 sigInst = &txbuilder.SigningInstruction{}
293 if err := txbuilder.AddContractArgs(sigInst, a.Arguments); err != nil {
297 return b.AddInput(txInput, sigInst)
300 // UtxoToInputs convert an utxo to the txinput
301 func UtxoToInputs(signer *signers.Signer, u *UTXO) (*types.TxInput, *txbuilder.SigningInstruction, error) {
302 txInput := types.NewSpendInput(nil, u.SourceID, u.AssetID, u.Amount, u.SourcePos, u.ControlProgram, u.StateData)
304 txInput = types.NewVetoInput(nil, u.SourceID, u.AssetID, u.Amount, u.SourcePos, u.ControlProgram, u.Vote, nil)
306 sigInst := &txbuilder.SigningInstruction{}
308 return txInput, sigInst, nil
311 path, err := signers.Path(signer, signers.AccountKeySpace, u.Change, u.ControlProgramIndex)
316 sigInst.AddWitnessKeys(signer.XPubs, path, signer.Quorum)
317 return txInput, sigInst, nil
320 address, err := common.DecodeAddress(u.Address, &consensus.ActiveNetParams)
325 sigInst.AddRawWitnessKeys(signer.XPubs, path, signer.Quorum)
326 derivedXPubs := chainkd.DeriveXPubs(signer.XPubs, path)
328 switch address.(type) {
329 case *common.AddressWitnessPubKeyHash:
330 derivedPK := derivedXPubs[0].PublicKey()
331 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness([]byte(derivedPK)))
333 case *common.AddressWitnessScriptHash:
334 derivedPKs := chainkd.XPubKeys(derivedXPubs)
335 script, err := vmutil.P2SPMultiSigProgram(derivedPKs, signer.Quorum)
339 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness(script))
342 return nil, nil, errors.New("unsupport address type")
345 return txInput, sigInst, nil
348 //DecodeVetoAction unmarshal JSON-encoded data of spend action
349 func (m *Manager) DecodeVetoAction(data []byte) (txbuilder.Action, error) {
350 a := &vetoAction{accounts: m}
351 return a, stdjson.Unmarshal(data, a)
354 type vetoAction struct {
357 AccountID string `json:"account_id"`
358 Vote json.HexBytes `json:"vote"`
359 UseUnconfirmed bool `json:"use_unconfirmed"`
362 func (a *vetoAction) ActionType() string {
366 func (a *vetoAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
368 if a.AccountID == "" {
369 missing = append(missing, "account_id")
371 if a.AssetId.IsZero() {
372 missing = append(missing, "asset_id")
374 if len(missing) > 0 {
375 return txbuilder.MissingFieldsError(missing...)
378 acct, err := a.accounts.FindByID(a.AccountID)
380 return errors.Wrap(err, "get account info")
383 res, err := a.accounts.utxoKeeper.Reserve(a.AccountID, a.AssetId, a.Amount, a.UseUnconfirmed, a.Vote, b.MaxTime())
385 return errors.Wrap(err, "reserving utxos")
388 // Cancel the reservation if the build gets rolled back.
389 b.OnRollback(func() { a.accounts.utxoKeeper.Cancel(res.id) })
390 for _, r := range res.utxos {
391 txInput, sigInst, err := UtxoToInputs(acct.Signer, r)
393 return errors.Wrap(err, "creating inputs")
396 if err = b.AddInput(txInput, sigInst); err != nil {
397 return errors.Wrap(err, "adding inputs")
402 if err = b.AddOutput(types.NewOriginalTxOutput(*a.AssetId, res.change, res.utxos[0].ControlProgram, nil)); err != nil {
403 return errors.Wrap(err, "adding change output")