OSDN Git Service

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