OSDN Git Service

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