7 log "github.com/sirupsen/logrus"
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"
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)
28 type spendAction struct {
31 AccountID string `json:"account_id"`
32 ReferenceData chainjson.Map `json:"reference_data"`
33 ClientToken *string `json:"client_token"`
36 func (a *spendAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
38 if a.AccountID == "" {
39 missing = append(missing, "account_id")
41 if a.AssetId.IsZero() {
42 missing = append(missing, "asset_id")
45 return txbuilder.MissingFieldsError(missing...)
48 acct, err := a.accounts.findByID(ctx, a.AccountID)
50 return errors.Wrap(err, "get account info")
55 AccountID: a.AccountID,
57 res, err := a.accounts.utxoDB.Reserve(src, a.Amount, a.ClientToken, b.MaxTime())
59 return errors.Wrap(err, "reserving utxos")
62 // Cancel the reservation if the build gets rolled back.
63 b.OnRollback(canceler(ctx, a.accounts, res.ID))
65 for _, r := range res.UTXOs {
66 txInput, sigInst, err := UtxoToInputs(acct, r, a.ReferenceData)
68 return errors.Wrap(err, "creating inputs")
70 err = b.AddInput(txInput, sigInst)
72 return errors.Wrap(err, "adding inputs")
77 acp, err := a.accounts.createControlProgram(ctx, a.AccountID, true, b.MaxTime())
79 return errors.Wrap(err, "creating control program")
82 // Don't insert the control program until callbacks are executed.
83 a.accounts.insertControlProgramDelayed(ctx, b, acp)
85 err = b.AddOutput(legacy.NewTxOutput(*a.AssetId, res.Change, acp.ControlProgram, nil))
87 return errors.Wrap(err, "adding change output")
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)
100 type spendUTXOAction struct {
102 OutputID *bc.Hash `json:"output_id"`
104 ReferenceData chainjson.Map `json:"reference_data"`
105 ClientToken *string `json:"client_token"`
108 func (a *spendUTXOAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
109 if a.OutputID == nil {
110 return txbuilder.MissingFieldsError("output_id")
113 res, err := a.accounts.utxoDB.ReserveUTXO(ctx, *a.OutputID, a.ClientToken, b.MaxTime())
117 b.OnRollback(canceler(ctx, a.accounts, res.ID))
119 var acct *signers.Signer
120 if res.Source.AccountID == "" {
122 acct = &signers.Signer{}
124 acct, err = a.accounts.findByID(ctx, res.Source.AccountID)
130 txInput, sigInst, err := UtxoToInputs(acct, res.UTXOs[0], a.ReferenceData)
134 return b.AddInput(txInput, sigInst)
137 // Best-effort cancellation attempt to put in txbuilder.BuildResult.Rollback.
138 func canceler(ctx context.Context, m *Manager, rid uint64) 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")
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{}
152 sigInst.AddWitnessKeys(account.XPubs, path, account.Quorum)
153 return txInput, sigInst, nil
156 address, err := common.DecodeAddress(u.Address, &consensus.MainNetParams)
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)))
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)
177 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness(script))
180 return nil, nil, errors.New("unsupport address type")
183 return txInput, sigInst, nil
186 //NewControlAction create new control action
187 func (m *Manager) NewControlAction(amt bc.AssetAmount, accountID string, refData chainjson.Map) txbuilder.Action {
188 return &controlAction{
191 AccountID: accountID,
192 ReferenceData: refData,
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)
203 type controlAction struct {
206 AccountID string `json:"account_id"`
207 ReferenceData chainjson.Map `json:"reference_data"`
210 func (a *controlAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
212 if a.AccountID == "" {
213 missing = append(missing, "account_id")
215 if a.AssetId.IsZero() {
216 missing = append(missing, "asset_id")
218 if len(missing) > 0 {
219 return txbuilder.MissingFieldsError(missing...)
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())
227 a.accounts.insertControlProgramDelayed(ctx, b, acp)
229 return b.AddOutput(legacy.NewTxOutput(*a.AssetId, a.Amount, acp.ControlProgram, a.ReferenceData))
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()
242 b.OnRollback(func() {
243 m.delayedACPsMu.Lock()
244 delete(m.delayedACPs, b)
245 m.delayedACPsMu.Unlock()
247 b.OnBuild(func() error {
248 m.delayedACPsMu.Lock()
249 acps := m.delayedACPs[b]
250 delete(m.delayedACPs, b)
251 m.delayedACPsMu.Unlock()
253 // Insert all of the account control programs at once.
257 return m.insertAccountControlProgram(ctx, acps...)