OSDN Git Service

Try (#1165)
[bytom/bytom.git] / account / builder.go
1 package account
2
3 import (
4         "context"
5         "encoding/hex"
6         "encoding/json"
7
8         "github.com/bytom/blockchain/signers"
9         "github.com/bytom/blockchain/txbuilder"
10         "github.com/bytom/common"
11         "github.com/bytom/consensus"
12         "github.com/bytom/crypto/ed25519/chainkd"
13         chainjson "github.com/bytom/encoding/json"
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         return a, json.Unmarshal(data, a)
24 }
25
26 type spendAction struct {
27         accounts *Manager
28         bc.AssetAmount
29         AccountID      string `json:"account_id"`
30         UseUnconfirmed bool   `json:"use_unconfirmed"`
31 }
32
33 // MergeSpendAction merge common assetID and accountID spend action
34 func MergeSpendAction(actions []txbuilder.Action) []txbuilder.Action {
35         resultActions := []txbuilder.Action{}
36         spendActionMap := make(map[string]*spendAction)
37
38         for _, act := range actions {
39                 switch act := act.(type) {
40                 case *spendAction:
41                         actionKey := act.AssetId.String() + act.AccountID
42                         if tmpAct, ok := spendActionMap[actionKey]; ok {
43                                 tmpAct.Amount += act.Amount
44                                 tmpAct.UseUnconfirmed = tmpAct.UseUnconfirmed || act.UseUnconfirmed
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(a.AccountID)
69         if err != nil {
70                 return errors.Wrap(err, "get account info")
71         }
72
73         res, err := a.accounts.utxoKeeper.Reserve(a.AccountID, a.AssetId, a.Amount, a.UseUnconfirmed, b.MaxTime())
74         if err != nil {
75                 return errors.Wrap(err, "reserving utxos")
76         }
77
78         // Cancel the reservation if the build gets rolled back.
79         b.OnRollback(func() { a.accounts.utxoKeeper.Cancel(res.id) })
80         for _, r := range res.utxos {
81                 txInput, sigInst, err := UtxoToInputs(acct.Signer, r)
82                 if err != nil {
83                         return errors.Wrap(err, "creating inputs")
84                 }
85
86                 if err = b.AddInput(txInput, sigInst); err != nil {
87                         return errors.Wrap(err, "adding inputs")
88                 }
89         }
90
91         if res.change > 0 {
92                 acp, err := a.accounts.CreateAddress(a.AccountID, true)
93                 if err != nil {
94                         return errors.Wrap(err, "creating control program")
95                 }
96
97                 // Don't insert the control program until callbacks are executed.
98                 a.accounts.insertControlProgramDelayed(b, acp)
99                 if err = b.AddOutput(types.NewTxOutput(*a.AssetId, res.change, acp.ControlProgram)); err != nil {
100                         return errors.Wrap(err, "adding change output")
101                 }
102         }
103         return nil
104 }
105
106 //DecodeSpendUTXOAction unmarshal JSON-encoded data of spend utxo action
107 func (m *Manager) DecodeSpendUTXOAction(data []byte) (txbuilder.Action, error) {
108         a := &spendUTXOAction{accounts: m}
109         return a, json.Unmarshal(data, a)
110 }
111
112 type spendUTXOAction struct {
113         accounts       *Manager
114         OutputID       *bc.Hash           `json:"output_id"`
115         UseUnconfirmed bool               `json:"use_unconfirmed"`
116         Arguments      []contractArgument `json:"arguments"`
117 }
118
119 // contractArgument for smart contract
120 type contractArgument struct {
121         Type    string          `json:"type"`
122         RawData json.RawMessage `json:"raw_data"`
123 }
124
125 // rawTxSigArgument is signature-related argument for run contract
126 type rawTxSigArgument struct {
127         RootXPub chainkd.XPub         `json:"xpub"`
128         Path     []chainjson.HexBytes `json:"derivation_path"`
129 }
130
131 // dataArgument is the other argument for run contract
132 type dataArgument struct {
133         Value string `json:"value"`
134 }
135
136 func (a *spendUTXOAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
137         if a.OutputID == nil {
138                 return txbuilder.MissingFieldsError("output_id")
139         }
140
141         res, err := a.accounts.utxoKeeper.ReserveParticular(*a.OutputID, a.UseUnconfirmed, b.MaxTime())
142         if err != nil {
143                 return err
144         }
145
146         b.OnRollback(func() { a.accounts.utxoKeeper.Cancel(res.id) })
147         var accountSigner *signers.Signer
148         if len(res.utxos[0].AccountID) != 0 {
149                 account, err := a.accounts.FindByID(res.utxos[0].AccountID)
150                 if err != nil {
151                         return err
152                 }
153
154                 accountSigner = account.Signer
155         }
156
157         txInput, sigInst, err := UtxoToInputs(accountSigner, res.utxos[0])
158         if err != nil {
159                 return err
160         }
161
162         if a.Arguments == nil {
163                 return b.AddInput(txInput, sigInst)
164         }
165
166         sigInst = &txbuilder.SigningInstruction{}
167         for _, arg := range a.Arguments {
168                 switch arg.Type {
169                 case "raw_tx_signature":
170                         rawTxSig := &rawTxSigArgument{}
171                         if err = json.Unmarshal(arg.RawData, rawTxSig); err != nil {
172                                 return err
173                         }
174
175                         // convert path form chainjson.HexBytes to byte
176                         var path [][]byte
177                         for _, p := range rawTxSig.Path {
178                                 path = append(path, []byte(p))
179                         }
180                         sigInst.AddRawWitnessKeys([]chainkd.XPub{rawTxSig.RootXPub}, path, 1)
181
182                 case "data":
183                         data := &dataArgument{}
184                         if err = json.Unmarshal(arg.RawData, data); err != nil {
185                                 return err
186                         }
187
188                         value, err := hex.DecodeString(data.Value)
189                         if err != nil {
190                                 return err
191                         }
192                         sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness(value))
193
194                 default:
195                         return errors.New("contract argument type is not exist")
196                 }
197         }
198         return b.AddInput(txInput, sigInst)
199 }
200
201 // UtxoToInputs convert an utxo to the txinput
202 func UtxoToInputs(signer *signers.Signer, u *UTXO) (*types.TxInput, *txbuilder.SigningInstruction, error) {
203         txInput := types.NewSpendInput(nil, u.SourceID, u.AssetID, u.Amount, u.SourcePos, u.ControlProgram)
204         sigInst := &txbuilder.SigningInstruction{}
205         if signer == nil {
206                 return txInput, sigInst, nil
207         }
208
209         path := signers.Path(signer, signers.AccountKeySpace, u.ControlProgramIndex)
210         if u.Address == "" {
211                 sigInst.AddWitnessKeys(signer.XPubs, path, signer.Quorum)
212                 return txInput, sigInst, nil
213         }
214
215         address, err := common.DecodeAddress(u.Address, &consensus.ActiveNetParams)
216         if err != nil {
217                 return nil, nil, err
218         }
219
220         switch address.(type) {
221         case *common.AddressWitnessPubKeyHash:
222                 sigInst.AddRawWitnessKeys(signer.XPubs, path, signer.Quorum)
223                 derivedXPubs := chainkd.DeriveXPubs(signer.XPubs, path)
224                 derivedPK := derivedXPubs[0].PublicKey()
225                 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness([]byte(derivedPK)))
226
227         case *common.AddressWitnessScriptHash:
228                 sigInst.AddRawWitnessKeys(signer.XPubs, path, signer.Quorum)
229                 path := signers.Path(signer, signers.AccountKeySpace, u.ControlProgramIndex)
230                 derivedXPubs := chainkd.DeriveXPubs(signer.XPubs, path)
231                 derivedPKs := chainkd.XPubKeys(derivedXPubs)
232                 script, err := vmutil.P2SPMultiSigProgram(derivedPKs, signer.Quorum)
233                 if err != nil {
234                         return nil, nil, err
235                 }
236                 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness(script))
237
238         default:
239                 return nil, nil, errors.New("unsupport address type")
240         }
241
242         return txInput, sigInst, nil
243 }
244
245 // insertControlProgramDelayed takes a template builder and an account
246 // control program that hasn't been inserted to the database yet. It
247 // registers callbacks on the TemplateBuilder so that all of the template's
248 // account control programs are batch inserted if building the rest of
249 // the template is successful.
250 func (m *Manager) insertControlProgramDelayed(b *txbuilder.TemplateBuilder, acp *CtrlProgram) {
251         m.delayedACPsMu.Lock()
252         m.delayedACPs[b] = append(m.delayedACPs[b], acp)
253         m.delayedACPsMu.Unlock()
254
255         b.OnRollback(func() {
256                 m.delayedACPsMu.Lock()
257                 delete(m.delayedACPs, b)
258                 m.delayedACPsMu.Unlock()
259         })
260         b.OnBuild(func() error {
261                 m.delayedACPsMu.Lock()
262                 acps := m.delayedACPs[b]
263                 delete(m.delayedACPs, b)
264                 m.delayedACPsMu.Unlock()
265
266                 // Insert all of the account control programs at once.
267                 if len(acps) == 0 {
268                         return nil
269                 }
270                 return m.insertControlPrograms(acps...)
271         })
272 }