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"
19 //chainTxUtxoNum maximum utxo quantity in a tx
21 //chainTxMergeGas chain tx gas
22 chainTxMergeGas = uint64(10000000)
25 //DecodeSpendAction unmarshal JSON-encoded data of spend action
26 func (m *Manager) DecodeSpendAction(data []byte) (txbuilder.Action, error) {
27 a := &spendAction{accounts: m}
28 return a, json.Unmarshal(data, a)
31 type spendAction struct {
34 AccountID string `json:"account_id"`
35 UseUnconfirmed bool `json:"use_unconfirmed"`
38 func (a *spendAction) ActionType() string {
39 return "spend_account"
42 // MergeSpendAction merge common assetID and accountID spend action
43 func MergeSpendAction(actions []txbuilder.Action) []txbuilder.Action {
44 resultActions := []txbuilder.Action{}
45 spendActionMap := make(map[string]*spendAction)
47 for _, act := range actions {
48 switch act := act.(type) {
50 actionKey := act.AssetId.String() + act.AccountID
51 if tmpAct, ok := spendActionMap[actionKey]; ok {
52 tmpAct.Amount += act.Amount
53 tmpAct.UseUnconfirmed = tmpAct.UseUnconfirmed || act.UseUnconfirmed
55 spendActionMap[actionKey] = act
56 resultActions = append(resultActions, act)
59 resultActions = append(resultActions, act)
65 //calcMergeGas calculate the gas required that n utxos are merged into one
66 func calcMergeGas(num int) uint64 {
69 gas += chainTxMergeGas
70 num -= chainTxUtxoNum - 1
75 func (m *Manager) reserveBtmUtxoChain(builder *txbuilder.TemplateBuilder, accountID string, amount uint64, useUnconfirmed bool) ([]*UTXO, error) {
76 reservedAmount := uint64(0)
78 for gasAmount := uint64(0); reservedAmount < gasAmount+amount; gasAmount = calcMergeGas(len(utxos)) {
79 reserveAmount := amount + gasAmount - reservedAmount
80 res, err := m.utxoKeeper.Reserve(accountID, consensus.BTMAssetID, reserveAmount, useUnconfirmed, builder.MaxTime())
85 builder.OnRollback(func() { m.utxoKeeper.Cancel(res.id) })
86 reservedAmount += reserveAmount + res.change
87 utxos = append(utxos, res.utxos[:]...)
92 func (m *Manager) buildBtmTxChain(utxos []*UTXO, signer *signers.Signer) ([]*txbuilder.Template, *UTXO, error) {
94 return nil, nil, errors.New("mergeSpendActionUTXO utxos num 0")
97 tpls := []*txbuilder.Template{}
99 return tpls, utxos[len(utxos)-1], nil
102 acp, err := m.GetLocalCtrlProgramByAddress(utxos[0].Address)
107 buildAmount := uint64(0)
108 builder := &txbuilder.TemplateBuilder{}
109 for index := 0; index < len(utxos); index++ {
110 input, sigInst, err := UtxoToInputs(signer, utxos[index])
115 if err = builder.AddInput(input, sigInst); err != nil {
119 buildAmount += input.Amount()
120 if builder.InputCount() != chainTxUtxoNum && index != len(utxos)-1 {
124 outAmount := buildAmount - chainTxMergeGas
125 output := types.NewTxOutput(*consensus.BTMAssetID, outAmount, acp.ControlProgram)
126 if err := builder.AddOutput(output); err != nil {
130 tpl, _, err := builder.Build()
135 bcOut, err := tpl.Transaction.Output(*tpl.Transaction.ResultIds[0])
140 utxos = append(utxos, &UTXO{
141 OutputID: *tpl.Transaction.ResultIds[0],
142 AssetID: *consensus.BTMAssetID,
144 ControlProgram: acp.ControlProgram,
145 SourceID: *bcOut.Source.Ref,
146 SourcePos: bcOut.Source.Position,
147 ControlProgramIndex: acp.KeyIndex,
148 Address: acp.Address,
151 tpls = append(tpls, tpl)
153 builder = &txbuilder.TemplateBuilder{}
154 if index == len(utxos)-2 {
158 return tpls, utxos[len(utxos)-1], nil
161 // SpendAccountChain build the spend action with auto merge utxo function
162 func SpendAccountChain(ctx context.Context, builder *txbuilder.TemplateBuilder, action txbuilder.Action) ([]*txbuilder.Template, error) {
163 act, ok := action.(*spendAction)
165 return nil, errors.New("fail to convert the spend action")
167 if *act.AssetId != *consensus.BTMAssetID {
168 return nil, errors.New("spend chain action only support BTM")
171 utxos, err := act.accounts.reserveBtmUtxoChain(builder, act.AccountID, act.Amount, act.UseUnconfirmed)
176 acct, err := act.accounts.FindByID(act.AccountID)
181 tpls, utxo, err := act.accounts.buildBtmTxChain(utxos, acct.Signer)
186 input, sigInst, err := UtxoToInputs(acct.Signer, utxo)
191 if err := builder.AddInput(input, sigInst); err != nil {
195 if utxo.Amount > act.Amount {
196 if err = builder.AddOutput(types.NewTxOutput(*consensus.BTMAssetID, utxo.Amount-act.Amount, utxo.ControlProgram)); err != nil {
197 return nil, errors.Wrap(err, "adding change output")
203 func (a *spendAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
205 if a.AccountID == "" {
206 missing = append(missing, "account_id")
208 if a.AssetId.IsZero() {
209 missing = append(missing, "asset_id")
211 if len(missing) > 0 {
212 return txbuilder.MissingFieldsError(missing...)
215 acct, err := a.accounts.FindByID(a.AccountID)
217 return errors.Wrap(err, "get account info")
220 res, err := a.accounts.utxoKeeper.Reserve(a.AccountID, a.AssetId, a.Amount, a.UseUnconfirmed, b.MaxTime())
222 return errors.Wrap(err, "reserving utxos")
225 // Cancel the reservation if the build gets rolled back.
226 b.OnRollback(func() { a.accounts.utxoKeeper.Cancel(res.id) })
227 for _, r := range res.utxos {
228 txInput, sigInst, err := UtxoToInputs(acct.Signer, r)
230 return errors.Wrap(err, "creating inputs")
233 if err = b.AddInput(txInput, sigInst); err != nil {
234 return errors.Wrap(err, "adding inputs")
239 acp, err := a.accounts.CreateAddress(a.AccountID, true)
241 return errors.Wrap(err, "creating control program")
244 // Don't insert the control program until callbacks are executed.
245 a.accounts.insertControlProgramDelayed(b, acp)
246 if err = b.AddOutput(types.NewTxOutput(*a.AssetId, res.change, acp.ControlProgram)); err != nil {
247 return errors.Wrap(err, "adding change output")
253 //DecodeSpendUTXOAction unmarshal JSON-encoded data of spend utxo action
254 func (m *Manager) DecodeSpendUTXOAction(data []byte) (txbuilder.Action, error) {
255 a := &spendUTXOAction{accounts: m}
256 return a, json.Unmarshal(data, a)
259 type spendUTXOAction struct {
261 OutputID *bc.Hash `json:"output_id"`
262 UseUnconfirmed bool `json:"use_unconfirmed"`
263 Arguments []txbuilder.ContractArgument `json:"arguments"`
266 func (a *spendUTXOAction) ActionType() string {
267 return "spend_account_unspent_output"
270 func (a *spendUTXOAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
271 if a.OutputID == nil {
272 return txbuilder.MissingFieldsError("output_id")
275 res, err := a.accounts.utxoKeeper.ReserveParticular(*a.OutputID, a.UseUnconfirmed, b.MaxTime())
280 b.OnRollback(func() { a.accounts.utxoKeeper.Cancel(res.id) })
281 var accountSigner *signers.Signer
282 if len(res.utxos[0].AccountID) != 0 {
283 account, err := a.accounts.FindByID(res.utxos[0].AccountID)
288 accountSigner = account.Signer
291 txInput, sigInst, err := UtxoToInputs(accountSigner, res.utxos[0])
296 if a.Arguments == nil {
297 return b.AddInput(txInput, sigInst)
300 sigInst = &txbuilder.SigningInstruction{}
301 if err := txbuilder.AddContractArgs(sigInst, a.Arguments); err != nil {
305 return b.AddInput(txInput, sigInst)
308 // UtxoToInputs convert an utxo to the txinput
309 func UtxoToInputs(signer *signers.Signer, u *UTXO) (*types.TxInput, *txbuilder.SigningInstruction, error) {
310 txInput := types.NewSpendInput(nil, u.SourceID, u.AssetID, u.Amount, u.SourcePos, u.ControlProgram)
311 sigInst := &txbuilder.SigningInstruction{}
313 return txInput, sigInst, nil
316 path := signers.Path(signer, signers.AccountKeySpace, 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 switch address.(type) {
328 case *common.AddressWitnessPubKeyHash:
329 sigInst.AddRawWitnessKeys(signer.XPubs, path, signer.Quorum)
330 derivedXPubs := chainkd.DeriveXPubs(signer.XPubs, path)
331 derivedPK := derivedXPubs[0].PublicKey()
332 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness([]byte(derivedPK)))
334 case *common.AddressWitnessScriptHash:
335 sigInst.AddRawWitnessKeys(signer.XPubs, path, signer.Quorum)
336 path := signers.Path(signer, signers.AccountKeySpace, u.ControlProgramIndex)
337 derivedXPubs := chainkd.DeriveXPubs(signer.XPubs, path)
338 derivedPKs := chainkd.XPubKeys(derivedXPubs)
339 script, err := vmutil.P2SPMultiSigProgram(derivedPKs, signer.Quorum)
343 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness(script))
346 return nil, nil, errors.New("unsupport address type")
349 return txInput, sigInst, nil
352 // insertControlProgramDelayed takes a template builder and an account
353 // control program that hasn't been inserted to the database yet. It
354 // registers callbacks on the TemplateBuilder so that all of the template's
355 // account control programs are batch inserted if building the rest of
356 // the template is successful.
357 func (m *Manager) insertControlProgramDelayed(b *txbuilder.TemplateBuilder, acp *CtrlProgram) {
358 m.delayedACPsMu.Lock()
359 m.delayedACPs[b] = append(m.delayedACPs[b], acp)
360 m.delayedACPsMu.Unlock()
362 b.OnRollback(func() {
363 m.delayedACPsMu.Lock()
364 delete(m.delayedACPs, b)
365 m.delayedACPsMu.Unlock()
367 b.OnBuild(func() error {
368 m.delayedACPsMu.Lock()
369 acps := m.delayedACPs[b]
370 delete(m.delayedACPs, b)
371 m.delayedACPsMu.Unlock()
373 // Insert all of the account control programs at once.
377 return m.insertControlPrograms(acps...)