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 // MergeSpendAction merge common assetID and accountID spend action
35 func MergeSpendAction(actions []txbuilder.Action) []txbuilder.Action {
36 resultActions := []txbuilder.Action{}
37 spendActionMap := make(map[string]*spendAction)
39 for _, act := range actions {
40 switch act := act.(type) {
42 actionKey := act.AssetId.String() + act.AccountID
43 if tmpAct, ok := spendActionMap[actionKey]; ok {
44 tmpAct.Amount += act.Amount
46 spendActionMap[actionKey] = act
47 resultActions = append(resultActions, act)
50 resultActions = append(resultActions, act)
56 func (a *spendAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
58 if a.AccountID == "" {
59 missing = append(missing, "account_id")
61 if a.AssetId.IsZero() {
62 missing = append(missing, "asset_id")
65 return txbuilder.MissingFieldsError(missing...)
68 acct, err := a.accounts.FindByID(ctx, a.AccountID)
70 return errors.Wrap(err, "get account info")
75 AccountID: a.AccountID,
77 res, err := a.accounts.utxoDB.Reserve(src, a.Amount, a.ClientToken, b.MaxTime())
79 return errors.Wrap(err, "reserving utxos")
82 // Cancel the reservation if the build gets rolled back.
83 b.OnRollback(canceler(ctx, a.accounts, res.ID))
85 for _, r := range res.UTXOs {
86 txInput, sigInst, err := UtxoToInputs(acct.Signer, r)
88 return errors.Wrap(err, "creating inputs")
90 err = b.AddInput(txInput, sigInst)
92 return errors.Wrap(err, "adding inputs")
97 acp, err := a.accounts.CreateAddress(ctx, a.AccountID, true)
99 return errors.Wrap(err, "creating control program")
102 // Don't insert the control program until callbacks are executed.
103 a.accounts.insertControlProgramDelayed(ctx, b, acp)
105 err = b.AddOutput(types.NewTxOutput(*a.AssetId, res.Change, acp.ControlProgram))
107 return errors.Wrap(err, "adding change output")
113 //DecodeSpendUTXOAction unmarshal JSON-encoded data of spend utxo action
114 func (m *Manager) DecodeSpendUTXOAction(data []byte) (txbuilder.Action, error) {
115 a := &spendUTXOAction{accounts: m}
116 err := json.Unmarshal(data, a)
120 type spendUTXOAction struct {
122 OutputID *bc.Hash `json:"output_id"`
124 ClientToken *string `json:"client_token"`
127 func (a *spendUTXOAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
128 if a.OutputID == nil {
129 return txbuilder.MissingFieldsError("output_id")
132 res, err := a.accounts.utxoDB.ReserveUTXO(ctx, *a.OutputID, a.ClientToken, b.MaxTime())
136 b.OnRollback(canceler(ctx, a.accounts, res.ID))
138 var accountSigner *signers.Signer
139 if len(res.Source.AccountID) != 0 {
140 account, err := a.accounts.FindByID(ctx, res.Source.AccountID)
144 accountSigner = account.Signer
147 txInput, sigInst, err := UtxoToInputs(accountSigner, res.UTXOs[0])
151 return b.AddInput(txInput, sigInst)
154 // Best-effort cancellation attempt to put in txbuilder.BuildResult.Rollback.
155 func canceler(ctx context.Context, m *Manager, rid uint64) func() {
157 if err := m.utxoDB.Cancel(ctx, rid); err != nil {
158 log.WithField("error", err).Error("Best-effort cancellation attempt to put in txbuilder.BuildResult.Rollback")
163 // UtxoToInputs convert an utxo to the txinput
164 func UtxoToInputs(signer *signers.Signer, u *UTXO) (*types.TxInput, *txbuilder.SigningInstruction, error) {
165 txInput := types.NewSpendInput(nil, u.SourceID, u.AssetID, u.Amount, u.SourcePos, u.ControlProgram)
166 sigInst := &txbuilder.SigningInstruction{}
168 return txInput, sigInst, nil
171 path := signers.Path(signer, signers.AccountKeySpace, u.ControlProgramIndex)
173 sigInst.AddWitnessKeys(signer.XPubs, path, signer.Quorum)
174 return txInput, sigInst, nil
177 address, err := common.DecodeAddress(u.Address, &consensus.ActiveNetParams)
182 switch address.(type) {
183 case *common.AddressWitnessPubKeyHash:
184 sigInst.AddRawWitnessKeys(signer.XPubs, path, signer.Quorum)
185 derivedXPubs := chainkd.DeriveXPubs(signer.XPubs, path)
186 derivedPK := derivedXPubs[0].PublicKey()
187 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness([]byte(derivedPK)))
189 case *common.AddressWitnessScriptHash:
190 sigInst.AddRawWitnessKeys(signer.XPubs, path, signer.Quorum)
191 path := signers.Path(signer, signers.AccountKeySpace, u.ControlProgramIndex)
192 derivedXPubs := chainkd.DeriveXPubs(signer.XPubs, path)
193 derivedPKs := chainkd.XPubKeys(derivedXPubs)
194 script, err := vmutil.P2SPMultiSigProgram(derivedPKs, signer.Quorum)
198 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness(script))
201 return nil, nil, errors.New("unsupport address type")
204 return txInput, sigInst, nil
207 // insertControlProgramDelayed takes a template builder and an account
208 // control program that hasn't been inserted to the database yet. It
209 // registers callbacks on the TemplateBuilder so that all of the template's
210 // account control programs are batch inserted if building the rest of
211 // the template is successful.
212 func (m *Manager) insertControlProgramDelayed(ctx context.Context, b *txbuilder.TemplateBuilder, acp *CtrlProgram) {
213 m.delayedACPsMu.Lock()
214 m.delayedACPs[b] = append(m.delayedACPs[b], acp)
215 m.delayedACPsMu.Unlock()
217 b.OnRollback(func() {
218 m.delayedACPsMu.Lock()
219 delete(m.delayedACPs, b)
220 m.delayedACPsMu.Unlock()
222 b.OnBuild(func() error {
223 m.delayedACPsMu.Lock()
224 acps := m.delayedACPs[b]
225 delete(m.delayedACPs, b)
226 m.delayedACPsMu.Unlock()
228 // Insert all of the account control programs at once.
232 return m.insertAccountControlProgram(ctx, acps...)