OSDN Git Service

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