OSDN Git Service

init push for pay-to-script-hash (#235)
[bytom/bytom.git] / blockchain / 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/legacy"
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         ReferenceData chainjson.Map `json:"reference_data"`
33         ClientToken   *string       `json:"client_token"`
34 }
35
36 func (a *spendAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
37         var missing []string
38         if a.AccountID == "" {
39                 missing = append(missing, "account_id")
40         }
41         if a.AssetId.IsZero() {
42                 missing = append(missing, "asset_id")
43         }
44         if len(missing) > 0 {
45                 return txbuilder.MissingFieldsError(missing...)
46         }
47
48         acct, err := a.accounts.findByID(ctx, a.AccountID)
49         if err != nil {
50                 return errors.Wrap(err, "get account info")
51         }
52
53         src := source{
54                 AssetID:   *a.AssetId,
55                 AccountID: a.AccountID,
56         }
57         res, err := a.accounts.utxoDB.Reserve(src, a.Amount, a.ClientToken, b.MaxTime())
58         if err != nil {
59                 return errors.Wrap(err, "reserving utxos")
60         }
61
62         // Cancel the reservation if the build gets rolled back.
63         b.OnRollback(canceler(ctx, a.accounts, res.ID))
64
65         for _, r := range res.UTXOs {
66                 txInput, sigInst, err := UtxoToInputs(acct, r, a.ReferenceData)
67                 if err != nil {
68                         return errors.Wrap(err, "creating inputs")
69                 }
70                 err = b.AddInput(txInput, sigInst)
71                 if err != nil {
72                         return errors.Wrap(err, "adding inputs")
73                 }
74         }
75
76         if res.Change > 0 {
77                 acp, err := a.accounts.createControlProgram(ctx, a.AccountID, true, b.MaxTime())
78                 if err != nil {
79                         return errors.Wrap(err, "creating control program")
80                 }
81
82                 // Don't insert the control program until callbacks are executed.
83                 a.accounts.insertControlProgramDelayed(ctx, b, acp)
84
85                 err = b.AddOutput(legacy.NewTxOutput(*a.AssetId, res.Change, acp.ControlProgram, nil))
86                 if err != nil {
87                         return errors.Wrap(err, "adding change output")
88                 }
89         }
90         return nil
91 }
92
93 //DecodeSpendUTXOAction unmarshal JSON-encoded data of spend utxo action
94 func (m *Manager) DecodeSpendUTXOAction(data []byte) (txbuilder.Action, error) {
95         a := &spendUTXOAction{accounts: m}
96         err := json.Unmarshal(data, a)
97         return a, err
98 }
99
100 type spendUTXOAction struct {
101         accounts *Manager
102         OutputID *bc.Hash `json:"output_id"`
103
104         ReferenceData chainjson.Map `json:"reference_data"`
105         ClientToken   *string       `json:"client_token"`
106 }
107
108 func (a *spendUTXOAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
109         if a.OutputID == nil {
110                 return txbuilder.MissingFieldsError("output_id")
111         }
112
113         res, err := a.accounts.utxoDB.ReserveUTXO(ctx, *a.OutputID, a.ClientToken, b.MaxTime())
114         if err != nil {
115                 return err
116         }
117         b.OnRollback(canceler(ctx, a.accounts, res.ID))
118
119         var acct *signers.Signer
120         if res.Source.AccountID == "" {
121                 //TODO coinbase
122                 acct = &signers.Signer{}
123         } else {
124                 acct, err = a.accounts.findByID(ctx, res.Source.AccountID)
125                 if err != nil {
126                         return err
127                 }
128         }
129
130         txInput, sigInst, err := UtxoToInputs(acct, res.UTXOs[0], a.ReferenceData)
131         if err != nil {
132                 return err
133         }
134         return b.AddInput(txInput, sigInst)
135 }
136
137 // Best-effort cancellation attempt to put in txbuilder.BuildResult.Rollback.
138 func canceler(ctx context.Context, m *Manager, rid uint64) func() {
139         return func() {
140                 if err := m.utxoDB.Cancel(ctx, rid); err != nil {
141                         log.WithField("error", err).Error("Best-effort cancellation attempt to put in txbuilder.BuildResult.Rollback")
142                 }
143         }
144 }
145
146 // UtxoToInputs convert an utxo to the txinput
147 func UtxoToInputs(account *signers.Signer, u *UTXO, refData []byte) (*legacy.TxInput, *txbuilder.SigningInstruction, error) {
148         txInput := legacy.NewSpendInput(nil, u.SourceID, u.AssetID, u.Amount, u.SourcePos, u.ControlProgram, u.RefDataHash, refData)
149         path := signers.Path(account, signers.AccountKeySpace, u.ControlProgramIndex)
150         sigInst := &txbuilder.SigningInstruction{}
151         if u.Address == "" {
152                 sigInst.AddWitnessKeys(account.XPubs, path, account.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(account.XPubs, path, account.Quorum)
164                 derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
165                 derivedPK := derivedXPubs[0].PublicKey()
166                 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness([]byte(derivedPK)))
167
168         case *common.AddressWitnessScriptHash:
169                 sigInst.AddWitnessKeys(account.XPubs, path, account.Quorum)
170                 path := signers.Path(account, signers.AccountKeySpace, u.ControlProgramIndex)
171                 derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
172                 derivedPKs := chainkd.XPubKeys(derivedXPubs)
173                 script, err := vmutil.P2SPMultiSigProgram(derivedPKs, account.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.createControlProgram(ctx, a.AccountID, false, b.MaxTime())
224         if err != nil {
225                 return err
226         }
227         a.accounts.insertControlProgramDelayed(ctx, b, acp)
228
229         return b.AddOutput(legacy.NewTxOutput(*a.AssetId, a.Amount, acp.ControlProgram, a.ReferenceData))
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 }