8 "github.com/vapor/asset"
9 "github.com/vapor/blockchain/signers"
10 "github.com/vapor/blockchain/txbuilder"
11 "github.com/vapor/common"
12 "github.com/vapor/consensus"
13 "github.com/vapor/crypto/ed25519/chainkd"
14 chainjson "github.com/vapor/encoding/json"
15 "github.com/vapor/errors"
16 "github.com/vapor/protocol/bc"
17 "github.com/vapor/protocol/bc/types"
18 "github.com/vapor/protocol/vm/vmutil"
19 "github.com/vapor/testutil"
23 //chainTxUtxoNum maximum utxo quantity in a tx
25 //chainTxMergeGas chain tx gas
26 chainTxMergeGas = uint64(10000000)
29 // DecodeCrossInAction convert input data to action struct
30 func (m *Manager) DecodeCrossInAction(data []byte) (txbuilder.Action, error) {
31 a := &spendAction{accounts: m}
32 err := json.Unmarshal(data, a)
36 type crossInAction struct {
39 SourceID string `json:"source_id"` // AnnotatedUTXO
40 SourcePos uint64 `json:"source_pos"`
41 AssetDefinition map[string]interface{} `json:"asset_definition"`
42 UpdateAssetDef bool `json:"update_asset_definition"`
45 // type AnnotatedInput struct {
46 // Type string `json:"type"`
47 // AssetID bc.AssetID `json:"asset_id"`
48 // AssetAlias string `json:"asset_alias,omitempty"`
49 // AssetDefinition *json.RawMessage `json:"asset_definition,omitempty"`
50 // Amount uint64 `json:"amount"`
51 // ControlProgram chainjson.HexBytes `json:"control_program,omitempty"`
52 // Address string `json:"address,omitempty"`
53 // SpentOutputID *bc.Hash `json:"spent_output_id,omitempty"`
54 // AccountID string `json:"account_id,omitempty"`
55 // AccountAlias string `json:"account_alias,omitempty"`
56 // Arbitrary chainjson.HexBytes `json:"arbitrary,omitempty"`
57 // InputID bc.Hash `json:"input_id"`
58 // WitnessArguments []chainjson.HexBytes `json:"witness_arguments"`
61 func (a *crossInAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
63 if a.AssetId.IsZero() {
64 missing = append(missing, "asset_id")
67 missing = append(missing, "amount")
70 return txbuilder.MissingFieldsError(missing...)
73 // Handle asset definition.
74 // Asset issuance's legality is guaranteed by the federation.
75 rawDefinition, err := asset.SerializeAssetDef(a.AssetDefinition)
77 return asset.ErrSerializing
79 // TODO: may need to skip here
80 if !chainjson.IsValidJSON(rawDefinition) {
81 return errors.New("asset definition is not in valid json format")
83 if preAsset, _ := a.accounts.assetReg.GetAsset(a.AssetId.String()); preAsset != nil {
84 // GetAsset() doesn't unmashall for RawDefinitionBytes
85 preRawDefinition, err := asset.SerializeAssetDef(preAsset.DefinitionMap)
87 return asset.ErrSerializing
90 if !testutil.DeepEqual(preRawDefinition, rawDefinition) && !UpdateAssetDef {
91 return errors.New("asset definition mismatch with previous definition")
93 // TODO: update asset def here?
96 // TODO: IssuanceProgram vs arguments?
97 // TODO: also need to hard-code mapTx
98 // TODO: save AssetDefinition
100 // txin := types.NewIssuanceInput(nonce[:], a.Amount, asset.IssuanceProgram, nil, asset.RawDefinitionByte)
101 // tplIn := &txbuilder.SigningInstruction{}
102 // if asset.Signer != nil {
103 // path := signers.GetBip0032Path(asset.Signer, signers.AssetKeySpace)
104 // tplIn.AddRawWitnessKeys(asset.Signer.XPubs, path, asset.Signer.Quorum)
105 // } else if a.Arguments != nil {
106 // if err := txbuilder.AddContractArgs(tplIn, a.Arguments); err != nil {
111 // log.Info("Issue action build")
112 // builder.RestrictMinTime(time.Now())
113 // return builder.AddInput(txin, tplIn)
115 // in := types.NewCrossChainInput(arguments [][]byte, sourceID bc.Hash, assetID bc.AssetID, amount, sourcePos uint64, controlProgram, assetDefinition []byte)
116 sourceID := testutil.MustDecodeHash(a.SourceID)
117 in := types.NewCrossChainInput(nil, sourceID, *a.AssetId, a.Amount, a.SourcePos, nil, rawDefinition)
118 return b.AddInput(in, nil)
121 func (a *crossInAction) ActionType() string {
122 return "cross_chain_in"
125 //DecodeSpendAction unmarshal JSON-encoded data of spend action
126 func (m *Manager) DecodeSpendAction(data []byte) (txbuilder.Action, error) {
127 a := &spendAction{accounts: m}
128 return a, json.Unmarshal(data, a)
131 type spendAction struct {
134 AccountID string `json:"account_id"`
135 UseUnconfirmed bool `json:"use_unconfirmed"`
138 func (a *spendAction) ActionType() string {
139 return "spend_account"
142 // MergeSpendAction merge common assetID and accountID spend action
143 func MergeSpendAction(actions []txbuilder.Action) []txbuilder.Action {
144 resultActions := []txbuilder.Action{}
145 spendActionMap := make(map[string]*spendAction)
147 for _, act := range actions {
148 switch act := act.(type) {
150 actionKey := act.AssetId.String() + act.AccountID
151 if tmpAct, ok := spendActionMap[actionKey]; ok {
152 tmpAct.Amount += act.Amount
153 tmpAct.UseUnconfirmed = tmpAct.UseUnconfirmed || act.UseUnconfirmed
155 spendActionMap[actionKey] = act
156 resultActions = append(resultActions, act)
159 resultActions = append(resultActions, act)
165 //calcMergeGas calculate the gas required that n utxos are merged into one
166 func calcMergeGas(num int) uint64 {
169 gas += chainTxMergeGas
170 num -= chainTxUtxoNum - 1
175 func (m *Manager) reserveBtmUtxoChain(builder *txbuilder.TemplateBuilder, accountID string, amount uint64, useUnconfirmed bool) ([]*UTXO, error) {
176 reservedAmount := uint64(0)
178 for gasAmount := uint64(0); reservedAmount < gasAmount+amount; gasAmount = calcMergeGas(len(utxos)) {
179 reserveAmount := amount + gasAmount - reservedAmount
180 res, err := m.utxoKeeper.Reserve(accountID, consensus.BTMAssetID, reserveAmount, useUnconfirmed, builder.MaxTime())
185 builder.OnRollback(func() { m.utxoKeeper.Cancel(res.id) })
186 reservedAmount += reserveAmount + res.change
187 utxos = append(utxos, res.utxos[:]...)
192 func (m *Manager) buildBtmTxChain(utxos []*UTXO, signer *signers.Signer) ([]*txbuilder.Template, *UTXO, error) {
194 return nil, nil, errors.New("mergeSpendActionUTXO utxos num 0")
197 tpls := []*txbuilder.Template{}
199 return tpls, utxos[len(utxos)-1], nil
202 acp, err := m.GetLocalCtrlProgramByAddress(utxos[0].Address)
207 buildAmount := uint64(0)
208 builder := &txbuilder.TemplateBuilder{}
209 for index := 0; index < len(utxos); index++ {
210 input, sigInst, err := UtxoToInputs(signer, utxos[index])
215 if err = builder.AddInput(input, sigInst); err != nil {
219 buildAmount += input.Amount()
220 if builder.InputCount() != chainTxUtxoNum && index != len(utxos)-1 {
224 outAmount := buildAmount - chainTxMergeGas
225 output := types.NewIntraChainOutput(*consensus.BTMAssetID, outAmount, acp.ControlProgram)
226 if err := builder.AddOutput(output); err != nil {
230 tpl, _, err := builder.Build()
235 bcOut, err := tpl.Transaction.IntraChainOutput(*tpl.Transaction.ResultIds[0])
240 utxos = append(utxos, &UTXO{
241 OutputID: *tpl.Transaction.ResultIds[0],
242 AssetID: *consensus.BTMAssetID,
244 ControlProgram: acp.ControlProgram,
245 SourceID: *bcOut.Source.Ref,
246 SourcePos: bcOut.Source.Position,
247 ControlProgramIndex: acp.KeyIndex,
248 Address: acp.Address,
252 tpls = append(tpls, tpl)
254 builder = &txbuilder.TemplateBuilder{}
255 if index == len(utxos)-2 {
259 return tpls, utxos[len(utxos)-1], nil
262 // SpendAccountChain build the spend action with auto merge utxo function
263 func SpendAccountChain(ctx context.Context, builder *txbuilder.TemplateBuilder, action txbuilder.Action) ([]*txbuilder.Template, error) {
264 act, ok := action.(*spendAction)
266 return nil, errors.New("fail to convert the spend action")
268 if *act.AssetId != *consensus.BTMAssetID {
269 return nil, errors.New("spend chain action only support BTM")
272 utxos, err := act.accounts.reserveBtmUtxoChain(builder, act.AccountID, act.Amount, act.UseUnconfirmed)
277 acct, err := act.accounts.FindByID(act.AccountID)
282 tpls, utxo, err := act.accounts.buildBtmTxChain(utxos, acct.Signer)
287 input, sigInst, err := UtxoToInputs(acct.Signer, utxo)
292 if err := builder.AddInput(input, sigInst); err != nil {
296 if utxo.Amount > act.Amount {
297 if err = builder.AddOutput(types.NewIntraChainOutput(*consensus.BTMAssetID, utxo.Amount-act.Amount, utxo.ControlProgram)); err != nil {
298 return nil, errors.Wrap(err, "adding change output")
304 func (a *spendAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
306 if a.AccountID == "" {
307 missing = append(missing, "account_id")
309 if a.AssetId.IsZero() {
310 missing = append(missing, "asset_id")
312 if len(missing) > 0 {
313 return txbuilder.MissingFieldsError(missing...)
316 acct, err := a.accounts.FindByID(a.AccountID)
318 return errors.Wrap(err, "get account info")
321 res, err := a.accounts.utxoKeeper.Reserve(a.AccountID, a.AssetId, a.Amount, a.UseUnconfirmed, b.MaxTime())
323 return errors.Wrap(err, "reserving utxos")
326 // Cancel the reservation if the build gets rolled back.
327 b.OnRollback(func() { a.accounts.utxoKeeper.Cancel(res.id) })
328 for _, r := range res.utxos {
329 txInput, sigInst, err := UtxoToInputs(acct.Signer, r)
331 return errors.Wrap(err, "creating inputs")
334 if err = b.AddInput(txInput, sigInst); err != nil {
335 return errors.Wrap(err, "adding inputs")
340 acp, err := a.accounts.CreateAddress(a.AccountID, true)
342 return errors.Wrap(err, "creating control program")
345 // Don't insert the control program until callbacks are executed.
346 a.accounts.insertControlProgramDelayed(b, acp)
347 if err = b.AddOutput(types.NewIntraChainOutput(*a.AssetId, res.change, acp.ControlProgram)); err != nil {
348 return errors.Wrap(err, "adding change output")
354 //DecodeSpendUTXOAction unmarshal JSON-encoded data of spend utxo action
355 func (m *Manager) DecodeSpendUTXOAction(data []byte) (txbuilder.Action, error) {
356 a := &spendUTXOAction{accounts: m}
357 return a, json.Unmarshal(data, a)
360 type spendUTXOAction struct {
362 OutputID *bc.Hash `json:"output_id"`
363 UseUnconfirmed bool `json:"use_unconfirmed"`
364 Arguments []txbuilder.ContractArgument `json:"arguments"`
367 func (a *spendUTXOAction) ActionType() string {
368 return "spend_account_unspent_output"
371 func (a *spendUTXOAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
372 if a.OutputID == nil {
373 return txbuilder.MissingFieldsError("output_id")
376 res, err := a.accounts.utxoKeeper.ReserveParticular(*a.OutputID, a.UseUnconfirmed, b.MaxTime())
381 b.OnRollback(func() { a.accounts.utxoKeeper.Cancel(res.id) })
382 var accountSigner *signers.Signer
383 if len(res.utxos[0].AccountID) != 0 {
384 account, err := a.accounts.FindByID(res.utxos[0].AccountID)
389 accountSigner = account.Signer
392 txInput, sigInst, err := UtxoToInputs(accountSigner, res.utxos[0])
397 if a.Arguments == nil {
398 return b.AddInput(txInput, sigInst)
401 sigInst = &txbuilder.SigningInstruction{}
402 if err := txbuilder.AddContractArgs(sigInst, a.Arguments); err != nil {
406 return b.AddInput(txInput, sigInst)
409 // UtxoToInputs convert an utxo to the txinput
410 func UtxoToInputs(signer *signers.Signer, u *UTXO) (*types.TxInput, *txbuilder.SigningInstruction, error) {
411 txInput := types.NewSpendInput(nil, u.SourceID, u.AssetID, u.Amount, u.SourcePos, u.ControlProgram)
412 sigInst := &txbuilder.SigningInstruction{}
414 return txInput, sigInst, nil
417 path, err := signers.Path(signer, signers.AccountKeySpace, u.Change, u.ControlProgramIndex)
422 sigInst.AddWitnessKeys(signer.XPubs, path, signer.Quorum)
423 return txInput, sigInst, nil
426 address, err := common.DecodeAddress(u.Address, &consensus.ActiveNetParams)
431 sigInst.AddRawWitnessKeys(signer.XPubs, path, signer.Quorum)
432 derivedXPubs := chainkd.DeriveXPubs(signer.XPubs, path)
434 switch address.(type) {
435 case *common.AddressWitnessPubKeyHash:
436 derivedPK := derivedXPubs[0].PublicKey()
437 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness([]byte(derivedPK)))
439 case *common.AddressWitnessScriptHash:
440 derivedPKs := chainkd.XPubKeys(derivedXPubs)
441 script, err := vmutil.P2SPMultiSigProgram(derivedPKs, signer.Quorum)
445 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness(script))
448 return nil, nil, errors.New("unsupport address type")
451 return txInput, sigInst, nil
454 // insertControlProgramDelayed takes a template builder and an account
455 // control program that hasn't been inserted to the database yet. It
456 // registers callbacks on the TemplateBuilder so that all of the template's
457 // account control programs are batch inserted if building the rest of
458 // the template is successful.
459 func (m *Manager) insertControlProgramDelayed(b *txbuilder.TemplateBuilder, acp *CtrlProgram) {
460 m.delayedACPsMu.Lock()
461 m.delayedACPs[b] = append(m.delayedACPs[b], acp)
462 m.delayedACPsMu.Unlock()
464 b.OnRollback(func() {
465 m.delayedACPsMu.Lock()
466 delete(m.delayedACPs, b)
467 m.delayedACPsMu.Unlock()
469 b.OnBuild(func() error {
470 m.delayedACPsMu.Lock()
471 acps := m.delayedACPs[b]
472 delete(m.delayedACPs, b)
473 m.delayedACPsMu.Unlock()
475 // Insert all of the account control programs at once.
479 return m.SaveControlPrograms(acps...)