5 stdjson "encoding/json"
7 "github.com/vapor/blockchain/signers"
8 "github.com/vapor/blockchain/txbuilder"
9 "github.com/vapor/common"
10 "github.com/vapor/consensus"
11 "github.com/vapor/crypto/ed25519/chainkd"
12 "github.com/vapor/encoding/json"
13 "github.com/vapor/errors"
14 "github.com/vapor/protocol/bc"
15 "github.com/vapor/protocol/bc/types"
16 "github.com/vapor/protocol/vm/vmutil"
20 //chainTxUtxoNum maximum utxo quantity in a tx
22 //chainTxMergeGas chain tx gas
23 chainTxMergeGas = uint64(10000000)
26 //DecodeSpendAction unmarshal JSON-encoded data of spend action
27 func (m *Manager) DecodeSpendAction(data []byte) (txbuilder.Action, error) {
28 a := &spendAction{accounts: m}
29 return a, stdjson.Unmarshal(data, a)
32 type spendAction struct {
35 AccountID string `json:"account_id"`
36 UseUnconfirmed bool `json:"use_unconfirmed"`
39 func (a *spendAction) ActionType() string {
40 return "spend_account"
43 // MergeSpendAction merge common assetID and accountID spend action
44 func MergeSpendAction(actions []txbuilder.Action) []txbuilder.Action {
45 resultActions := []txbuilder.Action{}
46 spendActionMap := make(map[string]*spendAction)
48 for _, act := range actions {
49 switch act := act.(type) {
51 actionKey := act.AssetId.String() + act.AccountID
52 if tmpAct, ok := spendActionMap[actionKey]; ok {
53 tmpAct.Amount += act.Amount
54 tmpAct.UseUnconfirmed = tmpAct.UseUnconfirmed || act.UseUnconfirmed
56 spendActionMap[actionKey] = act
57 resultActions = append(resultActions, act)
60 resultActions = append(resultActions, act)
66 //calcMergeGas calculate the gas required that n utxos are merged into one
67 func calcMergeGas(num int) uint64 {
70 gas += chainTxMergeGas
71 num -= chainTxUtxoNum - 1
76 func (m *Manager) reserveBtmUtxoChain(builder *txbuilder.TemplateBuilder, accountID string, amount uint64, useUnconfirmed bool) ([]*UTXO, error) {
77 reservedAmount := uint64(0)
79 for gasAmount := uint64(0); reservedAmount < gasAmount+amount; gasAmount = calcMergeGas(len(utxos)) {
80 reserveAmount := amount + gasAmount - reservedAmount
81 res, err := m.utxoKeeper.Reserve(accountID, consensus.BTMAssetID, reserveAmount, useUnconfirmed, nil, builder.MaxTime())
86 builder.OnRollback(func() { m.utxoKeeper.Cancel(res.id) })
87 reservedAmount += reserveAmount + res.change
88 utxos = append(utxos, res.utxos[:]...)
93 func (m *Manager) BuildBtmTxChain(utxos []*UTXO, signer *signers.Signer) ([]*txbuilder.Template, *UTXO, error) {
95 return nil, nil, errors.New("mergeSpendActionUTXO utxos num 0")
98 tpls := []*txbuilder.Template{}
100 return tpls, utxos[len(utxos)-1], nil
103 acp, err := m.GetLocalCtrlProgramByAddress(utxos[0].Address)
108 buildAmount := uint64(0)
109 builder := &txbuilder.TemplateBuilder{}
110 for index := 0; index < len(utxos); index++ {
111 input, sigInst, err := UtxoToInputs(signer, utxos[index])
116 if err = builder.AddInput(input, sigInst); err != nil {
120 buildAmount += input.Amount()
121 if builder.InputCount() != chainTxUtxoNum && index != len(utxos)-1 {
125 outAmount := buildAmount - chainTxMergeGas
126 output := types.NewIntraChainOutput(*consensus.BTMAssetID, outAmount, acp.ControlProgram)
127 if err := builder.AddOutput(output); err != nil {
131 tpl, _, err := builder.Build()
136 bcOut, err := tpl.Transaction.IntraChainOutput(*tpl.Transaction.ResultIds[0])
141 utxos = append(utxos, &UTXO{
142 OutputID: *tpl.Transaction.ResultIds[0],
143 AssetID: *consensus.BTMAssetID,
145 ControlProgram: acp.ControlProgram,
146 SourceID: *bcOut.Source.Ref,
147 SourcePos: bcOut.Source.Position,
148 ControlProgramIndex: acp.KeyIndex,
149 Address: acp.Address,
153 tpls = append(tpls, tpl)
155 builder = &txbuilder.TemplateBuilder{}
156 if index == len(utxos)-2 {
160 return tpls, utxos[len(utxos)-1], nil
163 // SpendAccountChain build the spend action with auto merge utxo function
164 func SpendAccountChain(ctx context.Context, builder *txbuilder.TemplateBuilder, action txbuilder.Action) ([]*txbuilder.Template, error) {
165 act, ok := action.(*spendAction)
167 return nil, errors.New("fail to convert the spend action")
169 if *act.AssetId != *consensus.BTMAssetID {
170 return nil, errors.New("spend chain action only support BTM")
173 utxos, err := act.accounts.reserveBtmUtxoChain(builder, act.AccountID, act.Amount, act.UseUnconfirmed)
178 acct, err := act.accounts.FindByID(act.AccountID)
183 tpls, utxo, err := act.accounts.BuildBtmTxChain(utxos, acct.Signer)
188 input, sigInst, err := UtxoToInputs(acct.Signer, utxo)
193 if err := builder.AddInput(input, sigInst); err != nil {
197 if utxo.Amount > act.Amount {
198 if err = builder.AddOutput(types.NewIntraChainOutput(*consensus.BTMAssetID, utxo.Amount-act.Amount, utxo.ControlProgram)); err != nil {
199 return nil, errors.Wrap(err, "adding change output")
205 func (a *spendAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
207 if a.AccountID == "" {
208 missing = append(missing, "account_id")
210 if a.AssetId.IsZero() {
211 missing = append(missing, "asset_id")
213 if len(missing) > 0 {
214 return txbuilder.MissingFieldsError(missing...)
217 acct, err := a.accounts.FindByID(a.AccountID)
219 return errors.Wrap(err, "get account info")
222 res, err := a.accounts.utxoKeeper.Reserve(a.AccountID, a.AssetId, a.Amount, a.UseUnconfirmed, nil, b.MaxTime())
224 return errors.Wrap(err, "reserving utxos")
227 // Cancel the reservation if the build gets rolled back.
228 b.OnRollback(func() { a.accounts.utxoKeeper.Cancel(res.id) })
229 for _, r := range res.utxos {
230 txInput, sigInst, err := UtxoToInputs(acct.Signer, r)
232 return errors.Wrap(err, "creating inputs")
235 if err = b.AddInput(txInput, sigInst); err != nil {
236 return errors.Wrap(err, "adding inputs")
241 acp, err := a.accounts.CreateAddress(a.AccountID, true)
243 return errors.Wrap(err, "creating control program")
246 // Don't insert the control program until callbacks are executed.
247 a.accounts.insertControlProgramDelayed(b, acp)
248 if err = b.AddOutput(types.NewIntraChainOutput(*a.AssetId, res.change, acp.ControlProgram)); err != nil {
249 return errors.Wrap(err, "adding change output")
255 //DecodeSpendUTXOAction unmarshal JSON-encoded data of spend utxo action
256 func (m *Manager) DecodeSpendUTXOAction(data []byte) (txbuilder.Action, error) {
257 a := &spendUTXOAction{accounts: m}
258 return a, stdjson.Unmarshal(data, a)
261 type spendUTXOAction struct {
263 OutputID *bc.Hash `json:"output_id"`
264 UseUnconfirmed bool `json:"use_unconfirmed"`
265 Arguments []txbuilder.ContractArgument `json:"arguments"`
268 func (a *spendUTXOAction) ActionType() string {
269 return "spend_account_unspent_output"
272 func (a *spendUTXOAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
273 if a.OutputID == nil {
274 return txbuilder.MissingFieldsError("output_id")
277 res, err := a.accounts.utxoKeeper.ReserveParticular(*a.OutputID, a.UseUnconfirmed, b.MaxTime())
282 b.OnRollback(func() { a.accounts.utxoKeeper.Cancel(res.id) })
283 var accountSigner *signers.Signer
284 if len(res.utxos[0].AccountID) != 0 {
285 account, err := a.accounts.FindByID(res.utxos[0].AccountID)
290 accountSigner = account.Signer
293 txInput, sigInst, err := UtxoToInputs(accountSigner, res.utxos[0])
298 if a.Arguments == nil {
299 return b.AddInput(txInput, sigInst)
302 sigInst = &txbuilder.SigningInstruction{}
303 if err := txbuilder.AddContractArgs(sigInst, a.Arguments); err != nil {
307 return b.AddInput(txInput, sigInst)
310 // UtxoToInputs convert an utxo to the txinput
311 func UtxoToInputs(signer *signers.Signer, u *UTXO) (*types.TxInput, *txbuilder.SigningInstruction, error) {
312 txInput := &types.TxInput{}
314 txInput = types.NewSpendInput(nil, u.SourceID, u.AssetID, u.Amount, u.SourcePos, u.ControlProgram)
316 txInput = types.NewVetoInput(nil, u.SourceID, u.AssetID, u.Amount, u.SourcePos, u.ControlProgram, u.Vote)
318 sigInst := &txbuilder.SigningInstruction{}
320 return txInput, sigInst, nil
323 path, err := signers.Path(signer, signers.AccountKeySpace, u.Change, u.ControlProgramIndex)
328 sigInst.AddWitnessKeys(signer.XPubs, path, signer.Quorum)
329 return txInput, sigInst, nil
332 address, err := common.DecodeAddress(u.Address, &consensus.ActiveNetParams)
337 sigInst.AddRawWitnessKeys(signer.XPubs, path, signer.Quorum)
338 derivedXPubs := chainkd.DeriveXPubs(signer.XPubs, path)
340 switch address.(type) {
341 case *common.AddressWitnessPubKeyHash:
342 derivedPK := derivedXPubs[0].PublicKey()
343 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness([]byte(derivedPK)))
345 case *common.AddressWitnessScriptHash:
346 derivedPKs := chainkd.XPubKeys(derivedXPubs)
347 script, err := vmutil.P2SPMultiSigProgram(derivedPKs, signer.Quorum)
351 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness(script))
354 return nil, nil, errors.New("unsupport address type")
357 return txInput, sigInst, nil
360 // insertControlProgramDelayed takes a template builder and an account
361 // control program that hasn't been inserted to the database yet. It
362 // registers callbacks on the TemplateBuilder so that all of the template's
363 // account control programs are batch inserted if building the rest of
364 // the template is successful.
365 func (m *Manager) insertControlProgramDelayed(b *txbuilder.TemplateBuilder, acp *CtrlProgram) {
366 m.delayedACPsMu.Lock()
367 m.delayedACPs[b] = append(m.delayedACPs[b], acp)
368 m.delayedACPsMu.Unlock()
370 b.OnRollback(func() {
371 m.delayedACPsMu.Lock()
372 delete(m.delayedACPs, b)
373 m.delayedACPsMu.Unlock()
375 b.OnBuild(func() error {
376 m.delayedACPsMu.Lock()
377 acps := m.delayedACPs[b]
378 delete(m.delayedACPs, b)
379 m.delayedACPsMu.Unlock()
381 // Insert all of the account control programs at once.
385 return m.SaveControlPrograms(acps...)
389 //DecodeVetoAction unmarshal JSON-encoded data of spend action
390 func (m *Manager) DecodeVetoAction(data []byte) (txbuilder.Action, error) {
391 a := &vetoAction{accounts: m}
392 return a, stdjson.Unmarshal(data, a)
395 type vetoAction struct {
398 AccountID string `json:"account_id"`
399 Vote json.HexBytes `json:"vote"`
400 UseUnconfirmed bool `json:"use_unconfirmed"`
403 func (a *vetoAction) ActionType() string {
407 func (a *vetoAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
409 if a.AccountID == "" {
410 missing = append(missing, "account_id")
412 if a.AssetId.IsZero() {
413 missing = append(missing, "asset_id")
415 if len(missing) > 0 {
416 return txbuilder.MissingFieldsError(missing...)
419 acct, err := a.accounts.FindByID(a.AccountID)
421 return errors.Wrap(err, "get account info")
424 res, err := a.accounts.utxoKeeper.Reserve(a.AccountID, a.AssetId, a.Amount, a.UseUnconfirmed, a.Vote, b.MaxTime())
426 return errors.Wrap(err, "reserving utxos")
429 // Cancel the reservation if the build gets rolled back.
430 b.OnRollback(func() { a.accounts.utxoKeeper.Cancel(res.id) })
431 for _, r := range res.utxos {
432 txInput, sigInst, err := UtxoToInputs(acct.Signer, r)
434 return errors.Wrap(err, "creating inputs")
437 if err = b.AddInput(txInput, sigInst); err != nil {
438 return errors.Wrap(err, "adding inputs")
443 acp, err := a.accounts.CreateAddress(a.AccountID, true)
445 return errors.Wrap(err, "creating control program")
448 // Don't insert the control program until callbacks are executed.
449 a.accounts.insertControlProgramDelayed(b, acp)
450 if err = b.AddOutput(types.NewVoteOutput(*a.AssetId, res.change, acp.ControlProgram, a.Vote)); err != nil {
451 return errors.Wrap(err, "adding change voteOutput")