OSDN Git Service

add issue program error (#1281)
[bytom/bytom.git] / account / builder.go
1 package account
2
3 import (
4         "context"
5         "encoding/json"
6
7         "github.com/bytom/blockchain/signers"
8         "github.com/bytom/blockchain/txbuilder"
9         "github.com/bytom/common"
10         "github.com/bytom/consensus"
11         "github.com/bytom/crypto/ed25519/chainkd"
12         "github.com/bytom/errors"
13         "github.com/bytom/protocol/bc"
14         "github.com/bytom/protocol/bc/types"
15         "github.com/bytom/protocol/vm/vmutil"
16 )
17
18 //DecodeSpendAction unmarshal JSON-encoded data of spend action
19 func (m *Manager) DecodeSpendAction(data []byte) (txbuilder.Action, error) {
20         a := &spendAction{accounts: m}
21         return a, json.Unmarshal(data, a)
22 }
23
24 type spendAction struct {
25         accounts *Manager
26         bc.AssetAmount
27         AccountID      string `json:"account_id"`
28         UseUnconfirmed bool   `json:"use_unconfirmed"`
29 }
30
31 // MergeSpendAction merge common assetID and accountID spend action
32 func MergeSpendAction(actions []txbuilder.Action) []txbuilder.Action {
33         resultActions := []txbuilder.Action{}
34         spendActionMap := make(map[string]*spendAction)
35
36         for _, act := range actions {
37                 switch act := act.(type) {
38                 case *spendAction:
39                         actionKey := act.AssetId.String() + act.AccountID
40                         if tmpAct, ok := spendActionMap[actionKey]; ok {
41                                 tmpAct.Amount += act.Amount
42                                 tmpAct.UseUnconfirmed = tmpAct.UseUnconfirmed || act.UseUnconfirmed
43                         } else {
44                                 spendActionMap[actionKey] = act
45                                 resultActions = append(resultActions, act)
46                         }
47                 default:
48                         resultActions = append(resultActions, act)
49                 }
50         }
51         return resultActions
52 }
53
54 func (a *spendAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
55         var missing []string
56         if a.AccountID == "" {
57                 missing = append(missing, "account_id")
58         }
59         if a.AssetId.IsZero() {
60                 missing = append(missing, "asset_id")
61         }
62         if len(missing) > 0 {
63                 return txbuilder.MissingFieldsError(missing...)
64         }
65
66         acct, err := a.accounts.FindByID(a.AccountID)
67         if err != nil {
68                 return errors.Wrap(err, "get account info")
69         }
70
71         res, err := a.accounts.utxoKeeper.Reserve(a.AccountID, a.AssetId, a.Amount, a.UseUnconfirmed, b.MaxTime())
72         if err != nil {
73                 return errors.Wrap(err, "reserving utxos")
74         }
75
76         // Cancel the reservation if the build gets rolled back.
77         b.OnRollback(func() { a.accounts.utxoKeeper.Cancel(res.id) })
78         for _, r := range res.utxos {
79                 txInput, sigInst, err := UtxoToInputs(acct.Signer, r)
80                 if err != nil {
81                         return errors.Wrap(err, "creating inputs")
82                 }
83
84                 if err = b.AddInput(txInput, sigInst); err != nil {
85                         return errors.Wrap(err, "adding inputs")
86                 }
87         }
88
89         if res.change > 0 {
90                 acp, err := a.accounts.CreateAddress(a.AccountID, true)
91                 if err != nil {
92                         return errors.Wrap(err, "creating control program")
93                 }
94
95                 // Don't insert the control program until callbacks are executed.
96                 a.accounts.insertControlProgramDelayed(b, acp)
97                 if err = b.AddOutput(types.NewTxOutput(*a.AssetId, res.change, acp.ControlProgram)); err != nil {
98                         return errors.Wrap(err, "adding change output")
99                 }
100         }
101         return nil
102 }
103
104 //DecodeSpendUTXOAction unmarshal JSON-encoded data of spend utxo action
105 func (m *Manager) DecodeSpendUTXOAction(data []byte) (txbuilder.Action, error) {
106         a := &spendUTXOAction{accounts: m}
107         return a, json.Unmarshal(data, a)
108 }
109
110 type spendUTXOAction struct {
111         accounts       *Manager
112         OutputID       *bc.Hash                     `json:"output_id"`
113         UseUnconfirmed bool                         `json:"use_unconfirmed"`
114         Arguments      []txbuilder.ContractArgument `json:"arguments"`
115 }
116
117 func (a *spendUTXOAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
118         if a.OutputID == nil {
119                 return txbuilder.MissingFieldsError("output_id")
120         }
121
122         res, err := a.accounts.utxoKeeper.ReserveParticular(*a.OutputID, a.UseUnconfirmed, b.MaxTime())
123         if err != nil {
124                 return err
125         }
126
127         b.OnRollback(func() { a.accounts.utxoKeeper.Cancel(res.id) })
128         var accountSigner *signers.Signer
129         if len(res.utxos[0].AccountID) != 0 {
130                 account, err := a.accounts.FindByID(res.utxos[0].AccountID)
131                 if err != nil {
132                         return err
133                 }
134
135                 accountSigner = account.Signer
136         }
137
138         txInput, sigInst, err := UtxoToInputs(accountSigner, res.utxos[0])
139         if err != nil {
140                 return err
141         }
142
143         if a.Arguments == nil {
144                 return b.AddInput(txInput, sigInst)
145         }
146
147         sigInst = &txbuilder.SigningInstruction{}
148         if err := txbuilder.AddContractArgs(sigInst, a.Arguments); err != nil {
149                 return err
150         }
151
152         return b.AddInput(txInput, sigInst)
153 }
154
155 // UtxoToInputs convert an utxo to the txinput
156 func UtxoToInputs(signer *signers.Signer, u *UTXO) (*types.TxInput, *txbuilder.SigningInstruction, error) {
157         txInput := types.NewSpendInput(nil, u.SourceID, u.AssetID, u.Amount, u.SourcePos, u.ControlProgram)
158         sigInst := &txbuilder.SigningInstruction{}
159         if signer == nil {
160                 return txInput, sigInst, nil
161         }
162
163         path := signers.Path(signer, signers.AccountKeySpace, u.ControlProgramIndex)
164         if u.Address == "" {
165                 sigInst.AddWitnessKeys(signer.XPubs, path, signer.Quorum)
166                 return txInput, sigInst, nil
167         }
168
169         address, err := common.DecodeAddress(u.Address, &consensus.ActiveNetParams)
170         if err != nil {
171                 return nil, nil, err
172         }
173
174         switch address.(type) {
175         case *common.AddressWitnessPubKeyHash:
176                 sigInst.AddRawWitnessKeys(signer.XPubs, path, signer.Quorum)
177                 derivedXPubs := chainkd.DeriveXPubs(signer.XPubs, path)
178                 derivedPK := derivedXPubs[0].PublicKey()
179                 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness([]byte(derivedPK)))
180
181         case *common.AddressWitnessScriptHash:
182                 sigInst.AddRawWitnessKeys(signer.XPubs, path, signer.Quorum)
183                 path := signers.Path(signer, signers.AccountKeySpace, u.ControlProgramIndex)
184                 derivedXPubs := chainkd.DeriveXPubs(signer.XPubs, path)
185                 derivedPKs := chainkd.XPubKeys(derivedXPubs)
186                 script, err := vmutil.P2SPMultiSigProgram(derivedPKs, signer.Quorum)
187                 if err != nil {
188                         return nil, nil, err
189                 }
190                 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness(script))
191
192         default:
193                 return nil, nil, errors.New("unsupport address type")
194         }
195
196         return txInput, sigInst, nil
197 }
198
199 // insertControlProgramDelayed takes a template builder and an account
200 // control program that hasn't been inserted to the database yet. It
201 // registers callbacks on the TemplateBuilder so that all of the template's
202 // account control programs are batch inserted if building the rest of
203 // the template is successful.
204 func (m *Manager) insertControlProgramDelayed(b *txbuilder.TemplateBuilder, acp *CtrlProgram) {
205         m.delayedACPsMu.Lock()
206         m.delayedACPs[b] = append(m.delayedACPs[b], acp)
207         m.delayedACPsMu.Unlock()
208
209         b.OnRollback(func() {
210                 m.delayedACPsMu.Lock()
211                 delete(m.delayedACPs, b)
212                 m.delayedACPsMu.Unlock()
213         })
214         b.OnBuild(func() error {
215                 m.delayedACPsMu.Lock()
216                 acps := m.delayedACPs[b]
217                 delete(m.delayedACPs, b)
218                 m.delayedACPsMu.Unlock()
219
220                 // Insert all of the account control programs at once.
221                 if len(acps) == 0 {
222                         return nil
223                 }
224                 return m.insertControlPrograms(acps...)
225         })
226 }