OSDN Git Service

modify MergeSpendAction order by origin action list (#1048)
[bytom/bytom.git] / account / builder.go
1 package account
2
3 import (
4         "context"
5         "encoding/json"
6
7         log "github.com/sirupsen/logrus"
8
9         "github.com/bytom/blockchain/signers"
10         "github.com/bytom/blockchain/txbuilder"
11         "github.com/bytom/common"
12         "github.com/bytom/consensus"
13         "github.com/bytom/crypto/ed25519/chainkd"
14         "github.com/bytom/errors"
15         "github.com/bytom/protocol/bc"
16         "github.com/bytom/protocol/bc/types"
17         "github.com/bytom/protocol/vm/vmutil"
18 )
19
20 //DecodeSpendAction unmarshal JSON-encoded data of spend action
21 func (m *Manager) DecodeSpendAction(data []byte) (txbuilder.Action, error) {
22         a := &spendAction{accounts: m}
23         err := json.Unmarshal(data, a)
24         return a, err
25 }
26
27 type spendAction struct {
28         accounts *Manager
29         bc.AssetAmount
30         AccountID   string  `json:"account_id"`
31         ClientToken *string `json:"client_token"`
32 }
33
34 // MergeSpendAction merge common assetID and accountID spend action
35 func MergeSpendAction(actions []txbuilder.Action) []txbuilder.Action {
36         resultActions := []txbuilder.Action{}
37         spendActionMap := make(map[string]*spendAction)
38
39         for _, act := range actions {
40                 switch act := act.(type) {
41                 case *spendAction:
42                         actionKey := act.AssetId.String() + act.AccountID
43                         if tmpAct, ok := spendActionMap[actionKey]; ok {
44                                 tmpAct.Amount += act.Amount
45                         } else {
46                                 spendActionMap[actionKey] = act
47                                 resultActions = append(resultActions, act)
48                         }
49                 default:
50                         resultActions = append(resultActions, act)
51                 }
52         }
53         return resultActions
54 }
55
56 func (a *spendAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
57         var missing []string
58         if a.AccountID == "" {
59                 missing = append(missing, "account_id")
60         }
61         if a.AssetId.IsZero() {
62                 missing = append(missing, "asset_id")
63         }
64         if len(missing) > 0 {
65                 return txbuilder.MissingFieldsError(missing...)
66         }
67
68         acct, err := a.accounts.FindByID(ctx, a.AccountID)
69         if err != nil {
70                 return errors.Wrap(err, "get account info")
71         }
72
73         src := source{
74                 AssetID:   *a.AssetId,
75                 AccountID: a.AccountID,
76         }
77         res, err := a.accounts.utxoDB.Reserve(src, a.Amount, a.ClientToken, b.MaxTime())
78         if err != nil {
79                 return errors.Wrap(err, "reserving utxos")
80         }
81
82         // Cancel the reservation if the build gets rolled back.
83         b.OnRollback(canceler(ctx, a.accounts, res.ID))
84
85         for _, r := range res.UTXOs {
86                 txInput, sigInst, err := UtxoToInputs(acct.Signer, r)
87                 if err != nil {
88                         return errors.Wrap(err, "creating inputs")
89                 }
90                 err = b.AddInput(txInput, sigInst)
91                 if err != nil {
92                         return errors.Wrap(err, "adding inputs")
93                 }
94         }
95
96         if res.Change > 0 {
97                 acp, err := a.accounts.CreateAddress(ctx, a.AccountID, true)
98                 if err != nil {
99                         return errors.Wrap(err, "creating control program")
100                 }
101
102                 // Don't insert the control program until callbacks are executed.
103                 a.accounts.insertControlProgramDelayed(ctx, b, acp)
104
105                 err = b.AddOutput(types.NewTxOutput(*a.AssetId, res.Change, acp.ControlProgram))
106                 if err != nil {
107                         return errors.Wrap(err, "adding change output")
108                 }
109         }
110         return nil
111 }
112
113 //DecodeSpendUTXOAction unmarshal JSON-encoded data of spend utxo action
114 func (m *Manager) DecodeSpendUTXOAction(data []byte) (txbuilder.Action, error) {
115         a := &spendUTXOAction{accounts: m}
116         err := json.Unmarshal(data, a)
117         return a, err
118 }
119
120 type spendUTXOAction struct {
121         accounts *Manager
122         OutputID *bc.Hash `json:"output_id"`
123
124         ClientToken *string `json:"client_token"`
125 }
126
127 func (a *spendUTXOAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
128         if a.OutputID == nil {
129                 return txbuilder.MissingFieldsError("output_id")
130         }
131
132         res, err := a.accounts.utxoDB.ReserveUTXO(ctx, *a.OutputID, a.ClientToken, b.MaxTime())
133         if err != nil {
134                 return err
135         }
136         b.OnRollback(canceler(ctx, a.accounts, res.ID))
137
138         var accountSigner *signers.Signer
139         if len(res.Source.AccountID) != 0 {
140                 account, err := a.accounts.FindByID(ctx, res.Source.AccountID)
141                 if err != nil {
142                         return err
143                 }
144                 accountSigner = account.Signer
145         }
146
147         txInput, sigInst, err := UtxoToInputs(accountSigner, res.UTXOs[0])
148         if err != nil {
149                 return err
150         }
151         return b.AddInput(txInput, sigInst)
152 }
153
154 // Best-effort cancellation attempt to put in txbuilder.BuildResult.Rollback.
155 func canceler(ctx context.Context, m *Manager, rid uint64) func() {
156         return func() {
157                 if err := m.utxoDB.Cancel(ctx, rid); err != nil {
158                         log.WithField("error", err).Error("Best-effort cancellation attempt to put in txbuilder.BuildResult.Rollback")
159                 }
160         }
161 }
162
163 // UtxoToInputs convert an utxo to the txinput
164 func UtxoToInputs(signer *signers.Signer, u *UTXO) (*types.TxInput, *txbuilder.SigningInstruction, error) {
165         txInput := types.NewSpendInput(nil, u.SourceID, u.AssetID, u.Amount, u.SourcePos, u.ControlProgram)
166         sigInst := &txbuilder.SigningInstruction{}
167         if signer == nil {
168                 return txInput, sigInst, nil
169         }
170
171         path := signers.Path(signer, signers.AccountKeySpace, u.ControlProgramIndex)
172         if u.Address == "" {
173                 sigInst.AddWitnessKeys(signer.XPubs, path, signer.Quorum)
174                 return txInput, sigInst, nil
175         }
176
177         address, err := common.DecodeAddress(u.Address, &consensus.ActiveNetParams)
178         if err != nil {
179                 return nil, nil, err
180         }
181
182         switch address.(type) {
183         case *common.AddressWitnessPubKeyHash:
184                 sigInst.AddRawWitnessKeys(signer.XPubs, path, signer.Quorum)
185                 derivedXPubs := chainkd.DeriveXPubs(signer.XPubs, path)
186                 derivedPK := derivedXPubs[0].PublicKey()
187                 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness([]byte(derivedPK)))
188
189         case *common.AddressWitnessScriptHash:
190                 sigInst.AddRawWitnessKeys(signer.XPubs, path, signer.Quorum)
191                 path := signers.Path(signer, signers.AccountKeySpace, u.ControlProgramIndex)
192                 derivedXPubs := chainkd.DeriveXPubs(signer.XPubs, path)
193                 derivedPKs := chainkd.XPubKeys(derivedXPubs)
194                 script, err := vmutil.P2SPMultiSigProgram(derivedPKs, signer.Quorum)
195                 if err != nil {
196                         return nil, nil, err
197                 }
198                 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness(script))
199
200         default:
201                 return nil, nil, errors.New("unsupport address type")
202         }
203
204         return txInput, sigInst, nil
205 }
206
207 // insertControlProgramDelayed takes a template builder and an account
208 // control program that hasn't been inserted to the database yet. It
209 // registers callbacks on the TemplateBuilder so that all of the template's
210 // account control programs are batch inserted if building the rest of
211 // the template is successful.
212 func (m *Manager) insertControlProgramDelayed(ctx context.Context, b *txbuilder.TemplateBuilder, acp *CtrlProgram) {
213         m.delayedACPsMu.Lock()
214         m.delayedACPs[b] = append(m.delayedACPs[b], acp)
215         m.delayedACPsMu.Unlock()
216
217         b.OnRollback(func() {
218                 m.delayedACPsMu.Lock()
219                 delete(m.delayedACPs, b)
220                 m.delayedACPsMu.Unlock()
221         })
222         b.OnBuild(func() error {
223                 m.delayedACPsMu.Lock()
224                 acps := m.delayedACPs[b]
225                 delete(m.delayedACPs, b)
226                 m.delayedACPsMu.Unlock()
227
228                 // Insert all of the account control programs at once.
229                 if len(acps) == 0 {
230                         return nil
231                 }
232                 return m.insertAccountControlProgram(ctx, acps...)
233         })
234 }