OSDN Git Service

build vote tx (#1955)
authorPoseidon <shenao.78@163.com>
Wed, 9 Jun 2021 09:17:31 +0000 (17:17 +0800)
committerGitHub <noreply@github.com>
Wed, 9 Jun 2021 09:17:31 +0000 (17:17 +0800)
* build vote tx

* rerun ci

* fix ci

* fix ci

* fix ci

* fix ci

Co-authored-by: Paladz <yzhu101@uottawa.ca>
14 files changed:
account/builder.go
account/utxo_keeper.go
account/utxo_keeper_test.go
api/transact.go
blockchain/txbuilder/actions.go
consensus/general.go
protocol/apply_block.go
protocol/auth_verification.go
protocol/bc/tx.go
protocol/casper.go
protocol/state/checkpoint.go
protocol/validation/tx.go
wallet/utxo.go
wallet/utxo_test.go

index 5049920..b803299 100644 (file)
@@ -2,13 +2,14 @@ package account
 
 import (
        "context"
-       "encoding/json"
+       stdjson "encoding/json"
 
        "github.com/bytom/bytom/blockchain/signers"
        "github.com/bytom/bytom/blockchain/txbuilder"
        "github.com/bytom/bytom/common"
        "github.com/bytom/bytom/consensus"
        "github.com/bytom/bytom/crypto/ed25519/chainkd"
+       "github.com/bytom/bytom/encoding/json"
        "github.com/bytom/bytom/errors"
        "github.com/bytom/bytom/protocol/bc"
        "github.com/bytom/bytom/protocol/bc/types"
@@ -18,7 +19,7 @@ import (
 //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 {
@@ -70,7 +71,7 @@ func (m *Manager) reserveBtmUtxoChain(builder *txbuilder.TemplateBuilder, accoun
        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
                }
@@ -215,7 +216,7 @@ func (a *spendAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) e
                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")
        }
@@ -252,7 +253,7 @@ func (a *spendAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) e
 //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 {
@@ -307,6 +308,9 @@ func (a *spendUTXOAction) Build(ctx context.Context, b *txbuilder.TemplateBuilde
 // UtxoToInputs convert an utxo to the txinput
 func UtxoToInputs(signer *signers.Signer, u *UTXO) (*types.TxInput, *txbuilder.SigningInstruction, error) {
        txInput := types.NewSpendInput(nil, u.SourceID, u.AssetID, u.Amount, u.SourcePos, u.ControlProgram, u.StateData)
+       if u.Vote != nil {
+               txInput = types.NewVetoInput(nil, u.SourceID, u.AssetID, u.Amount, u.SourcePos, u.ControlProgram, u.Vote, nil)
+       }
        sigInst := &txbuilder.SigningInstruction{}
        if signer == nil {
                return txInput, sigInst, nil
@@ -349,6 +353,74 @@ func UtxoToInputs(signer *signers.Signer, u *UTXO) (*types.TxInput, *txbuilder.S
        return txInput, sigInst, nil
 }
 
+//DecodeVetoAction unmarshal JSON-encoded data of spend action
+func (m *Manager) DecodeVetoAction(data []byte) (txbuilder.Action, error) {
+       a := &vetoAction{accounts: m}
+       return a, stdjson.Unmarshal(data, a)
+}
+
+type vetoAction struct {
+       accounts *Manager
+       bc.AssetAmount
+       AccountID      string        `json:"account_id"`
+       Vote           json.HexBytes `json:"vote"`
+       UseUnconfirmed bool          `json:"use_unconfirmed"`
+}
+
+func (a *vetoAction) ActionType() string {
+       return "veto"
+}
+
+func (a *vetoAction) 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, nil)); err != nil {
+                       return errors.Wrap(err, "adding change voteOutput")
+               }
+       }
+       return nil
+}
+
 // insertControlProgramDelayed takes a template builder and an account
 // control program that hasn't been inserted to the database yet. It
 // registers callbacks on the TemplateBuilder so that all of the template's
index 2b48f8c..6130bec 100644 (file)
@@ -1,6 +1,7 @@
 package account
 
 import (
+       "bytes"
        "container/list"
        "encoding/json"
        "sort"
@@ -34,6 +35,7 @@ type UTXO struct {
        Amount              uint64
        SourcePos           uint64
        ControlProgram      []byte
+       Vote                []byte
        StateData           [][]byte
        AccountID           string
        Address             string
@@ -112,11 +114,11 @@ func (uk *utxoKeeper) RemoveUnconfirmedUtxo(hashes []*bc.Hash) {
        }
 }
 
-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
@@ -203,12 +205,12 @@ func (uk *utxoKeeper) expireReservation(t time.Time) {
        }
 }
 
-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 {
index c3426ad..fc337b6 100644 (file)
@@ -519,7 +519,7 @@ func TestReserve(t *testing.T) {
        }
 
        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, nil, 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)
@@ -869,7 +869,7 @@ func TestFindUtxos(t *testing.T) {
                        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, nil)
                if !testutil.DeepEqual(gotUtxos, c.wantUtxos) {
                        t.Errorf("case %d: got %v want %v", i, gotUtxos, c.wantUtxos)
                }
index dd8b7f9..921e9ea 100644 (file)
@@ -27,9 +27,11 @@ func (a *API) actionDecoder(action string) (func([]byte) (txbuilder.Action, erro
                "control_program":              txbuilder.DecodeControlProgramAction,
                "issue":                        a.wallet.AssetReg.DecodeIssueAction,
                "retire":                       txbuilder.DecodeRetireAction,
+               "vote_output":                  txbuilder.DecodeVoteOutputAction,
                "register_contract":            txbuilder.DecodeRegisterAction,
                "spend_account":                a.wallet.AccountMgr.DecodeSpendAction,
                "spend_account_unspent_output": a.wallet.AccountMgr.DecodeSpendUTXOAction,
+               "veto":                         a.wallet.AccountMgr.DecodeVetoAction,
        }
        decoder, ok := decoders[action]
        return decoder, ok
index e155c4a..0a03cc4 100644 (file)
@@ -183,3 +183,62 @@ func (a *registerAction) Build(ctx context.Context, b *TemplateBuilder) error {
 func (a *registerAction) ActionType() string {
        return "register_contract"
 }
+
+// DecodeVoteOutputAction convert input data to action struct
+func DecodeVoteOutputAction(data []byte) (Action, error) {
+       a := new(voteOutputAction)
+       err := stdjson.Unmarshal(data, a)
+       return a, err
+}
+
+type voteOutputAction struct {
+       bc.AssetAmount
+       Address string        `json:"address"`
+       Vote    json.HexBytes `json:"vote"`
+}
+
+func (a *voteOutputAction) Build(ctx context.Context, b *TemplateBuilder) error {
+       var missing []string
+       if a.Address == "" {
+               missing = append(missing, "address")
+       }
+       if a.AssetId.IsZero() {
+               missing = append(missing, "asset_id")
+       }
+       if a.Amount == 0 {
+               missing = append(missing, "amount")
+       }
+       if len(a.Vote) == 0 {
+               missing = append(missing, "vote")
+       }
+       if len(missing) > 0 {
+               return MissingFieldsError(missing...)
+       }
+
+       address, err := common.DecodeAddress(a.Address, &consensus.ActiveNetParams)
+       if err != nil {
+               return err
+       }
+
+       redeemContract := address.ScriptAddress()
+       program := []byte{}
+       switch address.(type) {
+       case *common.AddressWitnessPubKeyHash:
+               program, err = vmutil.P2WPKHProgram(redeemContract)
+       case *common.AddressWitnessScriptHash:
+               program, err = vmutil.P2WSHProgram(redeemContract)
+       default:
+               return errors.New("unsupport address type")
+       }
+       if err != nil {
+               return err
+       }
+
+       out := types.NewVoteOutput(*a.AssetId, a.Amount, program, a.Vote, nil)
+       return b.AddOutput(out)
+}
+
+func (a *voteOutputAction) ActionType() string {
+       return "vote_output"
+}
+
index d1868f5..d687c35 100644 (file)
@@ -19,6 +19,8 @@ const (
 
        // config parameter for coinbase reward
        CoinbasePendingBlockNumber = uint64(100)
+       VotePendingBlockNumber     = uint64(181440)
+       MinVoteOutputAmount        = uint64(100000000)
        subsidyReductionInterval   = uint64(840000)
        baseSubsidy                = uint64(41250000000)
        InitialBlockSubsidy        = uint64(140700041250000000)
index 715318c..1380497 100644 (file)
@@ -152,8 +152,7 @@ func (c *Casper) replayCheckpoint(hash bc.Hash) (*treeNode, error) {
        return node, nil
 }
 
-func applyTransactions(target *state.Checkpoint, transactions []*types.Tx) error {
-       for _, tx := range transactions {
+func applyTransactions(target *state.Checkpoint, transactions []*types.Tx) error {for _, tx := range transactions {
                for _, input := range tx.Inputs {
                        if vetoInput, ok := input.TypedInput.(*types.VetoInput); ok {
                                if err := processVeto(vetoInput, target); err != nil {
@@ -285,7 +284,7 @@ func processWithdrawal(guarantyArgs *guarantyArgs, checkpoint *state.Checkpoint)
        guarantyNum := checkpoint.Guaranties[pubKey]
        guarantyNum, ok := checked.SubUint64(guarantyNum, guarantyArgs.Amount)
        if !ok {
-               return errOverflow
+               return checked.ErrOverflow
        }
 
        checkpoint.Guaranties[pubKey] = guarantyNum
@@ -302,7 +301,7 @@ func processGuaranty(guarantyArgs *guarantyArgs, checkpoint *state.Checkpoint) e
        guarantyNum := checkpoint.Guaranties[pubKey]
        guarantyNum, ok := checked.AddUint64(guarantyNum, guarantyArgs.Amount)
        if !ok {
-               return errOverflow
+               return checked.ErrOverflow
        }
 
        checkpoint.Guaranties[pubKey] = guarantyNum
@@ -314,7 +313,7 @@ func processVeto(input *types.VetoInput, checkpoint *state.Checkpoint) error {
        voteNum := checkpoint.Votes[pubKey]
        voteNum, ok := checked.SubUint64(voteNum, input.Amount)
        if !ok {
-               return errOverflow
+               return checked.ErrOverflow
        }
 
        checkpoint.Votes[pubKey] = voteNum
@@ -331,7 +330,7 @@ func processVote(output *types.TxOutput, checkpoint *state.Checkpoint) error {
        voteNum := checkpoint.Votes[pubKey]
        voteNum, ok := checked.AddUint64(voteNum, output.Amount)
        if !ok {
-               return errOverflow
+               return checked.ErrOverflow
        }
 
        checkpoint.Votes[pubKey] = voteNum
index 104eab6..ee7c829 100644 (file)
@@ -48,11 +48,12 @@ func (c *Casper) AuthVerification(v *Verification) error {
                return nil
        }
 
+       oldBestHash := c.bestChain()
        if err := c.authVerification(v, targetNode.checkpoint, validators); err != nil {
                return err
        }
 
-       return c.tryRollback(c.bestChain())
+       return c.tryRollback(oldBestHash)
 }
 
 func (c *Casper) authVerification(v *Verification, target *state.Checkpoint, validators map[string]*state.Validator) error {
index 4455239..ccae7eb 100644 (file)
@@ -70,3 +70,29 @@ func (tx *Tx) Issuance(id Hash) (*Issuance, error) {
        }
        return iss, nil
 }
+
+// VetoInput try to get the veto entry by given hash
+func (tx *Tx) VetoInput(id Hash) (*VetoInput, error) {
+       e, ok := tx.Entries[id]
+       if !ok || e == nil {
+               return nil, errors.Wrapf(ErrMissingEntry, "id %x", id.Bytes())
+       }
+       sp, ok := e.(*VetoInput)
+       if !ok {
+               return nil, errors.Wrapf(ErrEntryType, "entry %x has unexpected type %T", id.Bytes(), e)
+       }
+       return sp, nil
+}
+
+// VoteOutput try to get the vote output entry by given hash
+func (tx *Tx) VoteOutput(id Hash) (*VoteOutput, error) {
+       e, ok := tx.Entries[id]
+       if !ok || e == nil {
+               return nil, errors.Wrapf(ErrMissingEntry, "id %x", id.Bytes())
+       }
+       o, ok := e.(*VoteOutput)
+       if !ok {
+               return nil, errors.Wrapf(ErrEntryType, "entry %x has unexpected type %T", id.Bytes(), e)
+       }
+       return o, nil
+}
index 3b733f7..5123f95 100644 (file)
@@ -19,10 +19,9 @@ var (
        errSpanHeightInVerification = errors.New("validator publish vote within the span of its other votes")
        errVoteToNonValidator       = errors.New("pubKey of vote is not validator")
        errGuarantyLessThanMinimum  = errors.New("guaranty less than minimum")
-       errOverflow                 = errors.New("arithmetic overflow/underflow")
 )
 
-const minGuaranty = 1E14
+const minGuaranty = 0
 
 // Casper is BFT based proof of stack consensus algorithm, it provides safety and liveness in theory,
 // it's design mainly refers to https://github.com/ethereum/research/blob/master/papers/casper-basics/casper_basics.pdf
index 3232fa3..1da4df2 100644 (file)
@@ -113,7 +113,8 @@ func (c *Checkpoint) Validators() map[string]*Validator {
                return nil
        }
 
-       for pubKey, mortgageNum := range c.Guaranties {
+       //  todo use vote num to generate validators in test version
+       for pubKey, mortgageNum := range c.Votes {
                if mortgageNum >= minMortgage {
                        validators = append(validators, &Validator{
                                PubKey:   pubKey,
index 4c14be3..db14ca0 100644 (file)
@@ -35,6 +35,9 @@ var (
        ErrUnbalanced                = errors.New("unbalanced asset amount between input and output")
        ErrOverGasCredit             = errors.New("all gas credit has been spend")
        ErrGasCalculate              = errors.New("gas usage calculate got a math error")
+       ErrVotePubKey                = errors.New("invalid public key of vote")
+       ErrVoteOutputAmount          = errors.New("invalid vote amount")
+       ErrVoteOutputAseet           = errors.New("incorrect asset_id while checking vote asset")
 )
 
 // GasState record the gas usage status
@@ -211,7 +214,24 @@ func checkValid(vs *validationState, e bc.Entry) (err error) {
                if err = checkValidSrc(&vs2, e.Source); err != nil {
                        return errors.Wrap(err, "checking retirement source")
                }
+       case *bc.VoteOutput:
+               if len(e.Vote) != 64 {
+                       return ErrVotePubKey
+               }
+
+               vs2 := *vs
+               vs2.sourcePos = 0
+               if err = checkValidSrc(&vs2, e.Source); err != nil {
+                       return errors.Wrap(err, "checking vote output source")
+               }
 
+               if e.Source.Value.Amount < consensus.MinVoteOutputAmount {
+                       return ErrVoteOutputAmount
+               }
+
+               if *e.Source.Value.AssetId != *consensus.BTMAssetID {
+                       return ErrVoteOutputAseet
+               }
        case *bc.Issuance:
                computedAssetID := e.WitnessAssetDefinition.ComputeAssetID()
                if computedAssetID != *e.Value.AssetId {
@@ -269,6 +289,47 @@ func checkValid(vs *validationState, e bc.Entry) (err error) {
                if err = checkValidDest(&vs2, e.WitnessDestination); err != nil {
                        return errors.Wrap(err, "checking spend destination")
                }
+       case *bc.VetoInput:
+               if e.SpentOutputId == nil {
+                       return errors.Wrap(ErrMissingField, "vetoInput without vetoInput output ID")
+               }
+
+               voteOutput, err := vs.tx.VoteOutput(*e.SpentOutputId)
+               if err != nil {
+                       return errors.Wrap(err, "getting vetoInput prevout")
+               }
+
+               if len(voteOutput.Vote) != 64 {
+                       return ErrVotePubKey
+               }
+
+               gasLeft, err := vm.Verify(NewTxVMContext(vs, e, voteOutput.ControlProgram, voteOutput.StateData, e.WitnessArguments), vs.gasStatus.GasLeft)
+               if err != nil {
+                       return errors.Wrap(err, "checking control program")
+               }
+               if err = vs.gasStatus.updateUsage(gasLeft); err != nil {
+                       return err
+               }
+
+               eq, err := voteOutput.Source.Value.Equal(e.WitnessDestination.Value)
+               if err != nil {
+                       return err
+               }
+               if !eq {
+                       return errors.WithDetailf(
+                               ErrMismatchedValue,
+                               "previous output is for %d unit(s) of %x, vetoInput wants %d unit(s) of %x",
+                               voteOutput.Source.Value.Amount,
+                               voteOutput.Source.Value.AssetId.Bytes(),
+                               e.WitnessDestination.Value.Amount,
+                               e.WitnessDestination.Value.AssetId.Bytes(),
+                       )
+               }
+               vs2 := *vs
+               vs2.destPos = 0
+               if err = checkValidDest(&vs2, e.WitnessDestination); err != nil {
+                       return errors.Wrap(err, "checking vetoInput destination")
+               }
 
        case *bc.Coinbase:
                if vs.block == nil || len(vs.block.Transactions) == 0 || vs.block.Transactions[0] != vs.tx {
@@ -339,6 +400,12 @@ func checkValidSrc(vstate *validationState, vs *bc.ValueSource) error {
                }
                dest = ref.WitnessDestination
 
+       case *bc.VetoInput:
+               if vs.Position != 0 {
+                       return errors.Wrapf(ErrPosition, "invalid position %d for veto-input source", vs.Position)
+               }
+               dest = ref.WitnessDestination
+
        case *bc.Mux:
                if vs.Position >= uint64(len(ref.WitnessDestinations)) {
                        return errors.Wrapf(ErrPosition, "invalid position %d for %d-destination mux source", vs.Position, len(ref.WitnessDestinations))
@@ -398,6 +465,12 @@ func checkValidDest(vs *validationState, vd *bc.ValueDestination) error {
                }
                src = ref.Source
 
+       case *bc.VoteOutput:
+               if vd.Position != 0 {
+                       return errors.Wrapf(ErrPosition, "invalid position %d for output destination", vd.Position)
+               }
+               src = ref.Source
+
        case *bc.Mux:
                if vd.Position >= uint64(len(ref.Sources)) {
                        return errors.Wrapf(ErrPosition, "invalid position %d for %d-source mux destination", vd.Position, len(ref.Sources))
index 4998fe7..7ff2d1d 100644 (file)
@@ -2,6 +2,7 @@ package wallet
 
 import (
        "encoding/json"
+
        log "github.com/sirupsen/logrus"
 
        "github.com/bytom/bytom/account"
@@ -10,6 +11,7 @@ import (
        "github.com/bytom/bytom/crypto/sha3pool"
        dbm "github.com/bytom/bytom/database/leveldb"
        "github.com/bytom/bytom/errors"
+       "github.com/bytom/bytom/protocol/bc"
        "github.com/bytom/bytom/protocol/bc/types"
 )
 
@@ -43,8 +45,8 @@ func (w *Wallet) GetAccountUtxos(accountID string, id string, unconfirmed, isSma
 }
 
 func (w *Wallet) attachUtxos(batch dbm.Batch, b *types.Block) {
-       for txIndex, tx := range b.Transactions {
-               //hand update the transaction input utxos
+       for _, tx := range b.Transactions {
+               // hand update the transaction input utxos
                inputUtxos := txInToUtxos(tx)
                for _, inputUtxo := range inputUtxos {
                        if segwit.IsP2WScript(inputUtxo.ControlProgram) {
@@ -54,12 +56,8 @@ func (w *Wallet) attachUtxos(batch dbm.Batch, b *types.Block) {
                        }
                }
 
-               //hand update the transaction output utxos
-               validHeight := uint64(0)
-               if txIndex == 0 {
-                       validHeight = b.Height + consensus.CoinbasePendingBlockNumber
-               }
-               outputUtxos := txOutToUtxos(tx, validHeight)
+               // hand update the transaction output utxos
+               outputUtxos := txOutToUtxos(tx, b.Height)
                utxos := w.filterAccountUtxo(outputUtxos)
                if err := batchSaveUtxos(utxos, batch); err != nil {
                        log.WithFields(log.Fields{"module": logModule, "err": err}).Error("attachUtxos fail on batchSaveUtxos")
@@ -148,45 +146,105 @@ func batchSaveUtxos(utxos []*account.UTXO, batch dbm.Batch) error {
 func txInToUtxos(tx *types.Tx) []*account.UTXO {
        utxos := []*account.UTXO{}
        for _, inpID := range tx.Tx.InputIDs {
-               sp, err := tx.Spend(inpID)
-               if err != nil {
+               var utxo *account.UTXO
+               e, ok := tx.Entries[inpID]
+               if !ok {
                        continue
                }
 
-               resOut, err := tx.Output(*sp.SpentOutputId)
-               if err != nil {
-                       log.WithFields(log.Fields{"module": logModule, "err": err}).Error("txInToUtxos fail on get resOut")
+               switch inp := e.(type) {
+               case *bc.Spend:
+                       resOut, err := tx.Output(*inp.SpentOutputId)
+                       if err != nil {
+                               log.WithFields(log.Fields{"module": logModule, "err": err}).Error("txInToUtxos fail on get resOut")
+                               continue
+                       }
+
+                       utxo = &account.UTXO{
+                               OutputID:       *inp.SpentOutputId,
+                               AssetID:        *resOut.Source.Value.AssetId,
+                               Amount:         resOut.Source.Value.Amount,
+                               ControlProgram: resOut.ControlProgram.Code,
+                               SourceID:       *resOut.Source.Ref,
+                               SourcePos:      resOut.Source.Position,
+                       }
+               case *bc.VetoInput:
+                       resOut, err := tx.VoteOutput(*inp.SpentOutputId)
+                       if err != nil {
+                               log.WithFields(log.Fields{"module": logModule, "err": err}).Error("txInToUtxos fail on get resOut for vetoInput")
+                               continue
+                       }
+                       if *resOut.Source.Value.AssetId != *consensus.BTMAssetID {
+                               continue
+                       }
+                       utxo = &account.UTXO{
+                               OutputID:       *inp.SpentOutputId,
+                               AssetID:        *resOut.Source.Value.AssetId,
+                               Amount:         resOut.Source.Value.Amount,
+                               ControlProgram: resOut.ControlProgram.Code,
+                               SourceID:       *resOut.Source.Ref,
+                               SourcePos:      resOut.Source.Position,
+                               Vote:           resOut.Vote,
+                       }
+               default:
                        continue
                }
-
-               utxos = append(utxos, &account.UTXO{
-                       OutputID:       *sp.SpentOutputId,
-                       AssetID:        *resOut.Source.Value.AssetId,
-                       Amount:         resOut.Source.Value.Amount,
-                       ControlProgram: resOut.ControlProgram.Code,
-                       SourceID:       *resOut.Source.Ref,
-                       SourcePos:      resOut.Source.Position,
-               })
+               utxos = append(utxos, utxo)
        }
        return utxos
 }
 
-func txOutToUtxos(tx *types.Tx, vaildHeight uint64) []*account.UTXO {
+func txOutToUtxos(tx *types.Tx, blockHeight uint64) []*account.UTXO {
        utxos := []*account.UTXO{}
        for i, out := range tx.Outputs {
-               bcOut, err := tx.Output(*tx.ResultIds[i])
-               if err != nil {
+               validHeight := uint64(0)
+               entryOutput, ok := tx.Entries[*tx.ResultIds[i]]
+               if !ok {
+                       log.WithFields(log.Fields{"module": logModule}).Error("txOutToUtxos fail on get entryOutput")
                        continue
                }
 
-               utxo := &account.UTXO{
-                       OutputID:       *tx.OutputID(i),
-                       AssetID:        *out.AssetAmount.AssetId,
-                       Amount:         out.Amount,
-                       ControlProgram: out.ControlProgram,
-                       SourceID:       *bcOut.Source.Ref,
-                       SourcePos:      bcOut.Source.Position,
-                       ValidHeight:    vaildHeight,
+               var utxo *account.UTXO
+               switch bcOut := entryOutput.(type) {
+               case *bc.Output:
+                       if out.AssetAmount.Amount == uint64(0) {
+                               continue
+                       }
+
+                       if tx.Inputs[0].InputType() == types.CoinbaseInputType {
+                               validHeight = blockHeight + consensus.CoinbasePendingBlockNumber
+                       }
+
+                       utxo = &account.UTXO{
+                               OutputID:       *tx.OutputID(i),
+                               AssetID:        *out.AssetAmount.AssetId,
+                               Amount:         out.AssetAmount.Amount,
+                               ControlProgram: out.ControlProgram,
+                               SourceID:       *bcOut.Source.Ref,
+                               SourcePos:      bcOut.Source.Position,
+                               ValidHeight:    validHeight,
+                       }
+
+               case *bc.VoteOutput:
+                       voteValidHeight := blockHeight + consensus.VotePendingBlockNumber
+                       if validHeight < voteValidHeight {
+                               validHeight = voteValidHeight
+                       }
+
+                       utxo = &account.UTXO{
+                               OutputID:       *tx.OutputID(i),
+                               AssetID:        *out.AssetAmount.AssetId,
+                               Amount:         out.AssetAmount.Amount,
+                               ControlProgram: out.ControlProgram,
+                               SourceID:       *bcOut.Source.Ref,
+                               SourcePos:      bcOut.Source.Position,
+                               ValidHeight:    validHeight,
+                               Vote:           bcOut.Vote,
+                       }
+
+               default:
+                       log.WithFields(log.Fields{"module": logModule}).Warn("txOutToUtxos fail on get bcOut")
+                       continue
                }
                utxos = append(utxos, utxo)
        }
index 3035278..980d84b 100644 (file)
@@ -495,7 +495,7 @@ func TestTxOutToUtxos(t *testing.T) {
        cases := []struct {
                tx          *types.Tx
                statusFail  bool
-               vaildHeight uint64
+               blockHeight uint64
                wantUtxos   []*account.UTXO
        }{
                {
@@ -508,7 +508,7 @@ func TestTxOutToUtxos(t *testing.T) {
                                },
                        }),
                        statusFail:  false,
-                       vaildHeight: 98,
+                       blockHeight: 0,
                        wantUtxos: []*account.UTXO{
                                &account.UTXO{
                                        OutputID:       bc.NewHash([32]byte{0x9c, 0xab, 0x55, 0xdc, 0x72, 0xb1, 0x42, 0x6d, 0x2a, 0x41, 0x92, 0xc3, 0x40, 0x32, 0x29, 0xf4, 0xa4, 0x11, 0xae, 0x54, 0x41, 0x54, 0x1a, 0xfe, 0x7c, 0x93, 0x4b, 0x8f, 0x6c, 0x61, 0x69, 0x9f}),
@@ -517,7 +517,7 @@ func TestTxOutToUtxos(t *testing.T) {
                                        ControlProgram: []byte{0x51},
                                        SourceID:       bc.NewHash([32]byte{0xb4, 0x7e, 0x94, 0x31, 0x88, 0xfe, 0xd3, 0xe9, 0xac, 0x99, 0x7c, 0xfc, 0x99, 0x6d, 0xd7, 0x4d, 0x04, 0x10, 0x77, 0xcb, 0x1c, 0xf8, 0x95, 0x14, 0x00, 0xe3, 0x42, 0x00, 0x8d, 0x05, 0xec, 0xdc}),
                                        SourcePos:      0,
-                                       ValidHeight:    98,
+                                       ValidHeight:    100,
                                },
                        },
                },
@@ -535,7 +535,7 @@ func TestTxOutToUtxos(t *testing.T) {
                                },
                        }),
                        statusFail:  false,
-                       vaildHeight: 0,
+                       blockHeight: 0,
                        wantUtxos: []*account.UTXO{
                                &account.UTXO{
                                        OutputID:       bc.NewHash([32]byte{0x49, 0x33, 0x66, 0x49, 0x4b, 0xaa, 0x57, 0x26, 0xc7, 0x21, 0x74, 0x75, 0x4e, 0x15, 0x59, 0xa4, 0x24, 0xa1, 0x92, 0xda, 0xb1, 0x88, 0x8f, 0xea, 0x51, 0xaf, 0xcf, 0x95, 0x21, 0xab, 0xe4, 0xe2}),
@@ -585,7 +585,7 @@ func TestTxOutToUtxos(t *testing.T) {
                                },
                        }),
                        statusFail:  true,
-                       vaildHeight: 0,
+                       blockHeight: 0,
                        wantUtxos: []*account.UTXO{
                                &account.UTXO{
                                        OutputID:       bc.NewHash([32]byte{0x49, 0x33, 0x66, 0x49, 0x4b, 0xaa, 0x57, 0x26, 0xc7, 0x21, 0x74, 0x75, 0x4e, 0x15, 0x59, 0xa4, 0x24, 0xa1, 0x92, 0xda, 0xb1, 0x88, 0x8f, 0xea, 0x51, 0xaf, 0xcf, 0x95, 0x21, 0xab, 0xe4, 0xe2}),
@@ -624,7 +624,7 @@ func TestTxOutToUtxos(t *testing.T) {
        }
 
        for i, c := range cases {
-               if gotUtxos := txOutToUtxos(c.tx, c.vaildHeight); !testutil.DeepEqual(gotUtxos, c.wantUtxos) {
+               if gotUtxos := txOutToUtxos(c.tx, c.blockHeight); !testutil.DeepEqual(gotUtxos, c.wantUtxos) {
                        for k, v := range gotUtxos {
 
                                data, _ := json.Marshal(v)