github.com/bytom/bytom@v1.1.2-0.20221014091027-bbcba3df6075/account/builder.go (about) 1 package account 2 3 import ( 4 "context" 5 stdjson "encoding/json" 6 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" 17 ) 18 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) 23 } 24 25 type spendAction struct { 26 accounts *Manager 27 bc.AssetAmount 28 AccountID string `json:"account_id"` 29 UseUnconfirmed bool `json:"use_unconfirmed"` 30 } 31 32 func (a *spendAction) ActionType() string { 33 return "spend_account" 34 } 35 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) 40 41 for _, act := range actions { 42 switch act := act.(type) { 43 case *spendAction: 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 48 } else { 49 spendActionMap[actionKey] = act 50 resultActions = append(resultActions, act) 51 } 52 default: 53 resultActions = append(resultActions, act) 54 } 55 } 56 return resultActions 57 } 58 59 //calcMergeGas calculate the gas required that n utxos are merged into one 60 func calcMergeGas(num int) uint64 { 61 gas := uint64(0) 62 for num > 1 { 63 gas += txbuilder.ChainTxMergeGas 64 num -= txbuilder.ChainTxUtxoNum - 1 65 } 66 return gas 67 } 68 69 func (m *Manager) reserveBtmUtxoChain(builder *txbuilder.TemplateBuilder, accountID string, amount uint64, useUnconfirmed bool) ([]*UTXO, error) { 70 reservedAmount := uint64(0) 71 utxos := []*UTXO{} 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()) 75 if err != nil { 76 return nil, err 77 } 78 79 builder.OnRollback(func() { m.utxoKeeper.Cancel(res.id) }) 80 reservedAmount += reserveAmount + res.change 81 utxos = append(utxos, res.utxos[:]...) 82 } 83 return utxos, nil 84 } 85 86 func (m *Manager) buildBtmTxChain(utxos []*UTXO, signer *signers.Signer) ([]*txbuilder.Template, *UTXO, error) { 87 if len(utxos) == 0 { 88 return nil, nil, errors.New("mergeSpendActionUTXO utxos num 0") 89 } 90 91 tpls := []*txbuilder.Template{} 92 if len(utxos) == 1 { 93 return tpls, utxos[len(utxos)-1], nil 94 } 95 96 acp, err := m.GetLocalCtrlProgramByAddress(utxos[0].Address) 97 if err != nil { 98 return nil, nil, err 99 } 100 101 buildAmount := uint64(0) 102 builder := &txbuilder.TemplateBuilder{} 103 for index := 0; index < len(utxos); index++ { 104 input, sigInst, err := UtxoToInputs(signer, utxos[index]) 105 if err != nil { 106 return nil, nil, err 107 } 108 109 if err = builder.AddInput(input, sigInst); err != nil { 110 return nil, nil, err 111 } 112 113 buildAmount += input.Amount() 114 if builder.InputCount() != txbuilder.ChainTxUtxoNum && index != len(utxos)-1 { 115 continue 116 } 117 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 { 121 return nil, nil, err 122 } 123 124 tpl, _, err := builder.Build() 125 if err != nil { 126 return nil, nil, err 127 } 128 129 bcOut, err := tpl.Transaction.OriginalOutput(*tpl.Transaction.ResultIds[0]) 130 if err != nil { 131 return nil, nil, err 132 } 133 134 utxos = append(utxos, &UTXO{ 135 OutputID: *tpl.Transaction.ResultIds[0], 136 AssetID: *consensus.BTMAssetID, 137 Amount: outAmount, 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, 144 Change: acp.Change, 145 }) 146 147 tpls = append(tpls, tpl) 148 buildAmount = 0 149 builder = &txbuilder.TemplateBuilder{} 150 if index == len(utxos)-2 { 151 break 152 } 153 } 154 return tpls, utxos[len(utxos)-1], nil 155 } 156 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) 160 if !ok { 161 return nil, errors.New("fail to convert the spend action") 162 } 163 if *act.AssetId != *consensus.BTMAssetID { 164 return nil, errors.New("spend chain action only support BTM") 165 } 166 167 utxos, err := act.accounts.reserveBtmUtxoChain(builder, act.AccountID, act.Amount, act.UseUnconfirmed) 168 if err != nil { 169 return nil, err 170 } 171 172 acct, err := act.accounts.FindByID(act.AccountID) 173 if err != nil { 174 return nil, err 175 } 176 177 tpls, utxo, err := act.accounts.buildBtmTxChain(utxos, acct.Signer) 178 if err != nil { 179 return nil, err 180 } 181 182 input, sigInst, err := UtxoToInputs(acct.Signer, utxo) 183 if err != nil { 184 return nil, err 185 } 186 187 if err := builder.AddInput(input, sigInst); err != nil { 188 return nil, err 189 } 190 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") 194 } 195 } 196 return tpls, nil 197 } 198 199 func (a *spendAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error { 200 var missing []string 201 if a.AccountID == "" { 202 missing = append(missing, "account_id") 203 } 204 if a.AssetId.IsZero() { 205 missing = append(missing, "asset_id") 206 } 207 if a.AssetAmount.Amount == 0 { 208 missing = append(missing, "amount") 209 } 210 if len(missing) > 0 { 211 return txbuilder.MissingFieldsError(missing...) 212 } 213 214 acct, err := a.accounts.FindByID(a.AccountID) 215 if err != nil { 216 return errors.Wrap(err, "get account info") 217 } 218 219 res, err := a.accounts.utxoKeeper.Reserve(a.AccountID, a.AssetId, a.Amount, a.UseUnconfirmed, nil, b.MaxTime()) 220 if err != nil { 221 return errors.Wrap(err, "reserving utxos") 222 } 223 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) 228 if err != nil { 229 return errors.Wrap(err, "creating inputs") 230 } 231 232 if err = b.AddInput(txInput, sigInst); err != nil { 233 return errors.Wrap(err, "adding inputs") 234 } 235 } 236 237 if res.change > 0 { 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") 240 } 241 } 242 return nil 243 } 244 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) 249 } 250 251 type spendUTXOAction struct { 252 accounts *Manager 253 OutputID *bc.Hash `json:"output_id"` 254 UseUnconfirmed bool `json:"use_unconfirmed"` 255 Arguments []txbuilder.ContractArgument `json:"arguments"` 256 } 257 258 func (a *spendUTXOAction) ActionType() string { 259 return "spend_account_unspent_output" 260 } 261 262 func (a *spendUTXOAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error { 263 if a.OutputID == nil { 264 return txbuilder.MissingFieldsError("output_id") 265 } 266 267 res, err := a.accounts.utxoKeeper.ReserveParticular(*a.OutputID, a.UseUnconfirmed, b.MaxTime()) 268 if err != nil { 269 return err 270 } 271 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) 276 if err != nil { 277 return err 278 } 279 280 accountSigner = account.Signer 281 } 282 283 txInput, sigInst, err := UtxoToInputs(accountSigner, res.utxos[0]) 284 if err != nil { 285 return err 286 } 287 288 if a.Arguments == nil { 289 return b.AddInput(txInput, sigInst) 290 } 291 292 sigInst = &txbuilder.SigningInstruction{} 293 if err := txbuilder.AddContractArgs(sigInst, a.Arguments); err != nil { 294 return err 295 } 296 297 return b.AddInput(txInput, sigInst) 298 } 299 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) 303 if u.Vote != nil { 304 txInput = types.NewVetoInput(nil, u.SourceID, u.AssetID, u.Amount, u.SourcePos, u.ControlProgram, u.Vote, nil) 305 } 306 sigInst := &txbuilder.SigningInstruction{} 307 if signer == nil { 308 return txInput, sigInst, nil 309 } 310 311 path, err := signers.Path(signer, signers.AccountKeySpace, u.Change, u.ControlProgramIndex) 312 if err != nil { 313 return nil, nil, err 314 } 315 if u.Address == "" { 316 sigInst.AddWitnessKeys(signer.XPubs, path, signer.Quorum) 317 return txInput, sigInst, nil 318 } 319 320 address, err := common.DecodeAddress(u.Address, &consensus.ActiveNetParams) 321 if err != nil { 322 return nil, nil, err 323 } 324 325 sigInst.AddRawWitnessKeys(signer.XPubs, path, signer.Quorum) 326 derivedXPubs := chainkd.DeriveXPubs(signer.XPubs, path) 327 328 switch address.(type) { 329 case *common.AddressWitnessPubKeyHash: 330 derivedPK := derivedXPubs[0].PublicKey() 331 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness([]byte(derivedPK))) 332 333 case *common.AddressWitnessScriptHash: 334 derivedPKs := chainkd.XPubKeys(derivedXPubs) 335 script, err := vmutil.P2SPMultiSigProgram(derivedPKs, signer.Quorum) 336 if err != nil { 337 return nil, nil, err 338 } 339 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness(script)) 340 341 default: 342 return nil, nil, errors.New("unsupport address type") 343 } 344 345 return txInput, sigInst, nil 346 } 347 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) 352 } 353 354 type vetoAction struct { 355 accounts *Manager 356 bc.AssetAmount 357 AccountID string `json:"account_id"` 358 Vote json.HexBytes `json:"vote"` 359 UseUnconfirmed bool `json:"use_unconfirmed"` 360 } 361 362 func (a *vetoAction) ActionType() string { 363 return "veto" 364 } 365 366 func (a *vetoAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error { 367 var missing []string 368 if a.AccountID == "" { 369 missing = append(missing, "account_id") 370 } 371 if a.AssetId.IsZero() { 372 missing = append(missing, "asset_id") 373 } 374 if len(missing) > 0 { 375 return txbuilder.MissingFieldsError(missing...) 376 } 377 378 acct, err := a.accounts.FindByID(a.AccountID) 379 if err != nil { 380 return errors.Wrap(err, "get account info") 381 } 382 383 res, err := a.accounts.utxoKeeper.Reserve(a.AccountID, a.AssetId, a.Amount, a.UseUnconfirmed, a.Vote, b.MaxTime()) 384 if err != nil { 385 return errors.Wrap(err, "reserving utxos") 386 } 387 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) 392 if err != nil { 393 return errors.Wrap(err, "creating inputs") 394 } 395 396 if err = b.AddInput(txInput, sigInst); err != nil { 397 return errors.Wrap(err, "adding inputs") 398 } 399 } 400 401 if res.change > 0 { 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") 404 } 405 } 406 return nil 407 }