8 "github.com/vapor/blockchain/signers"
9 "github.com/vapor/blockchain/txbuilder"
10 "github.com/vapor/common"
11 "github.com/vapor/consensus"
12 "github.com/vapor/crypto/ed25519/chainkd"
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"
17 "github.com/vapor/testutil"
21 //chainTxUtxoNum maximum utxo quantity in a tx
23 //chainTxMergeGas chain tx gas
24 chainTxMergeGas = uint64(10000000)
27 // DecodeCrossInAction convert input data to action struct
28 func (m *Manager) DecodeCrossInAction(data []byte) (txbuilder.Action, error) {
29 a := new(crossInAction)
30 err := json.Unmarshal(data, a)
34 type crossInAction struct {
36 SourceID string `json:"source_id"` // AnnotatedUTXO
37 SourcePos uint64 `json:"source_pos"`
40 func (a *crossInAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
42 if a.AssetId.IsZero() {
43 missing = append(missing, "asset_id")
46 missing = append(missing, "amount")
49 return txbuilder.MissingFieldsError(missing...)
52 sourceID := testutil.MustDecodeHash(a.SourceID)
53 // in := types.NewCrossChainInput(arguments [][]byte, sourceID bc.Hash, assetID bc.AssetID, amount, sourcePos uint64, controlProgram, assetDefinition []byte)
54 in := types.NewCrossChainInput(nil, sourceID, *a.AssetId, a.Amount, a.SourcePos, nil, nil)
55 return b.AddInput(in, nil)
58 func (a *crossInAction) ActionType() string {
59 return "cross_chain_in"
62 //DecodeSpendAction unmarshal JSON-encoded data of spend action
63 func (m *Manager) DecodeSpendAction(data []byte) (txbuilder.Action, error) {
64 a := &spendAction{accounts: m}
65 return a, json.Unmarshal(data, a)
68 type spendAction struct {
71 AccountID string `json:"account_id"`
72 UseUnconfirmed bool `json:"use_unconfirmed"`
75 func (a *spendAction) ActionType() string {
76 return "spend_account"
79 // MergeSpendAction merge common assetID and accountID spend action
80 func MergeSpendAction(actions []txbuilder.Action) []txbuilder.Action {
81 resultActions := []txbuilder.Action{}
82 spendActionMap := make(map[string]*spendAction)
84 for _, act := range actions {
85 switch act := act.(type) {
87 actionKey := act.AssetId.String() + act.AccountID
88 if tmpAct, ok := spendActionMap[actionKey]; ok {
89 tmpAct.Amount += act.Amount
90 tmpAct.UseUnconfirmed = tmpAct.UseUnconfirmed || act.UseUnconfirmed
92 spendActionMap[actionKey] = act
93 resultActions = append(resultActions, act)
96 resultActions = append(resultActions, act)
102 //calcMergeGas calculate the gas required that n utxos are merged into one
103 func calcMergeGas(num int) uint64 {
106 gas += chainTxMergeGas
107 num -= chainTxUtxoNum - 1
112 func (m *Manager) reserveBtmUtxoChain(builder *txbuilder.TemplateBuilder, accountID string, amount uint64, useUnconfirmed bool) ([]*UTXO, error) {
113 reservedAmount := uint64(0)
115 for gasAmount := uint64(0); reservedAmount < gasAmount+amount; gasAmount = calcMergeGas(len(utxos)) {
116 reserveAmount := amount + gasAmount - reservedAmount
117 res, err := m.utxoKeeper.Reserve(accountID, consensus.BTMAssetID, reserveAmount, useUnconfirmed, builder.MaxTime())
122 builder.OnRollback(func() { m.utxoKeeper.Cancel(res.id) })
123 reservedAmount += reserveAmount + res.change
124 utxos = append(utxos, res.utxos[:]...)
129 func (m *Manager) buildBtmTxChain(utxos []*UTXO, signer *signers.Signer) ([]*txbuilder.Template, *UTXO, error) {
131 return nil, nil, errors.New("mergeSpendActionUTXO utxos num 0")
134 tpls := []*txbuilder.Template{}
136 return tpls, utxos[len(utxos)-1], nil
139 acp, err := m.GetLocalCtrlProgramByAddress(utxos[0].Address)
144 buildAmount := uint64(0)
145 builder := &txbuilder.TemplateBuilder{}
146 for index := 0; index < len(utxos); index++ {
147 input, sigInst, err := UtxoToInputs(signer, utxos[index])
152 if err = builder.AddInput(input, sigInst); err != nil {
156 buildAmount += input.Amount()
157 if builder.InputCount() != chainTxUtxoNum && index != len(utxos)-1 {
161 outAmount := buildAmount - chainTxMergeGas
162 output := types.NewIntraChainOutput(*consensus.BTMAssetID, outAmount, acp.ControlProgram)
163 if err := builder.AddOutput(output); err != nil {
167 tpl, _, err := builder.Build()
172 bcOut, err := tpl.Transaction.IntraChainOutput(*tpl.Transaction.ResultIds[0])
177 utxos = append(utxos, &UTXO{
178 OutputID: *tpl.Transaction.ResultIds[0],
179 AssetID: *consensus.BTMAssetID,
181 ControlProgram: acp.ControlProgram,
182 SourceID: *bcOut.Source.Ref,
183 SourcePos: bcOut.Source.Position,
184 ControlProgramIndex: acp.KeyIndex,
185 Address: acp.Address,
189 tpls = append(tpls, tpl)
191 builder = &txbuilder.TemplateBuilder{}
192 if index == len(utxos)-2 {
196 return tpls, utxos[len(utxos)-1], nil
199 // SpendAccountChain build the spend action with auto merge utxo function
200 func SpendAccountChain(ctx context.Context, builder *txbuilder.TemplateBuilder, action txbuilder.Action) ([]*txbuilder.Template, error) {
201 act, ok := action.(*spendAction)
203 return nil, errors.New("fail to convert the spend action")
205 if *act.AssetId != *consensus.BTMAssetID {
206 return nil, errors.New("spend chain action only support BTM")
209 utxos, err := act.accounts.reserveBtmUtxoChain(builder, act.AccountID, act.Amount, act.UseUnconfirmed)
214 acct, err := act.accounts.FindByID(act.AccountID)
219 tpls, utxo, err := act.accounts.buildBtmTxChain(utxos, acct.Signer)
224 input, sigInst, err := UtxoToInputs(acct.Signer, utxo)
229 if err := builder.AddInput(input, sigInst); err != nil {
233 if utxo.Amount > act.Amount {
234 if err = builder.AddOutput(types.NewIntraChainOutput(*consensus.BTMAssetID, utxo.Amount-act.Amount, utxo.ControlProgram)); err != nil {
235 return nil, errors.Wrap(err, "adding change output")
241 func (a *spendAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
243 if a.AccountID == "" {
244 missing = append(missing, "account_id")
246 if a.AssetId.IsZero() {
247 missing = append(missing, "asset_id")
249 if len(missing) > 0 {
250 return txbuilder.MissingFieldsError(missing...)
253 acct, err := a.accounts.FindByID(a.AccountID)
255 return errors.Wrap(err, "get account info")
258 res, err := a.accounts.utxoKeeper.Reserve(a.AccountID, a.AssetId, a.Amount, a.UseUnconfirmed, b.MaxTime())
260 return errors.Wrap(err, "reserving utxos")
263 // Cancel the reservation if the build gets rolled back.
264 b.OnRollback(func() { a.accounts.utxoKeeper.Cancel(res.id) })
265 for _, r := range res.utxos {
266 txInput, sigInst, err := UtxoToInputs(acct.Signer, r)
268 return errors.Wrap(err, "creating inputs")
271 if err = b.AddInput(txInput, sigInst); err != nil {
272 return errors.Wrap(err, "adding inputs")
277 acp, err := a.accounts.CreateAddress(a.AccountID, true)
279 return errors.Wrap(err, "creating control program")
282 // Don't insert the control program until callbacks are executed.
283 a.accounts.insertControlProgramDelayed(b, acp)
284 if err = b.AddOutput(types.NewIntraChainOutput(*a.AssetId, res.change, acp.ControlProgram)); err != nil {
285 return errors.Wrap(err, "adding change output")
291 //DecodeSpendUTXOAction unmarshal JSON-encoded data of spend utxo action
292 func (m *Manager) DecodeSpendUTXOAction(data []byte) (txbuilder.Action, error) {
293 a := &spendUTXOAction{accounts: m}
294 return a, json.Unmarshal(data, a)
297 type spendUTXOAction struct {
299 OutputID *bc.Hash `json:"output_id"`
300 UseUnconfirmed bool `json:"use_unconfirmed"`
301 Arguments []txbuilder.ContractArgument `json:"arguments"`
304 func (a *spendUTXOAction) ActionType() string {
305 return "spend_account_unspent_output"
308 func (a *spendUTXOAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
309 if a.OutputID == nil {
310 return txbuilder.MissingFieldsError("output_id")
313 res, err := a.accounts.utxoKeeper.ReserveParticular(*a.OutputID, a.UseUnconfirmed, b.MaxTime())
318 b.OnRollback(func() { a.accounts.utxoKeeper.Cancel(res.id) })
319 var accountSigner *signers.Signer
320 if len(res.utxos[0].AccountID) != 0 {
321 account, err := a.accounts.FindByID(res.utxos[0].AccountID)
326 accountSigner = account.Signer
329 txInput, sigInst, err := UtxoToInputs(accountSigner, res.utxos[0])
334 if a.Arguments == nil {
335 return b.AddInput(txInput, sigInst)
338 sigInst = &txbuilder.SigningInstruction{}
339 if err := txbuilder.AddContractArgs(sigInst, a.Arguments); err != nil {
343 return b.AddInput(txInput, sigInst)
346 // UtxoToInputs convert an utxo to the txinput
347 func UtxoToInputs(signer *signers.Signer, u *UTXO) (*types.TxInput, *txbuilder.SigningInstruction, error) {
348 txInput := types.NewSpendInput(nil, u.SourceID, u.AssetID, u.Amount, u.SourcePos, u.ControlProgram)
349 sigInst := &txbuilder.SigningInstruction{}
351 return txInput, sigInst, nil
354 path, err := signers.Path(signer, signers.AccountKeySpace, u.Change, u.ControlProgramIndex)
359 sigInst.AddWitnessKeys(signer.XPubs, path, signer.Quorum)
360 return txInput, sigInst, nil
363 address, err := common.DecodeAddress(u.Address, &consensus.ActiveNetParams)
368 sigInst.AddRawWitnessKeys(signer.XPubs, path, signer.Quorum)
369 derivedXPubs := chainkd.DeriveXPubs(signer.XPubs, path)
371 switch address.(type) {
372 case *common.AddressWitnessPubKeyHash:
373 derivedPK := derivedXPubs[0].PublicKey()
374 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness([]byte(derivedPK)))
376 case *common.AddressWitnessScriptHash:
377 derivedPKs := chainkd.XPubKeys(derivedXPubs)
378 script, err := vmutil.P2SPMultiSigProgram(derivedPKs, signer.Quorum)
382 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness(script))
385 return nil, nil, errors.New("unsupport address type")
388 return txInput, sigInst, nil
391 // insertControlProgramDelayed takes a template builder and an account
392 // control program that hasn't been inserted to the database yet. It
393 // registers callbacks on the TemplateBuilder so that all of the template's
394 // account control programs are batch inserted if building the rest of
395 // the template is successful.
396 func (m *Manager) insertControlProgramDelayed(b *txbuilder.TemplateBuilder, acp *CtrlProgram) {
397 m.delayedACPsMu.Lock()
398 m.delayedACPs[b] = append(m.delayedACPs[b], acp)
399 m.delayedACPsMu.Unlock()
401 b.OnRollback(func() {
402 m.delayedACPsMu.Lock()
403 delete(m.delayedACPs, b)
404 m.delayedACPsMu.Unlock()
406 b.OnBuild(func() error {
407 m.delayedACPsMu.Lock()
408 acps := m.delayedACPs[b]
409 delete(m.delayedACPs, b)
410 m.delayedACPsMu.Unlock()
412 // Insert all of the account control programs at once.
416 return m.SaveControlPrograms(acps...)