8 "github.com/bytom/blockchain/signers"
9 "github.com/bytom/blockchain/txbuilder"
10 "github.com/bytom/common"
11 "github.com/bytom/consensus"
12 "github.com/bytom/crypto/ed25519/chainkd"
13 chainjson "github.com/bytom/encoding/json"
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 return a, json.Unmarshal(data, a)
26 type spendAction struct {
29 AccountID string `json:"account_id"`
30 UseUnconfirmed bool `json:"use_unconfirmed"`
33 // MergeSpendAction merge common assetID and accountID spend action
34 func MergeSpendAction(actions []txbuilder.Action) []txbuilder.Action {
35 resultActions := []txbuilder.Action{}
36 spendActionMap := make(map[string]*spendAction)
38 for _, act := range actions {
39 switch act := act.(type) {
41 actionKey := act.AssetId.String() + act.AccountID
42 if tmpAct, ok := spendActionMap[actionKey]; ok {
43 tmpAct.Amount += act.Amount
44 tmpAct.UseUnconfirmed = tmpAct.UseUnconfirmed || act.UseUnconfirmed
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(a.AccountID)
70 return errors.Wrap(err, "get account info")
73 res, err := a.accounts.utxoKeeper.Reserve(a.AccountID, a.AssetId, a.Amount, a.UseUnconfirmed, b.MaxTime())
75 return errors.Wrap(err, "reserving utxos")
78 // Cancel the reservation if the build gets rolled back.
79 b.OnRollback(func() { a.accounts.utxoKeeper.Cancel(res.id) })
80 for _, r := range res.utxos {
81 txInput, sigInst, err := UtxoToInputs(acct.Signer, r)
83 return errors.Wrap(err, "creating inputs")
86 if err = b.AddInput(txInput, sigInst); err != nil {
87 return errors.Wrap(err, "adding inputs")
92 acp, err := a.accounts.CreateAddress(a.AccountID, true)
94 return errors.Wrap(err, "creating control program")
97 // Don't insert the control program until callbacks are executed.
98 a.accounts.insertControlProgramDelayed(b, acp)
99 if err = b.AddOutput(types.NewTxOutput(*a.AssetId, res.change, acp.ControlProgram)); err != nil {
100 return errors.Wrap(err, "adding change output")
106 //DecodeSpendUTXOAction unmarshal JSON-encoded data of spend utxo action
107 func (m *Manager) DecodeSpendUTXOAction(data []byte) (txbuilder.Action, error) {
108 a := &spendUTXOAction{accounts: m}
109 return a, json.Unmarshal(data, a)
112 type spendUTXOAction struct {
114 OutputID *bc.Hash `json:"output_id"`
115 UseUnconfirmed bool `json:"use_unconfirmed"`
116 Arguments []contractArgument `json:"arguments"`
119 // contractArgument for smart contract
120 type contractArgument struct {
121 Type string `json:"type"`
122 RawData json.RawMessage `json:"raw_data"`
125 // rawTxSigArgument is signature-related argument for run contract
126 type rawTxSigArgument struct {
127 RootXPub chainkd.XPub `json:"xpub"`
128 Path []chainjson.HexBytes `json:"derivation_path"`
131 // dataArgument is the other argument for run contract
132 type dataArgument struct {
133 Value string `json:"value"`
136 func (a *spendUTXOAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
137 if a.OutputID == nil {
138 return txbuilder.MissingFieldsError("output_id")
141 res, err := a.accounts.utxoKeeper.ReserveParticular(*a.OutputID, a.UseUnconfirmed, b.MaxTime())
146 b.OnRollback(func() { a.accounts.utxoKeeper.Cancel(res.id) })
147 var accountSigner *signers.Signer
148 if len(res.utxos[0].AccountID) != 0 {
149 account, err := a.accounts.FindByID(res.utxos[0].AccountID)
154 accountSigner = account.Signer
157 txInput, sigInst, err := UtxoToInputs(accountSigner, res.utxos[0])
162 if a.Arguments == nil {
163 return b.AddInput(txInput, sigInst)
166 sigInst = &txbuilder.SigningInstruction{}
167 for _, arg := range a.Arguments {
169 case "raw_tx_signature":
170 rawTxSig := &rawTxSigArgument{}
171 if err = json.Unmarshal(arg.RawData, rawTxSig); err != nil {
175 // convert path form chainjson.HexBytes to byte
177 for _, p := range rawTxSig.Path {
178 path = append(path, []byte(p))
180 sigInst.AddRawWitnessKeys([]chainkd.XPub{rawTxSig.RootXPub}, path, 1)
183 data := &dataArgument{}
184 if err = json.Unmarshal(arg.RawData, data); err != nil {
188 value, err := hex.DecodeString(data.Value)
192 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness(value))
195 return errors.New("contract argument type is not exist")
198 return b.AddInput(txInput, sigInst)
201 // UtxoToInputs convert an utxo to the txinput
202 func UtxoToInputs(signer *signers.Signer, u *UTXO) (*types.TxInput, *txbuilder.SigningInstruction, error) {
203 txInput := types.NewSpendInput(nil, u.SourceID, u.AssetID, u.Amount, u.SourcePos, u.ControlProgram)
204 sigInst := &txbuilder.SigningInstruction{}
206 return txInput, sigInst, nil
209 path := signers.Path(signer, signers.AccountKeySpace, u.ControlProgramIndex)
211 sigInst.AddWitnessKeys(signer.XPubs, path, signer.Quorum)
212 return txInput, sigInst, nil
215 address, err := common.DecodeAddress(u.Address, &consensus.ActiveNetParams)
220 switch address.(type) {
221 case *common.AddressWitnessPubKeyHash:
222 sigInst.AddRawWitnessKeys(signer.XPubs, path, signer.Quorum)
223 derivedXPubs := chainkd.DeriveXPubs(signer.XPubs, path)
224 derivedPK := derivedXPubs[0].PublicKey()
225 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness([]byte(derivedPK)))
227 case *common.AddressWitnessScriptHash:
228 sigInst.AddRawWitnessKeys(signer.XPubs, path, signer.Quorum)
229 path := signers.Path(signer, signers.AccountKeySpace, u.ControlProgramIndex)
230 derivedXPubs := chainkd.DeriveXPubs(signer.XPubs, path)
231 derivedPKs := chainkd.XPubKeys(derivedXPubs)
232 script, err := vmutil.P2SPMultiSigProgram(derivedPKs, signer.Quorum)
236 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness(script))
239 return nil, nil, errors.New("unsupport address type")
242 return txInput, sigInst, nil
245 // insertControlProgramDelayed takes a template builder and an account
246 // control program that hasn't been inserted to the database yet. It
247 // registers callbacks on the TemplateBuilder so that all of the template's
248 // account control programs are batch inserted if building the rest of
249 // the template is successful.
250 func (m *Manager) insertControlProgramDelayed(b *txbuilder.TemplateBuilder, acp *CtrlProgram) {
251 m.delayedACPsMu.Lock()
252 m.delayedACPs[b] = append(m.delayedACPs[b], acp)
253 m.delayedACPsMu.Unlock()
255 b.OnRollback(func() {
256 m.delayedACPsMu.Lock()
257 delete(m.delayedACPs, b)
258 m.delayedACPsMu.Unlock()
260 b.OnBuild(func() error {
261 m.delayedACPsMu.Lock()
262 acps := m.delayedACPs[b]
263 delete(m.delayedACPs, b)
264 m.delayedACPsMu.Unlock()
266 // Insert all of the account control programs at once.
270 return m.insertControlPrograms(acps...)