7 "github.com/bytom/blockchain/signers"
8 "github.com/bytom/blockchain/txbuilder"
9 "github.com/bytom/common"
10 "github.com/bytom/consensus"
11 "github.com/bytom/crypto/ed25519/chainkd"
12 "github.com/bytom/errors"
13 "github.com/bytom/protocol/bc"
14 "github.com/bytom/protocol/bc/types"
15 "github.com/bytom/protocol/vm/vmutil"
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)
24 type spendAction struct {
27 AccountID string `json:"account_id"`
28 UseUnconfirmed bool `json:"use_unconfirmed"`
31 func (a *spendAction) ActionType() string {
32 return "spend_account"
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)
40 for _, act := range actions {
41 switch act := act.(type) {
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
48 spendActionMap[actionKey] = act
49 resultActions = append(resultActions, act)
52 resultActions = append(resultActions, act)
58 //calcMergeGas calculate the gas required that n utxos are merged into one
59 func calcMergeGas(num int) uint64 {
62 gas += txbuilder.ChainTxMergeGas
63 num -= txbuilder.ChainTxUtxoNum - 1
68 func (m *Manager) reserveBtmUtxoChain(builder *txbuilder.TemplateBuilder, accountID string, amount uint64, useUnconfirmed bool) ([]*UTXO, error) {
69 reservedAmount := uint64(0)
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())
78 builder.OnRollback(func() { m.utxoKeeper.Cancel(res.id) })
79 reservedAmount += reserveAmount + res.change
80 utxos = append(utxos, res.utxos[:]...)
85 func (m *Manager) buildBtmTxChain(utxos []*UTXO, signer *signers.Signer) ([]*txbuilder.Template, *UTXO, error) {
87 return nil, nil, errors.New("mergeSpendActionUTXO utxos num 0")
90 tpls := []*txbuilder.Template{}
92 return tpls, utxos[len(utxos)-1], nil
95 acp, err := m.GetLocalCtrlProgramByAddress(utxos[0].Address)
100 buildAmount := uint64(0)
101 builder := &txbuilder.TemplateBuilder{}
102 for index := 0; index < len(utxos); index++ {
103 input, sigInst, err := UtxoToInputs(signer, utxos[index])
108 if err = builder.AddInput(input, sigInst); err != nil {
112 buildAmount += input.Amount()
113 if builder.InputCount() != txbuilder.ChainTxUtxoNum && index != len(utxos)-1 {
117 outAmount := buildAmount - txbuilder.ChainTxMergeGas
118 output := types.NewTxOutput(*consensus.BTMAssetID, outAmount, acp.ControlProgram)
119 if err := builder.AddOutput(output); err != nil {
123 tpl, _, err := builder.Build()
128 bcOut, err := tpl.Transaction.Output(*tpl.Transaction.ResultIds[0])
133 utxos = append(utxos, &UTXO{
134 OutputID: *tpl.Transaction.ResultIds[0],
135 AssetID: *consensus.BTMAssetID,
137 ControlProgram: acp.ControlProgram,
138 SourceID: *bcOut.Source.Ref,
139 SourcePos: bcOut.Source.Position,
140 ControlProgramIndex: acp.KeyIndex,
141 Address: acp.Address,
145 tpls = append(tpls, tpl)
147 builder = &txbuilder.TemplateBuilder{}
148 if index == len(utxos)-2 {
152 return tpls, utxos[len(utxos)-1], nil
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)
159 return nil, errors.New("fail to convert the spend action")
161 if *act.AssetId != *consensus.BTMAssetID {
162 return nil, errors.New("spend chain action only support BTM")
165 utxos, err := act.accounts.reserveBtmUtxoChain(builder, act.AccountID, act.Amount, act.UseUnconfirmed)
170 acct, err := act.accounts.FindByID(act.AccountID)
175 tpls, utxo, err := act.accounts.buildBtmTxChain(utxos, acct.Signer)
180 input, sigInst, err := UtxoToInputs(acct.Signer, utxo)
185 if err := builder.AddInput(input, sigInst); err != nil {
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")
197 func (a *spendAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
199 if a.AccountID == "" {
200 missing = append(missing, "account_id")
202 if a.AssetId.IsZero() {
203 missing = append(missing, "asset_id")
205 if a.AssetAmount.Amount == 0 {
206 missing = append(missing, "amount")
208 if len(missing) > 0 {
209 return txbuilder.MissingFieldsError(missing...)
212 acct, err := a.accounts.FindByID(a.AccountID)
214 return errors.Wrap(err, "get account info")
217 res, err := a.accounts.utxoKeeper.Reserve(a.AccountID, a.AssetId, a.Amount, a.UseUnconfirmed, b.MaxTime())
219 return errors.Wrap(err, "reserving utxos")
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)
227 return errors.Wrap(err, "creating inputs")
230 if err = b.AddInput(txInput, sigInst); err != nil {
231 return errors.Wrap(err, "adding inputs")
236 acp, err := a.accounts.CreateAddress(a.AccountID, true)
238 return errors.Wrap(err, "creating control program")
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")
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)
256 type spendUTXOAction struct {
258 OutputID *bc.Hash `json:"output_id"`
259 UseUnconfirmed bool `json:"use_unconfirmed"`
260 Arguments []txbuilder.ContractArgument `json:"arguments"`
263 func (a *spendUTXOAction) ActionType() string {
264 return "spend_account_unspent_output"
267 func (a *spendUTXOAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
268 if a.OutputID == nil {
269 return txbuilder.MissingFieldsError("output_id")
272 res, err := a.accounts.utxoKeeper.ReserveParticular(*a.OutputID, a.UseUnconfirmed, b.MaxTime())
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)
285 accountSigner = account.Signer
288 txInput, sigInst, err := UtxoToInputs(accountSigner, res.utxos[0])
293 if a.Arguments == nil {
294 return b.AddInput(txInput, sigInst)
297 sigInst = &txbuilder.SigningInstruction{}
298 if err := txbuilder.AddContractArgs(sigInst, a.Arguments); err != nil {
302 return b.AddInput(txInput, sigInst)
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{}
310 return txInput, sigInst, nil
313 path, err := signers.Path(signer, signers.AccountKeySpace, u.Change, u.ControlProgramIndex)
318 sigInst.AddWitnessKeys(signer.XPubs, path, signer.Quorum)
319 return txInput, sigInst, nil
322 address, err := common.DecodeAddress(u.Address, &consensus.ActiveNetParams)
327 sigInst.AddRawWitnessKeys(signer.XPubs, path, signer.Quorum)
328 derivedXPubs := chainkd.DeriveXPubs(signer.XPubs, path)
330 switch address.(type) {
331 case *common.AddressWitnessPubKeyHash:
332 derivedPK := derivedXPubs[0].PublicKey()
333 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness([]byte(derivedPK)))
335 case *common.AddressWitnessScriptHash:
336 derivedPKs := chainkd.XPubKeys(derivedXPubs)
337 script, err := vmutil.P2SPMultiSigProgram(derivedPKs, signer.Quorum)
341 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness(script))
344 return nil, nil, errors.New("unsupport address type")
347 return txInput, sigInst, nil
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()
360 b.OnRollback(func() {
361 m.delayedACPsMu.Lock()
362 delete(m.delayedACPs, b)
363 m.delayedACPsMu.Unlock()
365 b.OnBuild(func() error {
366 m.delayedACPsMu.Lock()
367 acps := m.delayedACPs[b]
368 delete(m.delayedACPs, b)
369 m.delayedACPsMu.Unlock()
371 // Insert all of the account control programs at once.
375 return m.SaveControlPrograms(acps...)