7 log "github.com/sirupsen/logrus"
9 "github.com/bytom/blockchain/signers"
10 "github.com/bytom/blockchain/txbuilder"
11 "github.com/bytom/crypto/ed25519/chainkd"
12 chainjson "github.com/bytom/encoding/json"
13 "github.com/bytom/errors"
14 "github.com/bytom/protocol/bc"
15 "github.com/bytom/protocol/bc/legacy"
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 err := json.Unmarshal(data, a)
25 type spendAction struct {
28 AccountID string `json:"account_id"`
29 ReferenceData chainjson.Map `json:"reference_data"`
30 ClientToken *string `json:"client_token"`
33 func (a *spendAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
35 if a.AccountID == "" {
36 missing = append(missing, "account_id")
38 if a.AssetId.IsZero() {
39 missing = append(missing, "asset_id")
42 return txbuilder.MissingFieldsError(missing...)
45 acct, err := a.accounts.findByID(ctx, a.AccountID)
47 return errors.Wrap(err, "get account info")
52 AccountID: a.AccountID,
54 res, err := a.accounts.utxoDB.Reserve(src, a.Amount, a.ClientToken, b.MaxTime())
56 return errors.Wrap(err, "reserving utxos")
59 // Cancel the reservation if the build gets rolled back.
60 b.OnRollback(canceler(ctx, a.accounts, res.ID))
62 for _, r := range res.UTXOs {
63 txInput, sigInst, err := UtxoToInputs(acct, r, a.ReferenceData)
65 return errors.Wrap(err, "creating inputs")
67 err = b.AddInput(txInput, sigInst)
69 return errors.Wrap(err, "adding inputs")
74 acp, err := a.accounts.createControlProgram(ctx, a.AccountID, true, b.MaxTime())
76 return errors.Wrap(err, "creating control program")
79 // Don't insert the control program until callbacks are executed.
80 a.accounts.insertControlProgramDelayed(ctx, b, acp)
82 err = b.AddOutput(legacy.NewTxOutput(*a.AssetId, res.Change, acp.ControlProgram, nil))
84 return errors.Wrap(err, "adding change output")
90 //DecodeSpendUTXOAction unmarshal JSON-encoded data of spend utxo action
91 func (m *Manager) DecodeSpendUTXOAction(data []byte) (txbuilder.Action, error) {
92 a := &spendUTXOAction{accounts: m}
93 err := json.Unmarshal(data, a)
97 type spendUTXOAction struct {
99 OutputID *bc.Hash `json:"output_id"`
101 ReferenceData chainjson.Map `json:"reference_data"`
102 ClientToken *string `json:"client_token"`
105 func (a *spendUTXOAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
106 if a.OutputID == nil {
107 return txbuilder.MissingFieldsError("output_id")
110 res, err := a.accounts.utxoDB.ReserveUTXO(ctx, *a.OutputID, a.ClientToken, b.MaxTime())
114 b.OnRollback(canceler(ctx, a.accounts, res.ID))
116 var acct *signers.Signer
117 if res.Source.AccountID == "" {
119 acct = &signers.Signer{}
121 acct, err = a.accounts.findByID(ctx, res.Source.AccountID)
127 txInput, sigInst, err := UtxoToInputs(acct, res.UTXOs[0], a.ReferenceData)
131 return b.AddInput(txInput, sigInst)
134 // Best-effort cancellation attempt to put in txbuilder.BuildResult.Rollback.
135 func canceler(ctx context.Context, m *Manager, rid uint64) func() {
137 err := m.utxoDB.Cancel(ctx, rid)
139 log.WithField("error", err).Error("Best-effort cancellation attempt to put in txbuilder.BuildResult.Rollback")
144 func UtxoToInputs(account *signers.Signer, u *utxo, refData []byte) (*legacy.TxInput, *txbuilder.SigningInstruction, error) {
145 txInput := legacy.NewSpendInput(nil, u.SourceID, u.AssetID, u.Amount, u.SourcePos, u.ControlProgram, u.RefDataHash, refData)
147 path := signers.Path(account, signers.AccountKeySpace, u.ControlProgramIndex)
148 sigInst := &txbuilder.SigningInstruction{}
150 //TODO: handle pay to script hash address
152 sigInst.AddRawWitnessKeys(account.XPubs, path, account.Quorum)
153 derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
154 derivedPK := derivedXPubs[0].PublicKey()
155 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness([]byte(derivedPK)))
157 sigInst.AddWitnessKeys(account.XPubs, path, account.Quorum)
159 return txInput, sigInst, nil
162 //NewControlAction create new control action
163 func (m *Manager) NewControlAction(amt bc.AssetAmount, accountID string, refData chainjson.Map) txbuilder.Action {
164 return &controlAction{
167 AccountID: accountID,
168 ReferenceData: refData,
172 //DecodeControlAction unmarshal JSON-encoded data of control action
173 func (m *Manager) DecodeControlAction(data []byte) (txbuilder.Action, error) {
174 a := &controlAction{accounts: m}
175 err := json.Unmarshal(data, a)
179 type controlAction struct {
182 AccountID string `json:"account_id"`
183 ReferenceData chainjson.Map `json:"reference_data"`
186 func (a *controlAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
188 if a.AccountID == "" {
189 missing = append(missing, "account_id")
191 if a.AssetId.IsZero() {
192 missing = append(missing, "asset_id")
194 if len(missing) > 0 {
195 return txbuilder.MissingFieldsError(missing...)
198 // Produce a control program, but don't insert it into the database yet.
199 acp, err := a.accounts.createControlProgram(ctx, a.AccountID, false, b.MaxTime())
203 a.accounts.insertControlProgramDelayed(ctx, b, acp)
205 return b.AddOutput(legacy.NewTxOutput(*a.AssetId, a.Amount, acp.ControlProgram, a.ReferenceData))
208 // insertControlProgramDelayed takes a template builder and an account
209 // control program that hasn't been inserted to the database yet. It
210 // registers callbacks on the TemplateBuilder so that all of the template's
211 // account control programs are batch inserted if building the rest of
212 // the template is successful.
213 func (m *Manager) insertControlProgramDelayed(ctx context.Context, b *txbuilder.TemplateBuilder, acp *CtrlProgram) {
214 m.delayedACPsMu.Lock()
215 m.delayedACPs[b] = append(m.delayedACPs[b], acp)
216 m.delayedACPsMu.Unlock()
218 b.OnRollback(func() {
219 m.delayedACPsMu.Lock()
220 delete(m.delayedACPs, b)
221 m.delayedACPsMu.Unlock()
223 b.OnBuild(func() error {
224 m.delayedACPsMu.Lock()
225 acps := m.delayedACPs[b]
226 delete(m.delayedACPs, b)
227 m.delayedACPsMu.Unlock()
229 // Insert all of the account control programs at once.
233 return m.insertAccountControlProgram(ctx, acps...)