import (
"context"
- "encoding/json"
+ stdjson "encoding/json"
"github.com/vapor/blockchain/signers"
"github.com/vapor/blockchain/txbuilder"
"github.com/vapor/common"
"github.com/vapor/consensus"
"github.com/vapor/crypto/ed25519/chainkd"
+ "github.com/vapor/encoding/json"
"github.com/vapor/errors"
"github.com/vapor/protocol/bc"
"github.com/vapor/protocol/bc/types"
//DecodeSpendAction unmarshal JSON-encoded data of spend action
func (m *Manager) DecodeSpendAction(data []byte) (txbuilder.Action, error) {
a := &spendAction{accounts: m}
- return a, json.Unmarshal(data, a)
+ return a, stdjson.Unmarshal(data, a)
}
type spendAction struct {
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())
+ res, err := m.utxoKeeper.Reserve(accountID, consensus.BTMAssetID, reserveAmount, useUnconfirmed, nil, builder.MaxTime())
if err != nil {
return nil, err
}
return errors.Wrap(err, "get account info")
}
- res, err := a.accounts.utxoKeeper.Reserve(a.AccountID, a.AssetId, a.Amount, a.UseUnconfirmed, b.MaxTime())
+ res, err := a.accounts.utxoKeeper.Reserve(a.AccountID, a.AssetId, a.Amount, a.UseUnconfirmed, nil, b.MaxTime())
if err != nil {
return errors.Wrap(err, "reserving utxos")
}
//DecodeSpendUTXOAction unmarshal JSON-encoded data of spend utxo action
func (m *Manager) DecodeSpendUTXOAction(data []byte) (txbuilder.Action, error) {
a := &spendUTXOAction{accounts: m}
- return a, json.Unmarshal(data, a)
+ return a, stdjson.Unmarshal(data, a)
}
type spendUTXOAction struct {
return m.SaveControlPrograms(acps...)
})
}
+
+//DecodeUnvoteAction unmarshal JSON-encoded data of spend action
+func (m *Manager) DecodeUnvoteAction(data []byte) (txbuilder.Action, error) {
+ a := &unvoteAction{accounts: m}
+ return a, stdjson.Unmarshal(data, a)
+}
+
+type unvoteAction struct {
+ accounts *Manager
+ bc.AssetAmount
+ AccountID string `json:"account_id"`
+ Vote json.HexBytes `json:"vote"`
+ UseUnconfirmed bool `json:"use_unconfirmed"`
+}
+
+func (a *unvoteAction) ActionType() string {
+ return "unvote"
+}
+
+func (a *unvoteAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
+ var missing []string
+ if a.AccountID == "" {
+ missing = append(missing, "account_id")
+ }
+ if a.AssetId.IsZero() {
+ missing = append(missing, "asset_id")
+ }
+ if len(missing) > 0 {
+ return txbuilder.MissingFieldsError(missing...)
+ }
+
+ acct, err := a.accounts.FindByID(a.AccountID)
+ if err != nil {
+ return errors.Wrap(err, "get account info")
+ }
+
+ res, err := a.accounts.utxoKeeper.Reserve(a.AccountID, a.AssetId, a.Amount, a.UseUnconfirmed, a.Vote, b.MaxTime())
+ if err != nil {
+ return errors.Wrap(err, "reserving utxos")
+ }
+
+ // Cancel the reservation if the build gets rolled back.
+ b.OnRollback(func() { a.accounts.utxoKeeper.Cancel(res.id) })
+ for _, r := range res.utxos {
+ txInput, sigInst, err := UtxoToInputs(acct.Signer, r)
+ if err != nil {
+ return errors.Wrap(err, "creating inputs")
+ }
+
+ if err = b.AddInput(txInput, sigInst); err != nil {
+ return errors.Wrap(err, "adding inputs")
+ }
+ }
+
+ if res.change > 0 {
+ acp, err := a.accounts.CreateAddress(a.AccountID, true)
+ if err != nil {
+ return errors.Wrap(err, "creating control program")
+ }
+
+ // Don't insert the control program until callbacks are executed.
+ a.accounts.insertControlProgramDelayed(b, acp)
+ if err = b.AddOutput(types.NewVoteOutput(*a.AssetId, res.change, acp.ControlProgram, a.Vote)); err != nil {
+ return errors.Wrap(err, "adding change voteOutput")
+ }
+ }
+ return nil
+}
package account
import (
+ "bytes"
"container/list"
"encoding/json"
"sort"
}
}
-func (uk *utxoKeeper) Reserve(accountID string, assetID *bc.AssetID, amount uint64, useUnconfirmed bool, exp time.Time) (*reservation, error) {
+func (uk *utxoKeeper) Reserve(accountID string, assetID *bc.AssetID, amount uint64, useUnconfirmed bool, vote []byte, exp time.Time) (*reservation, error) {
uk.mtx.Lock()
defer uk.mtx.Unlock()
- utxos, immatureAmount := uk.findUtxos(accountID, assetID, useUnconfirmed)
+ utxos, immatureAmount := uk.findUtxos(accountID, assetID, useUnconfirmed, vote)
optUtxos, optAmount, reservedAmount := uk.optUTXOs(utxos, amount)
if optAmount+reservedAmount+immatureAmount < amount {
return nil, ErrInsufficient
}
}
-func (uk *utxoKeeper) findUtxos(accountID string, assetID *bc.AssetID, useUnconfirmed bool) ([]*UTXO, uint64) {
+func (uk *utxoKeeper) findUtxos(accountID string, assetID *bc.AssetID, useUnconfirmed bool, vote []byte) ([]*UTXO, uint64) {
immatureAmount := uint64(0)
currentHeight := uk.currentHeight()
utxos := []*UTXO{}
appendUtxo := func(u *UTXO) {
- if u.AccountID != accountID || u.AssetID != *assetID {
+ if u.AccountID != accountID || u.AssetID != *assetID || !bytes.Equal(u.Vote, vote) {
return
}
if u.ValidHeight > currentHeight {
func TestReserve(t *testing.T) {
currentHeight := func() uint64 { return 9527 }
testDB := dbm.NewDB("testdb", "leveldb", "temp")
- defer os.RemoveAll("temp")
+ defer func() {
+ testDB.Close()
+ os.RemoveAll("temp")
+ }()
cases := []struct {
before utxoKeeper
err error
reserveAmount uint64
exp time.Time
+ vote []byte
}{
{
before: utxoKeeper{
err: nil,
exp: time.Date(2016, 8, 10, 0, 0, 0, 0, time.UTC),
},
+ {
+ before: utxoKeeper{
+ db: testDB,
+ currentHeight: currentHeight,
+ unconfirmed: map[bc.Hash]*UTXO{
+ bc.NewHash([32]byte{0x01}): &UTXO{
+ OutputID: bc.NewHash([32]byte{0x01}),
+ AccountID: "testAccount",
+ Amount: 3,
+ Vote: []byte("af594006a40837d9f028daabb6d589df0b9138daefad5683e5233c2646279217294a8d532e60863bcf196625a35fb8ceeffa3c09610eb92dcfb655a947f13269"),
+ },
+ },
+ reserved: map[bc.Hash]uint64{},
+ reservations: map[uint64]*reservation{},
+ },
+ after: utxoKeeper{
+ db: testDB,
+ currentHeight: currentHeight,
+ unconfirmed: map[bc.Hash]*UTXO{
+ bc.NewHash([32]byte{0x01}): &UTXO{
+ OutputID: bc.NewHash([32]byte{0x01}),
+ AccountID: "testAccount",
+ Amount: 3,
+ Vote: []byte("af594006a40837d9f028daabb6d589df0b9138daefad5683e5233c2646279217294a8d532e60863bcf196625a35fb8ceeffa3c09610eb92dcfb655a947f13269"),
+ },
+ },
+ reserved: map[bc.Hash]uint64{
+ bc.NewHash([32]byte{0x01}): 1,
+ },
+ reservations: map[uint64]*reservation{
+ 1: &reservation{
+ id: 1,
+ utxos: []*UTXO{
+ &UTXO{
+ OutputID: bc.NewHash([32]byte{0x01}),
+ AccountID: "testAccount",
+ Amount: 3,
+ Vote: []byte("af594006a40837d9f028daabb6d589df0b9138daefad5683e5233c2646279217294a8d532e60863bcf196625a35fb8ceeffa3c09610eb92dcfb655a947f13269"),
+ },
+ },
+ change: 1,
+ expiry: time.Date(2016, 8, 10, 0, 0, 0, 0, time.UTC),
+ },
+ },
+ },
+ reserveAmount: 2,
+ err: nil,
+ exp: time.Date(2016, 8, 10, 0, 0, 0, 0, time.UTC),
+ vote: []byte("af594006a40837d9f028daabb6d589df0b9138daefad5683e5233c2646279217294a8d532e60863bcf196625a35fb8ceeffa3c09610eb92dcfb655a947f13269"),
+ },
}
for i, c := range cases {
- if _, err := c.before.Reserve("testAccount", &bc.AssetID{}, c.reserveAmount, true, c.exp); err != c.err {
+ if _, err := c.before.Reserve("testAccount", &bc.AssetID{}, c.reserveAmount, true, c.vote, c.exp); err != c.err {
t.Errorf("case %d: got error %v want error %v", i, err, c.err)
}
checkUtxoKeeperEqual(t, i, &c.before, &c.after)
func TestFindUtxos(t *testing.T) {
currentHeight := func() uint64 { return 9527 }
testDB := dbm.NewDB("testdb", "leveldb", "temp")
- defer os.RemoveAll("temp")
+ defer func() {
+ testDB.Close()
+ os.RemoveAll("temp")
+ }()
cases := []struct {
uk utxoKeeper
useUnconfirmed bool
wantUtxos []*UTXO
immatureAmount uint64
+ vote []byte
}{
{
uk: utxoKeeper{
},
immatureAmount: 0,
},
+ {
+ uk: utxoKeeper{
+ db: testDB,
+ currentHeight: currentHeight,
+ unconfirmed: map[bc.Hash]*UTXO{},
+ },
+ dbUtxos: []*UTXO{
+ &UTXO{
+ OutputID: bc.NewHash([32]byte{0x01}),
+ AccountID: "testAccount",
+ Amount: 6,
+ Vote: []byte("af594006a40837d9f028daabb6d589df0b9138daefad5683e5233c2646279217294a8d532e60863bcf196625a35fb8ceeffa3c09610eb92dcfb655a947f13269"),
+ },
+ },
+ useUnconfirmed: false,
+ wantUtxos: []*UTXO{
+ &UTXO{
+ OutputID: bc.NewHash([32]byte{0x01}),
+ AccountID: "testAccount",
+ Amount: 6,
+ Vote: []byte("af594006a40837d9f028daabb6d589df0b9138daefad5683e5233c2646279217294a8d532e60863bcf196625a35fb8ceeffa3c09610eb92dcfb655a947f13269"),
+ },
+ },
+ immatureAmount: 0,
+ vote: []byte("af594006a40837d9f028daabb6d589df0b9138daefad5683e5233c2646279217294a8d532e60863bcf196625a35fb8ceeffa3c09610eb92dcfb655a947f13269"),
+ },
}
for i, c := range cases {
testDB.Set(StandardUTXOKey(u.OutputID), data)
}
- gotUtxos, immatureAmount := c.uk.findUtxos("testAccount", &bc.AssetID{}, c.useUnconfirmed)
+ gotUtxos, immatureAmount := c.uk.findUtxos("testAccount", &bc.AssetID{}, c.useUnconfirmed, c.vote)
if !testutil.DeepEqual(gotUtxos, c.wantUtxos) {
t.Errorf("case %d: got %v want %v", i, gotUtxos, c.wantUtxos)
}
"vote_output": txbuilder.DecodeVoteOutputAction,
"spend_account": a.wallet.AccountMgr.DecodeSpendAction,
"spend_account_unspent_output": a.wallet.AccountMgr.DecodeSpendUTXOAction,
+ "unvote": a.wallet.AccountMgr.DecodeUnvoteAction,
}
decoder, ok := decoders[action]
return decoder, ok