7 "github.com/bytom/blockchain/signers"
8 "github.com/bytom/blockchain/txbuilder"
9 "github.com/bytom/common"
10 "github.com/bytom/consensus"
11 "github.com/bytom/crypto/ed25519/chainkd"
12 "github.com/bytom/errors"
13 "github.com/bytom/protocol/bc"
14 "github.com/bytom/protocol/bc/types"
15 "github.com/bytom/protocol/vm/vmutil"
18 //DecodeSpendAction unmarshal JSON-encoded data of spend action
19 func (m *Manager) DecodeSpendAction(data []byte) (txbuilder.Action, error) {
20 a := &spendAction{accounts: m}
21 return a, json.Unmarshal(data, a)
24 type spendAction struct {
27 AccountID string `json:"account_id"`
28 UseUnconfirmed bool `json:"use_unconfirmed"`
31 // MergeSpendAction merge common assetID and accountID spend action
32 func MergeSpendAction(actions []txbuilder.Action) []txbuilder.Action {
33 resultActions := []txbuilder.Action{}
34 spendActionMap := make(map[string]*spendAction)
36 for _, act := range actions {
37 switch act := act.(type) {
39 actionKey := act.AssetId.String() + act.AccountID
40 if tmpAct, ok := spendActionMap[actionKey]; ok {
41 tmpAct.Amount += act.Amount
42 tmpAct.UseUnconfirmed = tmpAct.UseUnconfirmed || act.UseUnconfirmed
44 spendActionMap[actionKey] = act
45 resultActions = append(resultActions, act)
48 resultActions = append(resultActions, act)
54 func (a *spendAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
56 if a.AccountID == "" {
57 missing = append(missing, "account_id")
59 if a.AssetId.IsZero() {
60 missing = append(missing, "asset_id")
63 return txbuilder.MissingFieldsError(missing...)
66 acct, err := a.accounts.FindByID(a.AccountID)
68 return errors.Wrap(err, "get account info")
71 res, err := a.accounts.utxoKeeper.Reserve(a.AccountID, a.AssetId, a.Amount, a.UseUnconfirmed, b.MaxTime())
73 return errors.Wrap(err, "reserving utxos")
76 // Cancel the reservation if the build gets rolled back.
77 b.OnRollback(func() { a.accounts.utxoKeeper.Cancel(res.id) })
78 for _, r := range res.utxos {
79 txInput, sigInst, err := UtxoToInputs(acct.Signer, r)
81 return errors.Wrap(err, "creating inputs")
84 if err = b.AddInput(txInput, sigInst); err != nil {
85 return errors.Wrap(err, "adding inputs")
90 acp, err := a.accounts.CreateAddress(a.AccountID, true)
92 return errors.Wrap(err, "creating control program")
95 // Don't insert the control program until callbacks are executed.
96 a.accounts.insertControlProgramDelayed(b, acp)
97 if err = b.AddOutput(types.NewTxOutput(*a.AssetId, res.change, acp.ControlProgram)); err != nil {
98 return errors.Wrap(err, "adding change output")
104 //DecodeSpendUTXOAction unmarshal JSON-encoded data of spend utxo action
105 func (m *Manager) DecodeSpendUTXOAction(data []byte) (txbuilder.Action, error) {
106 a := &spendUTXOAction{accounts: m}
107 return a, json.Unmarshal(data, a)
110 type spendUTXOAction struct {
112 OutputID *bc.Hash `json:"output_id"`
113 UseUnconfirmed bool `json:"use_unconfirmed"`
114 Arguments []txbuilder.ContractArgument `json:"arguments"`
117 func (a *spendUTXOAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
118 if a.OutputID == nil {
119 return txbuilder.MissingFieldsError("output_id")
122 res, err := a.accounts.utxoKeeper.ReserveParticular(*a.OutputID, a.UseUnconfirmed, b.MaxTime())
127 b.OnRollback(func() { a.accounts.utxoKeeper.Cancel(res.id) })
128 var accountSigner *signers.Signer
129 if len(res.utxos[0].AccountID) != 0 {
130 account, err := a.accounts.FindByID(res.utxos[0].AccountID)
135 accountSigner = account.Signer
138 txInput, sigInst, err := UtxoToInputs(accountSigner, res.utxos[0])
143 if a.Arguments == nil {
144 return b.AddInput(txInput, sigInst)
147 sigInst = &txbuilder.SigningInstruction{}
148 if err := txbuilder.AddContractArgs(sigInst, a.Arguments); err != nil {
152 return b.AddInput(txInput, sigInst)
155 // UtxoToInputs convert an utxo to the txinput
156 func UtxoToInputs(signer *signers.Signer, u *UTXO) (*types.TxInput, *txbuilder.SigningInstruction, error) {
157 txInput := types.NewSpendInput(nil, u.SourceID, u.AssetID, u.Amount, u.SourcePos, u.ControlProgram)
158 sigInst := &txbuilder.SigningInstruction{}
160 return txInput, sigInst, nil
163 path := signers.Path(signer, signers.AccountKeySpace, u.ControlProgramIndex)
165 sigInst.AddWitnessKeys(signer.XPubs, path, signer.Quorum)
166 return txInput, sigInst, nil
169 address, err := common.DecodeAddress(u.Address, &consensus.ActiveNetParams)
174 switch address.(type) {
175 case *common.AddressWitnessPubKeyHash:
176 sigInst.AddRawWitnessKeys(signer.XPubs, path, signer.Quorum)
177 derivedXPubs := chainkd.DeriveXPubs(signer.XPubs, path)
178 derivedPK := derivedXPubs[0].PublicKey()
179 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness([]byte(derivedPK)))
181 case *common.AddressWitnessScriptHash:
182 sigInst.AddRawWitnessKeys(signer.XPubs, path, signer.Quorum)
183 path := signers.Path(signer, signers.AccountKeySpace, u.ControlProgramIndex)
184 derivedXPubs := chainkd.DeriveXPubs(signer.XPubs, path)
185 derivedPKs := chainkd.XPubKeys(derivedXPubs)
186 script, err := vmutil.P2SPMultiSigProgram(derivedPKs, signer.Quorum)
190 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness(script))
193 return nil, nil, errors.New("unsupport address type")
196 return txInput, sigInst, nil
199 // insertControlProgramDelayed takes a template builder and an account
200 // control program that hasn't been inserted to the database yet. It
201 // registers callbacks on the TemplateBuilder so that all of the template's
202 // account control programs are batch inserted if building the rest of
203 // the template is successful.
204 func (m *Manager) insertControlProgramDelayed(b *txbuilder.TemplateBuilder, acp *CtrlProgram) {
205 m.delayedACPsMu.Lock()
206 m.delayedACPs[b] = append(m.delayedACPs[b], acp)
207 m.delayedACPsMu.Unlock()
209 b.OnRollback(func() {
210 m.delayedACPsMu.Lock()
211 delete(m.delayedACPs, b)
212 m.delayedACPsMu.Unlock()
214 b.OnBuild(func() error {
215 m.delayedACPsMu.Lock()
216 acps := m.delayedACPs[b]
217 delete(m.delayedACPs, b)
218 m.delayedACPsMu.Unlock()
220 // Insert all of the account control programs at once.
224 return m.insertControlPrograms(acps...)