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/errors"
13 "github.com/vapor/protocol/bc"
14 "github.com/vapor/protocol/bc/types"
15 "github.com/vapor/protocol/vm/vmutil"
19 //chainTxUtxoNum maximum utxo quantity in a tx
21 //chainTxMergeGas chain tx gas
22 chainTxMergeGas = uint64(10000000)
25 // DecodeCrossInAction convert input data to action struct
26 func (m *Manager) DecodeCrossInAction(data []byte) (Action, error) {
27 a := new(crossInAction)
28 err := stdjson.Unmarshal(data, a)
32 type crossInAction struct {
34 // Address string `json:"address"`
37 func (a *crossInAction) Build(ctx context.Context, b *TemplateBuilder) error {
39 // if a.Address == "" {
40 // missing = append(missing, "address")
42 if a.AssetId.IsZero() {
43 missing = append(missing, "asset_id")
46 missing = append(missing, "amount")
49 return MissingFieldsError(missing...)
52 // address, err := common.DecodeAddress(a.Address, &consensus.MainNetParams)
57 // redeemContract := address.ScriptAddress()
58 // program := []byte{}
59 // switch address.(type) {
60 // case *common.AddressWitnessPubKeyHash:
61 // program, err = vmutil.P2WPKHProgram(redeemContract)
62 // case *common.AddressWitnessScriptHash:
63 // program, err = vmutil.P2WSHProgram(redeemContract)
65 // return errors.New("unsupport address type")
71 // out := types.NewCrossChainOutput(*a.AssetId, a.Amount, program)
72 // in := types.NewCrossChainInput(arguments [][]byte, sourceID bc.Hash, assetID bc.AssetID, amount, sourcePos uint64, controlProgram, assetDefinition []byte)
73 in := types.NewCrossChainInput(nil, bc.Hash{}, bc.AssetID{}, 0, 0, nil, nil)
77 func (a *crossInAction) ActionType() string {
78 return "cross_chain_in"
81 //DecodeSpendAction unmarshal JSON-encoded data of spend action
82 func (m *Manager) DecodeSpendAction(data []byte) (txbuilder.Action, error) {
83 a := &spendAction{accounts: m}
84 return a, json.Unmarshal(data, a)
87 type spendAction struct {
90 AccountID string `json:"account_id"`
91 UseUnconfirmed bool `json:"use_unconfirmed"`
94 func (a *spendAction) ActionType() string {
95 return "spend_account"
98 // MergeSpendAction merge common assetID and accountID spend action
99 func MergeSpendAction(actions []txbuilder.Action) []txbuilder.Action {
100 resultActions := []txbuilder.Action{}
101 spendActionMap := make(map[string]*spendAction)
103 for _, act := range actions {
104 switch act := act.(type) {
106 actionKey := act.AssetId.String() + act.AccountID
107 if tmpAct, ok := spendActionMap[actionKey]; ok {
108 tmpAct.Amount += act.Amount
109 tmpAct.UseUnconfirmed = tmpAct.UseUnconfirmed || act.UseUnconfirmed
111 spendActionMap[actionKey] = act
112 resultActions = append(resultActions, act)
115 resultActions = append(resultActions, act)
121 //calcMergeGas calculate the gas required that n utxos are merged into one
122 func calcMergeGas(num int) uint64 {
125 gas += chainTxMergeGas
126 num -= chainTxUtxoNum - 1
131 func (m *Manager) reserveBtmUtxoChain(builder *txbuilder.TemplateBuilder, accountID string, amount uint64, useUnconfirmed bool) ([]*UTXO, error) {
132 reservedAmount := uint64(0)
134 for gasAmount := uint64(0); reservedAmount < gasAmount+amount; gasAmount = calcMergeGas(len(utxos)) {
135 reserveAmount := amount + gasAmount - reservedAmount
136 res, err := m.utxoKeeper.Reserve(accountID, consensus.BTMAssetID, reserveAmount, useUnconfirmed, builder.MaxTime())
141 builder.OnRollback(func() { m.utxoKeeper.Cancel(res.id) })
142 reservedAmount += reserveAmount + res.change
143 utxos = append(utxos, res.utxos[:]...)
148 func (m *Manager) buildBtmTxChain(utxos []*UTXO, signer *signers.Signer) ([]*txbuilder.Template, *UTXO, error) {
150 return nil, nil, errors.New("mergeSpendActionUTXO utxos num 0")
153 tpls := []*txbuilder.Template{}
155 return tpls, utxos[len(utxos)-1], nil
158 acp, err := m.GetLocalCtrlProgramByAddress(utxos[0].Address)
163 buildAmount := uint64(0)
164 builder := &txbuilder.TemplateBuilder{}
165 for index := 0; index < len(utxos); index++ {
166 input, sigInst, err := UtxoToInputs(signer, utxos[index])
171 if err = builder.AddInput(input, sigInst); err != nil {
175 buildAmount += input.Amount()
176 if builder.InputCount() != chainTxUtxoNum && index != len(utxos)-1 {
180 outAmount := buildAmount - chainTxMergeGas
181 output := types.NewIntraChainOutput(*consensus.BTMAssetID, outAmount, acp.ControlProgram)
182 if err := builder.AddOutput(output); err != nil {
186 tpl, _, err := builder.Build()
191 bcOut, err := tpl.Transaction.IntraChainOutput(*tpl.Transaction.ResultIds[0])
196 utxos = append(utxos, &UTXO{
197 OutputID: *tpl.Transaction.ResultIds[0],
198 AssetID: *consensus.BTMAssetID,
200 ControlProgram: acp.ControlProgram,
201 SourceID: *bcOut.Source.Ref,
202 SourcePos: bcOut.Source.Position,
203 ControlProgramIndex: acp.KeyIndex,
204 Address: acp.Address,
208 tpls = append(tpls, tpl)
210 builder = &txbuilder.TemplateBuilder{}
211 if index == len(utxos)-2 {
215 return tpls, utxos[len(utxos)-1], nil
218 // SpendAccountChain build the spend action with auto merge utxo function
219 func SpendAccountChain(ctx context.Context, builder *txbuilder.TemplateBuilder, action txbuilder.Action) ([]*txbuilder.Template, error) {
220 act, ok := action.(*spendAction)
222 return nil, errors.New("fail to convert the spend action")
224 if *act.AssetId != *consensus.BTMAssetID {
225 return nil, errors.New("spend chain action only support BTM")
228 utxos, err := act.accounts.reserveBtmUtxoChain(builder, act.AccountID, act.Amount, act.UseUnconfirmed)
233 acct, err := act.accounts.FindByID(act.AccountID)
238 tpls, utxo, err := act.accounts.buildBtmTxChain(utxos, acct.Signer)
243 input, sigInst, err := UtxoToInputs(acct.Signer, utxo)
248 if err := builder.AddInput(input, sigInst); err != nil {
252 if utxo.Amount > act.Amount {
253 if err = builder.AddOutput(types.NewIntraChainOutput(*consensus.BTMAssetID, utxo.Amount-act.Amount, utxo.ControlProgram)); err != nil {
254 return nil, errors.Wrap(err, "adding change output")
260 func (a *spendAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
262 if a.AccountID == "" {
263 missing = append(missing, "account_id")
265 if a.AssetId.IsZero() {
266 missing = append(missing, "asset_id")
268 if len(missing) > 0 {
269 return txbuilder.MissingFieldsError(missing...)
272 acct, err := a.accounts.FindByID(a.AccountID)
274 return errors.Wrap(err, "get account info")
277 res, err := a.accounts.utxoKeeper.Reserve(a.AccountID, a.AssetId, a.Amount, a.UseUnconfirmed, b.MaxTime())
279 return errors.Wrap(err, "reserving utxos")
282 // Cancel the reservation if the build gets rolled back.
283 b.OnRollback(func() { a.accounts.utxoKeeper.Cancel(res.id) })
284 for _, r := range res.utxos {
285 txInput, sigInst, err := UtxoToInputs(acct.Signer, r)
287 return errors.Wrap(err, "creating inputs")
290 if err = b.AddInput(txInput, sigInst); err != nil {
291 return errors.Wrap(err, "adding inputs")
296 acp, err := a.accounts.CreateAddress(a.AccountID, true)
298 return errors.Wrap(err, "creating control program")
301 // Don't insert the control program until callbacks are executed.
302 a.accounts.insertControlProgramDelayed(b, acp)
303 if err = b.AddOutput(types.NewIntraChainOutput(*a.AssetId, res.change, acp.ControlProgram)); err != nil {
304 return errors.Wrap(err, "adding change output")
310 //DecodeSpendUTXOAction unmarshal JSON-encoded data of spend utxo action
311 func (m *Manager) DecodeSpendUTXOAction(data []byte) (txbuilder.Action, error) {
312 a := &spendUTXOAction{accounts: m}
313 return a, json.Unmarshal(data, a)
316 type spendUTXOAction struct {
318 OutputID *bc.Hash `json:"output_id"`
319 UseUnconfirmed bool `json:"use_unconfirmed"`
320 Arguments []txbuilder.ContractArgument `json:"arguments"`
323 func (a *spendUTXOAction) ActionType() string {
324 return "spend_account_unspent_output"
327 func (a *spendUTXOAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
328 if a.OutputID == nil {
329 return txbuilder.MissingFieldsError("output_id")
332 res, err := a.accounts.utxoKeeper.ReserveParticular(*a.OutputID, a.UseUnconfirmed, b.MaxTime())
337 b.OnRollback(func() { a.accounts.utxoKeeper.Cancel(res.id) })
338 var accountSigner *signers.Signer
339 if len(res.utxos[0].AccountID) != 0 {
340 account, err := a.accounts.FindByID(res.utxos[0].AccountID)
345 accountSigner = account.Signer
348 txInput, sigInst, err := UtxoToInputs(accountSigner, res.utxos[0])
353 if a.Arguments == nil {
354 return b.AddInput(txInput, sigInst)
357 sigInst = &txbuilder.SigningInstruction{}
358 if err := txbuilder.AddContractArgs(sigInst, a.Arguments); err != nil {
362 return b.AddInput(txInput, sigInst)
365 // UtxoToInputs convert an utxo to the txinput
366 func UtxoToInputs(signer *signers.Signer, u *UTXO) (*types.TxInput, *txbuilder.SigningInstruction, error) {
367 txInput := types.NewSpendInput(nil, u.SourceID, u.AssetID, u.Amount, u.SourcePos, u.ControlProgram)
368 sigInst := &txbuilder.SigningInstruction{}
370 return txInput, sigInst, nil
373 path, err := signers.Path(signer, signers.AccountKeySpace, u.Change, u.ControlProgramIndex)
378 sigInst.AddWitnessKeys(signer.XPubs, path, signer.Quorum)
379 return txInput, sigInst, nil
382 address, err := common.DecodeAddress(u.Address, &consensus.ActiveNetParams)
387 sigInst.AddRawWitnessKeys(signer.XPubs, path, signer.Quorum)
388 derivedXPubs := chainkd.DeriveXPubs(signer.XPubs, path)
390 switch address.(type) {
391 case *common.AddressWitnessPubKeyHash:
392 derivedPK := derivedXPubs[0].PublicKey()
393 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness([]byte(derivedPK)))
395 case *common.AddressWitnessScriptHash:
396 derivedPKs := chainkd.XPubKeys(derivedXPubs)
397 script, err := vmutil.P2SPMultiSigProgram(derivedPKs, signer.Quorum)
401 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness(script))
404 return nil, nil, errors.New("unsupport address type")
407 return txInput, sigInst, nil
410 // insertControlProgramDelayed takes a template builder and an account
411 // control program that hasn't been inserted to the database yet. It
412 // registers callbacks on the TemplateBuilder so that all of the template's
413 // account control programs are batch inserted if building the rest of
414 // the template is successful.
415 func (m *Manager) insertControlProgramDelayed(b *txbuilder.TemplateBuilder, acp *CtrlProgram) {
416 m.delayedACPsMu.Lock()
417 m.delayedACPs[b] = append(m.delayedACPs[b], acp)
418 m.delayedACPsMu.Unlock()
420 b.OnRollback(func() {
421 m.delayedACPsMu.Lock()
422 delete(m.delayedACPs, b)
423 m.delayedACPsMu.Unlock()
425 b.OnBuild(func() error {
426 m.delayedACPsMu.Lock()
427 acps := m.delayedACPs[b]
428 delete(m.delayedACPs, b)
429 m.delayedACPsMu.Unlock()
431 // Insert all of the account control programs at once.
435 return m.SaveControlPrograms(acps...)