}
defer os.RemoveAll(dirPath)
- testDB := dbm.NewDB("testdb", "leveldb", "temp")
+ testDB := dbm.NewDB("testdb", "memdb", "temp")
defer os.RemoveAll("temp")
store := leveldb.NewStore(testDB)
"github.com/bytom/protocol/vm/vmutil"
)
+var (
+ //chainTxUtxoNum maximum utxo quantity in a tx
+ chainTxUtxoNum = 10
+ //chainTxMergeGas chain tx gas
+ chainTxMergeGas = uint64(10000000)
+)
+
//DecodeSpendAction unmarshal JSON-encoded data of spend action
func (m *Manager) DecodeSpendAction(data []byte) (txbuilder.Action, error) {
a := &spendAction{accounts: m}
UseUnconfirmed bool `json:"use_unconfirmed"`
}
+func (a *spendAction) ActionType() string {
+ return "spend_account"
+}
+
// MergeSpendAction merge common assetID and accountID spend action
func MergeSpendAction(actions []txbuilder.Action) []txbuilder.Action {
resultActions := []txbuilder.Action{}
return resultActions
}
+//calcMergeGas calculate the gas required that n utxos are merged into one
+func calcMergeGas(num int) uint64 {
+ gas := uint64(0)
+ for num > 1 {
+ gas += chainTxMergeGas
+ num -= chainTxUtxoNum - 1
+ }
+ return gas
+}
+
+func (m *Manager) reserveBtmUtxoChain(builder *txbuilder.TemplateBuilder, accountID string, amount uint64, useUnconfirmed bool) ([]*UTXO, error) {
+ reservedAmount := uint64(0)
+ utxos := []*UTXO{}
+ for gasAmount := uint64(0); reservedAmount < gasAmount+amount; gasAmount = calcMergeGas(len(utxos)) {
+ reserveAmount := amount + gasAmount - reservedAmount
+ res, err := m.utxoKeeper.Reserve(accountID, consensus.BTMAssetID, reserveAmount, useUnconfirmed, builder.MaxTime())
+ if err != nil {
+ return nil, err
+ }
+
+ builder.OnRollback(func() { m.utxoKeeper.Cancel(res.id) })
+ reservedAmount += reserveAmount + res.change
+ utxos = append(utxos, res.utxos[:]...)
+ }
+ return utxos, nil
+}
+
+func (m *Manager) buildBtmTxChain(utxos []*UTXO, signer *signers.Signer) ([]*txbuilder.Template, *UTXO, error) {
+ if len(utxos) == 0 {
+ return nil, nil, errors.New("mergeSpendActionUTXO utxos num 0")
+ }
+
+ tpls := []*txbuilder.Template{}
+ if len(utxos) == 1 {
+ return tpls, utxos[len(utxos)-1], nil
+ }
+
+ acp, err := m.GetLocalCtrlProgramByAddress(utxos[0].Address)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ buildAmount := uint64(0)
+ builder := &txbuilder.TemplateBuilder{}
+ for index := 0; index < len(utxos); index++ {
+ input, sigInst, err := UtxoToInputs(signer, utxos[index])
+ if err != nil {
+ return nil, nil, err
+ }
+
+ if err = builder.AddInput(input, sigInst); err != nil {
+ return nil, nil, err
+ }
+
+ buildAmount += input.Amount()
+ if builder.InputCount() != chainTxUtxoNum && index != len(utxos)-1 {
+ continue
+ }
+
+ outAmount := buildAmount - chainTxMergeGas
+ output := types.NewTxOutput(*consensus.BTMAssetID, outAmount, acp.ControlProgram)
+ if err := builder.AddOutput(output); err != nil {
+ return nil, nil, err
+ }
+
+ tpl, _, err := builder.Build()
+ if err != nil {
+ return nil, nil, err
+ }
+
+ bcOut, err := tpl.Transaction.Output(*tpl.Transaction.ResultIds[0])
+ if err != nil {
+ return nil, nil, err
+ }
+
+ utxos = append(utxos, &UTXO{
+ OutputID: *tpl.Transaction.ResultIds[0],
+ AssetID: *consensus.BTMAssetID,
+ Amount: outAmount,
+ ControlProgram: acp.ControlProgram,
+ SourceID: *bcOut.Source.Ref,
+ SourcePos: bcOut.Source.Position,
+ ControlProgramIndex: acp.KeyIndex,
+ Address: acp.Address,
+ })
+
+ tpls = append(tpls, tpl)
+ buildAmount = 0
+ builder = &txbuilder.TemplateBuilder{}
+ if index == len(utxos)-2 {
+ break
+ }
+ }
+ return tpls, utxos[len(utxos)-1], nil
+}
+
+// SpendAccountChain build the spend action with auto merge utxo function
+func SpendAccountChain(ctx context.Context, builder *txbuilder.TemplateBuilder, action txbuilder.Action) ([]*txbuilder.Template, error) {
+ act, ok := action.(*spendAction)
+ if !ok {
+ return nil, errors.New("fail to convert the spend action")
+ }
+ if *act.AssetId != *consensus.BTMAssetID {
+ return nil, errors.New("spend chain action only support BTM")
+ }
+
+ utxos, err := act.accounts.reserveBtmUtxoChain(builder, act.AccountID, act.Amount, act.UseUnconfirmed)
+ if err != nil {
+ return nil, err
+ }
+
+ acct, err := act.accounts.FindByID(act.AccountID)
+ if err != nil {
+ return nil, err
+ }
+
+ tpls, utxo, err := act.accounts.buildBtmTxChain(utxos, acct.Signer)
+ if err != nil {
+ return nil, err
+ }
+
+ input, sigInst, err := UtxoToInputs(acct.Signer, utxo)
+ if err != nil {
+ return nil, err
+ }
+
+ if err := builder.AddInput(input, sigInst); err != nil {
+ return nil, err
+ }
+
+ if utxo.Amount > act.Amount {
+ if err = builder.AddOutput(types.NewTxOutput(*consensus.BTMAssetID, utxo.Amount-act.Amount, utxo.ControlProgram)); err != nil {
+ return nil, errors.Wrap(err, "adding change output")
+ }
+ }
+ return tpls, nil
+}
+
func (a *spendAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
var missing []string
if a.AccountID == "" {
Arguments []txbuilder.ContractArgument `json:"arguments"`
}
+func (a *spendUTXOAction) ActionType() string {
+ return "spend_account_unspent_output"
+}
+
func (a *spendUTXOAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
if a.OutputID == nil {
return txbuilder.MissingFieldsError("output_id")
package account
import (
+ "encoding/json"
"testing"
+ "time"
"github.com/bytom/blockchain/txbuilder"
+ "github.com/bytom/consensus"
+ "github.com/bytom/crypto/ed25519/chainkd"
"github.com/bytom/protocol/bc"
+ "github.com/bytom/testutil"
)
+func TestReserveBtmUtxoChain(t *testing.T) {
+ chainTxUtxoNum = 3
+ utxos := []*UTXO{}
+ m := mockAccountManager(t)
+ for i := uint64(1); i <= 20; i++ {
+ utxo := &UTXO{
+ OutputID: bc.Hash{V0: i},
+ AccountID: "TestAccountID",
+ AssetID: *consensus.BTMAssetID,
+ Amount: i * chainTxMergeGas,
+ }
+ utxos = append(utxos, utxo)
+
+ data, err := json.Marshal(utxo)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ m.db.Set(StandardUTXOKey(utxo.OutputID), data)
+ }
+
+ cases := []struct {
+ amount uint64
+ want []uint64
+ err bool
+ }{
+ {
+ amount: 1 * chainTxMergeGas,
+ want: []uint64{1},
+ },
+ {
+ amount: 888888 * chainTxMergeGas,
+ want: []uint64{},
+ err: true,
+ },
+ {
+ amount: 7 * chainTxMergeGas,
+ want: []uint64{4, 3, 1},
+ },
+ {
+ amount: 15 * chainTxMergeGas,
+ want: []uint64{5, 4, 3, 2, 1, 6},
+ },
+ {
+ amount: 163 * chainTxMergeGas,
+ want: []uint64{20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 2, 1, 3},
+ },
+ }
+
+ for i, c := range cases {
+ m.utxoKeeper.expireReservation(time.Unix(999999999, 0))
+ utxos, err := m.reserveBtmUtxoChain(&txbuilder.TemplateBuilder{}, "TestAccountID", c.amount, false)
+
+ if err != nil != c.err {
+ t.Fatalf("case %d got err %v want err = %v", i, err, c.err)
+ }
+
+ got := []uint64{}
+ for _, utxo := range utxos {
+ got = append(got, utxo.Amount/chainTxMergeGas)
+ }
+
+ if !testutil.DeepEqual(got, c.want) {
+ t.Fatalf("case %d got %d want %d", i, got, c.want)
+ }
+ }
+
+}
+
+func TestBuildBtmTxChain(t *testing.T) {
+ chainTxUtxoNum = 3
+ m := mockAccountManager(t)
+ cases := []struct {
+ inputUtxo []uint64
+ wantInput [][]uint64
+ wantOutput [][]uint64
+ wantUtxo uint64
+ }{
+ {
+ inputUtxo: []uint64{5},
+ wantInput: [][]uint64{},
+ wantOutput: [][]uint64{},
+ wantUtxo: 5 * chainTxMergeGas,
+ },
+ {
+ inputUtxo: []uint64{5, 4},
+ wantInput: [][]uint64{
+ []uint64{5, 4},
+ },
+ wantOutput: [][]uint64{
+ []uint64{8},
+ },
+ wantUtxo: 8 * chainTxMergeGas,
+ },
+ {
+ inputUtxo: []uint64{5, 4, 1, 1},
+ wantInput: [][]uint64{
+ []uint64{5, 4, 1},
+ []uint64{1, 9},
+ },
+ wantOutput: [][]uint64{
+ []uint64{9},
+ []uint64{9},
+ },
+ wantUtxo: 9 * chainTxMergeGas,
+ },
+ {
+ inputUtxo: []uint64{22, 123, 53, 234, 23, 4, 2423, 24, 23, 43, 34, 234, 234, 24},
+ wantInput: [][]uint64{
+ []uint64{22, 123, 53},
+ []uint64{234, 23, 4},
+ []uint64{2423, 24, 23},
+ []uint64{43, 34, 234},
+ []uint64{234, 24, 197},
+ []uint64{260, 2469, 310},
+ []uint64{454, 3038},
+ },
+ wantOutput: [][]uint64{
+ []uint64{197},
+ []uint64{260},
+ []uint64{2469},
+ []uint64{310},
+ []uint64{454},
+ []uint64{3038},
+ []uint64{3491},
+ },
+ wantUtxo: 3491 * chainTxMergeGas,
+ },
+ }
+
+ acct, err := m.Create([]chainkd.XPub{testutil.TestXPub}, 1, "testAccount")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ acp, err := m.CreateAddress(acct.ID, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ for caseIndex, c := range cases {
+ utxos := []*UTXO{}
+ for _, amount := range c.inputUtxo {
+ utxos = append(utxos, &UTXO{
+ Amount: amount * chainTxMergeGas,
+ AssetID: *consensus.BTMAssetID,
+ Address: acp.Address,
+ ControlProgram: acp.ControlProgram,
+ })
+ }
+
+ tpls, gotUtxo, err := m.buildBtmTxChain(utxos, acct.Signer)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ for i, tpl := range tpls {
+ gotInput := []uint64{}
+ for _, input := range tpl.Transaction.Inputs {
+ gotInput = append(gotInput, input.Amount()/chainTxMergeGas)
+ }
+
+ gotOutput := []uint64{}
+ for _, output := range tpl.Transaction.Outputs {
+ gotOutput = append(gotOutput, output.Amount/chainTxMergeGas)
+ }
+
+ if !testutil.DeepEqual(c.wantInput[i], gotInput) {
+ t.Fatalf("case %d tx %d input got %d want %d", caseIndex, i, gotInput, c.wantInput[i])
+ }
+ if !testutil.DeepEqual(c.wantOutput[i], gotOutput) {
+ t.Fatalf("case %d tx %d output got %d want %d", caseIndex, i, gotOutput, c.wantOutput[i])
+ }
+ }
+
+ if c.wantUtxo != gotUtxo.Amount {
+ t.Fatalf("case %d got utxo=%d want utxo=%d", caseIndex, gotUtxo.Amount, c.wantUtxo)
+ }
+ }
+
+}
+
func TestMergeSpendAction(t *testing.T) {
testBTM := &bc.AssetID{}
if err := testBTM.UnmarshalText([]byte("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")); err != nil {
}
}
}
+
+func TestCalcMergeGas(t *testing.T) {
+ chainTxUtxoNum = 10
+ cases := []struct {
+ utxoNum int
+ gas uint64
+ }{
+ {
+ utxoNum: 0,
+ gas: 0,
+ },
+ {
+ utxoNum: 1,
+ gas: 0,
+ },
+ {
+ utxoNum: 9,
+ gas: chainTxMergeGas,
+ },
+ {
+ utxoNum: 10,
+ gas: chainTxMergeGas,
+ },
+ {
+ utxoNum: 11,
+ gas: chainTxMergeGas * 2,
+ },
+ {
+ utxoNum: 20,
+ gas: chainTxMergeGas * 3,
+ },
+ {
+ utxoNum: 21,
+ gas: chainTxMergeGas * 3,
+ },
+ {
+ utxoNum: 74,
+ gas: chainTxMergeGas * 9,
+ },
+ }
+
+ for i, c := range cases {
+ gas := calcMergeGas(c.utxoNum)
+ if gas != c.gas {
+ t.Fatalf("case %d got %d want %d", i, gas, c.gas)
+ }
+ }
+}
m.Handle("/sign-message", jsonHandler(a.signMessage))
m.Handle("/build-transaction", jsonHandler(a.build))
- m.Handle("/sign-transaction", jsonHandler(a.pseudohsmSignTemplates))
+ m.Handle("/build-chain-transactions", jsonHandler(a.buildChainTxs))
+ m.Handle("/sign-transaction", jsonHandler(a.signTemplate))
+ m.Handle("/sign-transactions", jsonHandler(a.signTemplates))
m.Handle("/get-transaction", jsonHandler(a.getTransaction))
m.Handle("/list-transactions", jsonHandler(a.listTransactions))
m.Handle("/list-transaction-feeds", jsonHandler(a.listTxFeeds))
m.Handle("/submit-transaction", jsonHandler(a.submit))
+ m.Handle("/submit-transactions", jsonHandler(a.submitTxs))
m.Handle("/estimate-transaction-gas", jsonHandler(a.estimateTxGas))
m.Handle("/get-unconfirmed-transaction", jsonHandler(a.getUnconfirmedTx))
return NewSuccessResponse(nil)
}
-type signResp struct {
+type signTemplateResp struct {
Tx *txbuilder.Template `json:"transaction"`
SignComplete bool `json:"sign_complete"`
}
-func (a *API) pseudohsmSignTemplates(ctx context.Context, x struct {
+func (a *API) signTemplate(ctx context.Context, x struct {
Password string `json:"password"`
Txs txbuilder.Template `json:"transaction"`
}) Response {
return NewErrorResponse(err)
}
log.Info("Sign Transaction complete.")
- return NewSuccessResponse(&signResp{Tx: &x.Txs, SignComplete: txbuilder.SignProgress(&x.Txs)})
+ return NewSuccessResponse(&signTemplateResp{Tx: &x.Txs, SignComplete: txbuilder.SignProgress(&x.Txs)})
+}
+
+type signTemplatesResp struct {
+ Tx []*txbuilder.Template `json:"transaction"`
+ SignComplete bool `json:"sign_complete"`
+}
+
+func (a *API) signTemplates(ctx context.Context, x struct {
+ Password string `json:"password"`
+ Txs []*txbuilder.Template `json:"transactions"`
+}) Response {
+ signComplete := true
+ for _, tx := range x.Txs {
+ if err := txbuilder.Sign(ctx, tx, x.Password, a.pseudohsmSignTemplate); err != nil {
+ log.WithField("build err", err).Error("fail on sign transaction.")
+ return NewErrorResponse(err)
+ }
+ signComplete = signComplete && txbuilder.SignProgress(tx)
+ }
+
+ log.Info("Sign Chain Tx complete.")
+ return NewSuccessResponse(&signTemplatesResp{Tx: x.Txs, SignComplete: signComplete})
}
func (a *API) pseudohsmSignTemplate(ctx context.Context, xpub chainkd.XPub, path [][]byte, data [32]byte, password string) ([]byte, error) {
}
func (a *API) buildSingle(ctx context.Context, req *BuildRequest) (*txbuilder.Template, error) {
- if err := a.completeMissingIDs(ctx, req); err != nil {
+ if err := a.checkRequestValidity(ctx, req); err != nil {
+ return nil, err
+ }
+ actions, err := a.mergeSpendActions(req)
+ if err != nil {
return nil, err
}
- if ok, err := onlyHaveInputActions(req); err != nil {
+ maxTime := time.Now().Add(req.TTL.Duration)
+ tpl, err := txbuilder.Build(ctx, req.Tx, actions, maxTime, req.TimeRange)
+ if errors.Root(err) == txbuilder.ErrAction {
+ // append each of the inner errors contained in the data.
+ var Errs string
+ var rootErr error
+ for i, innerErr := range errors.Data(err)["actions"].([]error) {
+ if i == 0 {
+ rootErr = errors.Root(innerErr)
+ }
+ Errs = Errs + innerErr.Error()
+ }
+ err = errors.WithDetail(rootErr, Errs)
+ }
+ if err != nil {
return nil, err
+ }
+
+ // ensure null is never returned for signing instructions
+ if tpl.SigningInstructions == nil {
+ tpl.SigningInstructions = []*txbuilder.SigningInstruction{}
+ }
+ return tpl, nil
+}
+
+// POST /build-transaction
+func (a *API) build(ctx context.Context, buildReqs *BuildRequest) Response {
+ subctx := reqid.NewSubContext(ctx, reqid.New())
+ tmpl, err := a.buildSingle(subctx, buildReqs)
+ if err != nil {
+ return NewErrorResponse(err)
+ }
+
+ return NewSuccessResponse(tmpl)
+}
+func (a *API) checkRequestValidity(ctx context.Context, req *BuildRequest) error {
+ if err := a.completeMissingIDs(ctx, req); err != nil {
+ return err
+ }
+
+ if req.TTL.Duration == 0 {
+ req.TTL.Duration = defaultTxTTL
+ }
+
+ if ok, err := onlyHaveInputActions(req); err != nil {
+ return err
} else if ok {
- return nil, errors.WithDetail(ErrBadActionConstruction, "transaction contains only input actions and no output actions")
+ return errors.WithDetail(ErrBadActionConstruction, "transaction contains only input actions and no output actions")
}
+ return nil
+}
+func (a *API) mergeSpendActions(req *BuildRequest) ([]txbuilder.Action, error) {
actions := make([]txbuilder.Action, 0, len(req.Actions))
for i, act := range req.Actions {
typ, ok := act["type"].(string)
actions = append(actions, action)
}
actions = account.MergeSpendAction(actions)
+ return actions, nil
+}
- ttl := req.TTL.Duration
- if ttl == 0 {
- ttl = defaultTxTTL
+func (a *API) buildTxs(ctx context.Context, req *BuildRequest) ([]*txbuilder.Template, error) {
+ if err := a.checkRequestValidity(ctx, req); err != nil {
+ return nil, err
+ }
+ actions, err := a.mergeSpendActions(req)
+ if err != nil {
+ return nil, err
}
- maxTime := time.Now().Add(ttl)
- tpl, err := txbuilder.Build(ctx, req.Tx, actions, maxTime, req.TimeRange)
- if errors.Root(err) == txbuilder.ErrAction {
- // append each of the inner errors contained in the data.
- var Errs string
- var rootErr error
- for i, innerErr := range errors.Data(err)["actions"].([]error) {
- if i == 0 {
- rootErr = errors.Root(innerErr)
- }
- Errs = Errs + innerErr.Error()
+ builder := txbuilder.NewBuilder(time.Now().Add(req.TTL.Duration))
+ tpls := []*txbuilder.Template{}
+ for _, action := range actions {
+ if action.ActionType() == "spend_account" {
+ tpls, err = account.SpendAccountChain(ctx, builder, action)
+ } else {
+ err = action.Build(ctx, builder)
+ }
+
+ if err != nil {
+ builder.Rollback()
+ return nil, err
}
- err = errors.WithDetail(rootErr, Errs)
}
+
+ tpl, _, err := builder.Build()
if err != nil {
+ builder.Rollback()
return nil, err
}
- // ensure null is never returned for signing instructions
- if tpl.SigningInstructions == nil {
- tpl.SigningInstructions = []*txbuilder.SigningInstruction{}
- }
- return tpl, nil
+ tpls = append(tpls, tpl)
+ return tpls, nil
}
-// POST /build-transaction
-func (a *API) build(ctx context.Context, buildReqs *BuildRequest) Response {
+// POST /build-chain-transactions
+func (a *API) buildChainTxs(ctx context.Context, buildReqs *BuildRequest) Response {
subctx := reqid.NewSubContext(ctx, reqid.New())
-
- tmpl, err := a.buildSingle(subctx, buildReqs)
+ tmpls, err := a.buildTxs(subctx, buildReqs)
if err != nil {
return NewErrorResponse(err)
}
-
- return NewSuccessResponse(tmpl)
+ return NewSuccessResponse(tmpls)
}
type submitTxResp struct {
return NewSuccessResponse(&submitTxResp{TxID: &ins.Tx.ID})
}
+type submitTxsResp struct {
+ TxID []*bc.Hash `json:"tx_id"`
+}
+
+// POST /submit-transactions
+func (a *API) submitTxs(ctx context.Context, ins struct {
+ Tx []types.Tx `json:"raw_transactions"`
+}) Response {
+ txHashs := []*bc.Hash{}
+ for i := range ins.Tx {
+ if err := txbuilder.FinalizeTx(ctx, a.chain, &ins.Tx[i]); err != nil {
+ return NewErrorResponse(err)
+ }
+ log.WithField("tx_id", ins.Tx[i].ID.String()).Info("submit single tx")
+ txHashs = append(txHashs, &ins.Tx[i].ID)
+ }
+ return NewSuccessResponse(&submitTxsResp{TxID: txHashs})
+}
+
// EstimateTxGasResp estimate transaction consumed gas
type EstimateTxGasResp struct {
TotalNeu int64 `json:"total_neu"`
builder.RestrictMinTime(time.Now())
return builder.AddInput(txin, tplIn)
}
+
+func (a *issueAction) ActionType() string {
+ return "issue"
+}
return b.AddOutput(out)
}
+func (a *controlAddressAction) ActionType() string {
+ return "control_address"
+}
+
// DecodeControlProgramAction convert input data to action struct
func DecodeControlProgramAction(data []byte) (Action, error) {
a := new(controlProgramAction)
return b.AddOutput(out)
}
+func (a *controlProgramAction) ActionType() string {
+ return "control_program"
+}
+
// DecodeRetireAction convert input data to action struct
func DecodeRetireAction(data []byte) (Action, error) {
a := new(retireAction)
out := types.NewTxOutput(*a.AssetId, a.Amount, program)
return b.AddOutput(out)
}
+
+func (a *retireAction) ActionType() string {
+ return "retire"
+}
return nil
}
+// InputCount return number of input in the template builder
+func (b *TemplateBuilder) InputCount() int {
+ return len(b.inputs)
+}
+
// RestrictMinTime set minTime
func (b *TemplateBuilder) RestrictMinTime(t time.Time) {
if t.After(b.minTime) {
b.callbacks = append(b.callbacks, buildFn)
}
-func (b *TemplateBuilder) rollback() {
+// Rollback action for handle fail build
+func (b *TemplateBuilder) Rollback() {
for _, f := range b.rollbacks {
f()
}
// If there were any errors, rollback and return a composite error.
if len(errs) > 0 {
- builder.rollback()
+ builder.Rollback()
return nil, errors.WithData(ErrAction, "actions", errs)
}
// Build the transaction template.
tpl, tx, err := builder.Build()
if err != nil {
- builder.rollback()
+ builder.Rollback()
return nil, err
}
return b.AddOutput(types.NewTxOutput(*t.AssetId, t.Amount, []byte("change")))
}
+func (t testAction) ActionType() string {
+ return "test-action"
+}
+
func newControlProgramAction(assetAmt bc.AssetAmount, script []byte) *controlProgramAction {
return &controlProgramAction{
AssetAmount: assetAmt,
// Action is a interface
type Action interface {
Build(context.Context, *TemplateBuilder) error
+ ActionType() string
}
// Receiver encapsulates information about where to send assets.
func (a byTime) Len() int { return len(a) }
func (a byTime) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
-func (a byTime) Less(i, j int) bool { return a[i].Added.Unix() < a[j].Added.Unix() }
+func (a byTime) Less(i, j int) bool { return a[i].Added.Before(a[j].Added) }
if input.SpentOutputID == nil {
continue
}
- localAccount, err := getAccountFromUTXO(*input.SpentOutputID, walletDB)
+ localAccount, err := getAccountFromACP(input.ControlProgram, walletDB)
if localAccount == nil || err != nil {
continue
}
}
}
-func getAccountFromUTXO(outputID bc.Hash, walletDB db.DB) (*account.Account, error) {
- accountUTXO := account.UTXO{}
- localAccount := account.Account{}
-
- accountUTXOValue := walletDB.Get(account.StandardUTXOKey(outputID))
- if accountUTXOValue == nil {
- return nil, fmt.Errorf("failed get account utxo:%x ", outputID)
- }
-
- if err := json.Unmarshal(accountUTXOValue, &accountUTXO); err != nil {
- return nil, err
- }
-
- accountValue := walletDB.Get(account.Key(accountUTXO.AccountID))
- if accountValue == nil {
- return nil, fmt.Errorf("failed get account:%s ", accountUTXO.AccountID)
- }
- if err := json.Unmarshal(accountValue, &localAccount); err != nil {
- return nil, err
- }
-
- return &localAccount, nil
-}
-
func getAccountFromACP(program []byte, walletDB db.DB) (*account.Account, error) {
var hash common.Hash
accountCP := account.CtrlProgram{}