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/types"
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 ClientToken *string `json:"client_token"`
35 func (a *spendAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
37 if a.AccountID == "" {
38 missing = append(missing, "account_id")
40 if a.AssetId.IsZero() {
41 missing = append(missing, "asset_id")
44 return txbuilder.MissingFieldsError(missing...)
47 acct, err := a.accounts.findByID(ctx, a.AccountID)
49 return errors.Wrap(err, "get account info")
54 AccountID: a.AccountID,
56 res, err := a.accounts.utxoDB.Reserve(src, a.Amount, a.ClientToken, b.MaxTime())
58 return errors.Wrap(err, "reserving utxos")
61 // Cancel the reservation if the build gets rolled back.
62 b.OnRollback(canceler(ctx, a.accounts, res.ID))
64 for _, r := range res.UTXOs {
65 txInput, sigInst, err := UtxoToInputs(acct.Signer, r)
67 return errors.Wrap(err, "creating inputs")
69 err = b.AddInput(txInput, sigInst)
71 return errors.Wrap(err, "adding inputs")
76 acp, err := a.accounts.CreateAddress(ctx, a.AccountID, true)
78 return errors.Wrap(err, "creating control program")
81 // Don't insert the control program until callbacks are executed.
82 a.accounts.insertControlProgramDelayed(ctx, b, acp)
84 err = b.AddOutput(types.NewTxOutput(*a.AssetId, res.Change, acp.ControlProgram))
86 return errors.Wrap(err, "adding change output")
92 //DecodeSpendUTXOAction unmarshal JSON-encoded data of spend utxo action
93 func (m *Manager) DecodeSpendUTXOAction(data []byte) (txbuilder.Action, error) {
94 a := &spendUTXOAction{accounts: m}
95 err := json.Unmarshal(data, a)
99 type spendUTXOAction struct {
101 OutputID *bc.Hash `json:"output_id"`
103 ClientToken *string `json:"client_token"`
106 func (a *spendUTXOAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
107 if a.OutputID == nil {
108 return txbuilder.MissingFieldsError("output_id")
111 res, err := a.accounts.utxoDB.ReserveUTXO(ctx, *a.OutputID, a.ClientToken, b.MaxTime())
115 b.OnRollback(canceler(ctx, a.accounts, res.ID))
117 var accountSigner *signers.Signer
118 if len(res.Source.AccountID) != 0 {
119 account, err := a.accounts.findByID(ctx, res.Source.AccountID)
123 accountSigner = account.Signer
126 txInput, sigInst, err := UtxoToInputs(accountSigner, res.UTXOs[0])
130 return b.AddInput(txInput, sigInst)
133 // Best-effort cancellation attempt to put in txbuilder.BuildResult.Rollback.
134 func canceler(ctx context.Context, m *Manager, rid uint64) func() {
136 if err := m.utxoDB.Cancel(ctx, rid); err != nil {
137 log.WithField("error", err).Error("Best-effort cancellation attempt to put in txbuilder.BuildResult.Rollback")
142 // UtxoToInputs convert an utxo to the txinput
143 func UtxoToInputs(signer *signers.Signer, u *UTXO) (*types.TxInput, *txbuilder.SigningInstruction, error) {
144 txInput := types.NewSpendInput(nil, u.SourceID, u.AssetID, u.Amount, u.SourcePos, u.ControlProgram)
145 sigInst := &txbuilder.SigningInstruction{}
147 return txInput, sigInst, nil
150 path := signers.Path(signer, signers.AccountKeySpace, u.ControlProgramIndex)
152 sigInst.AddWitnessKeys(signer.XPubs, path, signer.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(signer.XPubs, path, signer.Quorum)
164 derivedXPubs := chainkd.DeriveXPubs(signer.XPubs, path)
165 derivedPK := derivedXPubs[0].PublicKey()
166 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness([]byte(derivedPK)))
168 case *common.AddressWitnessScriptHash:
169 sigInst.AddRawWitnessKeys(signer.XPubs, path, signer.Quorum)
170 path := signers.Path(signer, signers.AccountKeySpace, u.ControlProgramIndex)
171 derivedXPubs := chainkd.DeriveXPubs(signer.XPubs, path)
172 derivedPKs := chainkd.XPubKeys(derivedXPubs)
173 script, err := vmutil.P2SPMultiSigProgram(derivedPKs, signer.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.CreateAddress(ctx, a.AccountID, false)
227 a.accounts.insertControlProgramDelayed(ctx, b, acp)
229 return b.AddOutput(types.NewTxOutput(*a.AssetId, a.Amount, acp.ControlProgram))
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...)