github.com/Bytom/bytom@v1.1.2-0.20210127130405-ae40204c0b09/account/builder.go (about) 1 package account 2 3 import ( 4 "context" 5 "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/errors" 13 "github.com/bytom/bytom/protocol/bc" 14 "github.com/bytom/bytom/protocol/bc/types" 15 "github.com/bytom/bytom/protocol/vm/vmutil" 16 ) 17 18 //DecodeSpendAction unmarshal JSON-encoded data of spend action 19 func (m *Manager) DecodeSpendAction(data []byte) (txbuilder.Action, error) { 20 a := &spendAction{accounts: m} 21 return a, json.Unmarshal(data, a) 22 } 23 24 type spendAction struct { 25 accounts *Manager 26 bc.AssetAmount 27 AccountID string `json:"account_id"` 28 UseUnconfirmed bool `json:"use_unconfirmed"` 29 } 30 31 func (a *spendAction) ActionType() string { 32 return "spend_account" 33 } 34 35 // MergeSpendAction merge common assetID and accountID spend action 36 func MergeSpendAction(actions []txbuilder.Action) []txbuilder.Action { 37 resultActions := []txbuilder.Action{} 38 spendActionMap := make(map[string]*spendAction) 39 40 for _, act := range actions { 41 switch act := act.(type) { 42 case *spendAction: 43 actionKey := act.AssetId.String() + act.AccountID 44 if tmpAct, ok := spendActionMap[actionKey]; ok { 45 tmpAct.Amount += act.Amount 46 tmpAct.UseUnconfirmed = tmpAct.UseUnconfirmed || act.UseUnconfirmed 47 } else { 48 spendActionMap[actionKey] = act 49 resultActions = append(resultActions, act) 50 } 51 default: 52 resultActions = append(resultActions, act) 53 } 54 } 55 return resultActions 56 } 57 58 //calcMergeGas calculate the gas required that n utxos are merged into one 59 func calcMergeGas(num int) uint64 { 60 gas := uint64(0) 61 for num > 1 { 62 gas += txbuilder.ChainTxMergeGas 63 num -= txbuilder.ChainTxUtxoNum - 1 64 } 65 return gas 66 } 67 68 func (m *Manager) reserveBtmUtxoChain(builder *txbuilder.TemplateBuilder, accountID string, amount uint64, useUnconfirmed bool) ([]*UTXO, error) { 69 reservedAmount := uint64(0) 70 utxos := []*UTXO{} 71 for gasAmount := uint64(0); reservedAmount < gasAmount+amount; gasAmount = calcMergeGas(len(utxos)) { 72 reserveAmount := amount + gasAmount - reservedAmount 73 res, err := m.utxoKeeper.Reserve(accountID, consensus.BTMAssetID, reserveAmount, useUnconfirmed, builder.MaxTime()) 74 if err != nil { 75 return nil, err 76 } 77 78 builder.OnRollback(func() { m.utxoKeeper.Cancel(res.id) }) 79 reservedAmount += reserveAmount + res.change 80 utxos = append(utxos, res.utxos[:]...) 81 } 82 return utxos, nil 83 } 84 85 func (m *Manager) buildBtmTxChain(utxos []*UTXO, signer *signers.Signer) ([]*txbuilder.Template, *UTXO, error) { 86 if len(utxos) == 0 { 87 return nil, nil, errors.New("mergeSpendActionUTXO utxos num 0") 88 } 89 90 tpls := []*txbuilder.Template{} 91 if len(utxos) == 1 { 92 return tpls, utxos[len(utxos)-1], nil 93 } 94 95 acp, err := m.GetLocalCtrlProgramByAddress(utxos[0].Address) 96 if err != nil { 97 return nil, nil, err 98 } 99 100 buildAmount := uint64(0) 101 builder := &txbuilder.TemplateBuilder{} 102 for index := 0; index < len(utxos); index++ { 103 input, sigInst, err := UtxoToInputs(signer, utxos[index]) 104 if err != nil { 105 return nil, nil, err 106 } 107 108 if err = builder.AddInput(input, sigInst); err != nil { 109 return nil, nil, err 110 } 111 112 buildAmount += input.Amount() 113 if builder.InputCount() != txbuilder.ChainTxUtxoNum && index != len(utxos)-1 { 114 continue 115 } 116 117 outAmount := buildAmount - txbuilder.ChainTxMergeGas 118 output := types.NewTxOutput(*consensus.BTMAssetID, outAmount, acp.ControlProgram) 119 if err := builder.AddOutput(output); err != nil { 120 return nil, nil, err 121 } 122 123 tpl, _, err := builder.Build() 124 if err != nil { 125 return nil, nil, err 126 } 127 128 bcOut, err := tpl.Transaction.Output(*tpl.Transaction.ResultIds[0]) 129 if err != nil { 130 return nil, nil, err 131 } 132 133 utxos = append(utxos, &UTXO{ 134 OutputID: *tpl.Transaction.ResultIds[0], 135 AssetID: *consensus.BTMAssetID, 136 Amount: outAmount, 137 ControlProgram: acp.ControlProgram, 138 SourceID: *bcOut.Source.Ref, 139 SourcePos: bcOut.Source.Position, 140 ControlProgramIndex: acp.KeyIndex, 141 Address: acp.Address, 142 Change: acp.Change, 143 }) 144 145 tpls = append(tpls, tpl) 146 buildAmount = 0 147 builder = &txbuilder.TemplateBuilder{} 148 if index == len(utxos)-2 { 149 break 150 } 151 } 152 return tpls, utxos[len(utxos)-1], nil 153 } 154 155 // SpendAccountChain build the spend action with auto merge utxo function 156 func SpendAccountChain(ctx context.Context, builder *txbuilder.TemplateBuilder, action txbuilder.Action) ([]*txbuilder.Template, error) { 157 act, ok := action.(*spendAction) 158 if !ok { 159 return nil, errors.New("fail to convert the spend action") 160 } 161 if *act.AssetId != *consensus.BTMAssetID { 162 return nil, errors.New("spend chain action only support BTM") 163 } 164 165 utxos, err := act.accounts.reserveBtmUtxoChain(builder, act.AccountID, act.Amount, act.UseUnconfirmed) 166 if err != nil { 167 return nil, err 168 } 169 170 acct, err := act.accounts.FindByID(act.AccountID) 171 if err != nil { 172 return nil, err 173 } 174 175 tpls, utxo, err := act.accounts.buildBtmTxChain(utxos, acct.Signer) 176 if err != nil { 177 return nil, err 178 } 179 180 input, sigInst, err := UtxoToInputs(acct.Signer, utxo) 181 if err != nil { 182 return nil, err 183 } 184 185 if err := builder.AddInput(input, sigInst); err != nil { 186 return nil, err 187 } 188 189 if utxo.Amount > act.Amount { 190 if err = builder.AddOutput(types.NewTxOutput(*consensus.BTMAssetID, utxo.Amount-act.Amount, utxo.ControlProgram)); err != nil { 191 return nil, errors.Wrap(err, "adding change output") 192 } 193 } 194 return tpls, nil 195 } 196 197 func (a *spendAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error { 198 var missing []string 199 if a.AccountID == "" { 200 missing = append(missing, "account_id") 201 } 202 if a.AssetId.IsZero() { 203 missing = append(missing, "asset_id") 204 } 205 if a.AssetAmount.Amount == 0 { 206 missing = append(missing, "amount") 207 } 208 if len(missing) > 0 { 209 return txbuilder.MissingFieldsError(missing...) 210 } 211 212 acct, err := a.accounts.FindByID(a.AccountID) 213 if err != nil { 214 return errors.Wrap(err, "get account info") 215 } 216 217 res, err := a.accounts.utxoKeeper.Reserve(a.AccountID, a.AssetId, a.Amount, a.UseUnconfirmed, b.MaxTime()) 218 if err != nil { 219 return errors.Wrap(err, "reserving utxos") 220 } 221 222 // Cancel the reservation if the build gets rolled back. 223 b.OnRollback(func() { a.accounts.utxoKeeper.Cancel(res.id) }) 224 for _, r := range res.utxos { 225 txInput, sigInst, err := UtxoToInputs(acct.Signer, r) 226 if err != nil { 227 return errors.Wrap(err, "creating inputs") 228 } 229 230 if err = b.AddInput(txInput, sigInst); err != nil { 231 return errors.Wrap(err, "adding inputs") 232 } 233 } 234 235 if res.change > 0 { 236 acp, err := a.accounts.CreateAddress(a.AccountID, true) 237 if err != nil { 238 return errors.Wrap(err, "creating control program") 239 } 240 241 // Don't insert the control program until callbacks are executed. 242 a.accounts.insertControlProgramDelayed(b, acp) 243 if err = b.AddOutput(types.NewTxOutput(*a.AssetId, res.change, acp.ControlProgram)); err != nil { 244 return errors.Wrap(err, "adding change output") 245 } 246 } 247 return nil 248 } 249 250 //DecodeSpendUTXOAction unmarshal JSON-encoded data of spend utxo action 251 func (m *Manager) DecodeSpendUTXOAction(data []byte) (txbuilder.Action, error) { 252 a := &spendUTXOAction{accounts: m} 253 return a, json.Unmarshal(data, a) 254 } 255 256 type spendUTXOAction struct { 257 accounts *Manager 258 OutputID *bc.Hash `json:"output_id"` 259 UseUnconfirmed bool `json:"use_unconfirmed"` 260 Arguments []txbuilder.ContractArgument `json:"arguments"` 261 } 262 263 func (a *spendUTXOAction) ActionType() string { 264 return "spend_account_unspent_output" 265 } 266 267 func (a *spendUTXOAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error { 268 if a.OutputID == nil { 269 return txbuilder.MissingFieldsError("output_id") 270 } 271 272 res, err := a.accounts.utxoKeeper.ReserveParticular(*a.OutputID, a.UseUnconfirmed, b.MaxTime()) 273 if err != nil { 274 return err 275 } 276 277 b.OnRollback(func() { a.accounts.utxoKeeper.Cancel(res.id) }) 278 var accountSigner *signers.Signer 279 if len(res.utxos[0].AccountID) != 0 { 280 account, err := a.accounts.FindByID(res.utxos[0].AccountID) 281 if err != nil { 282 return err 283 } 284 285 accountSigner = account.Signer 286 } 287 288 txInput, sigInst, err := UtxoToInputs(accountSigner, res.utxos[0]) 289 if err != nil { 290 return err 291 } 292 293 if a.Arguments == nil { 294 return b.AddInput(txInput, sigInst) 295 } 296 297 sigInst = &txbuilder.SigningInstruction{} 298 if err := txbuilder.AddContractArgs(sigInst, a.Arguments); err != nil { 299 return err 300 } 301 302 return b.AddInput(txInput, sigInst) 303 } 304 305 // UtxoToInputs convert an utxo to the txinput 306 func UtxoToInputs(signer *signers.Signer, u *UTXO) (*types.TxInput, *txbuilder.SigningInstruction, error) { 307 txInput := types.NewSpendInput(nil, u.SourceID, u.AssetID, u.Amount, u.SourcePos, u.ControlProgram) 308 sigInst := &txbuilder.SigningInstruction{} 309 if signer == nil { 310 return txInput, sigInst, nil 311 } 312 313 path, err := signers.Path(signer, signers.AccountKeySpace, u.Change, u.ControlProgramIndex) 314 if err != nil { 315 return nil, nil, err 316 } 317 if u.Address == "" { 318 sigInst.AddWitnessKeys(signer.XPubs, path, signer.Quorum) 319 return txInput, sigInst, nil 320 } 321 322 address, err := common.DecodeAddress(u.Address, &consensus.ActiveNetParams) 323 if err != nil { 324 return nil, nil, err 325 } 326 327 sigInst.AddRawWitnessKeys(signer.XPubs, path, signer.Quorum) 328 derivedXPubs := chainkd.DeriveXPubs(signer.XPubs, path) 329 330 switch address.(type) { 331 case *common.AddressWitnessPubKeyHash: 332 derivedPK := derivedXPubs[0].PublicKey() 333 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness([]byte(derivedPK))) 334 335 case *common.AddressWitnessScriptHash: 336 derivedPKs := chainkd.XPubKeys(derivedXPubs) 337 script, err := vmutil.P2SPMultiSigProgram(derivedPKs, signer.Quorum) 338 if err != nil { 339 return nil, nil, err 340 } 341 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness(script)) 342 343 default: 344 return nil, nil, errors.New("unsupport address type") 345 } 346 347 return txInput, sigInst, nil 348 } 349 350 // insertControlProgramDelayed takes a template builder and an account 351 // control program that hasn't been inserted to the database yet. It 352 // registers callbacks on the TemplateBuilder so that all of the template's 353 // account control programs are batch inserted if building the rest of 354 // the template is successful. 355 func (m *Manager) insertControlProgramDelayed(b *txbuilder.TemplateBuilder, acp *CtrlProgram) { 356 m.delayedACPsMu.Lock() 357 m.delayedACPs[b] = append(m.delayedACPs[b], acp) 358 m.delayedACPsMu.Unlock() 359 360 b.OnRollback(func() { 361 m.delayedACPsMu.Lock() 362 delete(m.delayedACPs, b) 363 m.delayedACPsMu.Unlock() 364 }) 365 b.OnBuild(func() error { 366 m.delayedACPsMu.Lock() 367 acps := m.delayedACPs[b] 368 delete(m.delayedACPs, b) 369 m.delayedACPsMu.Unlock() 370 371 // Insert all of the account control programs at once. 372 if len(acps) == 0 { 373 return nil 374 } 375 return m.SaveControlPrograms(acps...) 376 }) 377 }