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(spendActions []txbuilder.Action) []txbuilder.Action {
36 actions := []txbuilder.Action{}
37 actionMap := make(map[string]*spendAction)
39 for _, act := range spendActions {
40 switch act := act.(type) {
42 actionKey := act.AssetId.String() + act.AccountID
43 if tmpAct, ok := actionMap[actionKey]; ok {
44 tmpAct.Amount += act.Amount
46 actionMap[actionKey] = act
49 actions = append(actions, act)
53 for actKey := range actionMap {
54 spend := actionMap[actKey]
55 actions = append(actions, txbuilder.Action(spend))
61 func (a *spendAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
63 if a.AccountID == "" {
64 missing = append(missing, "account_id")
66 if a.AssetId.IsZero() {
67 missing = append(missing, "asset_id")
70 return txbuilder.MissingFieldsError(missing...)
73 acct, err := a.accounts.FindByID(ctx, a.AccountID)
75 return errors.Wrap(err, "get account info")
80 AccountID: a.AccountID,
82 res, err := a.accounts.utxoDB.Reserve(src, a.Amount, a.ClientToken, b.MaxTime())
84 return errors.Wrap(err, "reserving utxos")
87 // Cancel the reservation if the build gets rolled back.
88 b.OnRollback(canceler(ctx, a.accounts, res.ID))
90 for _, r := range res.UTXOs {
91 txInput, sigInst, err := UtxoToInputs(acct.Signer, r)
93 return errors.Wrap(err, "creating inputs")
95 err = b.AddInput(txInput, sigInst)
97 return errors.Wrap(err, "adding inputs")
102 acp, err := a.accounts.CreateAddress(ctx, a.AccountID, true)
104 return errors.Wrap(err, "creating control program")
107 // Don't insert the control program until callbacks are executed.
108 a.accounts.insertControlProgramDelayed(ctx, b, acp)
110 err = b.AddOutput(types.NewTxOutput(*a.AssetId, res.Change, acp.ControlProgram))
112 return errors.Wrap(err, "adding change output")
118 //DecodeSpendUTXOAction unmarshal JSON-encoded data of spend utxo action
119 func (m *Manager) DecodeSpendUTXOAction(data []byte) (txbuilder.Action, error) {
120 a := &spendUTXOAction{accounts: m}
121 err := json.Unmarshal(data, a)
125 type spendUTXOAction struct {
127 OutputID *bc.Hash `json:"output_id"`
129 ClientToken *string `json:"client_token"`
132 func (a *spendUTXOAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
133 if a.OutputID == nil {
134 return txbuilder.MissingFieldsError("output_id")
137 res, err := a.accounts.utxoDB.ReserveUTXO(ctx, *a.OutputID, a.ClientToken, b.MaxTime())
141 b.OnRollback(canceler(ctx, a.accounts, res.ID))
143 var accountSigner *signers.Signer
144 if len(res.Source.AccountID) != 0 {
145 account, err := a.accounts.FindByID(ctx, res.Source.AccountID)
149 accountSigner = account.Signer
152 txInput, sigInst, err := UtxoToInputs(accountSigner, res.UTXOs[0])
156 return b.AddInput(txInput, sigInst)
159 // Best-effort cancellation attempt to put in txbuilder.BuildResult.Rollback.
160 func canceler(ctx context.Context, m *Manager, rid uint64) func() {
162 if err := m.utxoDB.Cancel(ctx, rid); err != nil {
163 log.WithField("error", err).Error("Best-effort cancellation attempt to put in txbuilder.BuildResult.Rollback")
168 // UtxoToInputs convert an utxo to the txinput
169 func UtxoToInputs(signer *signers.Signer, u *UTXO) (*types.TxInput, *txbuilder.SigningInstruction, error) {
170 txInput := types.NewSpendInput(nil, u.SourceID, u.AssetID, u.Amount, u.SourcePos, u.ControlProgram)
171 sigInst := &txbuilder.SigningInstruction{}
173 return txInput, sigInst, nil
176 path := signers.Path(signer, signers.AccountKeySpace, u.ControlProgramIndex)
178 sigInst.AddWitnessKeys(signer.XPubs, path, signer.Quorum)
179 return txInput, sigInst, nil
182 address, err := common.DecodeAddress(u.Address, &consensus.ActiveNetParams)
187 switch address.(type) {
188 case *common.AddressWitnessPubKeyHash:
189 sigInst.AddRawWitnessKeys(signer.XPubs, path, signer.Quorum)
190 derivedXPubs := chainkd.DeriveXPubs(signer.XPubs, path)
191 derivedPK := derivedXPubs[0].PublicKey()
192 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness([]byte(derivedPK)))
194 case *common.AddressWitnessScriptHash:
195 sigInst.AddRawWitnessKeys(signer.XPubs, path, signer.Quorum)
196 path := signers.Path(signer, signers.AccountKeySpace, u.ControlProgramIndex)
197 derivedXPubs := chainkd.DeriveXPubs(signer.XPubs, path)
198 derivedPKs := chainkd.XPubKeys(derivedXPubs)
199 script, err := vmutil.P2SPMultiSigProgram(derivedPKs, signer.Quorum)
203 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness(script))
206 return nil, nil, errors.New("unsupport address type")
209 return txInput, sigInst, nil
212 // insertControlProgramDelayed takes a template builder and an account
213 // control program that hasn't been inserted to the database yet. It
214 // registers callbacks on the TemplateBuilder so that all of the template's
215 // account control programs are batch inserted if building the rest of
216 // the template is successful.
217 func (m *Manager) insertControlProgramDelayed(ctx context.Context, b *txbuilder.TemplateBuilder, acp *CtrlProgram) {
218 m.delayedACPsMu.Lock()
219 m.delayedACPs[b] = append(m.delayedACPs[b], acp)
220 m.delayedACPsMu.Unlock()
222 b.OnRollback(func() {
223 m.delayedACPsMu.Lock()
224 delete(m.delayedACPs, b)
225 m.delayedACPsMu.Unlock()
227 b.OnBuild(func() error {
228 m.delayedACPsMu.Lock()
229 acps := m.delayedACPs[b]
230 delete(m.delayedACPs, b)
231 m.delayedACPsMu.Unlock()
233 // Insert all of the account control programs at once.
237 return m.insertAccountControlProgram(ctx, acps...)