6 stdjson "encoding/json"
8 "github.com/bytom/vapor/blockchain/signers"
9 "github.com/bytom/vapor/blockchain/txbuilder"
10 "github.com/bytom/vapor/common"
11 "github.com/bytom/vapor/consensus"
12 "github.com/bytom/vapor/crypto/ed25519/chainkd"
13 "github.com/bytom/vapor/encoding/json"
14 "github.com/bytom/vapor/errors"
15 "github.com/bytom/vapor/protocol/bc"
16 "github.com/bytom/vapor/protocol/bc/types"
17 "github.com/bytom/vapor/protocol/vm/vmutil"
21 //chainTxUtxoNum maximum utxo quantity in a tx
23 //chainTxMergeGas chain tx gas
24 chainTxMergeGas = uint64(0)
27 //DecodeSpendAction unmarshal JSON-encoded data of spend action
28 func (m *Manager) DecodeSpendAction(data []byte) (txbuilder.Action, error) {
29 a := &spendAction{accounts: m}
30 return a, stdjson.Unmarshal(data, a)
33 type spendAction struct {
36 AccountID string `json:"account_id"`
37 UseUnconfirmed bool `json:"use_unconfirmed"`
40 func (a *spendAction) ActionType() string {
41 return "spend_account"
44 // MergeSpendAction merge common assetID and accountID spend action
45 func MergeSpendAction(actions []txbuilder.Action) []txbuilder.Action {
46 resultActions := []txbuilder.Action{}
47 spendActionMap := make(map[string]*spendAction)
49 for _, act := range actions {
50 switch act := act.(type) {
52 actionKey := act.AssetId.String() + act.AccountID
53 if tmpAct, ok := spendActionMap[actionKey]; ok {
54 tmpAct.Amount += act.Amount
55 tmpAct.UseUnconfirmed = tmpAct.UseUnconfirmed || act.UseUnconfirmed
57 spendActionMap[actionKey] = act
58 resultActions = append(resultActions, act)
61 resultActions = append(resultActions, act)
67 //calcMergeGas calculate the gas required that n utxos are merged into one
68 func calcMergeGas(num int) uint64 {
71 gas += chainTxMergeGas
72 num -= chainTxUtxoNum - 1
77 func (m *Manager) reserveBtmUtxoChain(builder *txbuilder.TemplateBuilder, accountID string, amount uint64, useUnconfirmed bool) ([]*UTXO, error) {
78 reservedAmount := uint64(0)
80 for gasAmount := uint64(0); reservedAmount < gasAmount+amount; gasAmount = calcMergeGas(len(utxos)) {
81 reserveAmount := amount + gasAmount - reservedAmount
82 res, err := m.utxoKeeper.Reserve(accountID, consensus.BTMAssetID, reserveAmount, useUnconfirmed, nil, builder.MaxTime())
87 builder.OnRollback(func() { m.utxoKeeper.Cancel(res.id) })
88 reservedAmount += reserveAmount + res.change
89 utxos = append(utxos, res.utxos[:]...)
94 func (m *Manager) BuildBtmTxChain(utxos []*UTXO, signer *signers.Signer) ([]*txbuilder.Template, *UTXO, error) {
96 return nil, nil, errors.New("mergeSpendActionUTXO utxos num 0")
99 tpls := []*txbuilder.Template{}
101 return tpls, utxos[len(utxos)-1], nil
104 acp, err := m.GetLocalCtrlProgramByAddress(utxos[0].Address)
109 buildAmount := uint64(0)
110 builder := &txbuilder.TemplateBuilder{}
111 for index := 0; index < len(utxos); index++ {
112 input, sigInst, err := UtxoToInputs(signer, utxos[index])
117 if err = builder.AddInput(input, sigInst); err != nil {
121 buildAmount += input.Amount()
122 if builder.InputCount() != chainTxUtxoNum && index != len(utxos)-1 {
126 outAmount := buildAmount - chainTxMergeGas
127 output := types.NewIntraChainOutput(*consensus.BTMAssetID, outAmount, acp.ControlProgram)
128 if err := builder.AddOutput(output); err != nil {
132 tpl, _, err := builder.Build()
137 bcOut, err := tpl.Transaction.IntraChainOutput(*tpl.Transaction.ResultIds[0])
142 utxos = append(utxos, &UTXO{
143 OutputID: *tpl.Transaction.ResultIds[0],
144 AssetID: *consensus.BTMAssetID,
146 ControlProgram: acp.ControlProgram,
147 SourceID: *bcOut.Source.Ref,
148 SourcePos: bcOut.Source.Position,
149 ControlProgramIndex: acp.KeyIndex,
150 Address: acp.Address,
154 tpls = append(tpls, tpl)
156 builder = &txbuilder.TemplateBuilder{}
157 if index == len(utxos)-2 {
161 return tpls, utxos[len(utxos)-1], nil
164 // SpendAccountChain build the spend action with auto merge utxo function
165 func SpendAccountChain(ctx context.Context, builder *txbuilder.TemplateBuilder, action txbuilder.Action) ([]*txbuilder.Template, error) {
166 act, ok := action.(*spendAction)
168 return nil, errors.New("fail to convert the spend action")
171 if *act.AssetId != *consensus.BTMAssetID {
172 return nil, errors.New("spend chain action only support BTM")
175 utxos, err := act.accounts.reserveBtmUtxoChain(builder, act.AccountID, act.Amount, act.UseUnconfirmed)
180 acct, err := act.accounts.FindByID(act.AccountID)
185 tpls, utxo, err := act.accounts.BuildBtmTxChain(utxos, acct.Signer)
190 input, sigInst, err := UtxoToInputs(acct.Signer, utxo)
195 if err := builder.AddInput(input, sigInst); err != nil {
199 if utxo.Amount > act.Amount {
200 if err = builder.AddOutput(types.NewIntraChainOutput(*consensus.BTMAssetID, utxo.Amount-act.Amount, utxo.ControlProgram)); err != nil {
201 return nil, errors.Wrap(err, "adding change output")
207 func (a *spendAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
209 if a.AccountID == "" {
210 missing = append(missing, "account_id")
212 if a.AssetId.IsZero() {
213 missing = append(missing, "asset_id")
215 if len(missing) > 0 {
216 return txbuilder.MissingFieldsError(missing...)
219 acct, err := a.accounts.FindByID(a.AccountID)
221 return errors.Wrap(err, "get account info")
224 res, err := a.accounts.utxoKeeper.Reserve(a.AccountID, a.AssetId, a.Amount, a.UseUnconfirmed, nil, b.MaxTime())
226 return errors.Wrap(err, "reserving utxos")
229 // Cancel the reservation if the build gets rolled back.
230 b.OnRollback(func() { a.accounts.utxoKeeper.Cancel(res.id) })
231 for _, r := range res.utxos {
232 txInput, sigInst, err := UtxoToInputs(acct.Signer, r)
234 return errors.Wrap(err, "creating inputs")
237 if err = b.AddInput(txInput, sigInst); err != nil {
238 return errors.Wrap(err, "adding inputs")
243 acp, err := a.accounts.CreateAddress(a.AccountID, true)
245 return errors.Wrap(err, "creating control program")
248 // Don't insert the control program until callbacks are executed.
249 a.accounts.insertControlProgramDelayed(b, acp)
250 if err = b.AddOutput(types.NewIntraChainOutput(*a.AssetId, res.change, acp.ControlProgram)); err != nil {
251 return errors.Wrap(err, "adding change output")
257 //DecodeSpendUTXOAction unmarshal JSON-encoded data of spend utxo action
258 func (m *Manager) DecodeSpendUTXOAction(data []byte) (txbuilder.Action, error) {
259 a := &spendUTXOAction{accounts: m}
260 return a, stdjson.Unmarshal(data, a)
263 type spendUTXOAction struct {
265 OutputID *bc.Hash `json:"output_id"`
266 UseUnconfirmed bool `json:"use_unconfirmed"`
267 Arguments []txbuilder.ContractArgument `json:"arguments"`
270 func (a *spendUTXOAction) ActionType() string {
271 return "spend_account_unspent_output"
274 func (a *spendUTXOAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
275 if a.OutputID == nil {
276 return txbuilder.MissingFieldsError("output_id")
279 res, err := a.accounts.utxoKeeper.ReserveParticular(*a.OutputID, a.UseUnconfirmed, b.MaxTime())
284 b.OnRollback(func() { a.accounts.utxoKeeper.Cancel(res.id) })
285 var accountSigner *signers.Signer
286 if len(res.utxos[0].AccountID) != 0 {
287 account, err := a.accounts.FindByID(res.utxos[0].AccountID)
292 accountSigner = account.Signer
295 txInput, sigInst, err := UtxoToInputs(accountSigner, res.utxos[0])
300 if a.Arguments == nil {
301 return b.AddInput(txInput, sigInst)
304 sigInst = &txbuilder.SigningInstruction{}
305 if err := txbuilder.AddContractArgs(sigInst, a.Arguments); err != nil {
309 return b.AddInput(txInput, sigInst)
312 // UtxoToInputs convert an utxo to the txinput
313 func UtxoToInputs(signer *signers.Signer, u *UTXO) (*types.TxInput, *txbuilder.SigningInstruction, error) {
314 txInput := &types.TxInput{}
316 txInput = types.NewSpendInput(nil, u.SourceID, u.AssetID, u.Amount, u.SourcePos, u.ControlProgram)
318 txInput = types.NewVetoInput(nil, u.SourceID, u.AssetID, u.Amount, u.SourcePos, u.ControlProgram, u.Vote)
320 sigInst := &txbuilder.SigningInstruction{}
322 return txInput, sigInst, nil
325 path, err := signers.Path(signer, signers.AccountKeySpace, u.Change, u.ControlProgramIndex)
331 sigInst.AddWitnessKeys(signer.XPubs, path, signer.Quorum)
332 return txInput, sigInst, nil
335 address, err := common.DecodeAddress(u.Address, &consensus.ActiveNetParams)
340 sigInst.AddRawWitnessKeys(signer.XPubs, path, signer.Quorum)
341 derivedXPubs := chainkd.DeriveXPubs(signer.XPubs, path)
343 switch address.(type) {
344 case *common.AddressWitnessPubKeyHash:
345 derivedPK := derivedXPubs[0].PublicKey()
346 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness([]byte(derivedPK)))
348 case *common.AddressWitnessScriptHash:
349 derivedPKs := chainkd.XPubKeys(derivedXPubs)
350 script, err := vmutil.P2SPMultiSigProgram(derivedPKs, signer.Quorum)
354 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness(script))
357 return nil, nil, errors.New("unsupport address type")
360 return txInput, sigInst, nil
363 // insertControlProgramDelayed takes a template builder and an account
364 // control program that hasn't been inserted to the database yet. It
365 // registers callbacks on the TemplateBuilder so that all of the template's
366 // account control programs are batch inserted if building the rest of
367 // the template is successful.
368 func (m *Manager) insertControlProgramDelayed(b *txbuilder.TemplateBuilder, acp *CtrlProgram) {
369 m.delayedACPsMu.Lock()
370 m.delayedACPs[b] = append(m.delayedACPs[b], acp)
371 m.delayedACPsMu.Unlock()
373 b.OnRollback(func() {
374 m.delayedACPsMu.Lock()
375 delete(m.delayedACPs, b)
376 m.delayedACPsMu.Unlock()
378 b.OnBuild(func() error {
379 m.delayedACPsMu.Lock()
380 acps := m.delayedACPs[b]
381 delete(m.delayedACPs, b)
382 m.delayedACPsMu.Unlock()
384 // Insert all of the account control programs at once.
389 return m.SaveControlPrograms(acps...)
393 //DecodeVetoAction unmarshal JSON-encoded data of spend action
394 func (m *Manager) DecodeVetoAction(data []byte) (txbuilder.Action, error) {
395 a := &vetoAction{accounts: m}
396 return a, stdjson.Unmarshal(data, a)
399 type vetoAction struct {
402 AccountID string `json:"account_id"`
403 Vote json.HexBytes `json:"vote"`
404 UseUnconfirmed bool `json:"use_unconfirmed"`
407 func (a *vetoAction) ActionType() string {
411 func (a *vetoAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
413 if a.AccountID == "" {
414 missing = append(missing, "account_id")
416 if a.AssetId.IsZero() {
417 missing = append(missing, "asset_id")
419 if len(missing) > 0 {
420 return txbuilder.MissingFieldsError(missing...)
423 acct, err := a.accounts.FindByID(a.AccountID)
425 return errors.Wrap(err, "get account info")
428 res, err := a.accounts.utxoKeeper.Reserve(a.AccountID, a.AssetId, a.Amount, a.UseUnconfirmed, a.Vote, b.MaxTime())
430 return errors.Wrap(err, "reserving utxos")
433 // Cancel the reservation if the build gets rolled back.
434 b.OnRollback(func() { a.accounts.utxoKeeper.Cancel(res.id) })
435 for _, r := range res.utxos {
436 txInput, sigInst, err := UtxoToInputs(acct.Signer, r)
438 return errors.Wrap(err, "creating inputs")
441 if err = b.AddInput(txInput, sigInst); err != nil {
442 return errors.Wrap(err, "adding inputs")
447 acp, err := a.accounts.CreateAddress(a.AccountID, true)
449 return errors.Wrap(err, "creating control program")
452 // Don't insert the control program until callbacks are executed.
453 a.accounts.insertControlProgramDelayed(b, acp)
454 if err = b.AddOutput(types.NewVoteOutput(*a.AssetId, res.change, acp.ControlProgram, a.Vote)); err != nil {
455 return errors.Wrap(err, "adding change voteOutput")
461 func (m *Manager) DecodeCrossInAction(data []byte) (txbuilder.Action, error) {
462 a := &crossInAction{accounts: m}
463 err := stdjson.Unmarshal(data, a)
467 type crossInAction struct {
472 func (a *crossInAction) Build(ctx context.Context, builder *txbuilder.TemplateBuilder) error {
473 if a.AssetId.IsZero() {
474 return txbuilder.MissingFieldsError("asset_id")
477 asset, err := a.accounts.store.GetAssetByID(a.AssetId)
483 if _, err := rand.Read(nonce[:]); err != nil {
487 txin := types.NewCrossChainInput(nil, bc.NewHash(nonce), *a.AssetId, a.Amount, 1, asset.VMVersion, asset.RawDefinitionByte, asset.IssuanceProgram)
488 tplIn := &txbuilder.SigningInstruction{}
489 path := signers.GetBip0032Path(asset.Signer, signers.AssetKeySpace)
490 tplIn.AddRawWitnessKeys(asset.Signer.XPubs, path, asset.Signer.Quorum)
491 return builder.AddInput(txin, tplIn)
494 func (a *crossInAction) ActionType() string {
495 return "cross_chain_in"