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 "github.com/bytom/errors"
15 "github.com/bytom/protocol/bc"
16 "github.com/bytom/protocol/bc/types"
17 "github.com/bytom/protocol/vm/vmutil"
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)
27 type spendAction struct {
30 AccountID string `json:"account_id"`
31 ClientToken *string `json:"client_token"`
34 func (a *spendAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
36 if a.AccountID == "" {
37 missing = append(missing, "account_id")
39 if a.AssetId.IsZero() {
40 missing = append(missing, "asset_id")
43 return txbuilder.MissingFieldsError(missing...)
46 acct, err := a.accounts.findByID(ctx, a.AccountID)
48 return errors.Wrap(err, "get account info")
53 AccountID: a.AccountID,
55 res, err := a.accounts.utxoDB.Reserve(src, a.Amount, a.ClientToken, b.MaxTime())
57 return errors.Wrap(err, "reserving utxos")
60 // Cancel the reservation if the build gets rolled back.
61 b.OnRollback(canceler(ctx, a.accounts, res.ID))
63 for _, r := range res.UTXOs {
64 txInput, sigInst, err := UtxoToInputs(acct.Signer, r)
66 return errors.Wrap(err, "creating inputs")
68 err = b.AddInput(txInput, sigInst)
70 return errors.Wrap(err, "adding inputs")
75 acp, err := a.accounts.CreateCtrlProgramForChange(ctx, a.AccountID)
77 return errors.Wrap(err, "creating control program")
80 // Don't insert the control program until callbacks are executed.
81 a.accounts.insertControlProgramDelayed(ctx, b, acp)
83 err = b.AddOutput(types.NewTxOutput(*a.AssetId, res.Change, acp.ControlProgram))
85 return errors.Wrap(err, "adding change output")
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)
98 type spendUTXOAction struct {
100 OutputID *bc.Hash `json:"output_id"`
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 accountSigner *signers.Signer
117 if len(res.Source.AccountID) != 0 {
118 account, err := a.accounts.findByID(ctx, res.Source.AccountID)
122 accountSigner = account.Signer
125 txInput, sigInst, err := UtxoToInputs(accountSigner, res.UTXOs[0])
129 return b.AddInput(txInput, sigInst)
132 // Best-effort cancellation attempt to put in txbuilder.BuildResult.Rollback.
133 func canceler(ctx context.Context, m *Manager, rid uint64) 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")
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{}
146 return txInput, sigInst, nil
149 path := signers.Path(signer, signers.AccountKeySpace, u.ControlProgramIndex)
151 sigInst.AddWitnessKeys(signer.XPubs, path, signer.Quorum)
152 return txInput, sigInst, nil
155 address, err := common.DecodeAddress(u.Address, &consensus.MainNetParams)
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)))
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)
176 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness(script))
179 return nil, nil, errors.New("unsupport address type")
182 return txInput, sigInst, nil
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()
195 b.OnRollback(func() {
196 m.delayedACPsMu.Lock()
197 delete(m.delayedACPs, b)
198 m.delayedACPsMu.Unlock()
200 b.OnBuild(func() error {
201 m.delayedACPsMu.Lock()
202 acps := m.delayedACPs[b]
203 delete(m.delayedACPs, b)
204 m.delayedACPsMu.Unlock()
206 // Insert all of the account control programs at once.
210 return m.insertAccountControlProgram(ctx, acps...)